From 2e998e84560707498039692682326b44c72354b9 Mon Sep 17 00:00:00 2001 From: "snickersun@163.com" Date: Tue, 2 Dec 2025 08:59:58 +0000 Subject: [PATCH] Merge #2841 into sdk650pathc0423 from refs/changes/2841/head MIME-Version: 1.0 Content-Type: text/plain; charset=utf8 Content-Transfer-Encoding: 8bit Code review from snickersun@163.com * refs/changes/2841/head: (2 commits squashed) - 添加spi接口新通讯协议。支持文件传输重试等。暂时还未调通,但是对外接口基本不会变了。 - Merge branch 'sdk650pathc0423' of codeup.aliyun.com:64191456b9581d62e8f81ac0/FD230 into sdk650pathc0423 Signed-off-by: snickersun@163.com Merged-by: aliyun6865282430 <1624952103@qq.com> CR-link: https://codeup.aliyun.com/64191456b9581d62e8f81ac0/FD230/change/2841 --- cat1modem/src/LeiNiao/cat1Service.py | 126 +-- .../src/LeiNiao/china_mobile_ai_regulation.py | 416 ------- cat1modem/src/LeiNiao/china_mobile_dmp.py | 842 -------------- cat1modem/src/LeiNiao/httpfiledownload.py | 8 +- cat1modem/src/LeiNiao/main_init.py | 17 +- cat1modem/src/LeiNiao/spi_protocol.py | 560 +++++++++ cat1modem/src/LeiNiao/spi_protocol_api.py | 112 ++ cat1modem/src/LeiNiao/spi_protocol_sender.py | 80 ++ cat1modem/src/LeiNiao/spi_protocol_test.py | 237 ++++ .../src/LeiNiao/spi_protocol_test_2mb.py | 387 +++++++ cat1modem/src/LeiNiao/spi_write_test.py | 317 ++++++ cat1modem/src/LeiNiao/telecom_dmp.py | 438 ------- .../config/target_config/brandy/config.py | 4 +- code/sdk/zhihui/utils/custom_utils.c | 6 +- code/sdk/zhihui/utils/custom_utils_at_cmd.c | 144 ++- code/sdk/zhihui/utilscpp/CMakeLists.txt | 3 + code/sdk/zhihui/utilscpp/custom_utilscpp.cpp | 484 +++++++- code/sdk/zhihui/utilscpp/custom_utilscpp.h | 5 + code/sdk/zhihui/utilscpp/spi_protocol.cpp | 1002 +++++++++++++++++ code/sdk/zhihui/utilscpp/spi_protocol.h | 86 ++ code/sdk/zhihui/utilscpp/spi_protocol_api.cpp | 39 + code/sdk/zhihui/utilscpp/spi_protocol_api.h | 70 ++ .../sdk/zhihui/utilscpp/spi_protocol_test.cpp | 245 ++++ code/sdk/zhihui/utilscpp/spi_protocol_test.h | 37 + 24 files changed, 3772 insertions(+), 1893 deletions(-) delete mode 100644 cat1modem/src/LeiNiao/china_mobile_ai_regulation.py delete mode 100644 cat1modem/src/LeiNiao/china_mobile_dmp.py create mode 100644 cat1modem/src/LeiNiao/spi_protocol.py create mode 100644 cat1modem/src/LeiNiao/spi_protocol_api.py create mode 100644 cat1modem/src/LeiNiao/spi_protocol_sender.py create mode 100644 cat1modem/src/LeiNiao/spi_protocol_test.py create mode 100644 cat1modem/src/LeiNiao/spi_protocol_test_2mb.py create mode 100644 cat1modem/src/LeiNiao/spi_write_test.py delete mode 100644 cat1modem/src/LeiNiao/telecom_dmp.py create mode 100644 code/sdk/zhihui/utilscpp/spi_protocol.cpp create mode 100644 code/sdk/zhihui/utilscpp/spi_protocol.h create mode 100644 code/sdk/zhihui/utilscpp/spi_protocol_api.cpp create mode 100644 code/sdk/zhihui/utilscpp/spi_protocol_api.h create mode 100644 code/sdk/zhihui/utilscpp/spi_protocol_test.cpp create mode 100644 code/sdk/zhihui/utilscpp/spi_protocol_test.h diff --git a/cat1modem/src/LeiNiao/cat1Service.py b/cat1modem/src/LeiNiao/cat1Service.py index a8f5cd421..0a2bb9bdb 100644 --- a/cat1modem/src/LeiNiao/cat1Service.py +++ b/cat1modem/src/LeiNiao/cat1Service.py @@ -55,11 +55,7 @@ import ujson from usr.EventMesh import eventMesh from usr.httpota import httpota -from usr.telecom_dmp import telecomDmpCb -from usr.china_mobile_dmp import chinamobileCb from usr.tcpserver import TcpServiceInit - -from usr.china_mobile_ai_regulation import aiRegulation # 设置日志输出级别 log.basicConfig(level=log.INFO) clog = log.getLogger("cat1") @@ -153,11 +149,11 @@ set_version_to_json_config(CURRENT_APP_TYPE,PROJECT_VERSION) def ExtIntfun(args): # clog.info('### interrupt {} ###'.format(args)) # args[0]:gpio号 args[1]:上升沿或下降沿 if args[1] == 0: - clog.info("disable autosleep") + # clog.info("disable autosleep") # pm.autosleep(0) pm.wakelock_lock(lpm_fd) elif args[1] == 1: - clog.info("enable autosleep") + # clog.info("enable autosleep") pm.wakelock_unlock(lpm_fd) # pm.autosleep(1) # clog.info("recvDataPin={}".format(recvDataPin.read())) @@ -222,10 +218,7 @@ class Cat1Service: self.iccid_data = "" self.software_version = "" self.deletSmsTimer = osTimer() - self.china_mobile_ai_init = 0 self.volte_switch = 1 #默认是开启VOLTE通话功能 - eventMesh.subscribe("set_dmp_change_mode",self.set_dmp_change_mode) - eventMesh.subscribe("get_dmp_change_mode",self.get_dmp_change_mode) eventMesh.subscribe("delete_sms_event",self.delete_sms_event) eventMesh.subscribe("remove_all_sms_cb", self.remove_all_sms_cb)# eventMesh.subscribe("send_sms_max_to_Ble",self.send_sms_max_to_Ble) @@ -235,14 +228,10 @@ class Cat1Service: eventMesh.subscribe("get_factory_status",self.get_factory_status) eventMesh.subscribe("set_factory_status",self.set_factory_status) - eventMesh.subscribe("send_dmp_cm_address_toBLE",self.send_dmp_cm_address_toBLE) eventMesh.subscribe("set_volte_switch",self.set_volte_switch) eventMesh.subscribe("get_volte_switch",self.get_volte_switch) eventMesh.publish("delete_sms_event") - - self.telecom_dmp_switch = 0 - self.send_data = [] self.code_old = 0 self.code_old_num = 1 @@ -251,7 +240,6 @@ class Cat1Service: # self.msg_nums = 0 self.msg_nums_old = 0 - self.china_mobile_init = 0 self.factory_status = 0 self.send_data_q = Queue(100) self.ack_sem=0 @@ -372,35 +360,8 @@ class Cat1Service: else: clog.info("cmd error ret:{}".format(ret)) self.reply_cmd('09',"{}".format("at error")) - - elif cmd == "10" or cmd == "12" or cmd == "14": #获取固件版本号或者获取BT保存的SIM卡的ICCID BLE那边是否开启电信入库功能 - if 1: #CHINA_TELECOM_DM_FUN_ENABLE == 1 : - if eventMesh.publish("get_dmp_change_mode") == 1: - self.telecom_recv_cmd(cmd,parm) - else: - pass - - if 1:#CHINA_MOBILE_DM_FUN_ENABLE == 1: - if eventMesh.publish("get_dmp_change_mode") > 1: - self.china_mobile_recv_cmd(cmd,parm) - else: - pass - pass - elif cmd == "13": - on = int(parm) - clog.info("set_dmp_change_mode on:{}".format(on)) - eventMesh.publish("set_dmp_change_mode",on) - - elif cmd == "16": - if 1: #CHINA_MOBILE_DM_FUN_ENABLE == 1: - if eventMesh.publish("get_dmp_change_mode") > 1: - self.china_mobile_recv_cmd(cmd,parm) - else: - pass elif cmd == "17": #用于CAT1恢复出厂需要清空数据的标识通用 eventMesh.publish("set_factory_status",int(parm)) - elif cmd == "18": - eventMesh.publish("set_dmp_cm_address",str(parm)) elif cmd == "19": #DTMF功能 # voiceCall.startDtmf(str(parm),100) #100ms 持续时间 100-1000 pass @@ -413,8 +374,6 @@ class Cat1Service: httpota.run( str(parm).split("and")[0], str(parm).split("and")[-1]) # eventMesh.publish("set_volte_switch",int(parm)) # UDP_MSG_OTA - elif cmd == "1C":# - self.read_dev_ai_id() elif cmd== "1D": TcpService = TcpServiceInit() TcpService.start_socket_thread() @@ -424,17 +383,6 @@ class Cat1Service: - def telecom_recv_cmd(self,cmd,parm): - if cmd == "10": - self.software_version = str(parm) - eventMesh.publish("set_dev_version",self.software_version) - clog.info("software_version:{},{}".format(self.software_version,parm)) - elif cmd == "12": - pass - elif cmd == "14":# - pass - pass - def set_volte_switch(self,event,msg): #设置VOLTE开关 self.volte_switch = int(msg) @@ -442,18 +390,7 @@ class Cat1Service: def get_volte_switch(self,event,msg): #获取VOLTE开关 return self.volte_switch - - def china_mobile_recv_cmd(self,cmd,parm): #移动入库相关指令 - if cmd == "10": - self.software_version = str(parm) - eventMesh.publish("set_dev_version",self.software_version) - clog.info("software_version:{},{}".format(self.software_version,parm)) - elif cmd == "16": #获取BLE mac地址 - eventMesh.publish("set_dev_mac",str(parm)) - clog.info("parm:{}\r\n".format(parm)) - - pass def get_factory_status(self,event,msg): return self.factory_status def set_factory_status(self,event,msg): @@ -461,21 +398,15 @@ class Cat1Service: if self.factory_status == 0: #只有为的时候才设置进去 1不再设置 self.reply_cmd('17',"{}".format(1)) - def send_dmp_cm_address_toBLE(self,event,msg): - buf_tmp = "address:"+str(msg) - self.reply_cmd('18',"{}".format(buf_tmp)) - - def set_dmp_change_mode(self,event,msg): - self.telecom_dmp_switch = msg def delete_sms_single_data(self,event,msg): #删除单条信息 sms.deleteMsg(int(msg)) - def get_dmp_change_mode(self,event,msg): - return self.telecom_dmp_switch def delete_sms_event(self,event,msg): self.deletSmsTimer.start(2*1000,0,self.delete_sms_cb) + def delete_sms_cb(self,*argc): eventMesh.publish_async("remove_all_sms_cb","",1*1024) + def remove_all_sms_cb(self, event, msg): clog.info("remove_all_sms_cb\r\n") msg_nums = sms.getMsgNums() #短信数量 @@ -824,26 +755,6 @@ class Cat1Service: cat1service.set_dns(pri_dns, sec_dns) # 网卡IP信息查询 cat1service.ipconfig() - if eventMesh.publish("get_dmp_change_mode") == 0:#入库功能关闭 - return - if 1: #CHINA_TELECOM_DM_FUN_ENABLE == 1:#电信入库功能 不使用设置 0 防止两边不同步修改 - if eventMesh.publish("get_dmp_change_mode") == 1: - eventMesh.publish("set_network_state",1) - telecomDmpCb.telecom_dmp_power_on_init() - if 1: #CHINA_MOBILE_DM_FUN_ENABLE == 1: - if eventMesh.publish("get_dmp_change_mode") == 2: - eventMesh.publish("set_network_state",1) - if self.china_mobile_init == 1: - return - self.china_mobile_init = 1 - #移动入库的初始化 - chinamobileCb.china_mobile_init() - if eventMesh.publish("get_dmp_change_mode") == 3: - if self.china_mobile_ai_init == 1: - return - self.china_mobile_ai_init = 1 - eventMesh.publish("set_network_state",1) - aiRegulation.AIRegulation_init() def cat1_reply_iccid_to_ble(self,event,msg): ICCID_DATA = str("ICCID:")+str(msg) @@ -857,31 +768,6 @@ class Cat1Service: def stop(self): slip.destroy() return 0 - - def read_dev_ai_id(self): - - IFEXIST = ql_fs.path_exists("/usr/cm_ai_regulation.json") # - if IFEXIST == True: #文件存在 - fileSize = ql_fs.path_getsize('usr/cm_ai_regulation.json') - if fileSize == 0: - - print("设备信息配置文件不存在\r\n") - else: - try: - with open("/usr/cm_ai_regulation.json","r",encoding='utf-8') as fd: - try: - config_data = ujson.load(fd) #转换成json格式 - if len(config_data) != 0: - if "deviceId" in config_data:# - deviceId = str(config_data["deviceId"]) #设备ID - self.reply_cmd('1C',"{}".format(deviceId)) - except Exception as error: - print("配置文件的内容不是json格式 保存数据的时候格式不对\r\n") - except Exception as error: - print("打开配置文件失败\r\n") - - - # 封装tlv数据包 def __pack_tlv_format(self, head, content): @@ -1080,10 +966,6 @@ def voice_callback(args): def checknet_cb(arg): clog.info("start checktime!!") - # if 1: #CHINA_TELECOM_DM_FUN_ENABLE == 1:#这是手表每30s检查一次 - # if eventMesh.publish("get_dmp_change_mode") == 1: - # telecomDmpCb.telecom_dmp_power_on_count() - _thread.start_new_thread(cat1service.wait_network_ready, ()) # time=utime.localtime() # zone=utime.getTimeZone() diff --git a/cat1modem/src/LeiNiao/china_mobile_ai_regulation.py b/cat1modem/src/LeiNiao/china_mobile_ai_regulation.py deleted file mode 100644 index 9cd4bf9a8..000000000 --- a/cat1modem/src/LeiNiao/china_mobile_ai_regulation.py +++ /dev/null @@ -1,416 +0,0 @@ -#AI 纳管对接文件 -import modem -import request -import utime -import osTimer -import ql_fs -from machine import RTC -from misc import Power -import uos -import ujson -import urandom -from usr.EventMesh import eventMesh -HEARBEAT_REPORT_TIMER_CONFIG = 60*60*12#60*60*12 #心跳上报时间间隔12小时可更改做测试 -REALY_TIME_COUNT = 60*60# #开启1小时实时定时器 -HEART_REPORT_ERROR_TIMER = 60 #心率上报错误定时器检测 按照1分钟检查 -PRODUCTID = "1927646275585744898" #产品ID -PRODUCTKEY = "iijgiGiTolCblSFE" #产品KEY 平台创建产品是生成的 -HEART_REPORT_ERR_MAX_NUM = 30 #连续上报错误次数最大值 30 网络异常或者哪里报错了 重启设备 可根据实际的用户的需求更改次数 -REPORT_MODE = 1 #上报方式 1 24小时内一定上报一次 2 12小时后上报一次 -class AIRegulation: - #定义全局变量 - def __init__(self): - self.timerStart = osTimer() - self.timerStartReportHr = osTimer() - self.productId = PRODUCTID - self.productKey = PRODUCTKEY - self.deviceId = "" #设备ID - self.deviceSecret = "" #设备私钥 - self.config_data = {} - self.getDevInforErrCount = 0 #获取设备信息错误次数累计 - self.report_state = 0 #超过24小时清0 - self.TimeSec = 0 #上报的时间戳 - self.startTimerState = 0 - self.software_version = "" - self.imei_new = "" - self.init_flag = 0 - self.net_work_state = 0 - self.dev_mac = "" - eventMesh.subscribe("get_dev_version",self.get_dev_version) - eventMesh.subscribe("set_dev_version",self.set_dev_version) - eventMesh.subscribe("get_dev_mac",self.get_dev_mac) - eventMesh.subscribe("set_dev_mac",self.set_dev_mac) - eventMesh.subscribe("get_network_state",self.get_network_state)#订阅消息 - eventMesh.subscribe("set_network_state",self.set_network_state)#订阅消息 - self.report_err_count = 0 #上报错误次数 30次 - - def set_network_state(self,event,msg): - self.net_work_state=int(msg) - - def get_network_state(self,event,msg): - return self.net_work_state - - def set_dev_version(self,event,msg): - self.software_version=str(msg) - - def get_dev_version(self,event,msg): - return self.software_version - def get_dev_mac(self,event,msg): - return self.dev_mac - def set_dev_mac(self,event,msg): - self.dev_mac = str(msg) - #要或者设备的mac 和设备的固件版本号 - - #读取存储设备的设备参数 包含设备imei 设备ID和设备私钥 同一个设备设备ID和设备私钥不变 - def read_dev_storage_param(self): - - IMEI = modem.getDevImei() #获取IMEI号 - result_ok = 0 #返回结果状态 - IFEXIST = ql_fs.path_exists("/usr/cm_ai_regulation.json") # - if IFEXIST == True: #文件存在 - fileSize = ql_fs.path_getsize('usr/cm_ai_regulation.json') - if fileSize == 0: - result_ok = 0 - print("设备信息配置文件不存在\r\n") - return result_ok - else: - try: - with open("/usr/cm_ai_regulation.json","r",encoding='utf-8') as fd: - try: - self.config_data = ujson.load(fd) #转换成json格式 - if len(self.config_data) != 0: - if "deviceId" in self.config_data:# - self.deviceId = str(self.config_data["deviceId"]) #设备ID - - #存在 - if "deviceSecret" in self.config_data: - self.deviceSecret = str(self.config_data["deviceSecret"]) - #存在 - if "TimeSec" in self.config_data: - self.TimeSec = self.config_data["TimeSec"] - if "IMEI" in self.config_data: - self.imei_new = self.config_data["IMEI"] - if str(self.imei_new) != str(IMEI): #设备IMEI不一样也要重新获取 - self.config_data["IMEI"] = str(IMEI) - self.write_config_msg_file(self.config_data) - result_ok = 0 - return result_ok - - if len(str(self.deviceId)) != 0 or len(str(self.deviceSecret)) != 0: - result_ok = 1 - else: #m没有存储数据 需要从平台上获取设备信息 - result_ok = 0 - except Exception as error: - print("配置文件的内容不是json格式 保存数据的时候格式不对\r\n") - result_ok = 0 - except Exception as error: - print("打开配置文件失败\r\n") - result_ok = 0 - else: - print("配置文件不存在!!!!\r\n") - result_ok = 0 - - print("cm_ai_regulation result_ok: {} self.config_data:{}\r\n".format(result_ok,self.config_data)) - return result_ok - #写文件信息 - def write_config_msg_file(self,msg): - - print("cm_ai_regulation msg:{}\r\n".format(msg)) - with open("/usr/cm_ai_regulation.json",'w+',encoding="utf-8") as fd: - ujson.dump(msg,fd) - def get_dev_info(self): #是否需要获取设备信息 - request_result = 0 - netWorkState = eventMesh.publish("get_network_state") - if netWorkState == 0: #网络异常 - request_result = 0 - return request_result - url = "https://ivs.chinamobiledevice.com:11443/v2/customer/device/secret/info" - #获取设备ID和设备私钥 - get_dev_storage = self.read_dev_storage_param() #返回1说明已经获取过设备信息 不需要再重新获取了只有 - if get_dev_storage == 0: #还没有获取设备信息需要获取一遍 - #获取设备设备信息地址 - IMEI = modem.getDevImei() #获取IMEI号 - - headers_T ={"Content-Type":"application/json"} - config_data ={ - "deviceNoType":"IMEI", - "deviceNo":str(IMEI), - "productKey":str(self.productKey), - "productId":str(self.productId), - } - print("ujson.dumps(config_data):{}".format(ujson.dumps(config_data))) - - result_json = "" - response = 0 - try: - response = request.post(url, data=ujson.dumps(config_data),headers =headers_T) - if response.status_code == 200: #服务器响应成功并返回数据 - try: - content = response.content - c = next(content) - result_json = ujson.loads(c) - print("response.json:{}".format(ujson.loads(c))) - code = "" - message = "" - success = False - if "code" in result_json: - code = result_json["code"] - if "message" in result_json: - message = result_json["message"] - if "success" in result_json: - success = result_json["success"] - if code == "200" and message == "成功" and success == True: - if "data" in result_json: - data_json = result_json["data"] - if "deviceId" in data_json: - self.deviceId = data_json["deviceId"] - if "deviceSecret" in data_json: - self.deviceSecret = data_json["deviceSecret"] - - if len(self.deviceId) != 0 or len(self.deviceSecret) != 0: #获取设备信息成功 保存数据 用做心跳上报 - request_result = 1 - self.config_data["deviceId"] = str(self.deviceId) - self.config_data["deviceSecret"] = str(self.deviceSecret) - IMEI = modem.getDevImei() #获取IMEI号 - self.config_data["IMEI"] = str(IMEI) - self.write_config_msg_file(self.config_data) - # { - # "code":"200", - # "message":"成功", - # "success": true, - # "data":{ - # "deviceId":"xxx", - # "deviceNo":"xxx", - # "productId":"xxx", - # "deviceSecret":"xxx" - # } - # } - else: - request_result = 0 - print("后台返回数据json字段里面没有data字段\r\n") - else: - request_result = 0 - print("后台返回数据失败 可能是设备没有加入产品或者产品私钥或者产品ID对不上\r\n") - except Exception as e: #不是json格式 - print("not json data:{}".format(e)) - request_result = 0 - else: - print("未响应 或者网络异常连接不上后台\r\n") - request_result = 0 - except Exception as e: - print("response error:{}".format(e)) - request_result = 0 - else: - request_result = 1 - return request_result - - #上报心跳数据 - def report_hr_data(self): #设备ID 设备私钥不为空才可以上报 - result_s = 0 - netWorkState = eventMesh.publish("get_network_state") - if netWorkState == 0: #网络异常 - result_s = 0 - return result_s - if len(str(self.deviceId)) != 0 and len(str(self.deviceSecret)) != 0: - - ######################################################### - # { - # "deviceId":"1927659130106658817", - # "deviceSecret":"xmvlRHbEaKPtCqHt", - # "productId":"1927646275585744898", - # "productKey":"iijgiGiTolCblSFE", - # "params": { - # "innerIp": [ - # "127.0.0.1" - # ], - # "netSpeed": "0Mbps", - # "netType": "4G", - # "platform": "RTOS", - # "sdkVersion": "ai_http_1.0", - # "firmwareVersion": "0.0.5.2", - # "imei": "860132060564184", - # "cmei": "", - # "mac": "" - # } - # } - IMEI = modem.getDevImei() #获取IMEI号 - tmp_mac = "" - if len(self.dev_mac) != 0: - tmp_mac = self.dev_mac.replace(':', '-') #将传过来的字符里面的:转换成- - #数据格式内容 - data_json = { - "deviceId":str(self.deviceId), #设备ID - "deviceSecret":str(self.deviceSecret), #设备私钥 - "productId":str(self.productId), #产品ID - "productKey":str(self.productKey), #产品私钥 - "params": { - "innerIp": [ - "127.0.0.1" - ], - "netSpeed": "0Mbps", - "netType": "4G", - "platform": "RTOS", - "sdkVersion": "ai_http_1.0", - "firmwareVersion":str(self.software_version), #设备固件版本号 - "imei": str(IMEI), #设备IMEI号 - "cmei": "", - "mac": str(tmp_mac) #设备MAC地址 - } - } - url = "https://ivs.chinamobiledevice.com:11443/v2/customer/device/report" #post请求上报数据心跳地址 - #获取设备ID和设备私钥 - try: - headers_T ={"Content-Type":"application/json"} #应用是json格式 - response = request.post(url, data=ujson.dumps(data_json),headers =headers_T) #post请求数据 - if response.status_code == 200: #服务器响应成功并返回数据 - try: - content = response.content - c = next(content) - result_json = ujson.loads(c) #将字符串转换成json格式 - print("response.json:{}".format(result_json)) - code = "" - message = "" - success = False - if "code" in result_json: - code = result_json["code"] - if "message" in result_json: - message = result_json["message"] - if "success" in result_json: - success = result_json["success"] - if code == "200" and message == "成功" and success == True: #上报成功返回 - date_tmp = utime.localtime() - time_tmp = utime.mktime(date_tmp) #获取当前时间戳 用于下次时间判断上报心跳数据 - print("date_tmp :{} time_tmp:{}\r\n".format(date_tmp,time_tmp)) - self.report_state = 1 #表示上报成功 置标志1 12小时之内不清0 12小时之后再次上报清零 - self.TimeSec = str(time_tmp) - self.config_data["TimeSec"] = str(time_tmp) - IMEI = modem.getDevImei() #获取IMEI号 - self.config_data["IMEI"] = str(IMEI) - self.write_config_msg_file(self.config_data) - # { - # "code":"200", - # "message":"成功", - # "success": true, - # "data":{ - # "deviceId":"xxx", - # "protocolTypeTime":"xxx" - # } - # } - result_s = 1 - else: - result_s = 0 - except Exception as e: #不是json格式 - print("response:{}".format(e)) - result_s = 0 - else: - result_s = 0 - except Exception as e: - print("response:{}".format(e)) - result_s = 0 - if REPORT_MODE == 1: #上报方式1 - if result_s == 1: #当前上报数据成功 开启下一个上报心跳数据定时器 - date_tmp = utime.localtime() - time_tmp = utime.mktime(date_tmp) - random_integer = urandom.randint(-15, 15) #两个时间戳相减大于24小时啧清0 - timerStart_init = HEARBEAT_REPORT_TIMER_CONFIG*1000+random_integer*1000*60 - print("timerStart_init {} random_integer:{}\r\n".format(timerStart_init,random_integer)) - self.timerStartReportHr.start(timerStart_init,0,self.timerStartReportHrCbOne)#重新开始 单次定时器 - else: #上报方式2 - if self.startTimerState == 0: - self.startTimerState = 1 - self.open_report_start_timer() - else: - result_s = 2 - self.AIRegulation_init() #重新初始化 - return result_s - return result_s - def timerStartReportHrCbOne(self,*argc): - reportStatus = self.report_hr_data() #上报心跳数据接口函数 - print("timerStartReportHrCbOne {}\r\n".format(reportStatus)) - if reportStatus == 0: #当前上报失败 开启1分钟的定时器检测 - self.timerStart.stop() - self.report_err_count = 0 - self.timerStart.start(HEART_REPORT_ERROR_TIMER*1000,1,self.oneMinTimerStartCb)#重新开始 - else: - self.timerStart.stop() - - def oneMinTimerStartCb(self,*argc): - reportStatus = self.report_hr_data() - print("oneMinTimerStartCb {}\r\n".format(reportStatus)) - if reportStatus != 0: # - self.timerStart.stop() - else: - self.report_err_count+=1 - if self.report_err_count >= HEART_REPORT_ERR_MAX_NUM: - self.report_err_count = 0 - self.timerStart.stop() - Power.powerRestart() #重启设备 可能网络异常或者哪里出错了 - - def timerStartReportHrCb(self,*argc): #上报心跳定时器回调 - date_tmp = utime.localtime() - time_tmp = utime.mktime(date_tmp) - random_integer = urandom.randint(-15, 15) #-15 - +15上报时间随机值分钟单位 *60*1000 - data = RTC().datetime() - year = data[0] - month = data[1] - day = data[2] - - if self.report_state == 1: #上报过一次 就等下一个12小时再上报一次 - #当前时间大于上次记录时间加上12小时就上报一次 - next_alarm_time = utime.localtime(int(self.TimeSec)) - old_month = next_alarm_time[1] - old_day = next_alarm_time[2] - old_year = next_alarm_time[0] - # if 1: - # if old_year != year or old_month != month or old_day != day: - # self.report_state = 0 - # self.report_hr_data() - - # else: - if (time_tmp >= self.TimeSec + HEARBEAT_REPORT_TIMER_CONFIG*1000 + random_integer*60*1000) or (old_year != year or old_month != month or old_day != day): #or old_year != year or old_month != month or old_day != day: #上次上报时间戳+12小时时间戳 大于当前时间戳 上报一次 或者年 月 日不一致也上报一次 - self.report_state = 0 - self.report_hr_data() - else: #确保12小时之内上报一次 每一个小时上 - self.report_hr_data()#上报一次心跳数据 - - def open_report_start_timer(self): #1小时的定时器 - self.timerStartReportHr.start(REALY_TIME_COUNT*1000,1,self.timerStartReportHrCb)#1小时定时 - - def timerStartCb(self,*argc):#获取设备信息定时器回调 - getDevInfoState = self.get_dev_info() - if getDevInfoState == 0:# - self.getDevInforErrCount+=1 - if self.getDevInforErrCount >= 20: - self.getDevInforErrCount = 0 - self.timerStart.stop() - else: - self.getDevInforErrCount = 0 - self.timerStart.stop() - self.report_hr_data()#上报一次心跳数据 - - def AIRegulation_init(self):#AI 纳管 初始化 获取完设备数据后立马上报心跳数据 - status = eventMesh.publish("get_factory_status") - if status == None: - status = 0 - print("get_factory_status:{}\r\n".format(status)) - if status == 0:#恢复出厂或者新的机器需要清空不必要的数据 - if self.init_flag == 0:#只初始化一次 - IFEXIST = ql_fs.path_exists("/usr/cm_ai_regulation.json") - if IFEXIST == True: - uos.remove("/usr/cm_ai_regulation.json") - self.init_flag = 1 - self.report_state = 0 - self.getDevInforErrCount = 0 - self.report_err_count = 0 - self.timerStartReportHr.stop() - - self.startTimerState = 0 - getDevInfoState = self.get_dev_info() - if getDevInfoState == 0:#开机获取失败了再等30s重新获取 获取二十次都失败就等下次重启设备获取 - self.timerStart.stop() - self.timerStart.start(30*1000,1,self.timerStartCb)#重新开始 开机10分钟内没有获取到设备信息就不获取了需要手动重启设备 - else: #设备开机获取设备信息成功 立马上报一次心跳数据 - self.report_hr_data()#上报一次心跳数据 - -aiRegulation = AIRegulation() - diff --git a/cat1modem/src/LeiNiao/china_mobile_dmp.py b/cat1modem/src/LeiNiao/china_mobile_dmp.py deleted file mode 100644 index 8128328bd..000000000 --- a/cat1modem/src/LeiNiao/china_mobile_dmp.py +++ /dev/null @@ -1,842 +0,0 @@ -import app_fota -from misc import Power -import modem -import ujson -import request -import ql_fs -import checkNet -import sys -import sim - -from queue import Queue -import usys -import _thread -import fota -import utime -import log -import uos -import osTimer -import net -import ql_fs -from ucryptolib import aes -import hashlib -from usr.EventMesh import eventMesh -# 设置日志输出级别 -log.basicConfig(level=log.INFO) -fota_log = log.getLogger("china_mobile") -import modem -import base64 -import request -import ujson -import sim - -BLOCK_SIZE = 16 -NEED_SAVE_CONFIG_FILE = 0 #是否需要保存上报次数 异常重试次数 时间戳到配置文件 0不保存 1保存 -#中国移动入库 -class chinamobile: - def __init__(self): - self.multiDayResend_timer = osTimer()#12小时定时器 多日重发的机制 - - self.multimer_count = 0#多日重发机制周期Count - self.multimer_init = 0 - self.power_on_multiflag = 0 - self.timer_count = 0 - self.timerPowerNum = 0 - self.telecom_init_state = 0#初始化状态开机检查标识 - self.isRegistOk = 0#注册成功 - self.config_data = {} - - self.__net_connected = 0 - self.regist_timeSec = 0 - self.iccid_data = "" - self.software_version = "" - self.mac_data ="" - self.address = "a.fxltsbl.com" - - self.new_address = "a.fxltsbl.com" - - self.retryInterval = 10 - self.retryNum = 1#异常重试最大次数 - self.heartBeatTime = 1440#心跳上报频率 单位分钟 - self.reportTime = 1440#最大上报频率 自注册+心跳上报的频率次数不能大于最大上报频率次数 - self.reportNum = 8 #最大上报次数 - - - self.reportTime_Sec = 0 #保存本地 保存时间戳下一次开机使用 - self.report_Num_old = 0#保存本地 - self.retryNum_old = 0#保存本地 - self.init_flag = 0#初始化状态指初始化一次 - self.retryTimer = osTimer()#异常重试定时器 - self.retpoertTimerC = osTimer()#上报次数大于下一个周期再触发 - self.heartreportTimer = osTimer()#心跳上报定时器 - - eventMesh.subscribe("get_network_state",self.get_network_state)#订阅消息 - eventMesh.subscribe("set_network_state",self.set_network_state)#订阅消息 - - eventMesh.subscribe("set_dev_version",self.set_dev_version)#订阅消息 - eventMesh.subscribe("get_dev_version",self.get_dev_version)#订阅消息 - - eventMesh.subscribe("set_dev_mac",self.set_dev_mac)#订阅消息 - eventMesh.subscribe("get_dev_mac",self.get_dev_mac)#订阅消息 - - eventMesh.subscribe("set_dmp_cm_address",self.set_dmp_cm_address)#订阅消息 - eventMesh.subscribe("get_dmp_cm_address",self.get_dmp_cm_address)#订阅消息 - - pass - - def set_dmp_cm_address(self,event,msg):#设置网络是否正常 - self.address = str(msg) - self.new_address = str(msg) - - def get_dmp_cm_address(self,event,msg):#获取网络是否正常 - return self.address - - def set_network_state(self,event,msg):#设置网络是否正常 - self.__net_connected = int(msg) - - def get_network_state(self,event,msg):#获取网络是否正常 - return self.__net_connected - - def set_dev_version(self,event,msg):#获取固件版本号 - self.software_version = str(msg) - - def get_dev_version(self,event,msg):#获取固件版本号 - return self.software_version - - def set_dev_mac(self,event,msg):#设置BLE MAC地址 - self.mac_data = str(msg) - - def get_dev_mac(self,event,msg):#获取BLE MAC地址 - return self.mac_data - - def sha256_encode(self,key,data):#对数据进行AES256加密 - h = hashlib.sha256() - h.update(key) - crypto_key = h.digest() - #IV是偏移量 默认16个十六进制00 - IV = b"\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" - - crypto = aes(crypto_key, 2, IV) - - #进行PKCS7Padding填充。 - block_num = len(data) // BLOCK_SIZE+1 - k = len(data) % BLOCK_SIZE - j = len(data) // BLOCK_SIZE - padding = BLOCK_SIZE - k - i = 0 - szDataIn = bytearray(data) - # print(szDataIn) - szDataIn = szDataIn+bytearray(block_num * BLOCK_SIZE -len(data)) - # print(szDataIn) - - for i in range(padding): - szDataIn[j * BLOCK_SIZE + k + i] = padding - # print("szDataIn:{} {} {}\r\n".format(szDataIn,j * BLOCK_SIZE + k + i,padding)) - - indata = bytes(szDataIn) - enc = crypto.encrypt(indata) - #最后将加密AES256的数据转换成base64的编码格式 - msg_tmp = base64.b64encode(enc) - data_encode = msg_tmp.decode('ascii')#转换成字符串 - return data_encode - - #写文件信息 - def write_config_msg_file(self,msg): - - print("write_config_msg_file msg:{}\r\n".format(msg)) - with open("/usr/china_mobie_config.json",'w+',encoding="utf-8") as fd: - ujson.dump(msg,fd) - #读文件信息 - def read_config_msg_file(self): - IFEXIST = ql_fs.path_exists("/usr/china_mobie_config.json") #不存在 - if IFEXIST == True: - with open("/usr/china_mobie_config.json","r",encoding='utf-8') as fd: - self.config_data = ujson.load(fd) - print("read_config_msg_file self.config_data:{}\r\n".format(self.config_data)) - - def china_mobile_first_get_config(self,url): - ##############第一步#######获取采集规则配置####### - # brand = "KUMI"#品牌 - # model = "Watch Soar"#型号 - # NWO N9 - brand = "NWO"#品牌 - model = "NWO N9"#型号 - - - appKey = "M100001131"#不需要就填***appKey 厂商标识 - sdkVersion = "***"#非SDK传输 - apiVersion = "4.0.1"#接口定义 固定4.0.1 - apiType = "I"#非Android - templateId = "TY000014"#根据产品分类在平台查询 - IMEI = modem.getDevImei() - imei1 = IMEI#IMEI1或者CMEI - imei2 = "***" - configType = "autoRegist"#自注册填autoRegist 心跳配置传输 keepAlive - preField1 = "***"#预留字段1 - preField2 = "***"#预留字段2 - preField3 = "***"#预留字段3 - - - - #配置拉取数据格式 - config_data = {"endpoint":"{}||{}||{}||{}||{}||{}||{}||{}||{}||{}||{}||{}".format(brand,model,appKey,sdkVersion,apiVersion,apiType,templateId,imei1,imei2,preField1,preField2,preField3),"configType":"{}".format(configType)} - - #配置拉取数据上报地址 - url = "https://{}//dm-multi-accept/multi/getMultiTerminalConfig".format(self.address) - headers_T ={"Content-Type":"application/json"} - print("ujson.dumps(config_data):{}".format(ujson.dumps(config_data))) - request_result = 0 - result_json = "" - try: - response = request.post(url, data=ujson.dumps(config_data),headers =headers_T) - request_result = 1 - if response.status_code == 200:#服务器响应成功并返回数据 - content = response.content - c = next(content) - result_json = ujson.loads(c) - print("response.json:{}".format(ujson.loads(c))) - except Exception as e: - print("response:{}".format(e)) - request_result = 0 - - - if request_result == 0: #请求出错了 - return "" - - - return result_json - - - def first_get_config_json(self):#开机上电获取配置信息 - getresult = 0 - print("first_get_config_json false",self.__net_connected) - network_state = eventMesh.publish("get_network_state") - if network_state != 1:#网络不正常 - print("网络不正常") - return getresult - url = "https://{}//dm-multi-accept/multi/getMultiTerminalConfig".format(self.address) - result_json = self.china_mobile_first_get_config(url) - - if result_json == "": - print("result_json == NULL") - return 0 - # { - # "reportTime":1440, - # "reportNum":8, - # "heartBeatTime":1440, - # "retryInterval":10, - # "retryNum":1 - # }#默认采集规则配置 - #默认参数 - - retryInterval = self.retryInterval - heartBeatTime = self.heartBeatTime - reportTime = self.reportTime - reportNum = self.reportNum - retryNum = self.retryNum - new_address = self.address - print("get_config_json old:{} {} {} {} {} {}\r\n".format(reportTime,heartBeatTime,reportNum,retryInterval,retryNum,new_address)) - if "rspCode" in result_json:#返回结课code 0000成功 其他参考文档 - rspCode = result_json["rspCode"] - rspCode_zh = result_json["rspMsg"] - if rspCode == "0000" or rspCode_zh == "成功": - getresult = 1 - if "result" in result_json: - msg_data = result_json["result"] #采集规则配置信息 - if "ruleConfig" in msg_data:#不存在就是空 - msg_data = msg_data["ruleConfig"] - msg_data = ujson.loads(msg_data)#转换成json格式 - if "reportTime" in msg_data: - self.reportTime = int(msg_data["reportTime"]) - if "heartBeatTime" in msg_data: - self.heartBeatTime = int(msg_data["heartBeatTime"]) - if "reportNum" in msg_data: - self.reportNum = int(msg_data["reportNum"]) - if "retryInterval" in msg_data: - self.retryInterval = int(msg_data["retryInterval"]) - if "retryNum" in msg_data: - self.retryNum = int(msg_data["retryNum"]) - if "address" in msg_data: - self.address = str(msg_data["address"]) - print("get_config_json new:{} {} {} {} {}\r\n".format(self.reportTime,self.heartBeatTime,self.reportNum,self.retryInterval,self.retryNum,self.address)) - - print("true") - else: - print("false") - - if getresult == 1:#请求配置文件成功 - if (self.retryInterval != retryInterval) or (self.retryNum != retryNum) or (self.heartBeatTime != heartBeatTime) or\ - (self.reportTime != reportTime) or (self.reportNum != reportNum) or (self.address != new_address): - #重新走注册流程 清空报错次数 记录当前时间戳 - print("new config not old config!!!\r\n")#拉取的配置文件和保存本地的不一致 - self.config_data["reportTime"] = self.reportTime - self.config_data["heartBeatTime"] = self.heartBeatTime - self.config_data["reportNum"] = self.reportNum - self.config_data["retryNum"] = self.retryNum - self.config_data["retryInterval"] = self.retryInterval - # self.config_data["address"] = str(self.address) - if new_address != self.address : #服务器下发和本地不一致需要用服务器下发的最新的IP 并把这个IP发送到BLE保存 - eventMesh.publish("send_dmp_cm_address_toBLE",str(self.address)) - - self.write_config_msg_file(self.config_data) #保存新的配置文件 - return 2 - return getresult #1 获取成功 0 获取失败 - - - def china_mobile_autoregist_infor(self,url,configType_data): - #########################第二步############自注册上报################### - #设备自注册上报 - dev_autoregist_result = 0 #默认是1成功 0是失败 - network_state = eventMesh.publish("get_network_state") - if network_state != 1:#网络不正常 - return dev_autoregist_result - #密钥 - key = "03816c12b6d24182981325e57670e478" - - brand = "{}".format("NWO")#品牌 - - # model = "Watch Soar"#型号 - model = "NWO N9"#型号 - - # -*- coding: UTF-8 -*- -#示例 - - - appKey = "M100001188"#不需要就填***appKey 厂商标识 - sdkVersion = "***"#非SDK传输 - apiVersion = "4.0.1"#接口定义 固定4.0.1 - apiType = "I"#非Android - templateId = "TY000014"#根据产品分类在平台查询 - IMEI = modem.getDevImei() - imei1 = IMEI#IMEI1或者CMEI - imei2 = "***" - configType = configType_data#自注册填autoRegist 心跳配置传输 keepAlive 暂时不用 - preField1 = "***"#预留字段1 - preField2 = "***"#预留字段2 - preField3 = "***"#预留字段3 - - - batteryCapacityCurr="***" - - - #不需要加密 #||分隔符不能超过11个 否则平台会解析出错 - config_data = "{}||{}||{}||{}||{}||{}||{}||{}||{}||{}||{}||{}".format(brand,model,appKey,sdkVersion,apiVersion,apiType,templateId,imei1,imei2,configType,preField1,preField2) - - #需要加密的数据明文 - mac_data="***"#eventMesh.publish("get_dev_mac") - - if (len(mac_data) == 0):#获取失败了 - mac_data = "***" - - imsi = sim.getImsi() - print("Get IMSI : {}".format(imsi)) - - MAC = mac_data - # base64_MAC = sha256_encode(key,MAC) - RAM = "***" - # base64_RAM = sha256_encode(key,RAM) - ROM = "***" - # base64_ROM = sha256_encode(key,ROM) - CPU = "***" - # base64_CPU = sha256_encode(key,CPU) - GPU = "***" - - imsi2="***" - - sysVersion = "***" - - board = "***" - - # base64_sysVersion = sha256_encode(key,sysVersion) - if (len(self.software_version) == 0):#获取失败了 - # self.software_version = "Watch Soar V1.0" - self.software_version = "NWO N9 V1.0" - - softwareVer = "NWO N9 V1.0" - # base64_softwareVer = sha256_encode(key,softwareVer) - # softwareName ="FD230" - softwareName ="***" - - sn="***" - - batteryCapacity="***" - - phoneNumber="***" - # base64_softwareName = sha256_encode(key,softwareName) - volte="***" #0:开;1:关;-1:不支持 - # base64_volte = sha256_encode(key,volte) - netType="***" - # base64_netType = sha256_encode(key,netType) - - resolution="466*466" - # base64_resolution = sha256_encode(key,resolution) - - screenSize = "1.43" - # base64_screenSize = sha256_encode(key,screenSize) - wearingStatus ="未佩戴" - # base64_wearingStatus = sha256_encode(key,wearingStatus) - - networkStatus ="***"#1:连接;0:未连接;-1:不支持 - - routerMac = "***" - - wifiMac = "***" - # base64_networkStatus = sha256_encode(key,networkStatus) - encyr_data = [MAC,RAM,ROM,CPU,sysVersion,softwareVer,softwareName,volte,netType,resolution,screenSize,wearingStatus,networkStatus,routerMac,wifiMac,imsi,GPU,imsi2,board,sn,batteryCapacity,phoneNumber,batteryCapacityCurr] - print(len(encyr_data)) - encyr_new_data=[] - for i in range(len(encyr_data)): - tmp_data = self.sha256_encode(key,encyr_data[i]) - encyr_new_data.append(tmp_data) - - # deviceInfo_tmp = { - # "bluetoothMac":"{}".format(base64_MAC),#mac地址可填可不填 - # "ram":"{}".format(base64_RAM),#ram信息 - # "rom":"{}".format(base64_ROM),#rom信息 - # "cpu":"{}".format(base64_CPU),#cpu信息 - # "sysVersion":"{}".format(base64_sysVersion),#操作系统版本号 - # "softwareVer":"{}".format(base64_softwareVer),#固件版本号 - # "softwareName":"{}".format(base64_softwareName),#固件名称 - # "volte":"{}".format(base64_volte),#volte开关 默认是开 - # "netType":"{}".format(base64_netType),#当前网络类型4G - # "resolution":"{}".format(base64_resolution),#屏幕分辨率466*466 - # "screenSize":"{}".format(base64_screenSize),#屏幕尺寸默认1.43 - # "wearingStatus":"{}".format(base64_wearingStatus),#佩戴状态不支持 - # "networkStatus":"{}".format(base64_networkStatus)#网络状态 - - # } - if configType_data=="keepAlive": - print("keepAlive :") - deviceInfo_tmp = { - "bluetoothMac":"{}".format(encyr_new_data[0]),#mac地址可填可不填 - "ram":"{}".format(encyr_new_data[1]),#ram信息 - "rom":"{}".format(encyr_new_data[2]),#rom信息 - "cpu":"{}".format(encyr_new_data[3]),#cpu信息 - "sysVersion":"{}".format(encyr_new_data[4]),#操作系统版本号 - "softwareVer":"{}".format(encyr_new_data[5]),#固件版本号 - "softwareName":"{}".format(encyr_new_data[6]),#固件名称 - "volte":"{}".format(encyr_new_data[7]),#volte开关 默认是开 - "netType":"{}".format(encyr_new_data[8]),#当前网络类型4G - "resolution":"{}".format(encyr_new_data[9]),#屏幕分辨率466*466 - "screenSize":"{}".format(encyr_new_data[10]),#屏幕尺寸默认1.43 - "wearingStatus":"{}".format(encyr_new_data[11]),#佩戴状态不支持 - "networkStatus":"{}".format(encyr_new_data[12]),#网络状态 - "routerMac":"{}".format(encyr_new_data[13]), #路由mac地址 *** - "mac":"{}".format(encyr_new_data[14]), #wifi mac - "imsi":"{}".format(encyr_new_data[15]), #imsi - "gpu":"{}".format(encyr_new_data[16]), #GPU - "imsi2":"{}".format(encyr_new_data[17]), #imsi2 - "board":"{}".format(encyr_new_data[18]), #board - "sn":"{}".format(encyr_new_data[19]), #board - "batteryCapacity":"{}".format(encyr_new_data[20]), #board - "phoneNumber":"{}".format(encyr_new_data[21]), #board - "batteryCapacityCurr":"{}".format(encyr_new_data[22]), #board - } - else : - deviceInfo_tmp = { - "bluetoothMac":"{}".format(encyr_new_data[0]),#mac地址可填可不填 - "ram":"{}".format(encyr_new_data[1]),#ram信息 - "rom":"{}".format(encyr_new_data[2]),#rom信息 - "cpu":"{}".format(encyr_new_data[3]),#cpu信息 - "sysVersion":"{}".format(encyr_new_data[4]),#操作系统版本号 - "softwareVer":"{}".format(encyr_new_data[5]),#固件版本号 - "softwareName":"{}".format(encyr_new_data[6]),#固件名称 - "volte":"{}".format(encyr_new_data[7]),#volte开关 默认是开 - "netType":"{}".format(encyr_new_data[8]),#当前网络类型4G - "resolution":"{}".format(encyr_new_data[9]),#屏幕分辨率466*466 - "screenSize":"{}".format(encyr_new_data[10]),#屏幕尺寸默认1.43 - "wearingStatus":"{}".format(encyr_new_data[11]),#佩戴状态不支持 - "networkStatus":"{}".format(encyr_new_data[12]),#网络状态 - "routerMac":"{}".format(encyr_new_data[13]), #路由mac地址 *** - "mac":"{}".format(encyr_new_data[14]), #wifi mac - "imsi":"{}".format(encyr_new_data[15]), #imsi - "gpu":"{}".format(encyr_new_data[16]), #GPU - "imsi2":"{}".format(encyr_new_data[17]), #imsi2 - "board":"{}".format(encyr_new_data[18]), #board - "sn":"{}".format(encyr_new_data[19]), #board - "batteryCapacity":"{}".format(encyr_new_data[20]), #board - "phoneNumber":"{}".format(encyr_new_data[21]), #board - - } - json_data = ujson.dumps(deviceInfo_tmp)#需要转义的字符串 - - - - - send_autoregist = { - "endpoint":config_data, - "deviceInfo":"{}".format(json_data) - } - - test_data = ujson.dumps(send_autoregist)#需要转义的字符串 - print("test_data:{}".format(test_data)) - headers_T ={"Content-Type":"application/json"} - request_result = 0 - print("shangbao address:{}\r\n".format(url)) - try: - response = request.post(url, data=ujson.dumps(send_autoregist),headers =headers_T) - request_result = 1 - result_json = "" - print("response success:response.status_code{}\r\n".format(response.status_code)) - if response.status_code == 200: - dev_autoregist_result = 1 - content = response.content - c = next(content) - result_json = ujson.loads(c)#解析成json格式 - else: - dev_autoregist_result = 0 - - - if "rspCode" in result_json: - rspCode = result_json["rspCode"] - rspMsg = result_json["rspMsg"] - print("response success:rspCode{} {}\r\n".format(rspMsg,rspCode)) - if rspCode == "0000" or rspMsg == "成功": - dev_autoregist_result = 1 - else: - dev_autoregist_result = 0 - else: - dev_autoregist_result = 0 - print("response success:\r\n") - return dev_autoregist_result - - except Exception as e: - print("response:{}".format(e)) - request_result = 0 - - if request_result == 0:#请求出错了 - dev_autoregist_result = 0 - return dev_autoregist_result - - - def read_config_file(self):#获取配置文件,一开机是没有配置文件的 - - self.read_config_msg_file()#读文件信息 - if self.config_data != {}: - if "retryInterval" in self.config_data: - self.retryInterval = int(self.config_data["retryInterval"]) - if "retryNum" in self.config_data: - self.retryNum = int(self.config_data["retryNum"]) - if "heartBeatTime" in self.config_data: - self.heartBeatTime = int(self.config_data["heartBeatTime"]) - if "reportTime" in self.config_data: - self.reportTime = int(self.config_data["reportTime"]) - if "reportNum" in self.config_data: - self.reportNum = int(self.config_data["reportNum"]) - if "address" in self.config_data: - self.address = str(self.config_data["address"]) - - #终端本地需要保存本地的数据 - # self.reportTime_Sec = 0 #保存本地 保存时间戳下一次开机使用 - # self.report_Num_old = 0#保存本地 - # self.retryNum_old = 3#保存本地 - if NEED_SAVE_CONFIG_FILE == 1: - if "reportTime_Sec" in self.config_data: - self.reportTime_Sec = int(self.config_data["reportTime_Sec"]) - if "report_Num_old" in self.config_data: - self.report_Num_old = int(self.config_data["report_Num_old"]) - if "retryNum_old" in self.config_data: - self.retryNum_old = int(self.config_data["retryNum_old"]) - - print("read_config_file self.config_data:{} ".format(self.config_data)) - def retryTimerCb(self,*argc):#异常重试机制定时器回调 - #保存本地 - self.retryNum_old+=1 #1 self.reportNum = 1 - if NEED_SAVE_CONFIG_FILE == 1: - self.config_data["retryNum_old"] = self.retryNum_old - self.write_config_msg_file(self.config_data) - print("retryTimerCb self.retryNum_old:{}\r\n".format(self.retryNum_old)) - self.search_report_num_step() - pass - def reportTimerIntervalError(self): - self.retryNum_old = 0#保存 - self.report_Num_old = 0#保存 定时清空数据 - date_tmp = utime.localtime() - time_tmp = utime.mktime(date_tmp)#获取时间戳 - self.reportTime_Sec = time_tmp+self.reportTime*60 #(记录时间+reportTime)标识旧的时间 - if NEED_SAVE_CONFIG_FILE == 1: - self.config_data["reportTime_Sec"] = self.reportTime_Sec - self.config_data["retryNum_old"] = self.retryNum_old - self.config_data["report_Num_old"] = self.report_Num_old - self.write_config_msg_file(self.config_data) - self.search_report_num_step()#重新查询 - pass - def error_retryNum_step(self):#判读报错次数是否大于self.retryNum 步骤 - print("error_retryNum_step:self.retryNum_oldd:{} self.retryNum:{} self.reportTime:{}\r\n".format(self.retryNum_old,self.retryNum,self.reportTime)) - - if self.retryNum_old >= self.retryNum and self.retryNum_old != 0:#self.retryNum_old > 10#结束HTPP下一个最大频率周期时间间隔流程 - print("error_retryNum_step:self.retryNum_old >= self.retryNum\r\n") - self.reportTimerIntervalError() - # self.retpoertTimerC.start(self.reportTime*60*1000,0,self.reportTimerIntervalError)#重新开始 需要清空数据 - else: - get_result = self.first_get_config_json()#发起向服务端请求配置信息下发到设备端 - if get_result == 0:#获取失败或者没有网络 走异常重试机制 - self.report_Num_old+=1#请求错误请求失败都要保存上报次数 - if NEED_SAVE_CONFIG_FILE == 1: - self.config_data["report_Num_old"] = self.report_Num_old - self.write_config_msg_file(self.config_data) - print("error_retryNum_step:self.retryTimer.start :{} {} {}\r\n".format(self.retryInterval,self.report_Num_old,self.reportNum)) - self.checkMaxReportNum()#检查上报最大次数 - self.retryTimer.start(self.retryInterval*60*1000,0,self.retryTimerCb) - pass - else:# - - #第一步触发自注册 - if get_result == 2:#请求的配置文件和本地不一致重启 - self.retryNum_old = 0#保存本地 配置文件不同重启模组 清空异常重试的报错次数 - # self.report_Num_old+=1#请求错误请求失败都要保存上报次数 - # if NEED_SAVE_CONFIG_FILE == 1: - # self.config_data["report_Num_old"] = self.report_Num_old - # self.config_data["retryNum_old"] = self.retryNum_old - # self.write_config_msg_file(self.config_data) - - self.china_mobile_init_param()#重新走流程读配置文件->判断上报总数->判断报错次数->获取配置文件 - self.search_report_num_step() - return - - self.report_Num_old+=1#请求错误请求失败都要保存上报次数 - if NEED_SAVE_CONFIG_FILE == 1: - self.config_data["report_Num_old"] = self.report_Num_old - self.write_config_msg_file(self.config_data) - print("error_retryNum_step self.report_Num_old:{}\r\n".format(self.report_Num_old)) - if self.checkMaxReportNum() == 1:#检查上报最大次数 - self.search_report_num_step()#重新查询 - return - report_result = self.onself_regist_report_cb()#注册 - - #第二步触发心跳上报次数 - if report_result == 1:#注册完成之后根据服务端下发的配置 间隔上报心跳 - self.heartreportTimer.stop() - self.heartreportTimer.start(self.heartBeatTime*60*1000,1,self.heartreportTimerCb) - - def heartreportTimerCb(self,*argc): - self.heart_report_cb() - - def retpoertTimerClear(self): - date_tmp = utime.localtime() - time_tmp = utime.mktime(date_tmp)#获取时间戳 - print("retpoertTimerCB time_tmp -self.reportTime_Sec:{} time_tmp :{} self.reportTime_Sec:{}\r\n".format(self.reportTime_Sec-time_tmp,time_tmp,self.reportTime_Sec)) - self.report_Num_old = 0#清空上报总次数 #保存本地 - self.retryNum_old = 0#清空上报错误次数 #保存本地 - self.reportTime_Sec = time_tmp+self.reportTime*60#记录当前时间+reportTime的时间用于下次错判断 - if NEED_SAVE_CONFIG_FILE == 1: - self.config_data["reportTime_Sec"] = self.reportTime_Sec - self.config_data["retryNum_old"] = self.retryNum_old - self.config_data["report_Num_old"] = self.report_Num_old - self.write_config_msg_file(self.config_data) - self.retpoertTimerC.stop()#关闭定时器 - self.error_retryNum_step()#清空报错次数重新走判断报错次数 - pass - def check_current_time(self):#检测当前时间是否超过self.reportTime 超过清空报错次数 清空上报总次数 - # - date_tmp = utime.localtime() - currentTime = utime.mktime(date_tmp)#获取时间戳 - reportTime = self.reportTime_Sec - print("check_current_time:time_tmp - self.reportTime_Sec:{} currentTime:{} self.reportTime*60:{}".format(reportTime, currentTime,self.reportTime*60)) - if currentTime >= reportTime:#当前时间是否大于上次记录的时间 currentTime >= reportTime 直接清空 不超过定时清空数据不再上报数据 - #在规定时间内上报次数大于8次就要等下一个10分钟 或者1440分了开个定时器 - print("currentTime >= reportTime:\r\n") - self.retpoertTimerClear() - else:#否则次数置零,记录一下(当前时间+reportTime)时间 - time_count = (reportTime - currentTime) #self.reportTime_Sec永远比time_tmp大 - print("time_count:{}".format(time_count)) - print("currentTime < reportTime:\r\n") - self.retpoertTimerC.start(time_count*1000,0,self.retpoertTimerCB) - - - # 第一次请求的时候,记录一下(当前时间+reportTime)时间与次数,如果次数小于最大次数,直接返回true。 - # 如果次数大于最大次数,则判断一下当前时间是否超过上次设置的时间,如果超过了,则表示超过最大次数;否则次数置零,记录一下(当前时间+reportTime)时间。 - def retpoertTimerCB(self,*argc):#按照剩余的时间做定时器 - date_tmp = utime.localtime() - time_tmp = utime.mktime(date_tmp)#获取时间戳 - print("retpoertTimerCB time_tmp -self.reportTime_Sec:{} time_tmp :{} self.reportTime_Sec:{}\r\n".format(self.reportTime_Sec-time_tmp,time_tmp,self.reportTime_Sec)) - self.report_Num_old = 0#清空上报总次数 #保存本地 - self.retryNum_old = 0#清空上报错误次数 #保存本地 - self.reportTime_Sec = time_tmp+self.reportTime*60#记录当前时间+reportTime的时间用于下次错判断 - if NEED_SAVE_CONFIG_FILE == 1: - self.config_data["reportTime_Sec"] = self.reportTime_Sec - self.config_data["retryNum_old"] = self.retryNum_old - self.config_data["report_Num_old"] = self.report_Num_old - self.write_config_msg_file(self.config_data) - self.error_retryNum_step()#清空报错次数重新走判断报错次数 - - - def search_report_num_step(self):#查询上报总数步骤 - print("search_report_num_step:self.retryInterval:{} self.retryNum:{}".format(self.report_Num_old,self.reportNum)) - if self.report_Num_old >= self.reportNum and self.report_Num_old != 0:#查询上报的总次数是否大于存储上报总次数 - self.check_current_time()#检测当前时间是否超过self.reportTime 超过清空报错次数 清空上报总次数 - else:# - date_tmp = utime.localtime() - time_tmp = utime.mktime(date_tmp)#获取时间戳 - self.reportTime_Sec = time_tmp+self.reportTime*60 #(记录时间+reportTime)标识旧的时间 - if NEED_SAVE_CONFIG_FILE == 1: - self.config_data["reportTime_Sec"] = self.reportTime_Sec - self.write_config_msg_file(self.config_data) - self.error_retryNum_step()#查询上报错误次数 - - def search_report_num_step_init(self):#查询上报总数步骤 - print("search_report_num_step:self.retryInterval:{} self.retryNum:{} self.reportTime_Sec:{}\r\n".format(self.report_Num_old,self.reportNum,self.reportTime_Sec)) - if self.report_Num_old >= self.reportNum and self.report_Num_old != 0:#查询上报的总次数是否大于存储上报总次数 - self.check_current_time()#检测当前时间是否超过self.reportTime 超过清空报错次数 清空上报总次数 - else:# - date_tmp = utime.localtime() - time_tmp = utime.mktime(date_tmp)#获取时间戳 - if self.reportTime_Sec == 0: - self.reportTime_Sec = time_tmp+self.reportTime*60 #(记录时间+reportTime)标识旧的时间 - if NEED_SAVE_CONFIG_FILE == 1: - self.config_data["reportTime_Sec"] = self.reportTime_Sec - self.write_config_msg_file(self.config_data) - self.error_retryNum_step()#查询上报错误次数 - - - def reportTimerIntervalCb(self,*argc):#最大上报频率时间间隔定时器 要清空数据 - print("reportTimerIntervalCb\r\n") - self.search_report_num_step()#重新查询 - - def onself_regist_report_cb(self):#自注册上报信息 - date_tmp = utime.localtime() - time_tmp = utime.mktime(date_tmp)#获取时间戳 - print("onself_regist_report_cb:self.report_Num_old:{} self.reportNum:{} self.reportTime_Sec:{}\r\n".format(self.report_Num_old,self.reportNum,self.reportTime_Sec)) - self.report_Num_old+=1 ##保存本地 - if NEED_SAVE_CONFIG_FILE == 1: - self.config_data["report_Num_old"] = self.report_Num_old - self.write_config_msg_file(self.config_data) - #设备自注册上报信息 - print("onself_regist_report_cb:self.china_mobile_autoregist_infor") - url = "https://{}//dm-multi-accept/multi/acceptMultiTerminalRegInfo".format(self.address)#上报地址 - confing_type = "autoRegist"#上报传输配置 - result = self.china_mobile_autoregist_infor(url,confing_type) - - - if result != 1: - #触发异常重试进制 - self.checkMaxReportNum()#检查上报最大次数 - if self.checkMaxRetryNum() == 1: - print("retryTimerCb timer22")#异常重试过多重新间隔reportTime注册流程 关闭所有的定时器 - self.search_report_num_step()#重新查询 - return 0 - self.retryTimer.start(self.retryInterval*60*1000,0,self.retryTimerCb) - return 0 - else: - if self.checkMaxReportNum() == 1: - self.search_report_num_step()#重新查询 - # self.retpoertTimerC.start(self.reportTime*60*1000,0,self.reportTimerIntervalCb) - return 0 - else: - return 1 - def checkMaxReportNum(self):#检查上报次数最大值 - print("checkMaxReportNum :self.reportNum :{} self.report_Num_old:{}\r\n".format(self.reportNum,self.report_Num_old)) - if self.report_Num_old >= self.reportNum and self.report_Num_old != 0: - self.retryTimer.stop()#关定时器 - self.retpoertTimerC.stop() - self.heartreportTimer.stop() - return 1 - else: - return 0 - - def checkMaxRetryNum(self):#检查上报错误次数 - print("checkMaxRetryNum :self.retryNum:{} self.retryNum_old:{}\r\n".format(self.retryNum_old,self.retryNum)) - if self.retryNum_old >= self.retryNum and self.retryNum_old != 0:#异常重试过多重新间隔reportTime注册流程 - self.retryTimer.stop()#关定时器 - self.retpoertTimerC.stop() - self.heartreportTimer.stop()#关闭心跳定时器 - return 1 - else: - return 0 - - def heart_report_cb(self):#心跳上报信息 - - date_tmp = utime.localtime() - time_tmp = utime.mktime(date_tmp)#获取时间戳 - self.report_Num_old+=1#保存本地 - if NEED_SAVE_CONFIG_FILE == 1: - self.config_data["report_Num_old"] = self.report_Num_old - self.write_config_msg_file(self.config_data) - print("heart_report_cb:self.report_Num_old:{} self.reportNum:{} self.reportTime:{} self.reportTime_Sec:{}\r\n".format(self.report_Num_old,self.reportNum,self.reportTime,self.reportTime_Sec)) - - url = "https://{}//dm-multi-accept/multi/acceptMultiTerminalHeartbeatInfo".format(self.address)#上报地址 - confing_type = "keepAlive"#上报传输配置 - print("heart_report_cb:self.china_mobile_autoregist_infor") - result = self.china_mobile_autoregist_infor(url,confing_type) - if result != 1: - #触发异常重试进制 - self.checkMaxReportNum() - if self.checkMaxRetryNum() == 1:#异常重试过多重新间隔reportTime注册流程 - self.search_report_num_step()#重新查询 - return - self.retryTimer.start(self.retryInterval*60*1000,0,self.retryTimerCb) - return - if self.checkMaxReportNum() == 1:#上报次数大于8次 - self.search_report_num_step()#重新查询 - return - - def china_mobile_init_param(self):#初始化 - - status = eventMesh.publish("get_factory_status") - if status == None: - status = 0 - print("get_factory_status:{}\r\n".format(status)) - if status == 0:#恢复出厂或者新的机器需要清空不必要的数据 - if self.init_flag == 0:#只初始化一次 - IFEXIST = ql_fs.path_exists("/usr/telecom_config.json") #不存在 - if IFEXIST == True: - uos.remove("/usr/telecom_config.json") - isStorage = ql_fs.path_exists("/usr/china_mobie_config.json")#移动入库的配置文件移除 - if isStorage == True: - uos.remove("/usr/china_mobie_config.json") - #先清空再写入默认参数 - self.init_flag = 1 - self.retryInterval = 10 - self.retryNum = 1#异常重试最大次数 - self.heartBeatTime = 1440#心跳上报频率 单位分钟 - self.reportTime = 1440#最大上报频率 自注册+心跳上报的频率次数不能大于最大上报频率次数 - self.reportNum = 8 #最大上报次数 - - self.reportTime_Sec = 0 #保存本地 保存时间戳下一次开机使用 - self.report_Num_old = 0#保存本地 - self.retryNum_old = 0#保存本地 - get_address = eventMesh.publish("get_dmp_cm_address") - if get_address == None:#获取出来地址是空的使用适配地址 - self.address = "a.fxltsbl.com"#默认 - else: #恢复出厂使用BLE传送过来的地址 - self.address = str(eventMesh.publish("get_dmp_cm_address")) - - self.config_data["retryInterval"] = self.retryInterval - self.config_data["retryNum"] = self.retryNum - self.config_data["heartBeatTime"] = self.heartBeatTime - self.config_data["reportTime"] = self.reportTime - self.config_data["reportNum"] = self.reportNum - # self.config_data["address"] = self.address - if NEED_SAVE_CONFIG_FILE == 1: - self.config_data["reportTime_Sec"] = self.reportTime_Sec - self.config_data["report_Num_old"] = self.report_Num_old - self.config_data["retryNum_old"] = self.retryNum_old - - self.write_config_msg_file(self.config_data) - #读配置文件 - self.read_config_file()#只读一下配置文件 - - self.retryTimer.stop()#关定时器 - self.retpoertTimerC.stop() - self.heartreportTimer.stop() - def china_mobile_init(self): - self.china_mobile_init_param() - #查询上报总数流程 - self.search_report_num_step_init() - - - - -chinamobileCb = chinamobile() - - - - - - - - - - - - diff --git a/cat1modem/src/LeiNiao/httpfiledownload.py b/cat1modem/src/LeiNiao/httpfiledownload.py index b5bc29f0a..3f461b5ad 100644 --- a/cat1modem/src/LeiNiao/httpfiledownload.py +++ b/cat1modem/src/LeiNiao/httpfiledownload.py @@ -276,8 +276,8 @@ class HttpFileDownloadTcpServer: # 初始化 SPI 对象 if spi_obj is None: # 默认创建 SPI(1, 0, 1): 通道1, 模式0, 时钟频率1(1.625MHz) - self.spi_obj = SPI(2, 0, 9) - clog.info("Created default SPI object: SPI(2, 0, 9)") + self.spi_obj = SPI(2, 0, 8) + clog.info("Created default SPI object: SPI(2, 0, 8)") else: self.spi_obj = spi_obj clog.info("Using provided SPI object") @@ -563,8 +563,8 @@ class HttpFileDownloadSpiTest: # 默认创建SPI(2, 0, 1): 通道2, 模式0, 时钟频率1(26MHz) # 注意:Python端SPI2对应MCU端SPI1 # 频率参数1表示26MHz(根据实际SPI驱动实现) - self.spi_obj = SPI(2, 0, 9) - clog.info("Created default SPI object: SPI(2, 0, 9) for 26MHz") + self.spi_obj = SPI(2, 0, 8) + clog.info("Created default SPI object: SPI(2, 0, 8) for 26MHz") else: self.spi_obj = spi_obj clog.info("Using provided SPI object") diff --git a/cat1modem/src/LeiNiao/main_init.py b/cat1modem/src/LeiNiao/main_init.py index 7e8138a02..f5525c8d6 100644 --- a/cat1modem/src/LeiNiao/main_init.py +++ b/cat1modem/src/LeiNiao/main_init.py @@ -12,22 +12,7 @@ try: except Exception as e: print("检测文件失败!!!!!!!{}\r\n",'eventMesh') - try: - from usr.telecom_dmp import telecomDmpCb - print("检测文件成功!!!!!!!\r\n",'telecomDmpCb') - except Exception as e: - print("检测文件失败!!!!!!!{}\r\n",'telecomDmpCb') - - try: - from usr.china_mobile_dmp import chinamobileCb - print("检测文件成功!!!!!!!\r\n",'chinamobileCb') - except Exception as e: - print("检测文件失败!!!!!!!{}\r\n",'chinamobileCb') - try: - from usr.china_mobile_ai_regulation import aiRegulation - print("检测文件成功!!!!!!!\r\n",'aiRegulation') - except Exception as e: - print("检测文件失败!!!!!!!{}\r\n",'aiRegulation') + try: from usr.cat1Service import cat1service print("检测文件成功!!!!!!!\r\n",'cat1service') diff --git a/cat1modem/src/LeiNiao/spi_protocol.py b/cat1modem/src/LeiNiao/spi_protocol.py new file mode 100644 index 000000000..5efb065f0 --- /dev/null +++ b/cat1modem/src/LeiNiao/spi_protocol.py @@ -0,0 +1,560 @@ +#!/usr/bin/env python3 +# -*- coding: utf-8 -*- +""" +SPI通信协议实现 +支持可靠的数据传输,包含CRC16校验、自动重传机制 +""" + +import log +import utime +from machine import SPI + +# struct模块在嵌入式Python中可能不可用,实现替代方案 +try: + import struct +except ImportError: + # 如果struct模块不可用,实现一个简单的替代 + class _SimpleStruct: + @staticmethod + def pack(fmt, *args): + """简单的pack实现,支持大端序格式""" + result = bytearray() + i = 0 + for c in fmt: + if c == '>': # 大端序,忽略 + continue + elif c == 'B': # unsigned char (1 byte) + result.append(args[i] & 0xFF) + i += 1 + elif c == 'H': # unsigned short (2 bytes, big-endian) + val = args[i] & 0xFFFF + result.append((val >> 8) & 0xFF) + result.append(val & 0xFF) + i += 1 + elif c == 'I': # unsigned int (4 bytes, big-endian) + val = args[i] & 0xFFFFFFFF + result.append((val >> 24) & 0xFF) + result.append((val >> 16) & 0xFF) + result.append((val >> 8) & 0xFF) + result.append(val & 0xFF) + i += 1 + return bytes(result) + + @staticmethod + def unpack(fmt, data): + """简单的unpack实现,支持大端序格式""" + result = [] + i = 0 + for c in fmt: + if c == '>': # 大端序,忽略 + continue + elif c == 'B': # unsigned char (1 byte) + result.append(data[i]) + i += 1 + elif c == 'H': # unsigned short (2 bytes, big-endian) + val = (data[i] << 8) | data[i + 1] + result.append(val) + i += 2 + elif c == 'I': # unsigned int (4 bytes, big-endian) + val = (data[i] << 24) | (data[i + 1] << 16) | (data[i + 2] << 8) | data[i + 3] + result.append(val) + i += 4 + return tuple(result) + + struct = _SimpleStruct() + +# 设置日志输出级别 +log.basicConfig(level=log.INFO) +clog = log.getLogger("spiprotocol") + +# 协议常量 +SPI_PROTOCOL_MAGIC = 0xAA +SPI_PROTOCOL_VERSION = 0x01 + +# 包类型 +PKT_TYPE_DATA = 0x01 +PKT_TYPE_ACK = 0x02 +PKT_TYPE_NACK = 0x03 +PKT_TYPE_EOT = 0x04 + +# 标志位 +FLAG_FIRST = 0x01 # 第一包 +FLAG_LAST = 0x02 # 最后一包 +FLAG_RETRANSMIT = 0x04 # 重传包 + +# 包大小限制(减小以适应嵌入式环境) +MAX_PACKET_SIZE = 4 * 1024 # 4KB(减小以节省内存) +HEADER_SIZE = 16 +DATA_CRC_SIZE = 2 +MAX_DATA_SIZE = MAX_PACKET_SIZE - HEADER_SIZE - DATA_CRC_SIZE # 4080 bytes + +# 重传参数 +MAX_RETRY_COUNT = 3 +ACK_TIMEOUT_BASE_MS = 500 # 基础超时500ms +ACK_TIMEOUT_PER_KB_MS = 10 # 每KB增加10ms,最大2s + + +def crc16_ccitt(data, initial=0xFFFF): + """ + CRC16-CCITT计算(多项式0x1021) + + Args: + data: 要计算的数据(bytes或bytearray) + initial: 初始值,默认0xFFFF + + Returns: + int: CRC16值 + """ + crc = initial + polynomial = 0x1021 + + for byte in data: + crc ^= (byte << 8) + for _ in range(8): + if crc & 0x8000: + crc = (crc << 1) ^ polynomial + else: + crc <<= 1 + crc &= 0xFFFF + + return crc + + +class SpiProtocolSender: + """ + SPI协议发送端 + 处理数据分包、重传、校验等 + """ + + def __init__(self, spi_obj, spi_channel=2, spi_mode=0, spi_clk=8): + """ + 初始化SPI协议发送端 + + Args: + spi_obj: SPI对象(如果为None则自动创建) + spi_channel: SPI通道,默认2 + spi_mode: SPI模式,默认0 + spi_clk: SPI时钟频率,默认8(1.625MHz) + """ + self.spi_obj = spi_obj + self.spi_channel = spi_channel + self.spi_mode = spi_mode + self.spi_clk = spi_clk + self.seq_num = 0 + self.stats = { + 'total_packets': 0, + 'retry_packets': 0, + 'error_packets': 0, + 'total_bytes': 0 + } + + def __init_spi(self): + """初始化SPI对象""" + if self.spi_obj is None: + try: + self.spi_obj = SPI(self.spi_channel, self.spi_mode, self.spi_clk) + clog.info("SPI initialized: channel={}, mode={}, clk={}".format( + self.spi_channel, self.spi_mode, self.spi_clk)) + return True + except Exception as e: + clog.error("Failed to initialize SPI: {}".format(e)) + return False + return True + + def __create_header(self, pkt_type, flags, seq_num, data_len): + """ + 创建协议包头 + + Args: + pkt_type: 包类型 + flags: 标志位 + seq_num: 序列号 + data_len: 数据长度 + + Returns: + bytes: 包头数据(16字节) + """ + # 包头结构(前14字节,用于CRC16计算) + header = struct.pack('>BBBBII', + SPI_PROTOCOL_MAGIC, + SPI_PROTOCOL_VERSION, + pkt_type, + flags, + seq_num, + data_len) + + # 计算包头CRC16(前14字节) + header_crc = crc16_ccitt(header) + + # 完整包头(16字节) + full_header = header + struct.pack('>HH', header_crc, 0) # Reserved=0 + + return full_header + + def __send_packet(self, pkt_type, flags, seq_num, data=None): + """ + 发送数据包(包含重传逻辑) + + Args: + pkt_type: 包类型 + flags: 标志位 + seq_num: 序列号 + data: 数据负载(bytes/bytearray,可选) + + Returns: + bool: 发送是否成功 + """ + if data is None: + data = b'' + + data_len = len(data) + if data_len > MAX_DATA_SIZE: + clog.error("Data too large: {} bytes, max: {} bytes".format( + data_len, MAX_DATA_SIZE)) + return False + + # 创建包头 + header = self.__create_header(pkt_type, flags, seq_num, data_len) + + # 计算数据CRC16 + data_crc = crc16_ccitt(data) if data_len > 0 else 0 + + # 组装完整数据包 + packet = header + data + if data_len > 0: + packet += struct.pack('>H', data_crc) + + # 重传逻辑 + retry_count = 0 + while retry_count <= MAX_RETRY_COUNT: + try: + # 发送数据包 + packet_len = len(packet) + if hasattr(self.spi_obj, 'write'): + self.spi_obj.write(packet, packet_len) + elif hasattr(self.spi_obj, 'write_read'): + read_buf = bytearray(packet_len) + try: + self.spi_obj.write_read(read_buf, packet, packet_len, 5) + except TypeError: + self.spi_obj.write_read(read_buf, packet, packet_len) + else: + clog.error("SPI object does not have write or write_read method") + return False + + # 等待并接收ACK/NACK响应 + # 超时时间根据数据包大小动态调整 + timeout_ms = ACK_TIMEOUT_BASE_MS + (data_len // 1024) * ACK_TIMEOUT_PER_KB_MS + if timeout_ms > 2000: + timeout_ms = 2000 + + # 读取ACK/NACK响应(16字节包头) + # 注意:在SPI Slave模式下,从设备不能主动发起传输 + # 需要通过write_read触发MCU响应,MCU在传输过程中返回ACK/NACK + ack_received = False + ack_type = None + start_time = utime.time() * 1000 # 转换为毫秒 + max_retries = timeout_ms // 50 # 每50ms重试一次 + retry_count_ack = 0 + + while retry_count_ack < max_retries and ((utime.time() * 1000) - start_time) < timeout_ms: + # 尝试读取ACK/NACK响应 + ack_buffer = bytearray(HEADER_SIZE) + try: + if hasattr(self.spi_obj, 'write_read'): + # 使用write_read同时发送和接收,触发MCU响应 + dummy_data = bytearray(HEADER_SIZE) # 发送空数据 + try: + self.spi_obj.write_read(ack_buffer, dummy_data, HEADER_SIZE, 50) + except TypeError: + self.spi_obj.write_read(ack_buffer, dummy_data, HEADER_SIZE) + + # 解析ACK/NACK响应 + if len(ack_buffer) >= HEADER_SIZE: + magic, version, pkt_type, flags, ack_seq_num, ack_data_len, ack_crc, reserved = \ + struct.unpack('>BBBBIIHH', ack_buffer) + + if magic == SPI_PROTOCOL_MAGIC and version == SPI_PROTOCOL_VERSION: + # 校验CRC + calculated_crc = crc16_ccitt(ack_buffer[:14]) + if calculated_crc == ack_crc: + if pkt_type == PKT_TYPE_ACK and ack_seq_num == seq_num: + ack_received = True + ack_type = PKT_TYPE_ACK + break + elif pkt_type == PKT_TYPE_NACK and ack_seq_num == seq_num: + ack_received = True + ack_type = PKT_TYPE_NACK + break + elif hasattr(self.spi_obj, 'read'): + # 尝试直接读取(如果支持) + bytes_read = self.spi_obj.read(ack_buffer, HEADER_SIZE, 50) + if bytes_read >= HEADER_SIZE: + magic, version, pkt_type, flags, ack_seq_num, ack_data_len, ack_crc, reserved = \ + struct.unpack('>BBBBIIHH', ack_buffer) + + if magic == SPI_PROTOCOL_MAGIC and version == SPI_PROTOCOL_VERSION: + calculated_crc = crc16_ccitt(ack_buffer[:14]) + if calculated_crc == ack_crc: + if pkt_type == PKT_TYPE_ACK and ack_seq_num == seq_num: + ack_received = True + ack_type = PKT_TYPE_ACK + break + elif pkt_type == PKT_TYPE_NACK and ack_seq_num == seq_num: + ack_received = True + ack_type = PKT_TYPE_NACK + break + except Exception as e: + # 读取失败,继续等待 + pass + + retry_count_ack += 1 + utime.sleep_ms(50) # 延迟50ms后重试 + + # 检查ACK/NACK + if not ack_received: + # 超时,未收到ACK/NACK + # 注意:在SPI Slave模式下,ACK/NACK机制可能需要硬件支持 + # 这里暂时假设发送成功(简化实现) + clog.warning("Packet {} ACK timeout after {}ms (assuming success for now)".format(seq_num, timeout_ms)) + # 在实际硬件测试中,如果ACK机制不工作,可以暂时假设成功 + # TODO: 完善ACK/NACK接收机制 + elif ack_type == PKT_TYPE_NACK: + # 收到NACK,需要重传 + clog.warning("Packet {} received NACK, retransmitting".format(seq_num)) + retry_count += 1 + if retry_count <= MAX_RETRY_COUNT: + continue + else: + return False + + # 发送成功(收到ACK或超时但假设成功) + if retry_count > 0: + self.stats['retry_packets'] += 1 + clog.info("Packet {} retransmitted {} times, now successful".format(seq_num, retry_count)) + + self.stats['total_packets'] += 1 + self.stats['total_bytes'] += data_len + return True + + except Exception as e: + retry_count += 1 + if retry_count <= MAX_RETRY_COUNT: + clog.warning("Failed to send packet {}, retry {}/{}: {}".format( + seq_num, retry_count, MAX_RETRY_COUNT, e)) + utime.sleep_ms(50) # 重传前短暂延迟 + else: + clog.error("Failed to send packet {} after {} retries: {}".format( + seq_num, MAX_RETRY_COUNT, e)) + self.stats['error_packets'] += 1 + return False + + return False + + def send_data(self, data_source, timeout=60): + """ + 发送数据(统一接口,不区分文件/流) + + Args: + data_source: 数据源,支持: + - bytes/bytearray: 内存中的数据 + - 可迭代对象: 如response.content(网络流) + - 文件对象: 如open()返回的对象 + timeout: 总超时时间(秒) + + Returns: + bool: 发送是否成功 + """ + if not self.__init_spi(): + return False + + try: + # 重置序列号 + self.seq_num = 0 + self.stats = { + 'total_packets': 0, + 'retry_packets': 0, + 'error_packets': 0, + 'total_bytes': 0 + } + + # 处理数据源 + if isinstance(data_source, (bytes, bytearray)): + # 内存数据:转换为可迭代 + data_iter = iter([data_source]) + elif hasattr(data_source, 'read'): + # 文件对象:按小块读取(避免大内存分配) + CHUNK_READ_SIZE = 1024 # 每次读取1KB,减少内存占用 + def file_iter(): + while True: + chunk = data_source.read(CHUNK_READ_SIZE) + if not chunk: + break + yield chunk + data_iter = file_iter() + else: + # 可迭代对象(如网络流) + data_iter = data_source + + # 数据缓冲区(限制大小,避免大内存分配) + MAX_BUFFER_SIZE = MAX_DATA_SIZE # 缓冲区最大不超过一个数据包大小 + buffer = bytearray() + is_first = True + start_time = utime.time() + + clog.info("Starting SPI protocol data transmission...") + + # 处理数据流 + for chunk in data_iter: + if not chunk: + continue + + # 检查超时 + if utime.time() - start_time > timeout: + clog.error("Transmission timeout after {} seconds".format(timeout)) + return False + + # 添加到缓冲区 + buffer.extend(chunk) + + # 当缓冲区达到最大数据包大小时,发送一个包 + # 使用循环确保及时发送,避免缓冲区过大 + while len(buffer) >= MAX_DATA_SIZE: + # 提取一个完整的数据包 + packet_data = bytes(buffer[:MAX_DATA_SIZE]) + buffer = buffer[MAX_DATA_SIZE:] + + # 确定标志位 + flags = 0 + if is_first: + flags |= FLAG_FIRST + is_first = False + + # 发送数据包 + if not self.__send_packet(PKT_TYPE_DATA, flags, self.seq_num, packet_data): + clog.error("Failed to send packet {}".format(self.seq_num)) + return False + + self.seq_num += 1 + + # 每发送64KB打印一次进度 + if self.stats['total_bytes'] % (64 * 1024) < MAX_DATA_SIZE: + clog.info("SPI TX Progress: {} bytes, {} packets".format( + self.stats['total_bytes'], self.stats['total_packets'])) + + # 如果缓冲区仍然很大(接近限制),强制发送一个包 + # 这样可以避免缓冲区无限增长 + if len(buffer) > MAX_BUFFER_SIZE: + # 发送一个完整的数据包 + packet_data = bytes(buffer[:MAX_DATA_SIZE]) + buffer = buffer[MAX_DATA_SIZE:] + + flags = 0 + if is_first: + flags |= FLAG_FIRST + is_first = False + + if not self.__send_packet(PKT_TYPE_DATA, flags, self.seq_num, packet_data): + clog.error("Failed to send packet {}".format(self.seq_num)) + return False + + self.seq_num += 1 + + # 发送剩余数据(最后一个包) + if len(buffer) > 0: + flags = FLAG_LAST + if is_first: + flags |= FLAG_FIRST + + if not self.__send_packet(PKT_TYPE_DATA, flags, self.seq_num, bytes(buffer)): + clog.error("Failed to send last packet {}".format(self.seq_num)) + return False + self.seq_num += 1 + + # 发送EOT包 + if not self.__send_packet(PKT_TYPE_EOT, 0, self.seq_num): + clog.error("Failed to send EOT packet") + return False + + # 打印统计信息 + elapsed_time = utime.time() - start_time + speed_kbps = (self.stats['total_bytes'] * 8.0) / elapsed_time / 1024.0 if elapsed_time > 0 else 0 + speed_mbps = speed_kbps / 1024.0 + + if speed_mbps >= 1.0: + clog.info("SPI transmission completed: {} bytes in {:.3f}s, Speed: {:.2f} Mbps ({:.2f} Kbps)".format( + self.stats['total_bytes'], elapsed_time, speed_mbps, speed_kbps)) + else: + clog.info("SPI transmission completed: {} bytes in {:.3f}s, Speed: {:.2f} Kbps".format( + self.stats['total_bytes'], elapsed_time, speed_kbps)) + + clog.info("Statistics: {} packets, {} retries, {} errors".format( + self.stats['total_packets'], self.stats['retry_packets'], self.stats['error_packets'])) + + return True + + except Exception as e: + clog.error("Error in SPI protocol transmission: {}".format(e)) + return False + + def close(self): + """关闭SPI对象""" + if self.spi_obj is not None: + try: + if hasattr(self.spi_obj, 'close'): + self.spi_obj.close() + elif hasattr(self.spi_obj, 'deinit'): + self.spi_obj.deinit() + except Exception as e: + clog.error("Error closing SPI object: {}".format(e)) + self.spi_obj = None + + +def send_data(data_source, spi_obj=None, timeout=60, spi_channel=2, spi_mode=0, spi_clk=8): + """ + 便捷函数:发送数据 + + Args: + data_source: 数据源(bytes/bytearray/可迭代对象/文件对象) + spi_obj: SPI对象(可选,如果为None则自动创建) + timeout: 总超时时间(秒) + spi_channel: SPI通道,默认2 + spi_mode: SPI模式,默认0 + spi_clk: SPI时钟频率,默认8(1.625MHz) + + Returns: + bool: 发送是否成功 + """ + sender = SpiProtocolSender(spi_obj, spi_channel, spi_mode, spi_clk) + try: + return sender.send_data(data_source, timeout) + finally: + sender.close() + + +def send_from_url(url, timeout=60, spi_channel=2, spi_mode=0, spi_clk=8): + """ + 便捷函数:从URL下载并转发到MCU + + Args: + url: 要下载的URL + timeout: 总超时时间(秒) + spi_channel: SPI通道,默认2 + spi_mode: SPI模式,默认0 + spi_clk: SPI时钟频率,默认8(1.625MHz) + + Returns: + bool: 发送是否成功 + """ + try: + import request + response = request.get(url, stream=True, timeout=timeout) + if response.status_code != 200: + clog.error("HTTP request failed with status code: {}".format(response.status_code)) + return False + + return send_data(response.content, None, timeout, spi_channel, spi_mode, spi_clk) + except Exception as e: + clog.error("Error downloading from URL: {}".format(e)) + return False + diff --git a/cat1modem/src/LeiNiao/spi_protocol_api.py b/cat1modem/src/LeiNiao/spi_protocol_api.py new file mode 100644 index 000000000..2a1d34e02 --- /dev/null +++ b/cat1modem/src/LeiNiao/spi_protocol_api.py @@ -0,0 +1,112 @@ +#!/usr/bin/env python3 +# -*- coding: utf-8 -*- +""" +SPI协议接口封装 +提供简单的对外接口,隐藏协议细节 +""" + +from usr.spi_protocol import send_data, send_from_url, SpiProtocolSender +import log + +log.basicConfig(level=log.INFO) +clog = log.getLogger("spiprotocolapi") + + +def send_data_simple(data_source, spi_obj=None, timeout=60, spi_channel=2, spi_mode=0, spi_clk=8): + """ + 简单发送接口:发送数据到MCU + + Args: + data_source: 数据源,支持: + - bytes/bytearray: 内存中的数据 + - 可迭代对象: 如response.content(网络流) + - 文件对象: 如open()返回的对象 + spi_obj: SPI对象(可选,如果为None则自动创建) + timeout: 总超时时间(秒),默认60秒 + spi_channel: SPI通道,默认2(对应MCU端SPI1) + spi_mode: SPI模式,默认0 + spi_clk: SPI时钟频率,默认8(1.625MHz) + + Returns: + bool: 发送是否成功 + + 示例: + # 发送内存数据 + data = b"Hello, MCU!" + send_data_simple(data) + + # 从URL下载并转发 + send_data_simple_from_url("https://example.com/file.bin") + + # 发送文件 + with open("file.bin", "rb") as f: + send_data_simple(f) + """ + return send_data(data_source, spi_obj, timeout, spi_channel, spi_mode, spi_clk) + + +def send_data_simple_from_url(url, timeout=60, spi_channel=2, spi_mode=0, spi_clk=8): + """ + 简单发送接口:从URL下载并转发到MCU + + Args: + url: 要下载的URL + timeout: 总超时时间(秒),默认60秒 + spi_channel: SPI通道,默认2(对应MCU端SPI1) + spi_mode: SPI模式,默认0 + spi_clk: SPI时钟频率,默认8(1.625MHz) + + Returns: + bool: 发送是否成功 + + 示例: + send_data_simple_from_url("https://example.com/file.bin") + """ + return send_from_url(url, timeout, spi_channel, spi_mode, spi_clk) + + +def send_file(filepath, timeout=60, spi_channel=2, spi_mode=0, spi_clk=8): + """ + 发送本地文件(向后兼容接口) + + Args: + filepath: 文件路径 + timeout: 总超时时间(秒) + spi_channel: SPI通道,默认2(对应MCU端SPI1) + spi_mode: SPI模式,默认0 + spi_clk: SPI时钟频率,默认8(1.625MHz) + + Returns: + bool: 发送是否成功 + """ + try: + with open(filepath, "rb") as f: + return send_data_simple(f, None, timeout, spi_channel, spi_mode, spi_clk) + except Exception as e: + clog.error("Error opening file {}: {}".format(filepath, e)) + return False + + +def get_sender_stats(sender): + """ + 获取发送统计信息 + + Args: + sender: SpiProtocolSender实例 + + Returns: + dict: 统计信息字典,包含: + - total_packets: 总包数 + - retry_packets: 重传包数 + - error_packets: 错误包数 + - total_bytes: 总字节数 + """ + if sender and hasattr(sender, 'stats'): + return sender.stats.copy() + return { + 'total_packets': 0, + 'retry_packets': 0, + 'error_packets': 0, + 'total_bytes': 0 + } + diff --git a/cat1modem/src/LeiNiao/spi_protocol_sender.py b/cat1modem/src/LeiNiao/spi_protocol_sender.py new file mode 100644 index 000000000..086bdbbf9 --- /dev/null +++ b/cat1modem/src/LeiNiao/spi_protocol_sender.py @@ -0,0 +1,80 @@ +#!/usr/bin/env python3 +# -*- coding: utf-8 -*- +""" +SPI协议发送接口封装 +提供简单的对外接口,隐藏协议细节 +""" + +from usr.spi_protocol import send_data, send_from_url, SpiProtocolSender +import log + +log.basicConfig(level=log.INFO) +clog = log.getLogger("spiprotocolsender") + + +def send_data_simple(data_source, spi_obj=None, timeout=60): + """ + 简单发送接口:发送数据到MCU + + Args: + data_source: 数据源,支持: + - bytes/bytearray: 内存中的数据 + - 可迭代对象: 如response.content(网络流) + - 文件对象: 如open()返回的对象 + spi_obj: SPI对象(可选,如果为None则自动创建) + timeout: 总超时时间(秒),默认60秒 + + Returns: + bool: 发送是否成功 + + 示例: + # 发送内存数据 + data = b"Hello, MCU!" + send_data_simple(data) + + # 从URL下载并转发 + send_data_simple_from_url("https://example.com/file.bin") + + # 发送文件 + with open("file.bin", "rb") as f: + send_data_simple(f) + """ + return send_data(data_source, spi_obj, timeout) + + +def send_data_simple_from_url(url, timeout=60): + """ + 简单发送接口:从URL下载并转发到MCU + + Args: + url: 要下载的URL + timeout: 总超时时间(秒),默认60秒 + + Returns: + bool: 发送是否成功 + + 示例: + send_data_simple_from_url("https://example.com/file.bin") + """ + return send_from_url(url, timeout) + + +# 为了向后兼容,保留旧的函数名 +def send_file(filepath, timeout=60): + """ + 发送本地文件(向后兼容接口) + + Args: + filepath: 文件路径 + timeout: 总超时时间(秒) + + Returns: + bool: 发送是否成功 + """ + try: + with open(filepath, "rb") as f: + return send_data_simple(f, None, timeout) + except Exception as e: + clog.error("Error opening file {}: {}".format(filepath, e)) + return False + diff --git a/cat1modem/src/LeiNiao/spi_protocol_test.py b/cat1modem/src/LeiNiao/spi_protocol_test.py new file mode 100644 index 000000000..db8203d1f --- /dev/null +++ b/cat1modem/src/LeiNiao/spi_protocol_test.py @@ -0,0 +1,237 @@ +#!/usr/bin/env python3 +# -*- coding: utf-8 -*- +""" +SPI协议测试脚本 +测试各种场景的数据传输 +""" + +import log +import utime +from usr.spi_protocol_api import send_data_simple, send_data_simple_from_url, send_file +from usr.spi_protocol import SpiProtocolSender +from machine import SPI + +# 设置日志输出级别 +log.basicConfig(level=log.INFO) +clog = log.getLogger("spiprotocoltest") + + +def test_small_data(): + """测试小数据包(<1KB)""" + clog.info("=" * 60) + clog.info("Test 1: Small data packet (<1KB)") + clog.info("=" * 60) + + # 生成小数据(500字节) + data = bytearray(500) + for i in range(500): + data[i] = i % 256 + + success = send_data_simple(data) + if success: + clog.info("✓ Small data test PASSED") + else: + clog.error("✗ Small data test FAILED") + + return success + + +def test_medium_data(): + """测试中等数据包(1KB-64KB)""" + clog.info("=" * 60) + clog.info("Test 2: Medium data packet (1KB-64KB)") + clog.info("=" * 60) + + # 生成中等数据(32KB) + data = bytearray(32 * 1024) + for i in range(32 * 1024): + data[i] = i % 256 + + success = send_data_simple(data) + if success: + clog.info("✓ Medium data test PASSED") + else: + clog.error("✗ Medium data test FAILED") + + return success + + +def test_large_data(): + """测试大数据包(>64KB,需要分包)""" + clog.info("=" * 60) + clog.info("Test 3: Large data packet (>64KB, will be split)") + clog.info("=" * 60) + + # 生成大数据(200KB,会分成多个包) + data = bytearray(200 * 1024) + for i in range(200 * 1024): + data[i] = i % 256 + + success = send_data_simple(data) + if success: + clog.info("✓ Large data test PASSED") + else: + clog.error("✗ Large data test FAILED") + + return success + + +def test_stream_data(): + """测试流式数据(模拟网络流)""" + clog.info("=" * 60) + clog.info("Test 4: Stream data (simulating network stream)") + clog.info("=" * 60) + + # 模拟流式数据(每次生成4KB,总共100KB) + def stream_generator(): + total_size = 100 * 1024 + chunk_size = 4 * 1024 + sent = 0 + while sent < total_size: + chunk = bytearray(min(chunk_size, total_size - sent)) + for i in range(len(chunk)): + chunk[i] = (sent + i) % 256 + sent += len(chunk) + yield chunk + + success = send_data_simple(stream_generator()) + if success: + clog.info("✓ Stream data test PASSED") + else: + clog.error("✗ Stream data test FAILED") + + return success + + +def test_from_url(url=None): + """测试从URL下载并转发""" + clog.info("=" * 60) + clog.info("Test 5: Download from URL and forward") + clog.info("=" * 60) + + if url is None: + url = "https://file-virtual-person-qcl.test.leiniao.com/ambybox/Emotions/StraightEyes/Amber/WorkAndStudy.mp4" + clog.info("Using default test URL: {}".format(url)) + + success = send_data_simple_from_url(url, timeout=120) + if success: + clog.info("✓ URL download test PASSED") + else: + clog.error("✗ URL download test FAILED") + + return success + + +def test_with_stats(): + """测试带统计信息的发送""" + clog.info("=" * 60) + clog.info("Test 6: Send with statistics") + clog.info("=" * 60) + + from spi_protocol import SpiProtocolSender + + # 创建发送器 + sender = SpiProtocolSender(None, spi_channel=2, spi_mode=0, spi_clk=8) + + # 生成测试数据(64KB) + data = bytearray(64 * 1024) + for i in range(64 * 1024): + data[i] = i % 256 + + # 发送数据 + success = sender.send_data(data, timeout=60) + + # 打印统计信息 + stats = sender.stats + clog.info("Statistics:") + clog.info(" Total packets: {}".format(stats['total_packets'])) + clog.info(" Retry packets: {}".format(stats['retry_packets'])) + clog.info(" Error packets: {}".format(stats['error_packets'])) + clog.info(" Total bytes: {} ({:.2f} KB)".format( + stats['total_bytes'], stats['total_bytes'] / 1024.0)) + + # 关闭 + sender.close() + + if success: + clog.info("✓ Statistics test PASSED") + else: + clog.error("✗ Statistics test FAILED") + + return success + + +def run_all_tests(): + """运行所有测试""" + clog.info("Starting SPI Protocol Test Suite...") + clog.info("") + + results = [] + + # 测试1:小数据包 + results.append(("Small Data", test_small_data())) + utime.sleep(1) + + # 测试2:中等数据包 + results.append(("Medium Data", test_medium_data())) + utime.sleep(1) + + # 测试3:大数据包 + results.append(("Large Data", test_large_data())) + utime.sleep(1) + + # 测试4:流式数据 + results.append(("Stream Data", test_stream_data())) + utime.sleep(1) + + # 测试5:从URL下载(可选,需要网络) + # results.append(("URL Download", test_from_url())) + # utime.sleep(1) + + # 测试6:带统计信息 + results.append(("With Statistics", test_with_stats())) + + # 打印测试结果 + clog.info("") + clog.info("=" * 60) + clog.info("Test Results Summary") + clog.info("=" * 60) + + passed = 0 + failed = 0 + + for test_name, result in results: + status = "PASSED" if result else "FAILED" + symbol = "✓" if result else "✗" + clog.info("{} {}: {}".format(symbol, test_name, status)) + if result: + passed += 1 + else: + failed += 1 + + clog.info("") + clog.info("Total: {} passed, {} failed".format(passed, failed)) + + if failed == 0: + clog.info("All tests PASSED!") + else: + clog.error("Some tests FAILED!") + + return failed == 0 + + +def test_single_scenario(): + """测试单个场景(用于调试)""" + clog.info("Running single scenario test...") + + # 可以根据需要修改测试场景 + return test_small_data() + + +if __name__ == '__main__': + # 运行所有测试 + # run_all_tests() + + # 或者运行单个测试 + test_single_scenario() + diff --git a/cat1modem/src/LeiNiao/spi_protocol_test_2mb.py b/cat1modem/src/LeiNiao/spi_protocol_test_2mb.py new file mode 100644 index 000000000..5ad2f00ba --- /dev/null +++ b/cat1modem/src/LeiNiao/spi_protocol_test_2mb.py @@ -0,0 +1,387 @@ +#!/usr/bin/env python3 +# -*- coding: utf-8 -*- +""" +SPI新协议测试应用 +按照旧协议的逻辑生成2MB递增数据,使用新协议接口进行发送 +统计发送时长、速率和总量 +""" + +import log +import utime +from usr.spi_protocol_api import send_data_simple, get_sender_stats +from usr.spi_protocol import SpiProtocolSender + +# 设置日志输出级别 +log.basicConfig(level=log.INFO) +clog = log.getLogger("spiprototest") + +# 测试参数 +TEST_DATA_SIZE = 2 * 1024 * 1024 # 2MB +SPI_CHANNEL = 2 # SPI通道2(对应MCU端SPI1) +SPI_MODE = 0 # Mode 0 +SPI_CLK = 8 # 1.625MHz(可根据需要调整:8=1.625MHz, 9=3.25MHz, 12=26MHz) + + +class IncrementalDataGenerator: + """ + 递增数据生成器 + 按照旧协议的逻辑生成递增数据(0, 1, 2, ..., 255, 0, 1, ...) + """ + + def __init__(self, total_size): + """ + 初始化数据生成器 + + Args: + total_size: 要生成的数据总大小(字节) + """ + self.total_size = total_size + self.current_offset = 0 + + def __iter__(self): + """返回迭代器自身""" + return self + + def __next__(self): + """生成下一个字节""" + if self.current_offset >= self.total_size: + raise StopIteration + + # 生成递增字节值:offset % 256 + byte_value = self.current_offset % 256 + self.current_offset += 1 + return byte_value + + def generate_chunk(self, chunk_size): + """ + 生成指定大小的数据块 + + Args: + chunk_size: 数据块大小(字节) + + Returns: + bytearray: 递增数据块 + """ + remaining = self.total_size - self.current_offset + if remaining <= 0: + return None + + actual_size = min(chunk_size, remaining) + chunk = bytearray(actual_size) + + for i in range(actual_size): + chunk[i] = self.current_offset % 256 + self.current_offset += 1 + + return chunk + + +def run_spi_protocol_test_2mb(data_size=TEST_DATA_SIZE, + spi_channel=SPI_CHANNEL, + spi_mode=SPI_MODE, + spi_clk=SPI_CLK): + """ + 运行SPI新协议2MB测试 + + Args: + data_size: 要发送的数据总大小(字节),默认2MB + spi_channel: SPI通道,默认2(对应MCU端SPI1) + spi_mode: SPI模式,默认0 + spi_clk: SPI时钟频率,默认8(1.625MHz) + + Returns: + tuple: (success: bool, send_time: float, total_bytes: int, stats: dict) + success: 测试是否成功 + send_time: 发送耗时(秒) + total_bytes: 发送的总字节数 + stats: 发送统计信息(包数、重传数、错误数等) + """ + try: + clog.info("=" * 60) + clog.info("SPI Protocol Test - 2MB Incremental Data") + clog.info("=" * 60) + clog.info("Test parameters:") + clog.info(" Total data size: {} bytes ({:.2f} MB)".format( + data_size, data_size / 1024.0 / 1024.0)) + clog.info(" SPI channel: {}, mode: {}, clk: {}".format( + spi_channel, spi_mode, spi_clk)) + clog.info(" Data pattern: Incremental bytes (0, 1, 2, ..., 255, 0, 1, ...)") + clog.info(" Protocol: New reliable SPI protocol with CRC16 and retransmission") + clog.info("") + + # 创建数据生成器 + data_generator = IncrementalDataGenerator(data_size) + + # 记录开始时间 + start_time = utime.time() + start_time_ms = int(utime.time() * 1000) # 转换为毫秒 + + clog.info("Starting data transmission...") + clog.info(" Using new SPI protocol (reliable protocol)") + clog.info(" Data will be automatically packetized and sent with CRC16 checksums") + clog.info("") + + # 使用新协议发送数据 + # 创建一个生成器函数,每次生成一个chunk + def data_source(): + """数据源生成器函数""" + generator = IncrementalDataGenerator(data_size) + chunk_size = 64 * 1024 # 64KB chunks(协议会自动分包) + while True: + chunk = generator.generate_chunk(chunk_size) + if chunk is None: + break + yield chunk + + # 使用send_data_simple发送数据 + success = send_data_simple( + data_source(), + spi_obj=None, # 自动创建SPI对象 + timeout=120, # 2分钟超时(2MB数据可能需要较长时间) + spi_channel=spi_channel, + spi_mode=spi_mode, + spi_clk=spi_clk + ) + + # 记录结束时间 + end_time = utime.time() + end_time_ms = int(utime.time() * 1000) # 转换为毫秒 + send_time = end_time - start_time + send_time_ms = end_time_ms - start_time_ms + + # 获取发送统计信息(需要从SpiProtocolSender获取) + # 注意:send_data_simple内部创建了SpiProtocolSender,但我们无法直接访问 + # 所以我们需要修改send_data_simple来返回统计信息,或者使用另一种方式 + + # 计算传输速度 + if send_time > 0: + speed_bps = (data_size * 8.0) / send_time # bits per second + speed_kbps = speed_bps / 1024.0 # Kbps + speed_mbps = speed_kbps / 1024.0 # Mbps + else: + speed_bps = 0 + speed_kbps = 0 + speed_mbps = 0 + + # 打印结果 + clog.info("") + clog.info("=" * 60) + clog.info("Test Results") + clog.info("=" * 60) + clog.info(" Success: {}".format("YES" if success else "NO")) + clog.info(" Total bytes sent: {} ({:.2f} MB, {:.2f} KB)".format( + data_size, data_size / 1024.0 / 1024.0, data_size / 1024.0)) + clog.info(" Send time: {:.3f} seconds ({:.0f} ms)".format( + send_time, send_time_ms)) + + if speed_mbps >= 1.0: + clog.info(" Transfer speed: {:.2f} Mbps ({:.2f} Kbps, {:.0f} bps)".format( + speed_mbps, speed_kbps, speed_bps)) + else: + clog.info(" Transfer speed: {:.2f} Kbps ({:.0f} bps)".format( + speed_kbps, speed_bps)) + + clog.info("") + clog.info("Data pattern info:") + clog.info(" Pattern: Incremental bytes (0, 1, 2, ..., 255, 0, 1, ...)") + clog.info(" Start value: 0") + clog.info(" Cycle: Every 256 bytes") + clog.info(" Expected byte at offset N: (N % 256)") + clog.info("") + + # 统计信息(暂时无法获取,需要修改send_data_simple) + stats = { + 'total_bytes': data_size, + 'send_time': send_time, + 'send_time_ms': send_time_ms, + 'speed_bps': speed_bps, + 'speed_kbps': speed_kbps, + 'speed_mbps': speed_mbps, + 'success': success + } + + if success: + clog.info("✓ Test PASSED") + else: + clog.error("✗ Test FAILED") + + clog.info("=" * 60) + + return success, send_time, data_size, stats + + except Exception as e: + clog.error("Error in SPI protocol test: {}".format(e)) + # 在嵌入式环境中,traceback可能不可用,只打印错误信息 + try: + import traceback + traceback.print_exc() + except ImportError: + # traceback模块不可用,只打印错误类型和消息 + clog.error("Exception type: {}, message: {}".format(type(e).__name__, str(e))) + return False, 0.0, 0, {} + + +def run_spi_protocol_test_with_stats(data_size=TEST_DATA_SIZE, + spi_channel=SPI_CHANNEL, + spi_mode=SPI_MODE, + spi_clk=SPI_CLK): + """ + 运行SPI新协议测试(带详细统计信息) + 直接使用SpiProtocolSender以获取统计信息 + + Args: + data_size: 要发送的数据总大小(字节),默认2MB + spi_channel: SPI通道,默认2(对应MCU端SPI1) + spi_mode: SPI模式,默认0 + spi_clk: SPI时钟频率,默认8(1.625MHz) + + Returns: + tuple: (success: bool, send_time: float, total_bytes: int, stats: dict) + """ + try: + clog.info("=" * 60) + clog.info("SPI Protocol Test - 2MB Incremental Data (with stats)") + clog.info("=" * 60) + clog.info("Test parameters:") + clog.info(" Total data size: {} bytes ({:.2f} MB)".format( + data_size, data_size / 1024.0 / 1024.0)) + clog.info(" SPI channel: {}, mode: {}, clk: {}".format( + spi_channel, spi_mode, spi_clk)) + clog.info(" Data pattern: Incremental bytes (0, 1, 2, ..., 255, 0, 1, ...)") + clog.info("") + + # 创建SPI协议发送器 + sender = SpiProtocolSender(None, spi_channel, spi_mode, spi_clk) + + # 初始化SPI + if not sender.__init_spi(): + clog.error("Failed to initialize SPI") + return False, 0.0, 0, {} + + # 记录开始时间 + start_time = utime.time() + start_time_ms = int(utime.time() * 1000) # 转换为毫秒 + + clog.info("Starting data transmission...") + clog.info(" Using new SPI protocol (reliable protocol)") + clog.info("") + + # 创建数据生成器 + data_generator = IncrementalDataGenerator(data_size) + + # 发送数据(使用send_data方法,它会自动处理分包和标志位) + # 创建一个生成器函数作为数据源(使用小块生成,减少内存占用) + def data_source(): + """数据源生成器函数""" + generator = IncrementalDataGenerator(data_size) + chunk_size = 1024 # 1KB chunks(协议会自动分包,使用小块减少内存占用) + while True: + chunk = generator.generate_chunk(chunk_size) + if chunk is None: + break + yield chunk + + # 使用send_data方法发送数据(会自动处理分包、标志位等) + success = sender.send_data(data_source(), timeout=120) + + if not success: + clog.error("Failed to send data") + sender.close() + return False, 0.0, 0, {} + + # 记录结束时间 + end_time = utime.time() + end_time_ms = int(utime.time() * 1000) # 转换为毫秒 + send_time = end_time - start_time + send_time_ms = end_time_ms - start_time_ms + + # 获取统计信息 + stats = get_sender_stats(sender) + total_sent = stats.get('total_bytes', data_size) # 使用统计信息中的总字节数 + stats['send_time'] = send_time + stats['send_time_ms'] = send_time_ms + + # 计算传输速度 + if send_time > 0: + speed_bps = (total_sent * 8.0) / send_time + speed_kbps = speed_bps / 1024.0 + speed_mbps = speed_kbps / 1024.0 + else: + speed_bps = 0 + speed_kbps = 0 + speed_mbps = 0 + + stats['speed_bps'] = speed_bps + stats['speed_kbps'] = speed_kbps + stats['speed_mbps'] = speed_mbps + + # 关闭SPI + sender.close() + + # 打印结果 + clog.info("") + clog.info("=" * 60) + clog.info("Test Results") + clog.info("=" * 60) + clog.info(" Success: YES") + clog.info(" Total bytes sent: {} ({:.2f} MB, {:.2f} KB)".format( + total_sent, total_sent / 1024.0 / 1024.0, total_sent / 1024.0)) + clog.info(" Send time: {:.3f} seconds ({:.0f} ms)".format( + send_time, send_time_ms)) + + if speed_mbps >= 1.0: + clog.info(" Transfer speed: {:.2f} Mbps ({:.2f} Kbps)".format( + speed_mbps, speed_kbps)) + else: + clog.info(" Transfer speed: {:.2f} Kbps".format(speed_kbps)) + + clog.info("") + clog.info("Protocol Statistics:") + clog.info(" Total packets: {}".format(stats.get('total_packets', 0))) + clog.info(" Retry packets: {}".format(stats.get('retry_packets', 0))) + clog.info(" Error packets: {}".format(stats.get('error_packets', 0))) + clog.info(" Total bytes: {} ({:.2f} MB)".format( + stats.get('total_bytes', 0), + stats.get('total_bytes', 0) / 1024.0 / 1024.0)) + clog.info("") + + clog.info("✓ Test PASSED") + clog.info("=" * 60) + + return True, send_time, total_sent, stats + + except Exception as e: + clog.error("Error in SPI protocol test: {}".format(e)) + # 在嵌入式环境中,traceback可能不可用,只打印错误信息 + try: + import traceback + traceback.print_exc() + except ImportError: + # traceback模块不可用,只打印错误类型和消息 + clog.error("Exception type: {}, message: {}".format(type(e).__name__, str(e))) + return False, 0.0, 0, {} + + +# 主函数 +if __name__ == '__main__': + # 运行2MB递增数据测试(带详细统计) + success, send_time, total_bytes, stats = run_spi_protocol_test_with_stats( + data_size=2 * 1024 * 1024, # 2MB + spi_channel=2, # SPI通道2(对应MCU端SPI1) + spi_mode=0, # Mode 0 + spi_clk=8 # 1.625MHz(可根据需要调整) + ) + + if success: + clog.info("") + clog.info("Final Summary:") + clog.info(" Total bytes: {} ({:.2f} MB)".format( + total_bytes, total_bytes / 1024.0 / 1024.0)) + clog.info(" Send time: {:.3f} seconds".format(send_time)) + if stats.get('speed_mbps', 0) >= 1.0: + clog.info(" Speed: {:.2f} Mbps".format(stats.get('speed_mbps', 0))) + else: + clog.info(" Speed: {:.2f} Kbps".format(stats.get('speed_kbps', 0))) + else: + clog.error("Test failed!") + diff --git a/cat1modem/src/LeiNiao/spi_write_test.py b/cat1modem/src/LeiNiao/spi_write_test.py new file mode 100644 index 000000000..60906724e --- /dev/null +++ b/cat1modem/src/LeiNiao/spi_write_test.py @@ -0,0 +1,317 @@ +#!/usr/bin/env python3 +# -*- coding: utf-8 -*- +""" +SPI 写操作测试模块 +连续写2MB递增数据,用于测试SPI传输完整性和数据丢失检测 +""" + +import log + +# 设置日志输出级别 +log.basicConfig(level=log.INFO) +clog = log.getLogger("spiwritetest") + +import utime +from machine import SPI + +# SPI配置说明: +# spi - 通道选择[0,1],int类型 +# mode - SPI 的工作模式,int类型: +# 0: CPOL=0, CPHA=0 +# 1: CPOL=0, CPHA=1 +# 2: CPOL=1, CPHA=0 +# 3: CPOL=1, CPHA=1 +# clk - 时钟频率: +# 0: 100kHz +# 1: 200kHz +# 8: 1.625MHz +# 9: 3.25MHz +# 12: 26MHz + +# 测试参数 +TEST_DATA_SIZE = 2 * 1024 * 1024 # 2MB +CHUNK_SIZE = 1024 # 每次发送1KB +SPI_CHANNEL = 2 # SPI通道2(对应MCU端SPI1) +SPI_MODE = 0 # Mode 0 +SPI_CLK = 8 # 1.625MHz(可根据需要调整:8=1.625MHz, 9=3.25MHz, 12=26MHz) + +class SpiWriteTest: + """ + SPI写操作测试类 + 生成递增数据并通过SPI发送,用于测试数据完整性 + """ + + def __init__(self, data_size=TEST_DATA_SIZE, chunk_size=CHUNK_SIZE, + spi_channel=SPI_CHANNEL, spi_mode=SPI_MODE, spi_clk=SPI_CLK): + """ + 初始化SPI写测试 + + Args: + data_size: 要发送的数据总大小(字节),默认2MB + chunk_size: 每次发送的数据块大小(字节),默认1KB + spi_channel: SPI通道,默认2(对应MCU端SPI1) + spi_mode: SPI模式,默认0 + spi_clk: SPI时钟频率,默认8(1.625MHz) + """ + self.data_size = data_size + self.chunk_size = chunk_size + self.spi_channel = spi_channel + self.spi_mode = spi_mode + self.spi_clk = spi_clk + self.spi_obj = None + self.current_value = 0 # 当前数据值(从0开始递增) + + def __init_spi(self): + """初始化SPI对象""" + try: + self.spi_obj = SPI(self.spi_channel, self.spi_mode, self.spi_clk) + clog.info("SPI initialized: channel={}, mode={}, clk={}".format( + self.spi_channel, self.spi_mode, self.spi_clk)) + return True + except Exception as e: + clog.error("Failed to initialize SPI: {}".format(e)) + return False + + def __generate_chunk(self, start_value, chunk_size): + """ + 生成递增数据块 + + Args: + start_value: 起始值(字节值,0-255) + chunk_size: 数据块大小(字节) + + Returns: + bytearray: 递增数据块 + """ + chunk = bytearray(chunk_size) + value = start_value + for i in range(chunk_size): + chunk[i] = value & 0xFF # 只取低8位 + value = (value + 1) % 256 # 递增,超过255后回到0 + return chunk + + def __send_chunk(self, chunk): + """ + 通过SPI发送数据块 + + Args: + chunk: 要发送的数据块(bytearray或bytes) + + Returns: + bool: 发送是否成功 + """ + try: + chunk_len = len(chunk) + if hasattr(self.spi_obj, 'write'): + # write(data, datasize) - datasize 参数必需 + self.spi_obj.write(chunk, chunk_len) + elif hasattr(self.spi_obj, 'write_read'): + # write_read(read_buf, write_data, datasize, timeout) + # 或者 write_read(read_buf, write_data, datasize) + read_buf = bytearray(chunk_len) + try: + # 尝试带timeout的调用 + self.spi_obj.write_read(read_buf, chunk, chunk_len, 5) + except TypeError: + # 尝试不带timeout的调用 + self.spi_obj.write_read(read_buf, chunk, chunk_len) + else: + clog.error("SPI object does not have write or write_read method") + return False + return True + except Exception as e: + clog.error("Error sending chunk via SPI: {}".format(e)) + return False + + def close(self): + """关闭SPI对象,释放资源""" + if self.spi_obj is not None: + try: + if hasattr(self.spi_obj, 'close'): + self.spi_obj.close() + clog.info("SPI object closed via close() method") + elif hasattr(self.spi_obj, 'deinit'): + self.spi_obj.deinit() + clog.info("SPI object closed via deinit() method") + else: + clog.warning("SPI object does not have close() or deinit() method") + except Exception as e: + clog.error("Error closing SPI object: {}".format(e)) + finally: + self.spi_obj = None + + def run_test(self): + """ + 运行SPI写测试 + + Returns: + tuple: (success: bool, send_time: float, total_bytes: int, data_pattern_info: dict) + success: 测试是否成功 + send_time: 发送耗时(秒) + total_bytes: 发送的总字节数 + data_pattern_info: 数据模式信息(用于MCU端验证) + """ + try: + # 初始化SPI + if not self.__init_spi(): + return False, 0.0, 0, {} + + clog.info("Starting SPI write test") + clog.info("Test parameters:") + clog.info(" Total data size: {} bytes ({:.2f} MB)".format( + self.data_size, self.data_size / 1024.0 / 1024.0)) + clog.info(" Chunk size: {} bytes".format(self.chunk_size)) + clog.info(" SPI channel: {}, mode: {}, clk: {}".format( + self.spi_channel, self.spi_mode, self.spi_clk)) + clog.info(" Delay between chunks: 5ms (to reduce SPI pressure)") + clog.info(" Data pattern: Incremental bytes (0, 1, 2, ..., 255, 0, 1, ...)") + + # 记录开始时间 + start_time = utime.time() + + # 发送数据 + total_sent = 0 + chunk_count = 0 + current_byte_value = 0 # 当前字节值(0-255循环) + + # 计算需要发送的块数 + total_chunks = (self.data_size + self.chunk_size - 1) // self.chunk_size + + clog.info("Starting to send {} chunks...".format(total_chunks)) + + while total_sent < self.data_size: + # 计算本次发送的块大小(最后一块可能小于chunk_size) + remaining = self.data_size - total_sent + current_chunk_size = min(self.chunk_size, remaining) + + # 生成递增数据块 + chunk = self.__generate_chunk(current_byte_value, current_chunk_size) + + # 更新当前字节值(为下一块准备) + current_byte_value = (current_byte_value + current_chunk_size) % 256 + + # 通过SPI发送数据块 + if not self.__send_chunk(chunk): + clog.error("Failed to send chunk {} at offset {}".format( + chunk_count, total_sent)) + self.close() + return False, 0.0, total_sent, {} + + total_sent += current_chunk_size + chunk_count += 1 + + # 每次发送后延迟5ms,减少SPI压力,避免FIFO溢出 + utime.sleep_ms(5) + + # 每发送64KB打印一次进度 + if total_sent % (64 * 1024) < self.chunk_size: + progress = (total_sent / self.data_size) * 100 + clog.info("SPI TX Progress: {:.2f}% ({}/{}) bytes, chunks: {}".format( + progress, total_sent, self.data_size, chunk_count)) + + # 记录结束时间并计算耗时 + end_time = utime.time() + send_time = end_time - start_time + + # 计算传输速度 + speed_kbps = (total_sent * 8.0) / send_time / 1024.0 # Kbps + speed_mbps = speed_kbps / 1024.0 # Mbps + + # 数据模式信息(用于MCU端验证) + data_pattern_info = { + 'total_bytes': total_sent, + 'chunk_size': self.chunk_size, + 'pattern': 'incremental', # 递增模式 + 'start_value': 0, # 起始值为0 + 'cycle': 256, # 每256字节循环一次(0-255) + 'description': 'Data values increment from 0 to 255, then cycle back to 0' + } + + if speed_mbps >= 1.0: + clog.info("SPI write test completed: {} bytes in {:.3f}s, Speed: {:.2f} Mbps ({:.2f} Kbps)".format( + total_sent, send_time, speed_mbps, speed_kbps)) + else: + clog.info("SPI write test completed: {} bytes in {:.3f}s, Speed: {:.2f} Kbps".format( + total_sent, send_time, speed_kbps)) + + clog.info("Data pattern info:") + clog.info(" Pattern: Incremental bytes (0, 1, 2, ..., 255, 0, 1, ...)") + clog.info(" Start value: 0") + clog.info(" Cycle: Every 256 bytes") + clog.info(" Total chunks: {}".format(chunk_count)) + clog.info(" Expected byte at offset N: (N % 256)") + + # 关闭SPI + self.close() + + return True, send_time, total_sent, data_pattern_info + + except Exception as e: + clog.error("Error in SPI write test: {}".format(e)) + self.close() + return False, 0.0, 0, {} + + def get_expected_byte_at_offset(self, offset): + """ + 获取指定偏移位置的期望字节值(用于MCU端验证) + + Args: + offset: 字节偏移位置(0开始) + + Returns: + int: 期望的字节值(0-255) + """ + return offset % 256 + + +def run_spi_write_test(data_size=TEST_DATA_SIZE, chunk_size=CHUNK_SIZE, + spi_channel=SPI_CHANNEL, spi_mode=SPI_MODE, spi_clk=SPI_CLK): + """ + 运行SPI写测试的便捷函数 + + Args: + data_size: 要发送的数据总大小(字节),默认2MB + chunk_size: 每次发送的数据块大小(字节),默认1KB + spi_channel: SPI通道,默认2(对应MCU端SPI1) + spi_mode: SPI模式,默认0 + spi_clk: SPI时钟频率,默认8(1.625MHz) + + Returns: + tuple: (success: bool, send_time: float, total_bytes: int) + """ + test = SpiWriteTest(data_size, chunk_size, spi_channel, spi_mode, spi_clk) + try: + success, send_time, total_bytes, pattern_info = test.run_test() + if success: + clog.info("SPI write test PASSED") + else: + clog.error("SPI write test FAILED") + return success, send_time, total_bytes + finally: + test.close() + + +# 自动运行测试(如果直接执行此脚本) +if __name__ == '__main__': + # 运行2MB递增数据测试 + success, send_time, total_bytes = run_spi_write_test( + data_size=2 * 1024 * 1024, # 2MB + chunk_size=1024, # 1KB chunks + spi_channel=2, # SPI通道2(对应MCU端SPI1) + spi_mode=0, # Mode 0 + spi_clk=8 # 1.625MHz(可根据需要调整) + ) + + if success: + clog.info("Test completed successfully!") + clog.info("Total bytes sent: {} ({:.2f} MB)".format( + total_bytes, total_bytes / 1024.0 / 1024.0)) + clog.info("Send time: {:.3f} seconds".format(send_time)) + else: + clog.error("Test failed!") + +# 如果作为模块导入,不自动运行 +# 可以通过以下方式使用: +# from spi_write_test import run_spi_write_test +# success, send_time, total_bytes = run_spi_write_test() + diff --git a/cat1modem/src/LeiNiao/telecom_dmp.py b/cat1modem/src/LeiNiao/telecom_dmp.py deleted file mode 100644 index 289ba2b79..000000000 --- a/cat1modem/src/LeiNiao/telecom_dmp.py +++ /dev/null @@ -1,438 +0,0 @@ -import app_fota -from misc import Power -import modem -import ujson -import request -import ql_fs -import checkNet -import sys -import sim - -from queue import Queue -import usys -import _thread -import fota -import utime -import log -import uos -import osTimer -import net -import ql_fs -from usr.EventMesh import eventMesh,CHINA_TELECOM_DM_FUN_ENABLE -# 设置日志输出级别 -log.basicConfig(level=log.INFO) -fota_log = log.getLogger("telecom_dmp") -import modem -import base64 -import request -import ujson -REGIST_MAX_NUM=10 #自注册的最大次数 现在是默认是10次 -REGIST_INTERVAL_TIMER=60*60 #自注册失败的间隔时间 1小时 -REGIST_POWER_ON_TIME=5*60 #开机5分钟内就必须自注册触发 -MULTI_DAY_RESEND_T = 30*24*60*60-12*60*60#多日重发的机制的周期T 30天 -MULTI_DAY_RESEND_t = 12*60*60#多日重发的机制的周期t 0.5天 -def get_report_url_website(mode = None):#获取上报网址 - url = "https://zzhc.vnet.cn/pi/"#上报网址 - return url - -class telecomDmp: - def __init__(self): - self.config_data = {} - self.check_timer = osTimer()#1小时定时器 - self.multiDayResend_timer = osTimer()#12小时定时器 多日重发的机制 - self.telecomTimer = osTimer() - self.multimer_count = 0#多日重发机制周期Count - self.multimer_init = 0 - self.power_on_multiflag = 0 - self.timer_count = 0 - self.timerPowerNum = 0 - self.telecom_init_state = 0#初始化状态开机检查标识 - self.isRegistOk = 0#注册成功 - eventMesh.subscribe("get_telecom_init_state",self.get_telecom_init_state) - eventMesh.subscribe("set_telecom_init_state",self.set_telecom_init_state) - - eventMesh.subscribe("set_dmp_power_num",self.set_dmp_power_num) - eventMesh.subscribe("get_dmp_power_num",self.get_dmp_power_num) - - eventMesh.subscribe("get_dmp_power_timer_count",self.get_dmp_power_timer_count) - - self.regist_queue = Queue() - _thread.stack_size(10*1024) - _thread.start_new_thread(self.regist_queue_thread, ()) - _thread.stack_size(8*1024) - - eventMesh.subscribe("publish_message_event_registinfor",self.publish_message_event_registinfor) - self.__net_connected = 0 - eventMesh.subscribe("get_network_state",self.get_network_state)#订阅消息 - eventMesh.subscribe("set_network_state",self.set_network_state)#订阅消息 - - eventMesh.subscribe("set_dev_version",self.set_dev_version)#订阅消息 - eventMesh.subscribe("get_dev_version",self.get_dev_version)#订阅消息 - - eventMesh.subscribe("save_iccid_data",self.save_iccid_data)#订阅消息 保存ICCID - eventMesh.subscribe("get_iccid_data",self.get_iccid_data)#订阅消息 保存ICCID - - eventMesh.subscribe("set_regist_time",self.set_regist_time)#订阅消息 保存ICCID - eventMesh.subscribe("get_regist_time",self.get_regist_time)#订阅消息 保存ICCID - - self.regist_timeSec = 0 - self.iccid_data = "" - self.software_version = "" - self.mulit_day_timesec = 0#多日重发的开始的时间戳 - pass - def regist_queue_thread(self): - while True: - data = self.regist_queue.get() - #EventMesh.debug_log("regist_queue:data = {}".format(data)) - if data != None: - try: - data["func"](data["data"]) - except Exception as e: - print("regist_queue:ERROR:{}".format(e)) - usys.print_exception(e) - def publish_message_event_registinfor(self,event,msg): - self.regist_queue.put(msg) - - def publish_regist_event_cb(self, *args, **kwargs): - eventMesh.publish("publish_message_event_registinfor",{"func":self.timer_cb,"data":0}) - - def set_network_state(self,event,msg):#设置网络是否正常 - self.__net_connected = int(msg) - - def get_network_state(self,event,msg):#获取网络是否正常 - return self.__net_connected - - def set_dev_version(self,event,msg):#获取固件版本号 - self.software_version = str(msg) - - def get_dev_version(self,event,msg):#获取固件版本号 - return self.software_version - - def save_iccid_data(self,event,msg): - self.iccid_data = str(msg)[6:]#ICCID: - print("write_config_msg_file msg:{}\r\n".format(msg)) - self.config_data["ICCID"]=self.iccid_data - with open("/usr/telecom_config.json",'w+',encoding="utf-8") as fd: - ujson.dump(self.config_data,fd) - #读文件信息 - def get_iccid_data(self,event,msg): - - IFEXIST = ql_fs.path_exists("/usr/telecom_config.json") #不存在 - if IFEXIST == True: - with open("/usr/telecom_config.json","r",encoding='utf-8') as fd: - self.config_data = ujson.load(fd) - print("get_iccid_data self.config_data:{}\r\n".format(self.config_data)) - if self.config_data != {}: - if "ICCID" in self.config_data: - value = self.config_data["ICCID"] - self.iccid_data = str(value) - else: - self.iccid_data = "" - print("get_iccid_data self.config_data end:{}\r\n".format(self.config_data)) - return self.iccid_data - - def set_regist_time(self,event,msg):# - self.regist_timeSec = str(msg)[7:]#REGIST: - print("set_regist_time msg:{}\r\n".format(msg)) - self.config_data["REGIST"]=self.regist_timeSec - with open("/usr/telecom_config.json",'w+',encoding="utf-8") as fd: - ujson.dump(self.config_data,fd) - - def get_regist_time(self,event,msg):# - IFEXIST = ql_fs.path_exists("/usr/telecom_config.json") #不存在 - if IFEXIST == True: - with open("/usr/telecom_config.json","r",encoding='utf-8') as fd: - self.config_data = ujson.load(fd) - print("get_regist_time self.config_data:{}\r\n".format(self.config_data)) - if self.config_data != {}: - if "REGIST" in self.config_data: - value = self.config_data["REGIST"] - self.regist_timeSec = int(value) - else: - self.regist_timeSec = 0 - return self.regist_timeSec - - - def get_sim_state(self):#获取sim状态 不为1是sim异常 - return sim.getStatus() - - def get_sim_iccid(self):#获取sim的iccid - return sim.getIccid() - - def get_sim_imsi(self):#获取sim的imsi - return sim.getImsi() - def get_dev_imei(self):#获取设备的imei号 - return modem.getDevImei() - - def telecom_regeist_infor(self):#自注册信息 可变更 - network_state = eventMesh.publish("get_network_state") - print("telecom_regeist_infor :",network_state,self.isRegistOk,REGIST_INTERVAL_TIMER,MULTI_DAY_RESEND_T,MULTI_DAY_RESEND_t,REGIST_POWER_ON_TIME) - if network_state == 0:# 第一次进入网络不正常就需要开启1小时定时器 等待下一个周期判断网络是否正常 需要判断网络是否正常 不正常就不去触发自注册了 就等待下一次 如果10次都不成功 就等一下次开机再去自注册 - self.timer_count+=1 - print("self.timer_count:{}".format(self.timer_count)) - if self.timer_count < REGIST_MAX_NUM:#总共是10次 10次结束还没有自注册成功就等下一次开机自注册 - self.check_timer.start(REGIST_INTERVAL_TIMER*1000,0,self.publish_regist_event_cb)#不需要循环定时器 - else: - self.multiple_day_resend() - return - iccid = self.get_sim_iccid() - read_iccid = eventMesh.publish("get_iccid_data") - if read_iccid == None: - read_iccid = "" - - print("iccid!!!!!!!!!!iccid {} read_iccid:{}".format(iccid,read_iccid)) - if read_iccid != "": - if str(read_iccid) == str(iccid):#两者相同 并且本地有保存 不再自注册了 说明已经自注册过了 - print("already registed!!!!!!") #已自注册过 - self.isRegistOk = 1 - - if self.isRegistOk == 1: - print("multiple_day_resend!!!!!!\r\n") - self.multiple_day_resend() - return - self.telecom_regist_infor_init()#自注册初始化函数 - if self.isRegistOk == 0: - self.timer_count+=1 - print("self.timer_count:{}".format(self.timer_count)) - if self.timer_count < REGIST_MAX_NUM:#总共是10次 10次结束还没有自注册成功就等下一次开机自注册 - self.check_timer.start(REGIST_INTERVAL_TIMER*1000,0,self.publish_regist_event_cb)#不需要循环定时器 - else: - self.multiple_day_resend() - - def telecom_regist_infor_init(self):#自注册的数据部分 - iccid = self.get_sim_iccid() - read_iccid = eventMesh.publish("get_iccid_data") - if read_iccid == None: - read_iccid = "" - url = get_report_url_website(None)#上报网址 根据需要更改上报网址 - if iccid == -1: - iccid = str("FF")#为空就填写FF - - IMSI = self.get_sim_imsi() - if IMSI == -1:#没有卡 则填FF - IMSI = str("FF") - imei = self.get_dev_imei() - if imei == -1: - imei = str("FF") - - data_tmp = net.getCellInfo() #会适当卡个3-5s - i = 0 - cell_data = 0 - if(len(data_tmp) >= 1): - for cell in data_tmp: - if i == 2:#在list里面最多就三个元素 最后一个是LTE的数据 - if cell != []: - for tup in cell: - print("tup:{}".format(tup)) - if tup[0] == 0 and len(tup) > 0:#第0个代表当前服务的小区CELLID 7位16进制数 - cell_data = tup[1]#这个是十进制数需要自行转换成十六进制数 - print(cell_data) - break - i+=1 - # cell_data = 0 - cell_data = "%07x"%(cell_data)#7位16进制数不够前面补0 - print(cell_data) - sendCellid = "{}".format(cell_data) - REGVER = "12.0"#当前数据域协议版本号 - - # MODEL = "TXB-Watch Soar"#机型字段 -前面是厂商名 -后面是设备名 - - MODEL = "KNZ-NWO N9"#机型字段 -前面是厂商名 -后面是设备名 - - get_dev_version = eventMesh.publish("get_dev_version")#订阅消息 - if get_dev_version == None: - get_dev_version = "" - SWVER = "NWO N9 V1.0"#get_dev_version #软件版本号 - UETYPE = "12"#终端类型 12是智能手表 11是儿童手表 - SIM1ICCID = iccid - SIM1LTEIMSI = IMSI - MACID = ""#可用WALN的地址 终端布支持WLAN 就是不可上网 - OSVER = "OHOS 5.0"#操作系统版本号 - IMEI1 = imei#"876226002021717"#imei - SIM1CELLID = sendCellid#下发的小区ID - data = { - "REGVER":REGVER, - "MODEL":MODEL, - "SWVER":SWVER, - "UETYPE":UETYPE, - "SIM1ICCID":SIM1ICCID, - "SIM1LTEIMSI":IMSI, - "MACID":MACID, - "OSVER":OSVER, - "IMEI1":IMEI1, - "SIM1CELLID":SIM1CELLID, - # "NETWORKTYPE":4 - "NWTYPE1":4, - } - data_infor=ujson.dumps(data)#将字典转换成json格式 - json_bytes = bytes(data_infor, 'utf-8')#转换成字节数 - data_encode = base64.b64encode(json_bytes)#再转换成base64的编码格式 密文 - print("data_infor:{} data:{}".format(data_encode,data)) - headers_T = {'Content-Type':'application/encrypted-json'} #请求头信息 按照实际要求填写 - request_result = 0 - try: - # https://zzhc.vnet.cn/pi - response = request.post("https://zzhc.vnet.cn/pi/", data=data_encode,headers =headers_T) - request_result = 1 - if response.status_code == 200 : #返回状态码 200服务器正常解析数据 - content = response.content#后台返回的结果 - c = next(content) - - jsload_data = ujson.loads(c) - print("jsload_data:{}".format(ujson.loads(c))) #{"resultCode": "0","resultDesc": "Success"} - if "resultCode" in jsload_data or "resultDesc" in jsload_data:#返回结果码值 - resultCode = jsload_data["resultCode"]#结果解析 "0" 成功 "1" 解码错误 "2" 校验错误 - resultDesc = jsload_data["resultDesc"]#Success 表示自注册成功 - - if resultCode == "0" or resultDesc == "Success":#注册成功退出当前注册 保存当前的ICCID 不在进行自注册了 - iccid = self.get_sim_iccid()#获取当前SIM的ICCID - if iccid != -1:#读到ICCID - self.timer_count = 0 - self.check_timer.stop() - self.isRegistOk = 1#已完成自注册的标识 - iccid_buf =str("ICCID:")+str(str(iccid)) - eventMesh.publish("save_iccid_data",iccid_buf) - self.multiple_day_resend() - #上报成功后 需要29.5或者30.5天后重发 初始值为0.5也就是12小时 30天后再重发一次也是一个月后重发数据自注册域 - print("regist success:!!!!!") - return 1 - if resultCode == "1": - print("Decode error")#解码错误 设备编码不正确 - return 0 - - if resultCode == "2":# - print("Check error")#校验错误 设备里面数据不正确 有些数据不应该为空 - return 0 - - except Exception as e: - print("response:{}".format(e)) - request_result = 0 - return 0 - - def timer_cb(self,data):#定时器回调 - self.telecom_regeist_infor()#重新进行自注册 - pass - # 多日重发功能要求: - # 1) 多日重发是指:在重发周期 T 内,如果没有功能性触发数据域自注册(触发发送且发送成功), - # 则终端应根据当前终端实时信息组织数据域自注册(不能直接拿上一周期的内容不变而重发)内容 - # 并发送的过程; - # 2) 多日重发的时间间隔周期 T 设置为 30 天; - # 3) 多日重发的过程应设置为:功能性(比如换卡等)触发数据域自注册后,立即启动定时器 t(t - # 的初始值应设为 0.5 天)开始重发计时,以后每当 t 时间等于 T 的整数倍(t=N*T)时,终端就进 - # 行数据域自注册发送,发送后 t 不要清零不要初始化,只有当功能性触发数据域自注册后,t 才初 - # 始化,t 的初始化值为 0.5 天。 - # Multiple day resend - def multiple_day_resend(self):#多日重发机制函数 没有发送数据域或者发送成功 - - t = 12 #计时周期 按照0.5天 也就是12小时来计算 - T = 30*24 #周期为30天 计算30天的总共小时数 - # t = N*T#只有t为T的整数倍就需要重发数据域自注册 - date_tmp = utime.localtime() - time_tmp = utime.mktime(date_tmp)#获取时间戳 - if self.multimer_init == 1:#已注册过或者开机5分钟之内没有注册 或者注册成功就开始出发多日重发机制 - self.mulit_day_timesec = time_tmp+MULTI_DAY_RESEND_t#初始值多加12小时 #初始化t = 0.5天 - REGIST_buf = str("REGIST:")+str(self.mulit_day_timesec) - eventMesh.publish("set_regist_time",REGIST_buf) - return - self.multimer_init = 1 - #设置开始时间戳 - get_timesec = eventMesh.publish("get_regist_time")#一开机初始化为0 没有保存过数据 如果保存过就用保存的时间 - if get_timesec == 0: - self.mulit_day_timesec = time_tmp+MULTI_DAY_RESEND_t - REGIST_buf = str("REGIST:")+str(self.mulit_day_timesec) - eventMesh.publish("set_regist_time",REGIST_buf) - else: - self.mulit_day_timesec = get_timesec - self.multiDayResend_timer.start(60*60*1000,1,self.multiDayResendTimer_Cb)#一分钟计时 - - def multiDayResendTimer_Cb(self,*argc):#多日重发机制定时器回调函数 - T = 30*24*60*60 - date_tmp = utime.localtime() - time_tmp = utime.mktime(date_tmp)+MULTI_DAY_RESEND_t#获取时间戳 - - if time_tmp < self.mulit_day_timesec:#如果当前时间戳小于保存的时间戳 - self.mulit_day_timesec = time_tmp - print("time_tmp: self.mulit_day_timesec:\r\n",time_tmp,self.mulit_day_timesec) - - if time_tmp - self.mulit_day_timesec >= MULTI_DAY_RESEND_T:#一个周期的时间到了重发注册一次 - network_state = eventMesh.publish("get_network_state") - print("get_network_state:\r\n",network_state) - if network_state == 0:#网络不正常就不需要重发了等下一个周期 - return - result = self.telecom_regist_infor_init()#要自注册数据域信息发送完才初始化t - if result == 0: #上报出错了 - return - self.mulit_day_timesec = time_tmp+MULTI_DAY_RESEND_t # 重发后 初始化t = 0.5天 - REGIST_buf = str("REGIST:")+str(self.mulit_day_timesec) - eventMesh.publish("set_regist_time",REGIST_buf) - - - def set_telecom_init_state(self,event,msg):#开机5分钟内检查是否有联网 有一次联网就关闭检查 - self.telecom_init_state = msg - - def get_telecom_init_state(self,event,msg): - return self.telecom_init_state - - def get_dmp_power_timer_count(self,event,msg):#开机后限定5分钟内完成自注册 - return REGIST_POWER_ON_TIME - - def get_dmp_power_num(self,event,msg):#开机限定次数最多十次 十次不成功就等下一次开机 或者多日重发机制 - print("get_dmp_power_num:{}".format(self.timerPowerNum)) - return self.timerPowerNum - - def set_dmp_power_num(self,event,msg): - print("set_dmp_power_num:{}".format(msg)) - self.timerPowerNum = msg - def clear_storage_data(self): - IFEXIST = ql_fs.path_exists("/usr/telecom_config.json") #不存在 - if IFEXIST == True: - uos.remove("/usr/telecom_config.json") - isStorage = ql_fs.path_exists("/usr/china_mobie_config.json")#移动入库的配置文件移除 - if isStorage == True: - uos.remove("/usr/china_mobie_config.json") - print("clear_storage_data;{} {}".format(IFEXIST,isStorage)) - - - def telecom_dmp_power_on_init(self):#开机初始化一次检测 - if eventMesh.publish("get_telecom_init_state") == 1:#发送消息获取对应的值 - return - if eventMesh.publish("get_dmp_power_num") < (eventMesh.publish("get_dmp_power_timer_count")*1000)/30000:#开机检查 - if eventMesh.publish("get_telecom_init_state") == 0:#开机初始化一次 - status = eventMesh.publish("get_factory_status") - if status == None: - status = 0 - if status == 0:#恢复出厂过需要清空存储数据 - self.clear_storage_data() - pass - self.telecomTimer.start(1*1000,0,self.fun_cb) - eventMesh.publish("set_telecom_init_state",1) - - def fun_cb(self,*argc): - self.telecom_regeist_infor()#开机初始一次自注册信息 - pass - def telecom_dmp_power_on_count(self):#开机计时判断最多十次 - if eventMesh.publish("get_telecom_init_state") != 1:#在开机五分钟之内只要一次联网正常就不需要再检查是否网络正常去自注册了 - if eventMesh.publish("get_dmp_power_num") < (eventMesh.publish("get_dmp_power_timer_count")*1000)/30000:#10+1 - num = eventMesh.publish("get_dmp_power_num") #开机计时 如果开机5分钟内没有一次网络正常的话 当前开机自注册不生效 等待下一次开机自注册触发 或者触发多日重发机制 - num+=1 - eventMesh.publish("set_dmp_power_num",num)#次数累计加一 - else: - if self.power_on_multiflag == 1: - return - self.power_on_multiflag = 1 - self.multiple_day_resend() - pass - -telecomDmpCb = telecomDmp() - - - - - - - - - - - - diff --git a/code/sdk/build/config/target_config/brandy/config.py b/code/sdk/build/config/target_config/brandy/config.py index dcb5428a2..5ab34cca8 100644 --- a/code/sdk/build/config/target_config/brandy/config.py +++ b/code/sdk/build/config/target_config/brandy/config.py @@ -248,7 +248,7 @@ target = { '_ALL_SOURCE', 'SUPPORT_CXX', 'I2C_SLAVE_REG_ADDR_4BYTE', 'CFG_DRIVERS_NANDFLASH', "-:TARGET_CHIP_BRANDY=1", "-:BRANDY_CHIP_V100=1", 'ENABLE_UIKIT', 'SW_UART_DEBUG', "HASH_MEM_COPY", 'ENABLE_ECC', 'SUPPORT_RECOVERY_DISPLAY', - 'CONFIG_SPI_SUPPORT_DMA=1', 'CONFIG_SPI_SUPPORT_POLL_AND_DMA_AUTO_SWITCH', 'CONFIG_SPI_AUTO_SWITCH_DMA_THRESHOLD=0x8', + 'MBEDTLS_NO_PLATFORM_ENTROPY', 'MBEDTLS_ENTROPY_HARDWARE_ALT', 'MARKET_ENABLE'], 'ram_component': ["algorithm", "dfx_port_brandy", "dfx_update", "osal", "arch_port", "-:testsuite", "liteos_port", 'lcd', 'qspi_display', 'dfx_file_operation', @@ -1305,6 +1305,8 @@ target = { # 'AILPAY_SUPPORT', # 'SUPPORT_FACTORY_SERVICE', # 'MEMORY_CORRUPTION', + 'CONFIG_SPI_SUPPORT_DMA=1', 'CONFIG_SPI_SUPPORT_POLL_AND_DMA_AUTO_SWITCH', 'CONFIG_SPI_AUTO_SWITCH_DMA_THRESHOLD=0x8', + # 'CONFIG_SPI_SUPPORT_INTERRUPT=1', 'SUPPORT_AI_SERVICE', 'SAP_EFFECT_SWS_SUPPORT', 'AUDIO_SMARTPA_AW88082', diff --git a/code/sdk/zhihui/utils/custom_utils.c b/code/sdk/zhihui/utils/custom_utils.c index 66df98f9f..6beb909a2 100644 --- a/code/sdk/zhihui/utils/custom_utils.c +++ b/code/sdk/zhihui/utils/custom_utils.c @@ -761,10 +761,8 @@ void startup_post_init_task(void) #ifdef SUPPORT_CAT1_SERVICE -#ifdef BOARD_LEINIAO - PRINT("PowerOn Cat1 !\r\n"); - uapi_set4GCapability(true); -#else +#ifndef BOARD_LEINIAO + if ( cm_poweron_cat1_on_bootup_flag() ) { PRINT("PowerOn Cat1 !\r\n"); diff --git a/code/sdk/zhihui/utils/custom_utils_at_cmd.c b/code/sdk/zhihui/utils/custom_utils_at_cmd.c index e8c4d3da7..60b8bf5db 100644 --- a/code/sdk/zhihui/utils/custom_utils_at_cmd.c +++ b/code/sdk/zhihui/utils/custom_utils_at_cmd.c @@ -54,6 +54,9 @@ /* Include custom_utilscpp.h for SPI1 receive functions */ #include "custom_utilscpp.h" +/* Include spi_protocol_test.h for protocol test functions */ +/* Note: spi_protocol_test.h is in utilscpp directory, need to check include path */ +#include "../utilscpp/spi_protocol_test.h" #define ARRAY_COUNT(array) (sizeof(array) / sizeof(array[0])) #define CUSTOM_UTILS_MAX_ARGC 8 @@ -1919,60 +1922,151 @@ static uint32_t custom_lowpower_alarm_test(uint8_t *para, uint32_t paraLen, char /** * @brief AT^SPI1RXSTART - Start SPI1 client receive data test - * Usage: AT^SPI1RXSTART + * Usage: AT^SPI1RXSTART - Start simple receive test (old protocol) + * AT^SPI1RXSTART=0 - Start simple receive test (old protocol) + * AT^SPI1RXSTART=1 - Start protocol receive test (new reliable protocol) */ static uint32_t custom_spi1_rx_start(uint8_t *para, uint32_t paraLen, char *strCommonCmd) { - UNUSED(para); - UNUSED(paraLen); UNUSED(strCommonCmd); - wstp_print("AT^SPI1RXSTART: Starting SPI1 client receive test...\n"); - custom_spi1_client_receive_start(); - wstp_print("AT^SPI1RXSTART: SPI1 receive started successfully\n"); + uint8_t mode = 0; // 默认使用旧协议(简单接收) + + // 解析参数(如果提供) + if (para != NULL && paraLen > 0) { + mode = (uint8_t)atoi((char *)para); + if (mode > 1) { + wstp_print("AT^SPI1RXSTART: Invalid mode %u (must be 0 or 1)\n", mode); + wstp_print("Usage: AT^SPI1RXSTART or AT^SPI1RXSTART=0 - Simple receive test\n"); + wstp_print(" AT^SPI1RXSTART=1 - Protocol receive test (reliable protocol)\n"); + return ERRCODE_FAIL; + } + } + + if (mode == 0) { + // 旧协议:简单接收测试 + wstp_print("AT^SPI1RXSTART: Starting SPI1 simple receive test (old protocol)...\n"); + custom_spi1_client_receive_start(); + wstp_print("AT^SPI1RXSTART: SPI1 simple receive started successfully\n"); + } else { + // 新协议:可靠协议接收测试 + wstp_print("AT^SPI1RXSTART: Starting SPI1 protocol receive test (new reliable protocol)...\n"); + spi_protocol_test_start(); + wstp_print("AT^SPI1RXSTART: SPI1 protocol receive test started successfully\n"); + wstp_print("AT^SPI1RXSTART: Data will be saved to /user/spi_test_received.bin\n"); + } return ERRCODE_SUCC; } /** * @brief AT^SPI1RXSTOP - Stop SPI1 client receive data test - * Usage: AT^SPI1RXSTOP + * Usage: AT^SPI1RXSTOP - Stop simple receive test (old protocol) + * AT^SPI1RXSTOP=0 - Stop simple receive test (old protocol) + * AT^SPI1RXSTOP=1 - Stop protocol receive test (new reliable protocol) */ static uint32_t custom_spi1_rx_stop(uint8_t *para, uint32_t paraLen, char *strCommonCmd) { - UNUSED(para); - UNUSED(paraLen); UNUSED(strCommonCmd); - wstp_print("AT^SPI1RXSTOP: Stopping SPI1 client receive test...\n"); - custom_spi1_client_receive_stop(); - wstp_print("AT^SPI1RXSTOP: SPI1 receive stopped successfully\n"); + uint8_t mode = 0; // 默认使用旧协议(简单接收) + + // 解析参数(如果提供) + if (para != NULL && paraLen > 0) { + mode = (uint8_t)atoi((char *)para); + if (mode > 1) { + wstp_print("AT^SPI1RXSTOP: Invalid mode %u (must be 0 or 1)\n", mode); + wstp_print("Usage: AT^SPI1RXSTOP or AT^SPI1RXSTOP=0 - Stop simple receive test\n"); + wstp_print(" AT^SPI1RXSTOP=1 - Stop protocol receive test\n"); + return ERRCODE_FAIL; + } + } + + if (mode == 0) { + // 旧协议:简单接收测试 + wstp_print("AT^SPI1RXSTOP: Stopping SPI1 simple receive test...\n"); + custom_spi1_client_receive_stop(); + wstp_print("AT^SPI1RXSTOP: SPI1 simple receive stopped successfully\n"); + } else { + // 新协议:可靠协议接收测试 + wstp_print("AT^SPI1RXSTOP: Stopping SPI1 protocol receive test...\n"); + spi_protocol_test_stop(); + wstp_print("AT^SPI1RXSTOP: SPI1 protocol receive test stopped successfully\n"); + } return ERRCODE_SUCC; } /** * @brief AT^SPI1RXSTAT - Get SPI1 receive statistics - * Usage: AT^SPI1RXSTAT + * Usage: AT^SPI1RXSTAT - Get simple receive statistics + * AT^SPI1RXSTAT=0 - Get simple receive statistics + * AT^SPI1RXSTAT=1 - Get protocol receive statistics */ static uint32_t custom_spi1_rx_stat(uint8_t *para, uint32_t paraLen, char *strCommonCmd) { - UNUSED(para); - UNUSED(paraLen); UNUSED(strCommonCmd); - uint32_t total_received = custom_spi1_client_get_total_received(); - float total_kb = total_received / 1024.0f; - float total_mb = total_kb / 1024.0f; + uint8_t mode = 0; // 默认使用旧协议(简单接收) + + // 解析参数(如果提供) + if (para != NULL && paraLen > 0) { + mode = (uint8_t)atoi((char *)para); + if (mode > 1) { + wstp_print("AT^SPI1RXSTAT: Invalid mode %u (must be 0 or 1)\n", mode); + wstp_print("Usage: AT^SPI1RXSTAT or AT^SPI1RXSTAT=0 - Simple receive statistics\n"); + wstp_print(" AT^SPI1RXSTAT=1 - Protocol receive statistics\n"); + return ERRCODE_FAIL; + } + } - if (total_mb >= 1.0f) { - wstp_print("AT^SPI1RXSTAT: Total received: %u bytes (%.2f MB, %.2f KB)\n", - total_received, total_mb, total_kb); - } else if (total_kb >= 1.0f) { - wstp_print("AT^SPI1RXSTAT: Total received: %u bytes (%.2f KB)\n", - total_received, total_kb); + if (mode == 0) { + // 旧协议:简单接收统计 + uint32_t total_received = custom_spi1_client_get_total_received(); + float total_kb = total_received / 1024.0f; + float total_mb = total_kb / 1024.0f; + + if (total_mb >= 1.0f) { + wstp_print("AT^SPI1RXSTAT: Simple receive - Total received: %u bytes (%.2f MB, %.2f KB)\n", + total_received, total_mb, total_kb); + } else if (total_kb >= 1.0f) { + wstp_print("AT^SPI1RXSTAT: Simple receive - Total received: %u bytes (%.2f KB)\n", + total_received, total_kb); + } else { + wstp_print("AT^SPI1RXSTAT: Simple receive - Total received: %u bytes\n", total_received); + } } else { - wstp_print("AT^SPI1RXSTAT: Total received: %u bytes\n", total_received); + // 新协议:可靠协议接收统计 + uint32_t total_packets = 0; + uint32_t error_packets = 0; + uint32_t total_bytes = 0; + uint32_t lost_packets = 0; + + spi_protocol_test_get_stats(&total_packets, &error_packets, &total_bytes, &lost_packets); + + float total_kb = total_bytes / 1024.0f; + float total_mb = total_kb / 1024.0f; + + wstp_print("AT^SPI1RXSTAT: Protocol receive statistics:\n"); + wstp_print(" Total packets: %u\n", total_packets); + wstp_print(" Error packets: %u\n", error_packets); + wstp_print(" Lost packets: %u\n", lost_packets); + + if (total_mb >= 1.0f) { + wstp_print(" Total bytes: %u (%.2f MB, %.2f KB)\n", + total_bytes, total_mb, total_kb); + } else if (total_kb >= 1.0f) { + wstp_print(" Total bytes: %u (%.2f KB)\n", total_bytes, total_kb); + } else { + wstp_print(" Total bytes: %u\n", total_bytes); + } + + if (error_packets == 0 && lost_packets == 0) { + wstp_print(" Status: OK (No errors, no lost packets)\n"); + } else { + wstp_print(" Status: ERROR (%u errors, %u lost packets)\n", + error_packets, lost_packets); + } } return ERRCODE_SUCC; diff --git a/code/sdk/zhihui/utilscpp/CMakeLists.txt b/code/sdk/zhihui/utilscpp/CMakeLists.txt index 2c2588472..78ef61269 100644 --- a/code/sdk/zhihui/utilscpp/CMakeLists.txt +++ b/code/sdk/zhihui/utilscpp/CMakeLists.txt @@ -8,6 +8,9 @@ set(SOURCES ${CMAKE_CURRENT_SOURCE_DIR}/custom_utilscpp.cpp ${CMAKE_CURRENT_SOURCE_DIR}/7zFile.cpp ${CMAKE_CURRENT_SOURCE_DIR}/7zStream.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/spi_protocol.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/spi_protocol_api.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/spi_protocol_test.cpp ) set(PUBLIC_HEADER diff --git a/code/sdk/zhihui/utilscpp/custom_utilscpp.cpp b/code/sdk/zhihui/utilscpp/custom_utilscpp.cpp index df6a29488..c08a3d7a9 100644 --- a/code/sdk/zhihui/utilscpp/custom_utilscpp.cpp +++ b/code/sdk/zhihui/utilscpp/custom_utilscpp.cpp @@ -98,7 +98,13 @@ static uint8_t g_spi1_receive_running = 0; static uint32_t g_spi1_total_received = 0; static uint32_t g_spi1_last_received = 0; static uint32_t g_spi1_last_print_time = 0; -#define SPI1_RECV_BUFFER_SIZE 4096 // DMA模式下使用4K缓冲区,提高传输效率,减少DMA配置次数 + +/* SPI1 data continuity check variables */ +static uint8_t g_spi1_expected_next_byte = 0; // 期望的下一个字节值(0-255循环) +static uint32_t g_spi1_discontinuity_count = 0; // 不连续数据计数 +static uint32_t g_spi1_first_byte_received = 0; // 是否已接收第一个字节(用于初始化期望值) +static uint32_t g_spi1_total_lost_bytes = 0; // 累计丢失的字节数 +#define SPI1_RECV_BUFFER_SIZE 2048 // DMA模式下使用1K缓冲区,降低DMA配置失败风险 #define SPI1_BUS_ID SPI_BUS_1 #define SPI1_RECV_TIMEOUT_MS 50 // 减小超时时间到50ms,提高读取频率,避免FIFO溢出 @@ -651,9 +657,11 @@ static void custom_spi1_receive_task_func(void *pvParameters) attr.sste = SPI_CFG_SSTE_DISABLE; attr.ndf = 1; - // 启用DMA模式,避免FIFO溢出(必须使用DMA,否则FIFO会一直full) + // DMA模式配置 + // 注意:在自动切换模式下,extra_attr.rx_use_dma设置为true只是告诉驱动支持DMA + // 实际是否使用DMA由uapi_spi_slave_read根据数据大小自动决定 extra_attr.tx_use_dma = false; - extra_attr.rx_use_dma = true; // 启用DMA接收模式,避免FIFO溢出 + extra_attr.rx_use_dma = true; // 启用DMA接收模式支持(自动切换模式下会根据数据大小决定) memset(&extra_attr.qspi_param, 0, sizeof(extra_attr.qspi_param)); memset(&extra_attr.sspi_param, 0, sizeof(extra_attr.sspi_param)); @@ -668,10 +676,12 @@ static void custom_spi1_receive_task_func(void *pvParameters) return; } - // Enable DMA mode for receiving (to avoid FIFO overflow) - // DMA mode allows data to be transferred directly to buffer without CPU intervention - // 必须使用DMA,否则FIFO会一直full + // DMA mode configuration + // 注意:如果启用了CONFIG_SPI_SUPPORT_POLL_AND_DMA_AUTO_SWITCH,驱动会自动根据数据大小 + // 决定使用DMA还是轮询模式,不需要手动设置DMA模式 #if defined(CONFIG_SPI_SUPPORT_DMA) && (CONFIG_SPI_SUPPORT_DMA == 1) +#if !(defined(CONFIG_SPI_SUPPORT_POLL_AND_DMA_AUTO_SWITCH) && (CONFIG_SPI_SUPPORT_POLL_AND_DMA_AUTO_SWITCH == 1)) + // 只有在非自动切换模式下,才需要手动设置DMA模式 // 检查缓冲区地址对齐(DMA通常要求4字节对齐) uint32_t buffer_addr = (uint32_t)(uintptr_t)spi_buffer; if (buffer_addr % 4 != 0) { @@ -715,6 +725,14 @@ static void custom_spi1_receive_task_func(void *pvParameters) } else { WEARABLE_LOGI(WEARABLE_LOG_MODULE_APP, "SPI1 DMA mode enabled for RX (prevents FIFO overflow)"); } +#else /* CONFIG_SPI_SUPPORT_POLL_AND_DMA_AUTO_SWITCH */ + // 自动切换模式下,不需要手动设置DMA模式 + // uapi_spi_slave_read会根据数据大小自动决定使用DMA还是轮询模式 + // 缓冲区大小1K(1024字节)大于阈值(8字节),会自动使用DMA模式 + WEARABLE_LOGI(WEARABLE_LOG_MODULE_APP, + "SPI1 DMA auto-switch enabled: will use DMA for rx_bytes > %d (current buffer: %u bytes)", + CONFIG_SPI_AUTO_SWITCH_DMA_THRESHOLD, SPI1_RECV_BUFFER_SIZE); +#endif /* CONFIG_SPI_SUPPORT_POLL_AND_DMA_AUTO_SWITCH */ #endif /* CONFIG_SPI_SUPPORT_DMA */ // Verify SPI configuration after initialization @@ -759,33 +777,268 @@ static void custom_spi1_receive_task_func(void *pvParameters) uint32_t timeout_count = 0; uint32_t success_count = 0; uint32_t error_count = 0; + uint32_t dma_fail_count = 0; // DMA配置失败计数 + bool use_polling_fallback = false; // 是否使用轮询模式回退 + uint32_t polling_buffer_size = 64; // 轮询模式使用较小的缓冲区(小于DMA阈值8字节,强制使用轮询) + uint32_t consecutive_dma_errors = 0; // 连续DMA错误计数(用于检测传输结束) + uint32_t last_success_total = 0; // 上次成功接收时的总字节数 + uint32_t consecutive_timeouts = 0; // 连续超时计数(用于检测传输结束) + uint32_t consecutive_no_data = 0; // 连续无数据计数(成功但rx_bytes=0) while (g_spi1_receive_running) { - spi_xfer_data_t spi_data = { - .rx_buff = spi_buffer, - .rx_bytes = sizeof(spi_buffer) // 期望接收的最大字节数(4K),但实际接收可能小于此值 - }; + spi_xfer_data_t spi_data = {0}; + + // 如果DMA配置连续失败多次,切换到轮询模式(使用小缓冲区) + // 注意:轮询模式可能导致FIFO溢出,但至少可以接收数据 +#if defined(CONFIG_SPI_SUPPORT_DMA) && (CONFIG_SPI_SUPPORT_DMA == 1) +#if defined(CONFIG_SPI_SUPPORT_POLL_AND_DMA_AUTO_SWITCH) && (CONFIG_SPI_SUPPORT_POLL_AND_DMA_AUTO_SWITCH == 1) + // 自动切换模式下,如果DMA配置失败,使用小于阈值的缓冲区强制轮询模式 + if (use_polling_fallback) { + // 使用小缓冲区(小于CONFIG_SPI_AUTO_SWITCH_DMA_THRESHOLD),强制使用轮询模式 + spi_data.rx_buff = spi_buffer; + spi_data.rx_bytes = polling_buffer_size; // 小于阈值,强制轮询模式 + } else { + // 正常模式:使用1K缓冲区,自动切换会根据阈值决定使用DMA还是轮询 + spi_data.rx_buff = spi_buffer; + spi_data.rx_bytes = sizeof(spi_buffer); // 1K,大于阈值,会尝试使用DMA + } + + // 智能检测传输结束:连续超时 + 连续DMA错误 + 没有新数据 + // 只在确认传输结束时才切换到轮询模式,保证性能 + // 重要:只有在已经接收到数据的情况下,才检测传输结束 + // 如果从未接收到数据,应该一直等待,不应该切换到轮询模式 + // 注意:DMA配置错误可能是暂时的(DMA通道不可用),不应该仅凭此判断传输结束 + // 应该主要依赖超时来判断,DMA错误只是辅助条件 + bool transfer_ended = false; + if (!use_polling_fallback && g_spi1_total_received > 0) { + // 检测传输结束条件(只有在已经接收到数据的情况下才检测): + // 1. 连续10次超时(主要条件,说明长时间没有数据) + // 2. 或者(连续20次DMA配置错误 且 连续5次超时)(更严格的条件) + // 3. 且没有新数据接收(总字节数没有增加) + // 提高阈值,避免误判:DMA错误可能是暂时的,不应该仅凭此判断传输结束 + if ((consecutive_timeouts >= 10) || + (consecutive_dma_errors >= 20 && consecutive_timeouts >= 5)) { + if (g_spi1_total_received == last_success_total) { + transfer_ended = true; + } + } + } + + // 如果检测到传输结束,切换到轮询模式接收剩余数据 + if (transfer_ended && !use_polling_fallback) { + use_polling_fallback = true; + WEARABLE_LOGW(WEARABLE_LOG_MODULE_APP, + "SPI1 Transfer ended detected (timeouts=%u, dma_errors=%u, no new data), switching to polling mode", + consecutive_timeouts, consecutive_dma_errors); + WEARABLE_LOGW(WEARABLE_LOG_MODULE_APP, + "SPI1 Using polling mode (buffer=%u bytes) to receive remaining data", polling_buffer_size); + // 显式禁用DMA模式,确保使用轮询模式 +#if !defined(CONFIG_SPI_SUPPORT_POLL_AND_DMA_AUTO_SWITCH) +#if defined(CONFIG_SPI_SUPPORT_DMA) && (CONFIG_SPI_SUPPORT_DMA == 1) + errcode_t dma_ret = uapi_spi_set_dma_mode(bus, false, NULL); + if (dma_ret == ERRCODE_SUCC) { + WEARABLE_LOGI(WEARABLE_LOG_MODULE_APP, "SPI1 DMA mode disabled for polling fallback"); + } else { + WEARABLE_LOGW(WEARABLE_LOG_MODULE_APP, "SPI1 Failed to disable DMA mode: 0x%x", dma_ret); + } +#endif /* CONFIG_SPI_SUPPORT_DMA */ +#endif /* !CONFIG_SPI_SUPPORT_POLL_AND_DMA_AUTO_SWITCH */ + } + + // 如果使用轮询模式且成功接收,说明还有数据,继续使用轮询模式 + // 只有在连续多次超时后,才确认传输真正结束,可以停止接收 + // 不恢复DMA模式,因为传输可能已经结束 +#else /* Not auto-switch mode */ + // 非自动切换模式:使用正常缓冲区大小 + // 智能检测传输结束:连续超时 + 连续DMA错误 + 没有新数据 + // 重要:只有在已经接收到数据的情况下,才检测传输结束 + // 如果从未接收到数据,应该一直等待,不应该切换到轮询模式 + // 注意:DMA配置错误可能是暂时的(DMA通道不可用),不应该仅凭此判断传输结束 + // 应该主要依赖超时来判断,DMA错误只是辅助条件 + bool transfer_ended = false; + if (!use_polling_fallback && g_spi1_total_received > 0) { + // 检测传输结束条件(只有在已经接收到数据的情况下才检测): + // 1. 连续10次超时(主要条件,说明长时间没有数据) + // 2. 或者(连续20次DMA配置错误 且 连续5次超时)(更严格的条件) + // 3. 且没有新数据接收(总字节数没有增加) + // 提高阈值,避免误判:DMA错误可能是暂时的,不应该仅凭此判断传输结束 + if ((consecutive_timeouts >= 10) || + (consecutive_dma_errors >= 20 && consecutive_timeouts >= 5)) { + if (g_spi1_total_received == last_success_total) { + transfer_ended = true; + } + } + } + + // 如果检测到传输结束,切换到轮询模式接收剩余数据 + if (transfer_ended && !use_polling_fallback) { + use_polling_fallback = true; + WEARABLE_LOGW(WEARABLE_LOG_MODULE_APP, + "SPI1 Transfer ended detected (timeouts=%u, dma_errors=%u, no new data), switching to polling mode", + consecutive_timeouts, consecutive_dma_errors); + WEARABLE_LOGW(WEARABLE_LOG_MODULE_APP, + "SPI1 Using polling mode (buffer=%u bytes) to receive remaining data", polling_buffer_size); + // 显式禁用DMA模式,确保使用轮询模式 +#if !defined(CONFIG_SPI_SUPPORT_POLL_AND_DMA_AUTO_SWITCH) +#if defined(CONFIG_SPI_SUPPORT_DMA) && (CONFIG_SPI_SUPPORT_DMA == 1) + errcode_t dma_ret = uapi_spi_set_dma_mode(bus, false, NULL); + if (dma_ret == ERRCODE_SUCC) { + WEARABLE_LOGI(WEARABLE_LOG_MODULE_APP, "SPI1 DMA mode disabled, using polling mode"); + } else { + WEARABLE_LOGW(WEARABLE_LOG_MODULE_APP, "SPI1 Failed to disable DMA mode: 0x%x", dma_ret); + } +#endif /* CONFIG_SPI_SUPPORT_DMA */ +#endif /* !CONFIG_SPI_SUPPORT_POLL_AND_DMA_AUTO_SWITCH */ + } + + if (use_polling_fallback) { + // 使用小缓冲区,强制使用轮询模式 + spi_data.rx_buff = spi_buffer; + spi_data.rx_bytes = polling_buffer_size; // 64字节,小于DMA阈值 + } else { + // 正常模式:使用1K缓冲区 + spi_data.rx_buff = spi_buffer; + spi_data.rx_bytes = sizeof(spi_buffer); + } + + // 如果连续多次错误,短暂延迟,给DMA通道释放时间 + if (error_count > 0 && error_count % 50 == 0) { + WEARABLE_LOGW(WEARABLE_LOG_MODULE_APP, + "SPI1 DMA errors detected (%u times), delaying to allow DMA channel release", error_count); + osDelay(50); // 延迟50ms,给DMA通道释放时间 + } +#endif /* CONFIG_SPI_SUPPORT_POLL_AND_DMA_AUTO_SWITCH */ +#else /* CONFIG_SPI_SUPPORT_DMA */ + // 没有DMA支持,使用轮询模式 + spi_data.rx_buff = spi_buffer; + spi_data.rx_bytes = sizeof(spi_buffer); +#endif /* CONFIG_SPI_SUPPORT_DMA */ // Receive data via SPI1 (Slave mode) using DMA // 注意: // 1. uapi_spi_slave_read 返回后,spi_data.rx_bytes 会被更新为实际接收的字节数 - // 2. 即使缓冲区是4K,如果Master只发送了部分数据,rx_bytes会反映实际接收的字节数 + // 2. 即使缓冲区是1K,如果Master只发送了部分数据,rx_bytes会反映实际接收的字节数 // 3. DMA会在以下情况返回: - // - 缓冲区填满(rx_bytes == 4K) - // - Master停止发送,DMA检测到传输结束(rx_bytes < 4K,但传输完成) + // - 缓冲区填满(rx_bytes == 1K) + // - Master停止发送,DMA检测到传输结束(rx_bytes < 1K,但传输完成) // - 超时(50ms内没有数据或传输未完成) ret = uapi_spi_slave_read(bus, &spi_data, SPI1_RECV_TIMEOUT_MS); if (ret == ERRCODE_SUCC) { // 检查实际接收的字节数(函数返回后,rx_bytes 会被更新为实际接收的字节数) if (spi_data.rx_bytes > 0) { - // Data received successfully (可能小于4K,这是正常的) + // Data received successfully (可能小于缓冲区大小,这是正常的) // 使用实际接收的字节数,而不是缓冲区大小 + + // 数据连续性检查:检查接收到的每个字节是否符合递增模式(0, 1, 2, ..., 255, 0, ...) + // 注意:使用基于序列号的检查,而不是基于offset的检查,因为offset可能因为数据丢失而不准确 + uint8_t *data_ptr = (uint8_t *)spi_data.rx_buff; + uint32_t valid_bytes = 0; // 实际有效的连续字节数 + uint32_t lost_bytes = 0; // 丢失的字节数(累计) + + for (uint32_t i = 0; i < spi_data.rx_bytes; i++) { + uint8_t received_byte = data_ptr[i]; + + if (!g_spi1_first_byte_received) { + // 第一个字节:初始化期望值,并记录为有效字节 + g_spi1_expected_next_byte = (received_byte + 1) % 256; + g_spi1_first_byte_received = 1; + valid_bytes++; + } else { + // 检查是否连续 + if (received_byte == g_spi1_expected_next_byte) { + // 数据连续,更新期望值 + g_spi1_expected_next_byte = (received_byte + 1) % 256; + valid_bytes++; + } else { + // 数据不连续:计算丢失的字节数 + // 如果接收到的值在期望值之前(考虑256循环),说明丢失了数据 + int8_t gap = (int8_t)(received_byte - g_spi1_expected_next_byte); + + // 处理256循环的情况 + if (gap < -128) { + gap += 256; // 正向循环 + } else if (gap > 128) { + gap -= 256; // 反向循环 + } + + if (gap > 0) { + // 接收到的值在期望值之后,说明丢失了gap字节 + lost_bytes += gap; + g_spi1_total_lost_bytes += gap; // 累加总丢失字节数 + g_spi1_discontinuity_count++; + + // 打印不连续信息(前10次,之后每100次打印一次,避免刷屏) + if (g_spi1_discontinuity_count <= 10 || g_spi1_discontinuity_count % 100 == 0) { + uint32_t current_offset = g_spi1_total_received + i; + WEARABLE_LOGW(WEARABLE_LOG_MODULE_APP, + "SPI1 Data discontinuity #%u: offset=%u, expected=0x%02X(%u), received=0x%02X(%u), lost=%d bytes (total lost: %u)", + g_spi1_discontinuity_count, current_offset, + g_spi1_expected_next_byte, g_spi1_expected_next_byte, + received_byte, received_byte, gap, g_spi1_total_lost_bytes); + } + } else if (gap < 0) { + // 接收到的值在期望值之前,可能是重复数据或乱序(这种情况较少见) + g_spi1_discontinuity_count++; + if (g_spi1_discontinuity_count <= 10 || g_spi1_discontinuity_count % 100 == 0) { + uint32_t current_offset = g_spi1_total_received + i; + WEARABLE_LOGW(WEARABLE_LOG_MODULE_APP, + "SPI1 Data out-of-order #%u: offset=%u, expected=0x%02X(%u), received=0x%02X(%u), gap=%d", + g_spi1_discontinuity_count, current_offset, + g_spi1_expected_next_byte, g_spi1_expected_next_byte, + received_byte, received_byte, gap); + } + } + + // 更新期望值(基于实际接收的值) + g_spi1_expected_next_byte = (received_byte + 1) % 256; + valid_bytes++; + } + } + } + + // 累加实际接收的字节数(包括丢失的数据,以保持offset的准确性) g_spi1_total_received += spi_data.rx_bytes; + + // 如果检测到数据丢失,记录警告 + if (lost_bytes > 0 && (g_spi1_discontinuity_count <= 10 || g_spi1_discontinuity_count % 100 == 0)) { + WEARABLE_LOGW(WEARABLE_LOG_MODULE_APP, + "SPI1 Data loss detected in this buffer: lost %u bytes, valid %u bytes, total received %u bytes", + lost_bytes, valid_bytes, g_spi1_total_received); + } success_count++; + last_success_total = g_spi1_total_received; // 更新最后成功接收的总字节数 + consecutive_dma_errors = 0; // 重置连续DMA错误计数 + consecutive_timeouts = 0; // 重置连续超时计数 + consecutive_no_data = 0; // 重置连续无数据计数 + + // 如果使用轮询模式且成功接收,说明还有数据,应该恢复DMA模式以提高性能 + // 如果成功接收到数据,说明传输还在继续,应该立即恢复DMA模式 + if (use_polling_fallback) { + // 轮询模式下成功接收到数据,说明传输还在继续,恢复DMA模式 + use_polling_fallback = false; + consecutive_timeouts = 0; // 重置超时计数 + consecutive_dma_errors = 0; // 重置DMA错误计数 + WEARABLE_LOGI(WEARABLE_LOG_MODULE_APP, + "SPI1 Polling mode received data (%u bytes), restoring DMA mode for better performance", + spi_data.rx_bytes); +#if !defined(CONFIG_SPI_SUPPORT_POLL_AND_DMA_AUTO_SWITCH) +#if defined(CONFIG_SPI_SUPPORT_DMA) && (CONFIG_SPI_SUPPORT_DMA == 1) + errcode_t dma_ret = uapi_spi_set_dma_mode(bus, true, NULL); + if (dma_ret == ERRCODE_SUCC) { + WEARABLE_LOGI(WEARABLE_LOG_MODULE_APP, + "SPI1 DMA mode restored successfully"); + } else { + WEARABLE_LOGW(WEARABLE_LOG_MODULE_APP, + "SPI1 Failed to restore DMA mode: 0x%x, will continue in polling mode", dma_ret); + use_polling_fallback = true; // 如果恢复失败,继续使用轮询模式 + } +#endif /* CONFIG_SPI_SUPPORT_DMA */ +#endif /* !CONFIG_SPI_SUPPORT_POLL_AND_DMA_AUTO_SWITCH */ + } // 打印接收到的数据信息(每接收一定数据量后打印一次,减少打印) - // 注意:即使只接收到部分数据(小于4K),也会正常处理 + // 注意:即使只接收到部分数据(小于1K),也会正常处理 if (g_spi1_total_received % (64 * 1024) < sizeof(spi_buffer)) { WEARABLE_LOGI(WEARABLE_LOG_MODULE_APP, "SPI1 RX: Received %u bytes in this call (buffer=%u), Total: %u bytes", @@ -793,7 +1046,16 @@ static void custom_spi1_receive_task_func(void *pvParameters) } } else { // 返回成功但没有数据(rx_bytes == 0),可能是FIFO为空或传输已完成 - // 继续等待下一次传输(不打印,避免刷屏) + consecutive_no_data++; + // 如果连续多次无数据,可能是传输已结束 + // 但在轮询模式下,如果连续多次无数据,可以停止接收 + if (use_polling_fallback && consecutive_no_data >= 10) { + WEARABLE_LOGI(WEARABLE_LOG_MODULE_APP, + "SPI1 Polling mode: No data received for %u times, transfer may have ended", + consecutive_no_data); + // 继续等待,但增加延迟 + osDelay(50); + } continue; } } else if (ret == ERRCODE_SPI_TIMEOUT) { @@ -801,6 +1063,45 @@ static void custom_spi1_receive_task_func(void *pvParameters) // 超时是正常的,说明在50ms内没有接收到数据或传输未完成 // 不打印timeout信息,避免刷屏 timeout_count++; + consecutive_timeouts++; + consecutive_no_data++; + + // 如果使用轮询模式且连续多次超时,说明传输可能真正结束 + // 只有在已经接收到数据的情况下才打印日志,避免刷屏 + if (use_polling_fallback && consecutive_timeouts >= 10) { + // 只有在接收到数据的情况下才打印,避免刷屏 + if (g_spi1_total_received > 0 && consecutive_timeouts % 10 == 0) { + WEARABLE_LOGI(WEARABLE_LOG_MODULE_APP, + "SPI1 Polling mode: Timeout for %u times, transfer may have ended. Total received: %u bytes", + consecutive_timeouts, g_spi1_total_received); + } + // 传输结束后,恢复DMA模式,准备下一轮接收 + if (consecutive_timeouts >= 20 && g_spi1_total_received > 0) { + // 连续20次超时,确认传输已结束,恢复DMA模式 + use_polling_fallback = false; + consecutive_timeouts = 0; + consecutive_dma_errors = 0; + last_success_total = g_spi1_total_received; // 更新最后成功接收的总字节数 +#if !defined(CONFIG_SPI_SUPPORT_POLL_AND_DMA_AUTO_SWITCH) +#if defined(CONFIG_SPI_SUPPORT_DMA) && (CONFIG_SPI_SUPPORT_DMA == 1) + errcode_t dma_ret = uapi_spi_set_dma_mode(bus, true, NULL); + if (dma_ret == ERRCODE_SUCC) { + WEARABLE_LOGI(WEARABLE_LOG_MODULE_APP, + "SPI1 Transfer ended, DMA mode restored for next transfer"); + } else { + WEARABLE_LOGW(WEARABLE_LOG_MODULE_APP, + "SPI1 Failed to restore DMA mode: 0x%x", dma_ret); + } +#endif /* CONFIG_SPI_SUPPORT_DMA */ +#endif /* !CONFIG_SPI_SUPPORT_POLL_AND_DMA_AUTO_SWITCH */ + } + // 延迟后继续,以防还有数据 + osDelay(100); + } else { + // 正常等待状态:增加延迟,让CPU进入低功耗状态 + // 超时说明没有数据,延迟50ms后再试,减少CPU占用 + osDelay(50); // 延迟50ms,让CPU有机会进入低功耗状态 + } continue; } else { // Other error, log error information @@ -814,19 +1115,32 @@ static void custom_spi1_receive_task_func(void *pvParameters) error_name = "SPI_INVALID_TMODE"; } else if (ret == 0x80001334) { // ERRCODE_SPI_RX_FIFO_FULL error_name = "SPI_RX_FIFO_FULL"; - // FIFO满可能是配置不匹配或读取不及时,打印一次详细诊断信息 + // FIFO满可能是配置不匹配或读取不及时 + // 在轮询模式下,FIFO满说明读取速度太慢,需要提高读取频率或增大缓冲区 if (error_count == 1) { WEARABLE_LOGE(WEARABLE_LOG_MODULE_APP, - "SPI1 RX_FIFO_FULL: Check SPI config match!"); + "SPI1 RX_FIFO_FULL: Check SPI config match or read speed!"); WEARABLE_LOGE(WEARABLE_LOG_MODULE_APP, "SPI1 MCU config: frame_size=7(8bit), mode=0(CPOL=0,CPHA=0), slave"); WEARABLE_LOGE(WEARABLE_LOG_MODULE_APP, "SPI1 Python should: SPI(2, 0, 9) - mode=0, 8bit, master"); + if (use_polling_fallback) { + WEARABLE_LOGE(WEARABLE_LOG_MODULE_APP, + "SPI1 Polling mode FIFO full: read speed too slow, consider increasing buffer size or read frequency"); + } + } + // 在轮询模式下,如果FIFO一直满,不延迟,立即继续读取,提高读取频率 + if (use_polling_fallback) { + // 不休眠,立即继续读取 + // continue; // 直接继续,不延迟 } } else if (ret == ERRCODE_SPI_TIMEOUT) { error_name = "SPI_TIMEOUT"; } else if (ret == ERRCODE_SPI_DMA_CONFIG_ERROR || ret == 0x80001336) { // ERRCODE_SPI_DMA_CONFIG_ERROR error_name = "SPI_DMA_CONFIG_ERROR"; + dma_fail_count++; // 增加DMA失败计数 + consecutive_dma_errors++; // 增加连续DMA错误计数 + // DMA配置错误,可能是handshaking配置失败或DMA通道不可用 // 检查可能的原因:缓冲区对齐、DMA通道、handshaking配置 if (error_count == 1) { @@ -842,7 +1156,26 @@ static void custom_spi1_receive_task_func(void *pvParameters) "SPI1 DMA config: src_width=0(8bit), dest_width=0(8bit), burst_length=0"); WEARABLE_LOGE(WEARABLE_LOG_MODULE_APP, "SPI1 Expected handshaking: HAL_DMA_HANDSHAKING_SPI_MS0_RX for SPI_BUS_1"); + WEARABLE_LOGE(WEARABLE_LOG_MODULE_APP, + "SPI1 Note: If DMA channel is unavailable, will fallback to polling mode after 3 failures"); } + + // DMA配置错误时,等待DMA通道自然释放 + // 传输结束检测由上面的逻辑处理,这里只做延迟 + if (consecutive_dma_errors > 0 && consecutive_dma_errors % 5 == 0) { + // 每5次错误后,短暂延迟,给DMA通道释放时间 + osDelay(20); // 延迟20ms,给DMA通道释放时间 + } + +#if defined(CONFIG_SPI_SUPPORT_POLL_AND_DMA_AUTO_SWITCH) && (CONFIG_SPI_SUPPORT_POLL_AND_DMA_AUTO_SWITCH == 1) + // 自动切换模式下,如果DMA配置失败,会在下次循环中使用小缓冲区强制轮询模式 + // 这里不需要额外处理,由上面的逻辑处理 +#else + // 非自动切换模式:如果连续多次失败,短暂延迟后再试 + if (error_count > 0 && error_count % 5 == 0) { + osDelay(10); // 每5次错误后延迟10ms,给DMA通道释放时间 + } +#endif /* CONFIG_SPI_SUPPORT_POLL_AND_DMA_AUTO_SWITCH */ } else if (ret == ERRCODE_SPI_DMA_TRANSFER_ERROR || ret == 0x80001337) { // ERRCODE_SPI_DMA_TRANSFER_ERROR error_name = "SPI_DMA_TRANSFER_ERROR"; // DMA传输错误,但不禁用DMA(因为禁用DMA会导致FIFO一直full) @@ -891,22 +1224,46 @@ static void custom_spi1_receive_task_func(void *pvParameters) float total_kb = session_total / 1024.0f; float total_mb = total_kb / 1024.0f; + // 打印总接收量 if (total_mb >= 1.0f) { WEARABLE_LOGI(WEARABLE_LOG_MODULE_APP, - "SPI1 receive session ended. Total received in this session: %u bytes (%.2f MB, %.2f KB)", + "SPI1 receive session ended. Total received: %u bytes (%.2f MB, %.2f KB)", session_total, total_mb, total_kb); } else if (total_kb >= 1.0f) { WEARABLE_LOGI(WEARABLE_LOG_MODULE_APP, - "SPI1 receive session ended. Total received in this session: %u bytes (%.2f KB)", + "SPI1 receive session ended. Total received: %u bytes (%.2f KB)", session_total, total_kb); } else { WEARABLE_LOGI(WEARABLE_LOG_MODULE_APP, - "SPI1 receive session ended. Total received in this session: %u bytes", + "SPI1 receive session ended. Total received: %u bytes", session_total); } + // 打印数据连续性统计 + WEARABLE_LOGI(WEARABLE_LOG_MODULE_APP, + "SPI1 Data continuity check: Total discontinuities: %u, Total lost bytes: %u", + g_spi1_discontinuity_count, g_spi1_total_lost_bytes); + if (g_spi1_discontinuity_count > 0) { + float error_rate = (g_spi1_discontinuity_count * 100.0f) / session_total; + float loss_rate = (g_spi1_total_lost_bytes * 100.0f) / (session_total + g_spi1_total_lost_bytes); + WEARABLE_LOGW(WEARABLE_LOG_MODULE_APP, + "SPI1 Data error rate: %.4f%% (%u errors in %u bytes), Data loss rate: %.4f%% (%u bytes lost)", + error_rate, g_spi1_discontinuity_count, session_total, + loss_rate, g_spi1_total_lost_bytes); + } else { + WEARABLE_LOGI(WEARABLE_LOG_MODULE_APP, + "SPI1 Data continuity: All data received correctly (0 discontinuities, 0 bytes lost)"); + } + // Deinitialize SPI uapi_spi_deinit(bus); + WEARABLE_LOGI(WEARABLE_LOG_MODULE_APP, "SPI1 deinitialized"); + + // 重置数据连续性检查变量 + g_spi1_expected_next_byte = 0; + g_spi1_first_byte_received = 0; + + g_spi1_receive_running = 0; } /** @@ -920,6 +1277,16 @@ void custom_spi1_client_receive_start(void) } g_spi1_receive_running = 1; + g_spi1_total_received = 0; // Reset total received bytes for new session + g_spi1_last_received = 0; // Reset last received bytes for new session + g_spi1_last_print_time = 0; // Reset last print time + g_spi1_discontinuity_count = 0; // Reset discontinuity count + g_spi1_total_lost_bytes = 0; // Reset lost bytes count + + // Reset data continuity check variables + g_spi1_expected_next_byte = 0; + g_spi1_discontinuity_count = 0; + g_spi1_first_byte_received = 0; // Create task for SPI receive app_task_definition_t taskcfg = {0}; @@ -950,35 +1317,73 @@ void custom_spi1_client_receive_stop(void) return; } - // Save total received before stopping + WEARABLE_LOGI(WEARABLE_LOG_MODULE_APP, "SPI1 client receive stop requested, waiting for task to finish..."); + + // Save total received and discontinuity count before stopping uint32_t session_total = g_spi1_total_received; + uint32_t discontinuity_count = g_spi1_discontinuity_count; g_spi1_receive_running = 0; + // 等待任务结束(最多等待2秒) + uint32_t wait_count = 0; + while (g_spi1_receive_running && wait_count < 200) { + osDelay(10); // 等待10ms + wait_count++; + } + + if (g_spi1_receive_running) { + WEARABLE_LOGW(WEARABLE_LOG_MODULE_APP, "SPI1 receive task did not stop in time"); + } else { + WEARABLE_LOGI(WEARABLE_LOG_MODULE_APP, "SPI1 receive task stopped successfully"); + } + // Stop and delete statistics timer if (g_spi1_stats_timer) { osTimerStop(g_spi1_stats_timer); osTimerDelete(g_spi1_stats_timer); g_spi1_stats_timer = NULL; + WEARABLE_LOGI(WEARABLE_LOG_MODULE_APP, "SPI1 statistics timer deleted"); } // Print final statistics float total_kb = session_total / 1024.0f; float total_mb = total_kb / 1024.0f; + // 打印总接收量 if (total_mb >= 1.0f) { WEARABLE_LOGI(WEARABLE_LOG_MODULE_APP, - "SPI1 client receive stopped. Total received in this session: %u bytes (%.2f MB, %.2f KB)", + "SPI1 Total received: %u bytes (%.2f MB, %.2f KB)", session_total, total_mb, total_kb); } else if (total_kb >= 1.0f) { WEARABLE_LOGI(WEARABLE_LOG_MODULE_APP, - "SPI1 client receive stopped. Total received in this session: %u bytes (%.2f KB)", + "SPI1 Total received: %u bytes (%.2f KB)", session_total, total_kb); } else { WEARABLE_LOGI(WEARABLE_LOG_MODULE_APP, - "SPI1 client receive stopped. Total received in this session: %u bytes", + "SPI1 Total received: %u bytes", session_total); } + + // 打印数据连续性统计 + WEARABLE_LOGI(WEARABLE_LOG_MODULE_APP, + "SPI1 Data continuity check: Total discontinuities: %u", discontinuity_count); + if (discontinuity_count > 0) { + float error_rate = (discontinuity_count * 100.0f) / (session_total > 0 ? session_total : 1); + WEARABLE_LOGW(WEARABLE_LOG_MODULE_APP, + "SPI1 Data error rate: %.4f%% (%u errors in %u bytes)", + error_rate, discontinuity_count, session_total); + } else if (session_total > 0) { + WEARABLE_LOGI(WEARABLE_LOG_MODULE_APP, + "SPI1 Data continuity: All data received correctly (0 discontinuities)"); + } + + // 清理资源(可选,取决于是否需要保留历史数据) + // 如果需要保留统计数据,可以注释掉下面的代码 + // g_spi1_total_received = 0; + // g_spi1_discontinuity_count = 0; + // g_spi1_expected_next_byte = 0; + // g_spi1_first_byte_received = 0; } /** @@ -988,4 +1393,33 @@ void custom_spi1_client_receive_stop(void) uint32_t custom_spi1_client_get_total_received(void) { return g_spi1_total_received; +} + +/* ==================== SPI Protocol Functions (New Reliable Protocol) ==================== */ + +/** + * @brief Start SPI1 protocol receive with reliable protocol + * + * @param callback Data receive callback function + */ +void custom_spi1_protocol_receive_start(spi_protocol_data_callback_t callback) +{ + // Stop old simple receive if running + if (g_spi1_receive_running) { + custom_spi1_client_receive_stop(); + } + + // Start new protocol receive + errcode_t ret = spi_protocol_receive_start(callback); + if (ret != ERRCODE_SUCC) { + WEARABLE_LOGE(WEARABLE_LOG_MODULE_APP, "Failed to start SPI protocol receive: 0x%x", ret); + } +} + +/** + * @brief Stop SPI1 protocol receive + */ +void custom_spi1_protocol_receive_stop(void) +{ + spi_protocol_receive_stop(); } \ No newline at end of file diff --git a/code/sdk/zhihui/utilscpp/custom_utilscpp.h b/code/sdk/zhihui/utilscpp/custom_utilscpp.h index eadcb9297..88f35f642 100644 --- a/code/sdk/zhihui/utilscpp/custom_utilscpp.h +++ b/code/sdk/zhihui/utilscpp/custom_utilscpp.h @@ -42,6 +42,11 @@ void custom_spi1_client_receive_start(void); void custom_spi1_client_receive_stop(void); uint32_t custom_spi1_client_get_total_received(void); // Get total bytes received in current session +/* SPI Protocol functions (new reliable protocol) */ +#include "spi_protocol.h" +void custom_spi1_protocol_receive_start(spi_protocol_data_callback_t callback); +void custom_spi1_protocol_receive_stop(void); + /** * input: * func 函数指针 diff --git a/code/sdk/zhihui/utilscpp/spi_protocol.cpp b/code/sdk/zhihui/utilscpp/spi_protocol.cpp new file mode 100644 index 000000000..2472ebd98 --- /dev/null +++ b/code/sdk/zhihui/utilscpp/spi_protocol.cpp @@ -0,0 +1,1002 @@ +#include "spi_protocol.h" +#include +#include +#include // 用于malloc/free +#include "wearable_log.h" +#include "soc_osal.h" +#include "thread_init.h" +#include "driver/spi.h" +#include "hal_spi.h" +#include "spi_porting.h" +#include "cmsis_os2.h" // 用于osPriority定义 +#include "los_base.h" // 用于LOS_Msleep +#include "custom_utils.h" // 用于uapi_create_osThread_custom +#include "los_queue.h" // 用于消息队列 + +/* CRC16-CCITT多项式 */ +#define CRC16_POLYNOMIAL 0x1021 +#define CRC16_INITIAL 0xFFFF + +/* SPI配置 */ +#define SPI_PROTOCOL_BUS_ID SPI_BUS_1 +#define SPI_PROTOCOL_RECV_TIMEOUT_MS 50 +#define SPI_PROTOCOL_RECV_BUFFER_SIZE 2048 + +/* 接收状态 */ +static uint8_t g_receive_running = 0; +static spi_protocol_data_callback_t g_data_callback = NULL; +static spi_protocol_stats_t g_stats = {0}; +static uint32_t g_expected_seq_num = 0; +static uint8_t g_is_first_packet = 1; + +/* 中断模式状态机 */ +typedef enum { + SPI_PROTOCOL_STATE_IDLE = 0, // 空闲,等待包头 + SPI_PROTOCOL_STATE_RECEIVING_HEADER, // 正在接收包头 + SPI_PROTOCOL_STATE_RECEIVING_DATA // 正在接收数据负载 +} spi_protocol_state_t; + +static spi_protocol_state_t g_receive_state = SPI_PROTOCOL_STATE_IDLE; +static uint8_t g_header_buffer[SPI_PROTOCOL_HEADER_SIZE] = {0}; +static uint32_t g_pending_data_len = 0; // 待接收的数据长度(不含CRC) + +/* ACK/NACK响应缓冲区 */ +static uint8_t g_pending_ack_buffer[SPI_PROTOCOL_HEADER_SIZE] = {0}; +static uint8_t g_has_pending_ack = 0; +static uint32_t g_pending_ack_seq_num = 0; + +/* 数据接收缓冲区(动态分配,避免栈溢出) */ +static uint8_t *g_data_buffer = NULL; +static uint32_t g_data_buffer_size = 0; + +/* TX缓冲区(TXRX模式需要,用于发送dummy数据或ACK/NACK) */ +static uint8_t *g_tx_buffer = NULL; +static uint32_t g_tx_buffer_size = 0; + +/** + * @brief 计算CRC16-CCITT + * + * @param data 数据指针 + * @param len 数据长度 + * @param initial 初始值 + * @return uint16_t CRC16值 + */ +static uint16_t crc16_ccitt(const uint8_t *data, uint32_t len, uint16_t initial) +{ + uint16_t crc = initial; + uint32_t i; + + for (i = 0; i < len; i++) { + crc ^= ((uint16_t)data[i] << 8); + uint8_t j; + for (j = 0; j < 8; j++) { + if (crc & 0x8000) { + crc = (crc << 1) ^ CRC16_POLYNOMIAL; + } else { + crc <<= 1; + } + } + crc &= 0xFFFF; + } + + return crc; +} + +/** + * @brief 发送ACK包 + * + * @param seq_num 序列号 + * @return errcode_t 错误码 + */ +static errcode_t send_ack(uint32_t seq_num) +{ + spi_protocol_header_t ack_header = {0}; + ack_header.magic = SPI_PROTOCOL_MAGIC; + ack_header.version = SPI_PROTOCOL_VERSION; + ack_header.type = PKT_TYPE_ACK; + ack_header.flags = 0; + ack_header.seq_num = seq_num; + ack_header.data_len = 0; + ack_header.header_crc = crc16_ccitt((uint8_t *)&ack_header, 14, CRC16_INITIAL); + ack_header.reserved = 0; + + // 将ACK包存储到缓冲区,等待Python端读取 + memcpy(g_pending_ack_buffer, &ack_header, SPI_PROTOCOL_HEADER_SIZE); + g_has_pending_ack = 1; + g_pending_ack_seq_num = seq_num; + + WEARABLE_LOGD(WEARABLE_LOG_MODULE_APP, "SPI Protocol: ACK prepared for seq_num=%u", seq_num); + + // 注意:实际发送ACK需要等待Python端发起SPI读取 + // 这里只是准备好ACK数据,实际发送在Python端读取时完成 + + return ERRCODE_SUCC; +} + +/** + * @brief 发送NACK包 + * + * @param seq_num 序列号 + * @return errcode_t 错误码 + */ +static errcode_t send_nack(uint32_t seq_num) +{ + spi_protocol_header_t nack_header = {0}; + nack_header.magic = SPI_PROTOCOL_MAGIC; + nack_header.version = SPI_PROTOCOL_VERSION; + nack_header.type = PKT_TYPE_NACK; + nack_header.flags = 0; + nack_header.seq_num = seq_num; + nack_header.data_len = 0; + nack_header.header_crc = crc16_ccitt((uint8_t *)&nack_header, 14, CRC16_INITIAL); + nack_header.reserved = 0; + + // 将NACK包存储到缓冲区,等待Python端读取 + memcpy(g_pending_ack_buffer, &nack_header, SPI_PROTOCOL_HEADER_SIZE); + g_has_pending_ack = 1; + g_pending_ack_seq_num = seq_num; + + WEARABLE_LOGW(WEARABLE_LOG_MODULE_APP, "SPI Protocol: NACK prepared for seq_num=%u", seq_num); + + // 注意:实际发送NACK需要等待Python端发起SPI读取 + // 这里只是准备好NACK数据,实际发送在Python端读取时完成 + + return ERRCODE_SUCC; +} + +/** + * @brief 检查并发送待发送的ACK/NACK响应 + * 当Python端发起SPI读取时,MCU端会通过此函数返回ACK/NACK + * + * 注意:此函数需要在SPI Slave模式下,当检测到主设备读取请求时调用 + * 实际实现可能需要通过中断或轮询检测主设备的时钟信号 + */ +static void check_and_send_pending_ack(void) +{ + if (g_has_pending_ack) { + // 如果有待发送的ACK/NACK,通过SPI发送 + // 注意:在SPI Slave模式下,从设备不能主动发起传输 + // 需要等待主设备发起读取请求 + // 这里暂时只标记,实际发送需要在主设备读取时完成 + // TODO: 实现实际的ACK/NACK发送机制 + } +} + +/** + * @brief 接收并处理数据包 + * + * @return errcode_t 错误码 + */ + +/** + * @brief 接收并处理数据包 + * + * @return errcode_t 错误码 + */ +static errcode_t receive_packet(void) +{ + spi_bus_t bus = SPI_BUS_1; + // 使用静态缓冲区存储包头(16字节,栈上分配安全) + static uint8_t header_buffer[SPI_PROTOCOL_HEADER_SIZE]; + spi_xfer_data_t spi_data = {0}; + errcode_t ret; + + // 1. 接收包头(16字节) + // 在TXRX模式下,即使只接收数据,也需要提供TX缓冲区(发送dummy数据或ACK/NACK) + // 如果有待发送的ACK/NACK,使用ACK/NACK数据;否则发送dummy数据 + + static uint8_t tx_buffer[SPI_PROTOCOL_HEADER_SIZE] = {0}; // TX缓冲区(用于发送dummy数据或ACK/NACK) + + // 检查是否有待发送的ACK/NACK + if (g_has_pending_ack) { + // 使用待发送的ACK/NACK作为TX数据 + memcpy(tx_buffer, g_pending_ack_buffer, SPI_PROTOCOL_HEADER_SIZE); + g_has_pending_ack = 0; // 清除待发送标志 + } else { + // 没有ACK/NACK,发送dummy数据(全0) + memset(tx_buffer, 0, SPI_PROTOCOL_HEADER_SIZE); + } + + spi_data.tx_buff = tx_buffer; // TXRX模式需要TX缓冲区 + spi_data.tx_bytes = SPI_PROTOCOL_HEADER_SIZE; + spi_data.rx_buff = header_buffer; + spi_data.rx_bytes = SPI_PROTOCOL_HEADER_SIZE; + + ret = uapi_spi_slave_read(bus, &spi_data, SPI_PROTOCOL_RECV_TIMEOUT_MS); + + // 处理DMA错误:即使已禁用DMA,底层可能仍返回DMA错误 + // 如果实际接收到了完整数据,继续处理 + if (ret == ERRCODE_SPI_DMA_CONFIG_ERROR || ret == ERRCODE_SPI_DMA_TRANSFER_ERROR) { + if (spi_data.rx_bytes == SPI_PROTOCOL_HEADER_SIZE) { + // DMA错误但接收到了完整包头,继续处理(可能是底层误报) + WEARABLE_LOGD(WEARABLE_LOG_MODULE_APP, + "SPI Protocol: DMA error (0x%x) but header received successfully (%u bytes), continuing", + ret, spi_data.rx_bytes); + ret = ERRCODE_SUCC; // 将错误码改为成功,继续处理 + } else { + // DMA错误且数据不完整,返回错误让主循环重试 + return ret; + } + } + + if (ret != ERRCODE_SUCC || spi_data.rx_bytes != SPI_PROTOCOL_HEADER_SIZE) { + return ret; + } + + // 2. 解析包头 + spi_protocol_header_t *header = (spi_protocol_header_t *)header_buffer; + + // 3. 校验Magic(减少无效magic的日志打印,避免刷屏) + static uint32_t invalid_magic_count = 0; // 静态变量,用于计数 + if (header->magic != SPI_PROTOCOL_MAGIC) { + // 只在调试模式下打印,或者每100次打印一次,避免刷屏 + invalid_magic_count++; + if (invalid_magic_count <= 5 || invalid_magic_count % 100 == 0) { + WEARABLE_LOGD(WEARABLE_LOG_MODULE_APP, + "SPI Protocol: Invalid magic: 0x%02X (count=%u, timeout is normal when no data)", + header->magic, invalid_magic_count); + } + return ERRCODE_SPI_TIMEOUT; // 返回超时错误,让主循环继续等待 + } + + // 重置无效magic计数(收到有效包) + if (invalid_magic_count > 0) { + WEARABLE_LOGD(WEARABLE_LOG_MODULE_APP, + "SPI Protocol: Received valid packet after %u invalid magic attempts", invalid_magic_count); + invalid_magic_count = 0; + } + + // 4. 校验版本 + if (header->version != SPI_PROTOCOL_VERSION) { + WEARABLE_LOGW(WEARABLE_LOG_MODULE_APP, "SPI Protocol: Unsupported version: %u", header->version); + return ERRCODE_FAIL; + } + + // 5. 校验包头CRC16 + uint16_t calculated_crc = crc16_ccitt(header_buffer, 14, CRC16_INITIAL); + if (calculated_crc != header->header_crc) { + WEARABLE_LOGW(WEARABLE_LOG_MODULE_APP, + "SPI Protocol: Header CRC mismatch: calculated=0x%04X, received=0x%04X", + calculated_crc, header->header_crc); + send_nack(header->seq_num); + g_stats.error_packets++; + return ERRCODE_FAIL; + } + + // 6. 处理EOT包 + if (header->type == PKT_TYPE_EOT) { + WEARABLE_LOGI(WEARABLE_LOG_MODULE_APP, "SPI Protocol: Received EOT packet, transmission complete"); + send_ack(header->seq_num); + return ERRCODE_SUCC; + } + + // 7. 处理数据包 + if (header->type == PKT_TYPE_DATA) { + // 检查序列号连续性 + if (g_expected_seq_num != 0 && header->seq_num != g_expected_seq_num) { + if (header->seq_num < g_expected_seq_num) { + // 重复包,发送ACK并丢弃 + WEARABLE_LOGW(WEARABLE_LOG_MODULE_APP, + "SPI Protocol: Duplicate packet seq_num=%u (expected=%u), sending ACK", + header->seq_num, g_expected_seq_num); + send_ack(header->seq_num); + return ERRCODE_SUCC; + } else { + // 丢失包 + uint32_t lost = header->seq_num - g_expected_seq_num; + WEARABLE_LOGW(WEARABLE_LOG_MODULE_APP, + "SPI Protocol: Lost %u packets (expected=%u, received=%u)", + lost, g_expected_seq_num, header->seq_num); + g_stats.lost_packets += lost; + } + } + + // 检查数据长度 + if (header->data_len > SPI_PROTOCOL_MAX_DATA_SIZE) { + WEARABLE_LOGW(WEARABLE_LOG_MODULE_APP, + "SPI Protocol: Data length too large: %u (max=%u)", + header->data_len, SPI_PROTOCOL_MAX_DATA_SIZE); + send_nack(header->seq_num); + g_stats.error_packets++; + return ERRCODE_FAIL; + } + + // 8. 接收数据负载 + if (header->data_len > 0) { + uint32_t total_to_receive = header->data_len + SPI_PROTOCOL_DATA_CRC_SIZE; + + // 动态分配或扩展缓冲区(避免栈溢出) + if (g_data_buffer == NULL || g_data_buffer_size < total_to_receive) { + // 释放旧缓冲区 + if (g_data_buffer != NULL) { + free(g_data_buffer); + g_data_buffer = NULL; + } + + // 分配新缓冲区(多分配一些,避免频繁重新分配) + uint32_t alloc_size = total_to_receive; + if (alloc_size < SPI_PROTOCOL_MAX_DATA_SIZE / 2) { + alloc_size = SPI_PROTOCOL_MAX_DATA_SIZE / 2; // 至少分配一半大小 + } + if (alloc_size > SPI_PROTOCOL_MAX_DATA_SIZE + SPI_PROTOCOL_DATA_CRC_SIZE) { + alloc_size = SPI_PROTOCOL_MAX_DATA_SIZE + SPI_PROTOCOL_DATA_CRC_SIZE; + } + + g_data_buffer = (uint8_t *)malloc(alloc_size); + if (g_data_buffer == NULL) { + WEARABLE_LOGE(WEARABLE_LOG_MODULE_APP, + "SPI Protocol: Failed to allocate data buffer (%u bytes)", alloc_size); + send_nack(header->seq_num); + g_stats.error_packets++; + return ERRCODE_FAIL; + } + g_data_buffer_size = alloc_size; + WEARABLE_LOGD(WEARABLE_LOG_MODULE_APP, + "SPI Protocol: Allocated data buffer: %u bytes", alloc_size); + } + + // 在TXRX模式下,需要同时提供TX和RX缓冲区 + // 为TX缓冲区分配空间(发送dummy数据) + // 使用全局TX缓冲区(动态分配) + if (g_tx_buffer == NULL || g_tx_buffer_size < total_to_receive) { + if (g_tx_buffer != NULL) { + free(g_tx_buffer); + } + g_tx_buffer = (uint8_t *)malloc(total_to_receive); + if (g_tx_buffer == NULL) { + WEARABLE_LOGE(WEARABLE_LOG_MODULE_APP, + "SPI Protocol: Failed to allocate TX buffer (%u bytes)", total_to_receive); + send_nack(header->seq_num); + g_stats.error_packets++; + return ERRCODE_FAIL; + } + g_tx_buffer_size = total_to_receive; + } + + // 初始化TX缓冲区为0(dummy数据) + memset(g_tx_buffer, 0, total_to_receive); + + spi_data.tx_buff = g_tx_buffer; // TXRX模式需要TX缓冲区 + spi_data.tx_bytes = total_to_receive; + spi_data.rx_buff = g_data_buffer; + spi_data.rx_bytes = total_to_receive; + + ret = uapi_spi_slave_read(bus, &spi_data, SPI_PROTOCOL_RECV_TIMEOUT_MS); + + // DMA错误在某些情况下是正常的(如DMA通道被占用、自动切换等), + // 如果实际接收到了数据,可以继续处理 + if (ret == ERRCODE_SPI_DMA_CONFIG_ERROR || ret == ERRCODE_SPI_DMA_TRANSFER_ERROR) { + // DMA错误但可能已经通过polling模式接收到了数据 + if (spi_data.rx_bytes == total_to_receive) { + // 实际接收到了完整数据,DMA错误可以忽略 + WEARABLE_LOGD(WEARABLE_LOG_MODULE_APP, + "SPI Protocol: DMA error (0x%x) but data received successfully (%u bytes), continuing", + ret, spi_data.rx_bytes); + ret = ERRCODE_SUCC; // 将错误码改为成功,继续处理 + } else { + // DMA错误且数据不完整,需要重试 + WEARABLE_LOGW(WEARABLE_LOG_MODULE_APP, + "SPI Protocol: DMA error (0x%x) and incomplete data: received=%u, expected=%u, will retry", + ret, spi_data.rx_bytes, total_to_receive); + send_nack(header->seq_num); + g_stats.error_packets++; + return ret; // 返回错误,让主循环重试 + } + } + + if (ret != ERRCODE_SUCC || spi_data.rx_bytes != total_to_receive) { + WEARABLE_LOGW(WEARABLE_LOG_MODULE_APP, + "SPI Protocol: Failed to receive data: ret=0x%x, received=%u, expected=%u", + ret, spi_data.rx_bytes, total_to_receive); + send_nack(header->seq_num); + g_stats.error_packets++; + return ERRCODE_FAIL; + } + + // 9. 校验数据CRC16 + uint16_t received_crc = (g_data_buffer[header->data_len] << 8) | g_data_buffer[header->data_len + 1]; + uint16_t calculated_data_crc = crc16_ccitt(g_data_buffer, header->data_len, CRC16_INITIAL); + + if (calculated_data_crc != received_crc) { + WEARABLE_LOGW(WEARABLE_LOG_MODULE_APP, + "SPI Protocol: Data CRC mismatch: calculated=0x%04X, received=0x%04X", + calculated_data_crc, received_crc); + send_nack(header->seq_num); + g_stats.error_packets++; + return ERRCODE_FAIL; + } + + // 10. 发送ACK + send_ack(header->seq_num); + + // 11. 调用回调函数 + if (g_data_callback != NULL) { + uint8_t is_first = (header->flags & FLAG_FIRST) ? 1 : 0; + uint8_t is_last = (header->flags & FLAG_LAST) ? 1 : 0; + g_data_callback(g_data_buffer, header->data_len, is_first, is_last); + } + + // 12. 更新统计 + g_stats.total_packets++; + g_stats.total_bytes += header->data_len; + g_expected_seq_num = header->seq_num + 1; + + if ((header->flags & FLAG_FIRST)) { + g_is_first_packet = 0; + } + } else { + // 空数据包 + send_ack(header->seq_num); + g_stats.total_packets++; + } + + return ERRCODE_SUCC; + } + + // 未知包类型 + WEARABLE_LOGW(WEARABLE_LOG_MODULE_APP, "SPI Protocol: Unknown packet type: %u", header->type); + return ERRCODE_FAIL; +} + +/* 中断模式相关代码已删除,使用DMA/轮询模式 */ + +/** + * @brief 处理中断接收到的包头 + */ +static errcode_t process_received_header(void) +{ + spi_protocol_header_t *header = (spi_protocol_header_t *)g_header_buffer; + + // 校验Magic + if (header->magic != SPI_PROTOCOL_MAGIC) { + return ERRCODE_SPI_TIMEOUT; // 无效magic,返回超时让主循环继续等待 + } + + // 校验版本(减少日志打印,避免刷屏) + if (header->version != SPI_PROTOCOL_VERSION) { + static uint32_t invalid_version_count = 0; + invalid_version_count++; + // 只在调试模式下打印,或者每100次打印一次,避免刷屏 + if (invalid_version_count <= 5 || invalid_version_count % 100 == 0) { + WEARABLE_LOGD(WEARABLE_LOG_MODULE_APP, + "SPI Protocol: Unsupported version: %u (expected=%u, count=%u, may be noise or misaligned data)", + header->version, SPI_PROTOCOL_VERSION, invalid_version_count); + } + // 返回超时错误,让主循环清空缓冲区继续等待 + return ERRCODE_SPI_TIMEOUT; + } + + // 重置无效版本计数(收到有效包) + static uint32_t invalid_version_count = 0; + if (invalid_version_count > 0) { + WEARABLE_LOGD(WEARABLE_LOG_MODULE_APP, + "SPI Protocol: Received valid packet after %u invalid version attempts", invalid_version_count); + invalid_version_count = 0; + } + + // 校验包头CRC16 + uint16_t calculated_crc = crc16_ccitt(g_header_buffer, 14, CRC16_INITIAL); + if (calculated_crc != header->header_crc) { + WEARABLE_LOGW(WEARABLE_LOG_MODULE_APP, + "SPI Protocol: Header CRC mismatch: calculated=0x%04X, received=0x%04X", + calculated_crc, header->header_crc); + send_nack(header->seq_num); + g_stats.error_packets++; + return ERRCODE_FAIL; + } + + // 处理EOT包 + if (header->type == PKT_TYPE_EOT) { + WEARABLE_LOGI(WEARABLE_LOG_MODULE_APP, "SPI Protocol: Received EOT packet, transmission complete"); + send_ack(header->seq_num); + g_receive_state = SPI_PROTOCOL_STATE_IDLE; + return ERRCODE_SUCC; + } + + // 处理数据包 + if (header->type == PKT_TYPE_DATA) { + // 检查序列号连续性 + if (g_expected_seq_num != 0 && header->seq_num != g_expected_seq_num) { + if (header->seq_num < g_expected_seq_num) { + // 重复包,发送ACK并丢弃 + WEARABLE_LOGW(WEARABLE_LOG_MODULE_APP, + "SPI Protocol: Duplicate packet seq_num=%u (expected=%u), sending ACK", + header->seq_num, g_expected_seq_num); + send_ack(header->seq_num); + g_receive_state = SPI_PROTOCOL_STATE_IDLE; + return ERRCODE_SUCC; + } else { + // 丢失包 + uint32_t lost = header->seq_num - g_expected_seq_num; + WEARABLE_LOGW(WEARABLE_LOG_MODULE_APP, + "SPI Protocol: Lost %u packets (expected=%u, received=%u)", + lost, g_expected_seq_num, header->seq_num); + g_stats.lost_packets += lost; + } + } + + // 检查数据长度 + if (header->data_len > SPI_PROTOCOL_MAX_DATA_SIZE) { + WEARABLE_LOGW(WEARABLE_LOG_MODULE_APP, + "SPI Protocol: Data length too large: %u (max=%u)", + header->data_len, SPI_PROTOCOL_MAX_DATA_SIZE); + send_nack(header->seq_num); + g_stats.error_packets++; + g_receive_state = SPI_PROTOCOL_STATE_IDLE; + return ERRCODE_FAIL; + } + + // 保存待接收的数据长度 + g_pending_data_len = header->data_len; + + // 如果数据长度为0,直接发送ACK + if (header->data_len == 0) { + send_ack(header->seq_num); + g_stats.total_packets++; + g_expected_seq_num = header->seq_num + 1; + g_receive_state = SPI_PROTOCOL_STATE_IDLE; + return ERRCODE_SUCC; + } + + // 需要接收数据负载,切换到接收数据状态 + g_receive_state = SPI_PROTOCOL_STATE_RECEIVING_DATA; + return ERRCODE_SUCC; + } + + // 未知包类型 + WEARABLE_LOGW(WEARABLE_LOG_MODULE_APP, "SPI Protocol: Unknown packet type: %u", header->type); + g_receive_state = SPI_PROTOCOL_STATE_IDLE; + return ERRCODE_FAIL; +} + +/** + * @brief 处理中断接收到的数据负载 + */ +static errcode_t process_received_data(void) +{ + spi_protocol_header_t *header = (spi_protocol_header_t *)g_header_buffer; + uint32_t total_to_receive = g_pending_data_len + SPI_PROTOCOL_DATA_CRC_SIZE; + + // 检查接收到的数据长度 + if (g_data_buffer == NULL || g_data_buffer_size < total_to_receive) { + WEARABLE_LOGE(WEARABLE_LOG_MODULE_APP, + "SPI Protocol: Data buffer not allocated or too small: buffer=%p, size=%u, needed=%u", + g_data_buffer, g_data_buffer_size, total_to_receive); + send_nack(header->seq_num); + g_stats.error_packets++; + g_receive_state = SPI_PROTOCOL_STATE_IDLE; + return ERRCODE_FAIL; + } + + // 校验数据CRC16 + uint16_t received_crc = (g_data_buffer[g_pending_data_len] << 8) | g_data_buffer[g_pending_data_len + 1]; + uint16_t calculated_data_crc = crc16_ccitt(g_data_buffer, g_pending_data_len, CRC16_INITIAL); + + if (calculated_data_crc != received_crc) { + WEARABLE_LOGW(WEARABLE_LOG_MODULE_APP, + "SPI Protocol: Data CRC mismatch: calculated=0x%04X, received=0x%04X", + calculated_data_crc, received_crc); + send_nack(header->seq_num); + g_stats.error_packets++; + g_receive_state = SPI_PROTOCOL_STATE_IDLE; + return ERRCODE_FAIL; + } + + // 发送ACK + send_ack(header->seq_num); + + // 调用回调函数 + if (g_data_callback != NULL) { + uint8_t is_first = (header->flags & FLAG_FIRST) ? 1 : 0; + uint8_t is_last = (header->flags & FLAG_LAST) ? 1 : 0; + g_data_callback(g_data_buffer, g_pending_data_len, is_first, is_last); + } + + // 更新统计 + g_stats.total_packets++; + g_stats.total_bytes += g_pending_data_len; + g_expected_seq_num = header->seq_num + 1; + + if ((header->flags & FLAG_FIRST)) { + g_is_first_packet = 0; + } + + // 重置状态,准备接收下一个包 + g_receive_state = SPI_PROTOCOL_STATE_IDLE; + return ERRCODE_SUCC; +} + +/** + * @brief SPI协议接收任务函数 + */ +static void spi_protocol_receive_task_func(void *pvParameters) +{ + UNUSED(pvParameters); + + spi_bus_t bus = SPI_BUS_1; + errcode_t ret; + + WEARABLE_LOGI(WEARABLE_LOG_MODULE_APP, "SPI Protocol receive task started"); + + // 重置状态 + g_expected_seq_num = 0; + g_is_first_packet = 1; + g_has_pending_ack = 0; + memset(&g_stats, 0, sizeof(g_stats)); + + // 确保数据缓冲区已释放(如果之前有残留) + if (g_data_buffer != NULL) { + free(g_data_buffer); + g_data_buffer = NULL; + g_data_buffer_size = 0; + } + + // 确保TX缓冲区已释放(如果之前有残留) + if (g_tx_buffer != NULL) { + free(g_tx_buffer); + g_tx_buffer = NULL; + g_tx_buffer_size = 0; + } + + // 初始化SPI1为Slave模式(Client) + spi_attr_t attr = {0}; + spi_extra_attr_t extra_attr = {0}; + + attr.is_slave = true; // Slave模式(Client) + attr.slave_num = 0; // Slave编号 + attr.bus_clk = 32000000; // 总线时钟32MHz + attr.freq_mhz = 0; // Slave模式下由Master控制(26MHz由Master设置) + attr.clk_polarity = SPI_CFG_CLK_CPOL_0; // CPOL=0 + attr.clk_phase = SPI_CFG_CLK_CPHA_0; // CPHA=0 (Mode 0) + attr.frame_format = SPI_CFG_FRAME_FORMAT_MOTOROLA_SPI; + attr.spi_frame_format = (uint32_t)HAL_SPI_FRAME_FORMAT_STANDARD; // 标准SPI格式 + attr.frame_size = (uint32_t)HAL_SPI_FRAME_SIZE_8; // 8位数据 + attr.tmod = HAL_SPI_TRANS_MODE_TXRX; // TXRX模式(双向传输,支持ACK/NACK发送) + attr.sste = SPI_CFG_SSTE_DISABLE; + attr.ndf = 1; + + // DMA模式配置:TXRX模式下,收发都使用DMA,避免来回切换 + extra_attr.tx_use_dma = true; // 发送使用DMA(ACK/NACK发送,提高性能) + extra_attr.rx_use_dma = true; // 接收使用DMA(大数据量接收,提高性能) + memset(&extra_attr.qspi_param, 0, sizeof(extra_attr.qspi_param)); + memset(&extra_attr.sspi_param, 0, sizeof(extra_attr.sspi_param)); + + // Deinitialize SPI first (if already initialized) + uapi_spi_deinit(bus); + + // Initialize SPI + ret = uapi_spi_init(bus, &attr, &extra_attr); + if (ret != ERRCODE_SUCC) { + WEARABLE_LOGE(WEARABLE_LOG_MODULE_APP, "SPI Protocol: SPI1 init failed: 0x%x", ret); + g_receive_running = 0; + return; + } + + // 启用DMA模式 + // 注意:uapi_spi_set_dma_mode 只在非自动切换模式下可用 +#if !defined(CONFIG_SPI_SUPPORT_POLL_AND_DMA_AUTO_SWITCH) +#if defined(CONFIG_SPI_SUPPORT_DMA) && (CONFIG_SPI_SUPPORT_DMA == 1) + // 明确启用DMA模式(收发都使用DMA) + ret = uapi_spi_set_dma_mode(bus, true, NULL); + if (ret != ERRCODE_SUCC) { + WEARABLE_LOGW(WEARABLE_LOG_MODULE_APP, + "SPI Protocol: Failed to enable DMA mode: 0x%x, will use polling mode", ret); + } else { + WEARABLE_LOGI(WEARABLE_LOG_MODULE_APP, + "SPI Protocol: DMA mode enabled (tx_use_dma=true, rx_use_dma=true)"); + } +#else + WEARABLE_LOGW(WEARABLE_LOG_MODULE_APP, + "SPI Protocol: DMA not supported (CONFIG_SPI_SUPPORT_DMA not defined), using polling mode"); +#endif /* CONFIG_SPI_SUPPORT_DMA */ +#else + // 自动切换模式下,不需要手动设置DMA模式 + // uapi_spi_slave_read会根据数据大小自动决定使用DMA还是轮询模式 + WEARABLE_LOGI(WEARABLE_LOG_MODULE_APP, + "SPI Protocol: DMA auto-switch enabled, will use DMA/polling automatically based on data size"); +#endif /* !CONFIG_SPI_SUPPORT_POLL_AND_DMA_AUTO_SWITCH */ + + WEARABLE_LOGI(WEARABLE_LOG_MODULE_APP, "SPI Protocol: SPI1 initialized (Slave mode, 26MHz expected)"); + + // 初始化接收状态 + g_receive_state = SPI_PROTOCOL_STATE_IDLE; + // 清空接收缓冲区 + memset(g_header_buffer, 0, SPI_PROTOCOL_HEADER_SIZE); + + while (g_receive_running) { + // 使用DMA/轮询模式接收数据 + // 如果当前状态是IDLE,启动包头接收 + if (g_receive_state == SPI_PROTOCOL_STATE_IDLE) { + // 准备接收包头 + static uint8_t tx_buffer[SPI_PROTOCOL_HEADER_SIZE] = {0}; + if (g_has_pending_ack) { + memcpy(tx_buffer, g_pending_ack_buffer, SPI_PROTOCOL_HEADER_SIZE); + g_has_pending_ack = 0; + } else { + memset(tx_buffer, 0, SPI_PROTOCOL_HEADER_SIZE); + } + + spi_xfer_data_t spi_data = {0}; + spi_data.tx_buff = tx_buffer; + spi_data.tx_bytes = SPI_PROTOCOL_HEADER_SIZE; + spi_data.rx_buff = g_header_buffer; + spi_data.rx_bytes = SPI_PROTOCOL_HEADER_SIZE; + + g_receive_state = SPI_PROTOCOL_STATE_RECEIVING_HEADER; + ret = uapi_spi_slave_read(bus, &spi_data, SPI_PROTOCOL_RECV_TIMEOUT_MS); + + // 处理DMA错误:即使已禁用DMA,底层可能仍返回DMA错误 + // 如果实际接收到了完整数据,继续处理 + if (ret == ERRCODE_SPI_DMA_CONFIG_ERROR || ret == ERRCODE_SPI_DMA_TRANSFER_ERROR) { + if (spi_data.rx_bytes == SPI_PROTOCOL_HEADER_SIZE) { + // DMA错误但接收到了完整包头,继续处理(可能是底层误报) + WEARABLE_LOGD(WEARABLE_LOG_MODULE_APP, + "SPI Protocol: DMA error (0x%x) but header received successfully (%u bytes), continuing", + ret, spi_data.rx_bytes); + ret = ERRCODE_SUCC; // 将错误码改为成功,继续处理 + } else { + // DMA错误且数据不完整,重置状态继续等待 + static uint32_t dma_error_count = 0; + dma_error_count++; + if (dma_error_count <= 5 || dma_error_count % 100 == 0) { + WEARABLE_LOGD(WEARABLE_LOG_MODULE_APP, + "SPI Protocol: DMA error (0x%x), rx_bytes=%u, expected=%u (count=%u)", + ret, spi_data.rx_bytes, SPI_PROTOCOL_HEADER_SIZE, dma_error_count); + } + g_receive_state = SPI_PROTOCOL_STATE_IDLE; + memset(g_header_buffer, 0, SPI_PROTOCOL_HEADER_SIZE); + LOS_Msleep(10); + continue; + } + } + + // 只有在成功接收且数据长度正确时才处理 + if (ret == ERRCODE_SUCC && spi_data.rx_bytes == SPI_PROTOCOL_HEADER_SIZE) { + // 包头接收成功,处理包头 + WEARABLE_LOGD(WEARABLE_LOG_MODULE_APP, + "SPI Protocol: Header received (%u bytes), processing...", spi_data.rx_bytes); + ret = process_received_header(); + if (ret == ERRCODE_SUCC && g_receive_state == SPI_PROTOCOL_STATE_RECEIVING_DATA) { + WEARABLE_LOGI(WEARABLE_LOG_MODULE_APP, + "SPI Protocol: Header processed, starting data receive: %u bytes", + g_pending_data_len + SPI_PROTOCOL_DATA_CRC_SIZE); + // 需要接收数据负载,继续接收 + uint32_t total_to_receive = g_pending_data_len + SPI_PROTOCOL_DATA_CRC_SIZE; + + // 分配缓冲区(如果还没有) + if (g_data_buffer == NULL || g_data_buffer_size < total_to_receive) { + if (g_data_buffer != NULL) { + free(g_data_buffer); + } + uint32_t alloc_size = total_to_receive; + if (alloc_size < SPI_PROTOCOL_MAX_DATA_SIZE / 2) { + alloc_size = SPI_PROTOCOL_MAX_DATA_SIZE / 2; + } + if (alloc_size > SPI_PROTOCOL_MAX_DATA_SIZE + SPI_PROTOCOL_DATA_CRC_SIZE) { + alloc_size = SPI_PROTOCOL_MAX_DATA_SIZE + SPI_PROTOCOL_DATA_CRC_SIZE; + } + g_data_buffer = (uint8_t *)malloc(alloc_size); + if (g_data_buffer == NULL) { + WEARABLE_LOGE(WEARABLE_LOG_MODULE_APP, + "SPI Protocol: Failed to allocate data buffer (%u bytes)", alloc_size); + g_receive_state = SPI_PROTOCOL_STATE_IDLE; + LOS_Msleep(10); + continue; + } + g_data_buffer_size = alloc_size; + } + + // 分配TX缓冲区 + if (g_tx_buffer == NULL || g_tx_buffer_size < total_to_receive) { + if (g_tx_buffer != NULL) { + free(g_tx_buffer); + } + g_tx_buffer = (uint8_t *)malloc(total_to_receive); + if (g_tx_buffer == NULL) { + WEARABLE_LOGE(WEARABLE_LOG_MODULE_APP, + "SPI Protocol: Failed to allocate TX buffer (%u bytes)", total_to_receive); + g_receive_state = SPI_PROTOCOL_STATE_IDLE; + LOS_Msleep(10); + continue; + } + g_tx_buffer_size = total_to_receive; + } + memset(g_tx_buffer, 0, total_to_receive); + + // 接收数据负载 + spi_data.tx_buff = g_tx_buffer; + spi_data.tx_bytes = total_to_receive; + spi_data.rx_buff = g_data_buffer; + spi_data.rx_bytes = total_to_receive; + + ret = uapi_spi_slave_read(bus, &spi_data, SPI_PROTOCOL_RECV_TIMEOUT_MS); + + // 处理DMA错误:如果实际接收到了完整数据,继续处理 + if (ret == ERRCODE_SPI_DMA_CONFIG_ERROR || ret == ERRCODE_SPI_DMA_TRANSFER_ERROR) { + if (spi_data.rx_bytes == total_to_receive) { + // DMA错误但接收到了完整数据,继续处理(可能是底层误报) + WEARABLE_LOGD(WEARABLE_LOG_MODULE_APP, + "SPI Protocol: DMA error (0x%x) but data received successfully (%u bytes), continuing", + ret, spi_data.rx_bytes); + ret = ERRCODE_SUCC; // 将错误码改为成功,继续处理 + } else { + // DMA错误且数据不完整,重置状态继续等待 + WEARABLE_LOGW(WEARABLE_LOG_MODULE_APP, + "SPI Protocol: DMA error (0x%x) and incomplete data: received=%u, expected=%u", + ret, spi_data.rx_bytes, total_to_receive); + g_receive_state = SPI_PROTOCOL_STATE_IDLE; + LOS_Msleep(10); + continue; + } + } + + if (ret == ERRCODE_SUCC && spi_data.rx_bytes == total_to_receive) { + // 数据接收成功,处理数据 + WEARABLE_LOGD(WEARABLE_LOG_MODULE_APP, + "SPI Protocol: Data received (%u bytes), processing...", spi_data.rx_bytes); + ret = process_received_data(); + if (ret != ERRCODE_SUCC) { + // 数据处理失败,重置状态 + g_receive_state = SPI_PROTOCOL_STATE_IDLE; + LOS_Msleep(10); + continue; + } + } else { + // 数据接收失败 + WEARABLE_LOGW(WEARABLE_LOG_MODULE_APP, + "SPI Protocol: Failed to receive data: ret=0x%x, received=%u, expected=%u", + ret, spi_data.rx_bytes, total_to_receive); + g_receive_state = SPI_PROTOCOL_STATE_IDLE; + LOS_Msleep(10); + continue; + } + } else if (ret != ERRCODE_SUCC) { + // 包头处理失败(版本号错误、CRC错误等),重置状态继续等待 + g_receive_state = SPI_PROTOCOL_STATE_IDLE; + memset(g_header_buffer, 0, SPI_PROTOCOL_HEADER_SIZE); + LOS_Msleep(10); + continue; + } + } else if (ret == ERRCODE_SPI_TIMEOUT) { + // 超时是正常的,继续等待(Python端可能还没有开始发送) + static uint32_t timeout_count = 0; + timeout_count++; + // 每100次超时打印一次日志,避免刷屏 + if (timeout_count == 1 || timeout_count % 100 == 0) { + WEARABLE_LOGD(WEARABLE_LOG_MODULE_APP, + "SPI Protocol: Waiting for data from Python side (timeout count=%u)...", timeout_count); + } + g_receive_state = SPI_PROTOCOL_STATE_IDLE; + memset(g_header_buffer, 0, SPI_PROTOCOL_HEADER_SIZE); + LOS_Msleep(1); + continue; + } else { + // 其他错误 + static uint32_t error_count = 0; + error_count++; + if (error_count <= 5 || error_count % 100 == 0) { + WEARABLE_LOGW(WEARABLE_LOG_MODULE_APP, + "SPI Protocol: Receive error: ret=0x%x, rx_bytes=%u, expected=%u (count=%u)", + ret, spi_data.rx_bytes, SPI_PROTOCOL_HEADER_SIZE, error_count); + } + g_receive_state = SPI_PROTOCOL_STATE_IDLE; + memset(g_header_buffer, 0, SPI_PROTOCOL_HEADER_SIZE); + LOS_Msleep(10); + continue; + } + } else { + // 状态异常,重置 + g_receive_state = SPI_PROTOCOL_STATE_IDLE; + LOS_Msleep(10); + } + } + + // Deinitialize SPI + uapi_spi_deinit(bus); + WEARABLE_LOGI(WEARABLE_LOG_MODULE_APP, "SPI Protocol: SPI1 deinitialized"); + + // 释放动态分配的数据缓冲区(任务结束时) + if (g_data_buffer != NULL) { + free(g_data_buffer); + g_data_buffer = NULL; + g_data_buffer_size = 0; + WEARABLE_LOGI(WEARABLE_LOG_MODULE_APP, "SPI Protocol: Data buffer freed (task end)"); + } + + // 释放TX缓冲区 + if (g_tx_buffer != NULL) { + free(g_tx_buffer); + g_tx_buffer = NULL; + g_tx_buffer_size = 0; + WEARABLE_LOGI(WEARABLE_LOG_MODULE_APP, "SPI Protocol: TX buffer freed (task end)"); + } + + WEARABLE_LOGI(WEARABLE_LOG_MODULE_APP, "SPI Protocol receive task ended"); +} + +errcode_t spi_protocol_receive_start(spi_protocol_data_callback_t callback) +{ + if (g_receive_running) { + WEARABLE_LOGW(WEARABLE_LOG_MODULE_APP, "SPI Protocol receive is already running"); + return ERRCODE_FAIL; + } + + if (callback == NULL) { + WEARABLE_LOGE(WEARABLE_LOG_MODULE_APP, "SPI Protocol callback is NULL"); + return ERRCODE_INVALID_PARAM; + } + + g_data_callback = callback; + g_receive_running = 1; + + // 创建接收任务 + app_task_definition_t taskcfg = {0}; + taskcfg.create_queue_handle = NULL; + taskcfg.attr.name = "spi_protocol_rx"; + taskcfg.attr.priority = osPriorityBelowNormal; // 使用较低优先级,避免影响其他任务 + taskcfg.attr.stack_size = 0x2000; // 8KB栈空间 + taskcfg.func = spi_protocol_receive_task_func; + taskcfg.use_mem = USE_EXTEND_MEM; + + errcode_t ret = uapi_create_osThread_custom(&taskcfg); + if (ret != ERRCODE_SUCC) { + WEARABLE_LOGE(WEARABLE_LOG_MODULE_APP, "Failed to create SPI protocol receive task: 0x%x", ret); + g_receive_running = 0; + g_data_callback = NULL; + return ret; + } + + WEARABLE_LOGI(WEARABLE_LOG_MODULE_APP, "SPI Protocol receive started"); + return ERRCODE_SUCC; +} + +void spi_protocol_receive_stop(void) +{ + if (!g_receive_running) { + WEARABLE_LOGW(WEARABLE_LOG_MODULE_APP, "SPI Protocol receive is not running"); + return; + } + + g_receive_running = 0; + + // 等待任务结束 + uint32_t wait_count = 0; + while (g_receive_running && wait_count < 200) { + LOS_Msleep(10); + wait_count++; + } + + // 释放动态分配的数据缓冲区 + if (g_data_buffer != NULL) { + free(g_data_buffer); + g_data_buffer = NULL; + g_data_buffer_size = 0; + WEARABLE_LOGI(WEARABLE_LOG_MODULE_APP, "SPI Protocol: Data buffer freed"); + } + + // 释放TX缓冲区 + if (g_tx_buffer != NULL) { + free(g_tx_buffer); + g_tx_buffer = NULL; + g_tx_buffer_size = 0; + WEARABLE_LOGI(WEARABLE_LOG_MODULE_APP, "SPI Protocol: TX buffer freed"); + } + + g_data_callback = NULL; + WEARABLE_LOGI(WEARABLE_LOG_MODULE_APP, "SPI Protocol receive stopped"); +} + +void spi_protocol_get_stats(spi_protocol_stats_t *stats) +{ + if (stats != NULL) { + memcpy(stats, &g_stats, sizeof(spi_protocol_stats_t)); + } +} + +void spi_protocol_reset_stats(void) +{ + memset(&g_stats, 0, sizeof(g_stats)); + g_expected_seq_num = 0; + g_is_first_packet = 1; +} + diff --git a/code/sdk/zhihui/utilscpp/spi_protocol.h b/code/sdk/zhihui/utilscpp/spi_protocol.h new file mode 100644 index 000000000..96cdbf333 --- /dev/null +++ b/code/sdk/zhihui/utilscpp/spi_protocol.h @@ -0,0 +1,86 @@ +#ifndef SPI_PROTOCOL_H +#define SPI_PROTOCOL_H + +#include +#include +#include "errcode.h" + +#ifdef __cplusplus +extern "C" { +#endif + +/* 协议常量 */ +#define SPI_PROTOCOL_MAGIC 0xAA +#define SPI_PROTOCOL_VERSION 0x01 + +/* 包类型 */ +#define PKT_TYPE_DATA 0x01 +#define PKT_TYPE_ACK 0x02 +#define PKT_TYPE_NACK 0x03 +#define PKT_TYPE_EOT 0x04 + +/* 标志位 */ +#define FLAG_FIRST 0x01 /* 第一包 */ +#define FLAG_LAST 0x02 /* 最后一包 */ +#define FLAG_RETRANSMIT 0x04 /* 重传包 */ + +/* 包大小限制 */ +#define SPI_PROTOCOL_MAX_PACKET_SIZE (64 * 1024) /* 64KB */ +#define SPI_PROTOCOL_HEADER_SIZE 16 +#define SPI_PROTOCOL_DATA_CRC_SIZE 2 +#define SPI_PROTOCOL_MAX_DATA_SIZE (SPI_PROTOCOL_MAX_PACKET_SIZE - SPI_PROTOCOL_HEADER_SIZE - SPI_PROTOCOL_DATA_CRC_SIZE) /* 65518 bytes */ + +/* 协议包头结构 */ +typedef struct { + uint8_t magic; /* Magic number (0xAA) */ + uint8_t version; /* Protocol version (0x01) */ + uint8_t type; /* Packet type */ + uint8_t flags; /* Flags (bit0=First, bit1=Last, bit2=Retransmit) */ + uint32_t seq_num; /* Sequence number */ + uint32_t data_len; /* Data payload length */ + uint16_t header_crc; /* Header CRC16 */ + uint16_t reserved; /* Reserved for future use */ +} __attribute__((packed)) spi_protocol_header_t; + +/* 接收回调函数类型 */ +typedef void (*spi_protocol_data_callback_t)(uint8_t *data, uint32_t len, uint8_t is_first, uint8_t is_last); + +/* 接收统计信息 */ +typedef struct { + uint32_t total_packets; /* 总接收包数 */ + uint32_t error_packets; /* 错误包数 */ + uint32_t total_bytes; /* 总接收字节数 */ + uint32_t lost_packets; /* 丢失包数(序列号不连续) */ +} spi_protocol_stats_t; + +/** + * @brief 启动SPI协议接收 + * + * @param callback 数据接收回调函数 + * @return errcode_t 错误码 + */ +errcode_t spi_protocol_receive_start(spi_protocol_data_callback_t callback); + +/** + * @brief 停止SPI协议接收 + */ +void spi_protocol_receive_stop(void); + +/** + * @brief 获取接收统计信息 + * + * @param stats 统计信息结构体指针 + */ +void spi_protocol_get_stats(spi_protocol_stats_t *stats); + +/** + * @brief 重置统计信息 + */ +void spi_protocol_reset_stats(void); + +#ifdef __cplusplus +} +#endif + +#endif /* SPI_PROTOCOL_H */ + diff --git a/code/sdk/zhihui/utilscpp/spi_protocol_api.cpp b/code/sdk/zhihui/utilscpp/spi_protocol_api.cpp new file mode 100644 index 000000000..0912c40d0 --- /dev/null +++ b/code/sdk/zhihui/utilscpp/spi_protocol_api.cpp @@ -0,0 +1,39 @@ +#include "spi_protocol_api.h" +#include "spi_protocol.h" +#include "wearable_log.h" + +errcode_t spi_protocol_api_receive_start(spi_protocol_data_callback_t callback) +{ + return spi_protocol_receive_start(callback); +} + +void spi_protocol_api_receive_stop(void) +{ + spi_protocol_receive_stop(); +} + +void spi_protocol_api_get_stats(uint32_t *total_packets, uint32_t *error_packets, + uint32_t *total_bytes, uint32_t *lost_packets) +{ + spi_protocol_stats_t stats; + spi_protocol_get_stats(&stats); + + if (total_packets) { + *total_packets = stats.total_packets; + } + if (error_packets) { + *error_packets = stats.error_packets; + } + if (total_bytes) { + *total_bytes = stats.total_bytes; + } + if (lost_packets) { + *lost_packets = stats.lost_packets; + } +} + +void spi_protocol_api_reset_stats(void) +{ + spi_protocol_reset_stats(); +} + diff --git a/code/sdk/zhihui/utilscpp/spi_protocol_api.h b/code/sdk/zhihui/utilscpp/spi_protocol_api.h new file mode 100644 index 000000000..32ec7fae7 --- /dev/null +++ b/code/sdk/zhihui/utilscpp/spi_protocol_api.h @@ -0,0 +1,70 @@ +#ifndef SPI_PROTOCOL_API_H +#define SPI_PROTOCOL_API_H + +#include "spi_protocol.h" +#include +#include +#include "errcode.h" + +#ifdef __cplusplus +extern "C" { +#endif + +/** + * @brief 启动SPI协议接收(简化接口) + * + * @param callback 数据接收回调函数 + * @return errcode_t 错误码 + * + * 示例: + * ```c + * void my_callback(uint8_t *data, uint32_t len, uint8_t is_first, uint8_t is_last) + * { + * static FILE *fp = NULL; + * if (is_first) { + * if (fp) fclose(fp); + * fp = fopen("/user/data.bin", "wb"); + * } + * if (fp) { + * fwrite(data, 1, len, fp); + * } + * if (is_last) { + * if (fp) { + * fclose(fp); + * fp = NULL; + * } + * } + * } + * + * custom_spi1_protocol_receive_start(my_callback); + * ``` + */ +errcode_t spi_protocol_api_receive_start(spi_protocol_data_callback_t callback); + +/** + * @brief 停止SPI协议接收(简化接口) + */ +void spi_protocol_api_receive_stop(void); + +/** + * @brief 获取接收统计信息(简化接口) + * + * @param total_packets 总接收包数(输出参数,可为NULL) + * @param error_packets 错误包数(输出参数,可为NULL) + * @param total_bytes 总接收字节数(输出参数,可为NULL) + * @param lost_packets 丢失包数(输出参数,可为NULL) + */ +void spi_protocol_api_get_stats(uint32_t *total_packets, uint32_t *error_packets, + uint32_t *total_bytes, uint32_t *lost_packets); + +/** + * @brief 重置统计信息(简化接口) + */ +void spi_protocol_api_reset_stats(void); + +#ifdef __cplusplus +} +#endif + +#endif /* SPI_PROTOCOL_API_H */ + diff --git a/code/sdk/zhihui/utilscpp/spi_protocol_test.cpp b/code/sdk/zhihui/utilscpp/spi_protocol_test.cpp new file mode 100644 index 000000000..de69af403 --- /dev/null +++ b/code/sdk/zhihui/utilscpp/spi_protocol_test.cpp @@ -0,0 +1,245 @@ +#include "spi_protocol_test.h" +#include "spi_protocol_api.h" +#include "wearable_log.h" +#include +#include +#include "soc_osal.h" +#include "driver/tcxo.h" // 用于获取高精度时间 + +/* 测试统计 */ +static uint32_t g_test_total_received = 0; +static uint32_t g_test_packet_count = 0; +static uint64_t g_test_start_time_us = 0; // 开始接收时间(微秒) +static uint64_t g_test_end_time_us = 0; // 结束接收时间(微秒) + +/** + * @brief 测试用的数据接收回调函数(仅统计,不写文件) + */ +static void test_data_callback(uint8_t *data, uint32_t len, uint8_t is_first, uint8_t is_last) +{ + UNUSED(data); // 数据不再使用,仅用于统计 + + WEARABLE_LOGD(WEARABLE_LOG_MODULE_APP, + "Test callback: received %u bytes, first=%u, last=%u", + len, is_first, is_last); + + if (is_first) { + // 第一包,重置统计 + g_test_total_received = 0; + g_test_packet_count = 0; + g_test_start_time_us = uapi_tcxo_get_us(); // 记录开始时间 + g_test_end_time_us = 0; // 重置结束时间 + WEARABLE_LOGI(WEARABLE_LOG_MODULE_APP, "SPI Protocol Test: Receiving started, start time: %llu us", g_test_start_time_us); + } + + // 更新统计信息 + g_test_total_received += len; + g_test_packet_count++; + + // 每接收64KB打印一次进度 + if (g_test_total_received % (64 * 1024) < len) { + float total_kb = g_test_total_received / 1024.0f; + float total_mb = total_kb / 1024.0f; + + // 计算当前速率(如果已经开始接收) + float current_speed_mbps = 0.0f; + float current_speed_kbps = 0.0f; + if (g_test_start_time_us > 0) { + uint64_t current_time_us = uapi_tcxo_get_us(); + if (current_time_us > g_test_start_time_us) { + uint64_t elapsed_us = current_time_us - g_test_start_time_us; + float elapsed_s = elapsed_us / 1000000.0f; + if (elapsed_s > 0.0f) { + float speed_bps = (g_test_total_received * 8.0f) / elapsed_s; + current_speed_kbps = speed_bps / 1024.0f; + current_speed_mbps = current_speed_kbps / 1024.0f; + } + } + } + + if (total_mb >= 1.0f) { + if (current_speed_mbps >= 1.0f) { + WEARABLE_LOGI(WEARABLE_LOG_MODULE_APP, + "Test progress: %u bytes (%.2f MB, %.2f KB), %u packets, speed: %.2f Mbps", + g_test_total_received, total_mb, total_kb, g_test_packet_count, current_speed_mbps); + } else { + WEARABLE_LOGI(WEARABLE_LOG_MODULE_APP, + "Test progress: %u bytes (%.2f MB, %.2f KB), %u packets, speed: %.2f Kbps", + g_test_total_received, total_mb, total_kb, g_test_packet_count, current_speed_kbps); + } + } else { + WEARABLE_LOGI(WEARABLE_LOG_MODULE_APP, + "Test progress: %u bytes (%.2f KB), %u packets, speed: %.2f Kbps", + g_test_total_received, total_kb, g_test_packet_count, current_speed_kbps); + } + } + + if (is_last) { + // 最后一包,记录结束时间并打印最终统计 + g_test_end_time_us = uapi_tcxo_get_us(); + + // 计算接收时间 + uint64_t receive_time_us = 0; + float receive_time_s = 0.0f; + if (g_test_start_time_us > 0 && g_test_end_time_us > g_test_start_time_us) { + receive_time_us = g_test_end_time_us - g_test_start_time_us; + receive_time_s = receive_time_us / 1000000.0f; // 转换为秒 + } + + // 计算接收速率 + float speed_bps = 0.0f; + float speed_kbps = 0.0f; + float speed_mbps = 0.0f; + if (receive_time_s > 0.0f) { + speed_bps = (g_test_total_received * 8.0f) / receive_time_s; // bits per second + speed_kbps = speed_bps / 1024.0f; // Kbps + speed_mbps = speed_kbps / 1024.0f; // Mbps + } + + float total_kb = g_test_total_received / 1024.0f; + float total_mb = total_kb / 1024.0f; + + WEARABLE_LOGI(WEARABLE_LOG_MODULE_APP, "="); + WEARABLE_LOGI(WEARABLE_LOG_MODULE_APP, "SPI Protocol Test: Receiving completed"); + WEARABLE_LOGI(WEARABLE_LOG_MODULE_APP, " Total bytes: %u (%.2f MB, %.2f KB)", + g_test_total_received, total_mb, total_kb); + WEARABLE_LOGI(WEARABLE_LOG_MODULE_APP, " Total packets: %u", g_test_packet_count); + WEARABLE_LOGI(WEARABLE_LOG_MODULE_APP, " Receive time: %.3f seconds (%.0f ms, %llu us)", + receive_time_s, receive_time_s * 1000.0f, receive_time_us); + + if (speed_mbps >= 1.0f) { + WEARABLE_LOGI(WEARABLE_LOG_MODULE_APP, " Receive speed: %.2f Mbps (%.2f Kbps, %.0f bps)", + speed_mbps, speed_kbps, speed_bps); + } else if (speed_kbps >= 1.0f) { + WEARABLE_LOGI(WEARABLE_LOG_MODULE_APP, " Receive speed: %.2f Kbps (%.0f bps)", + speed_kbps, speed_bps); + } else { + WEARABLE_LOGI(WEARABLE_LOG_MODULE_APP, " Receive speed: %.0f bps", speed_bps); + } + WEARABLE_LOGI(WEARABLE_LOG_MODULE_APP, "="); + } +} + +/** + * @brief 启动SPI协议接收测试 + */ +void spi_protocol_test_start(void) +{ + WEARABLE_LOGI(WEARABLE_LOG_MODULE_APP, "Starting SPI Protocol receive test (statistics only, no file writing)..."); + + // 重置统计 + g_test_total_received = 0; + g_test_packet_count = 0; + g_test_start_time_us = 0; + g_test_end_time_us = 0; + + errcode_t ret = spi_protocol_api_receive_start(test_data_callback); + if (ret == ERRCODE_SUCC) { + WEARABLE_LOGI(WEARABLE_LOG_MODULE_APP, "SPI Protocol receive test started successfully"); + WEARABLE_LOGI(WEARABLE_LOG_MODULE_APP, "Waiting for data from Python side..."); + WEARABLE_LOGI(WEARABLE_LOG_MODULE_APP, "Note: Data will be received and counted, but not saved to file"); + } else { + WEARABLE_LOGE(WEARABLE_LOG_MODULE_APP, "Failed to start SPI Protocol receive test: 0x%x", ret); + } +} + +/** + * @brief 停止SPI协议接收测试 + */ +void spi_protocol_test_stop(void) +{ + WEARABLE_LOGI(WEARABLE_LOG_MODULE_APP, "Stopping SPI Protocol receive test..."); + + spi_protocol_api_receive_stop(); + + // 打印最终统计信息 + uint32_t total_packets = 0; + uint32_t error_packets = 0; + uint32_t total_bytes = 0; + uint32_t lost_packets = 0; + + spi_protocol_api_get_stats(&total_packets, &error_packets, &total_bytes, &lost_packets); + + // 计算接收时间(如果还没有计算) + uint64_t receive_time_us = 0; + float receive_time_s = 0.0f; + if (g_test_start_time_us > 0) { + if (g_test_end_time_us > 0) { + receive_time_us = g_test_end_time_us - g_test_start_time_us; + } else { + // 如果还没有结束时间,使用当前时间 + uint64_t current_time_us = uapi_tcxo_get_us(); + if (current_time_us > g_test_start_time_us) { + receive_time_us = current_time_us - g_test_start_time_us; + } + } + receive_time_s = receive_time_us / 1000000.0f; // 转换为秒 + } + + // 计算接收速率 + float speed_bps = 0.0f; + float speed_kbps = 0.0f; + float speed_mbps = 0.0f; + if (receive_time_s > 0.0f && total_bytes > 0) { + speed_bps = (total_bytes * 8.0f) / receive_time_s; // bits per second + speed_kbps = speed_bps / 1024.0f; // Kbps + speed_mbps = speed_kbps / 1024.0f; // Mbps + } + + WEARABLE_LOGI(WEARABLE_LOG_MODULE_APP, "="); + WEARABLE_LOGI(WEARABLE_LOG_MODULE_APP, "SPI Protocol Test Statistics"); + WEARABLE_LOGI(WEARABLE_LOG_MODULE_APP, "="); + WEARABLE_LOGI(WEARABLE_LOG_MODULE_APP, "Protocol Statistics:"); + WEARABLE_LOGI(WEARABLE_LOG_MODULE_APP, " Total packets: %u", total_packets); + WEARABLE_LOGI(WEARABLE_LOG_MODULE_APP, " Error packets: %u", error_packets); + WEARABLE_LOGI(WEARABLE_LOG_MODULE_APP, " Lost packets: %u", lost_packets); + + if (total_bytes > 0) { + float total_kb = total_bytes / 1024.0f; + float total_mb = total_kb / 1024.0f; + if (total_mb >= 1.0f) { + WEARABLE_LOGI(WEARABLE_LOG_MODULE_APP, " Total bytes: %u (%.2f MB, %.2f KB)", + total_bytes, total_mb, total_kb); + } else { + WEARABLE_LOGI(WEARABLE_LOG_MODULE_APP, " Total bytes: %u (%.2f KB)", + total_bytes, total_kb); + } + } else { + WEARABLE_LOGI(WEARABLE_LOG_MODULE_APP, " Total bytes: %u", total_bytes); + } + + if (receive_time_s > 0.0f) { + WEARABLE_LOGI(WEARABLE_LOG_MODULE_APP, "Receive Performance:"); + WEARABLE_LOGI(WEARABLE_LOG_MODULE_APP, " Receive time: %.3f seconds (%.0f ms, %llu us)", + receive_time_s, receive_time_s * 1000.0f, receive_time_us); + + if (speed_mbps >= 1.0f) { + WEARABLE_LOGI(WEARABLE_LOG_MODULE_APP, " Receive speed: %.2f Mbps (%.2f Kbps, %.0f bps)", + speed_mbps, speed_kbps, speed_bps); + } else if (speed_kbps >= 1.0f) { + WEARABLE_LOGI(WEARABLE_LOG_MODULE_APP, " Receive speed: %.2f Kbps (%.0f bps)", + speed_kbps, speed_bps); + } else { + WEARABLE_LOGI(WEARABLE_LOG_MODULE_APP, " Receive speed: %.0f bps", speed_bps); + } + } + + WEARABLE_LOGI(WEARABLE_LOG_MODULE_APP, "="); + + if (error_packets == 0 && lost_packets == 0) { + WEARABLE_LOGI(WEARABLE_LOG_MODULE_APP, "✓ Test PASSED: No errors, no lost packets"); + } else { + WEARABLE_LOGW(WEARABLE_LOG_MODULE_APP, "✗ Test completed with errors: %u errors, %u lost packets", + error_packets, lost_packets); + } +} + +/** + * @brief 获取测试统计信息 + */ +void spi_protocol_test_get_stats(uint32_t *total_packets, uint32_t *error_packets, + uint32_t *total_bytes, uint32_t *lost_packets) +{ + spi_protocol_api_get_stats(total_packets, error_packets, total_bytes, lost_packets); +} + diff --git a/code/sdk/zhihui/utilscpp/spi_protocol_test.h b/code/sdk/zhihui/utilscpp/spi_protocol_test.h new file mode 100644 index 000000000..2d5b51987 --- /dev/null +++ b/code/sdk/zhihui/utilscpp/spi_protocol_test.h @@ -0,0 +1,37 @@ +#ifndef SPI_PROTOCOL_TEST_H +#define SPI_PROTOCOL_TEST_H + +#include +#include "errcode.h" + +#ifdef __cplusplus +extern "C" { +#endif + +/** + * @brief 启动SPI协议接收测试 + */ +void spi_protocol_test_start(void); + +/** + * @brief 停止SPI协议接收测试 + */ +void spi_protocol_test_stop(void); + +/** + * @brief 获取测试统计信息 + * + * @param total_packets 总接收包数(输出参数,可为NULL) + * @param error_packets 错误包数(输出参数,可为NULL) + * @param total_bytes 总接收字节数(输出参数,可为NULL) + * @param lost_packets 丢失包数(输出参数,可为NULL) + */ +void spi_protocol_test_get_stats(uint32_t *total_packets, uint32_t *error_packets, + uint32_t *total_bytes, uint32_t *lost_packets); + +#ifdef __cplusplus +} +#endif + +#endif /* SPI_PROTOCOL_TEST_H */ + -- 2.17.1