#include "crc16.h" #include "mcu_ctrl_board.h" #include "../swapi/subjects/serial/serial.h" // 模块名称 static const char MODULE_NAME[] = "MCUCtrlBoard"; // 告诉编译器使用一字节内存对齐 #pragma pack(push, 1) // 单片机控制板运行配置(小端序) typedef struct { uint8_t version[4]; // 版本号 uint8_t ctrl_mode; // 定时控制模式(0=光控, 1=时控) uint8_t light_duration; // 光控定时时长(单位: 小时, 范围: 1-10, 仅光控模式有效) uint8_t start_hour; // 时控开始时间(小时, 0-23, 一天24小时, 仅时控模式有效) uint8_t end_hour; // 时控结束时间(小时, 0-23, 一天24小时, 仅时控模式有效) uint8_t tp_intvl_min[2];// 拍照间隔(分钟,其值不得超过当前控制模式下允许的最大值) } MCUScheduleCfg; // 恢复之前原有内存字节对齐方式 #pragma pack(pop) // 定义与单片机进行通讯的结构体 typedef struct { void *h; // 打开的串口句柄 char work_state[4]; // 工作状态位图(共32个状态) void *state_rwlock; // 状态位图读写锁 SensorEnvData data[24]; // 接收传感器采集的环境数据 void *edata_rwlock; // 环境数据读写锁 } SMCBCom; static MCUScheduleCfg cfg_params; static SMCBCom s_myCom; // 声明Go中的判断RTU是否已联网在线的函数(Go实现, 供C/C++调用) extern int RTU_IsInetAvailable(); // 0未联网, 1已联网 // 声明Go中的判断RTU是否已同步时间的函数(Go实现, 供C/C++调用) extern int RTU_IsSyncedNtpTime(); // 0未同步, 1已同步 // 声明Go中的判断RTU是否已登录后台服务器(Go实现, 供C/C++调用) extern int RTU_IsLoginMqServer(); // 0未登录, 1已登录 // 声明Go中发送一组 温湿度光照数据的函数(Go实现, 供C/C++调用) extern int RTU_SendGrpEnvData(SensorEnvData *pGrpData, int cnt/*数量, 一次全发是24*/); // 0成功, <0时失败 // 声明Go中发送一条 温湿度光照数据的函数(Go实现, 供C/C++调用) extern int RTU_SendOneEnvData(SensorEnvData *pOneData); // 0成功, <0时失败 // 声明Go中通知RTU需要进行一次拍照的函数(Go实现, 供C/C++调用) extern int RTU_RequestTakePhoto(); // 0成功, <0时失败 // 声明Go中加载MCU配置项"运行参数"的函数(Go实现, 供C/C++调用) extern int RTU_LoadMCUParamsCfg(); // 0成功, <0时失败 // 声明Go中通知RTU进行一次掉电关机的函数(Go实现, 供C/C++调用) extern int RTU_NotifyPwrWillOff(); // 0成功, <0时失败 // 反转字节, 可用于处理大小端字节序的相互转换,"n"不必是2的倍数 static inline void swap_bytes(char *a, size_t n) { for(char *p = a, *q = a + n - 1; p < q; p++, q--) { char t = *p; *p = *q; *q = t; } } // 获取32位秒级时间戳(存在"​​2038-01-19 03:14:07 UTC​​"溢出风险) static inline uint32_t now_sec32(void) { return (uint32_t)time(NULL); } // 发送数据前, 处理帧内容的转义, 返回添加转义后的数据帧及其长度 static int frame_doEscapeChars(const unsigned char *src, int len, unsigned char **dst) { if(!src ||!dst || len < 2 || src[0]!= 0x7D || src[len-1]!= 0x7E) return -1; unsigned char *ptr = (unsigned char *)sw_heap_malloc(2+2*(len-2)); if(!ptr) return -2; ptr[0] = src[0]; int j = 1; for(int i = 1; i < (len-1); i++) { if(0); else if(src[i] == 0x7D) { ptr[j++] = 0x7D; ptr[j++] = 0x5D; } else if(src[i] == 0x7E) { ptr[j++] = 0x7D; ptr[j++] = 0x5E; } else ptr[j++] = src[i]; } ptr[j++] = src[len-1]; *dst = ptr; return j; } // 接收数据后, 反转义帧数据内容, 返回去掉转义后的原始帧及其长度 static int frame_unescapeChars(const unsigned char *src, int len, unsigned char **dst) { if(!src ||!dst || len < 2 || src[0]!= 0x7D || src[len-1]!= 0x7E) return -1; unsigned char *ptr = (unsigned char *)sw_heap_malloc(2+1*(len-2)); if(!ptr) return -2; ptr[0] = src[0]; int j = 1; for(int i = 1; i < (len-1); i++) { if(0); else if(src[i] == 0x7D && src[i+1] == 0x5D) { ptr[j++] = 0x7D; i++; } else if(src[i] == 0x7D && src[i+1] == 0x5E) { ptr[j++] = 0x7E; i++; } else ptr[j++] = src[i]; } ptr[j++] = src[len-1]; *dst = ptr; return j; } // 接收、处理来自MCU控制板的数据报文帧, 串口-线程回调 #define MIN_FRAME_LEN 6 // 最小帧(数据区为零)的长度 static int comio_data_recv_proc(unsigned long wParam/*传递打开的串口句柄*/, unsigned long lParam/*保留暂未使用*/) { SMCBCom *pComIO = &s_myCom; void *pSerial = pComIO->h; const unsigned char *pRecvBuf = serial_get_recv_buffer(pSerial); int nRecvBytes = serial_get_recv_buffer_bytes(pSerial); const char *log_prefix = serial_get_log_prefix(pSerial); int ret; bool bPrintRecvBuf = true; unsigned char *req = NULL; int nReqBytes; u_int16_t crc1, crc2, crc3; int nReqDataLen; unsigned char sendBuf[MAX_LINE_CHARS] = { 0 }; int nSendBytes; unsigned char *rsp = NULL; int nRspBytes; char log_rsp[MAX_LINE_CHARS] = { 0 }; static bool bTakenPhoto = false; /*标记本次是否已拍照*/ // 0, 判定接收有效的请求帧 if((nRecvBytes == 1 && pRecvBuf[0] != 0x7D) || (nRecvBytes == 2 && pRecvBuf[1] == 0x5D)) goto ret_p3; else if(nRecvBytes >= MIN_FRAME_LEN && pRecvBuf[nRecvBytes-1] == 0x7E) goto ret_p1; else goto ret_p4; // 1, 收到一个完整的请求帧 ret_p1: // 1.1, 打印接收到的帧内容 bPrintRecvBuf = false; serial_printf_recv_buffer(pSerial, LEVEL_DEBUG); sw_log_debug("[%s] %s received a request(%d bytes)", MODULE_NAME, log_prefix, nRecvBytes); // 1.2, 去掉转义得到原始帧 ret = frame_unescapeChars(pRecvBuf, nRecvBytes, &req); if(ret < MIN_FRAME_LEN) { sw_log_error("[%s] %s failed to unescape the received request(%d)!!", MODULE_NAME, log_prefix, ret); goto ret_p3; } nReqBytes = ret; // 1.3, 该请求帧合法性校验 crc1 = MAKEWORD(req[nReqBytes-3], req[nReqBytes-2]); crc2 = CRC16(req+1, nReqBytes-4); if(crc1 != crc2) { sw_log_error("[%s] %s received a request with invalid crc16(0x%02x != 0x%02x)!!", MODULE_NAME, log_prefix, crc1, crc2); goto ret_p3; } // 1.4, 解析处理该帧的请求 nReqDataLen = nReqBytes-MIN_FRAME_LEN; // 数据区长度 switch(req[2]) { case 0x01: // MCU查询RTU的工作状态 if(nReqDataLen != 1) { sw_log_error("[%s] %s received a request with invalid data length!!", MODULE_NAME, log_prefix); goto ret_p3; } int pwr_reason = (int)req[3]; bool bTakePhoto = ((pwr_reason == 1 || pwr_reason == 2) ? true : false); if(bTakePhoto && !bTakenPhoto) { RTU_RequestTakePhoto()/*请求RTU进行拍照(异步执行)*/; bTakenPhoto = true/*只请求一次*/; } unsigned char tmpBuf1[10] = { 0x7D, 0x01, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x7E }; sw_rwlock_wrlock(pComIO->state_rwlock, WAIT_FOREVER); if(RTU_IsInetAvailable() == 1 && RTU_IsLoginMqServer() == 1) sw_chararry_setBit(pComIO->work_state, 4, 0, 0x01); // 联机 else sw_chararry_setBit(pComIO->work_state, 4, 0, 0x00); // 脱机 for(int i = 0; i < 4; i++) { tmpBuf1[3+i] = pComIO->work_state[3-i]; } // 小端转大端 sw_rwlock_unlock(pComIO->state_rwlock); crc3 = CRC16(tmpBuf1+1, 6); memcpy(&tmpBuf1[7], &crc3, 2); // 小端字节序 nSendBytes = sizeof(tmpBuf1); memcpy(sendBuf, tmpBuf1, nSendBytes); goto ret_p2; case 0x02: // 24小时温湿度光照数据, 每组14字节, 正常是24组 case 0x05: // RTU单次的请求MCU, 发送一组当前实时的环境数据 if(nReqDataLen <= 0 || (nReqDataLen%14) != 0 || (nReqDataLen/14) <= 0 || (nReqDataLen/14) > 24) { sw_log_error("[%s] %s received a request with invalid data length!!", MODULE_NAME, log_prefix); goto ret_p3; } int groupCnt = nReqDataLen/14; if(req[2] == 0x05 && groupCnt != 1) { sw_log_error("[%s] %s received a request with invalid data length!!", MODULE_NAME, log_prefix); goto ret_p3; } sw_rwlock_wrlock(pComIO->edata_rwlock, WAIT_FOREVER); for(int i = 0; i < groupCnt; i++) { memcpy(&pComIO->data[i].timestamp, req+3 +i*14, 4); // 时间戳(秒, 存在"​​2038-01-19 03:14:07 UTC​​"溢出风险) swap_bytes((char *)&pComIO->data[i].timestamp, 4); // 大端转小端 memcpy(&pComIO->data[i].temperature, req+7 +i*14, 2); // 温度, 单位: 0.1°C swap_bytes((char *)&pComIO->data[i].temperature, 2); // 大端转小端 memcpy(&pComIO->data[i].humidity, req+9 +i*14, 2); // 湿度, 单位: 0.1%RH swap_bytes((char *)&pComIO->data[i].humidity, 2); // 大端转小端 memcpy(&pComIO->data[i].illuminance, req+11+i*14, 4); // 光照, 单位: lux swap_bytes((char *)&pComIO->data[i].illuminance, 4); // 大端转小端 memcpy(&pComIO->data[i].voltage, req+15+i*14, 2); // 电压, 单位: 0.1V swap_bytes((char *)&pComIO->data[i].voltage, 2); // 大端转小端 } if(req[2] == 0x02) RTU_SendGrpEnvData(&pComIO->data[0], groupCnt); // 异步发送一组环境数据到RTU if(req[2] == 0x05) RTU_SendOneEnvData(&pComIO->data[0]); // 异步发送一条环境数据到RTU sw_rwlock_unlock(pComIO->edata_rwlock); if(req[2] == 0x05) goto ret_p3; // 应答帧不需要再回复 unsigned char tmpBuf2[6] = { 0x7D, 0x01, 0x02, 0x81, 0xE1, 0x7E }; nSendBytes = sizeof(tmpBuf2); memcpy(sendBuf, tmpBuf2, nSendBytes); goto ret_p2; case 0x03: // MCU请求RTU, 进行对时 if(nReqDataLen != 4) { sw_log_error("[%s] %s received a request with invalid data length!!", MODULE_NAME, log_prefix); goto ret_p3; } unsigned char tmpBuf3[10] = { 0x7D, 0x01, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x7E }; if(RTU_IsSyncedNtpTime() == 1) { // 使用RTU时间同步返回 uint32_t now = now_sec32(); swap_bytes((char *)&now, 4); // 小端转大端 memcpy(&tmpBuf3[3], &now, 4); } else { // 使用MCU时间原路返回 tmpBuf3[3] = req[3]; tmpBuf3[4] = req[4]; tmpBuf3[5] = req[5]; tmpBuf3[6] = req[6]; uint32_t now = ((uint32_t)req[3] << 24) | ((uint32_t)req[4] << 16) | ((uint32_t)req[5] << 8) | (uint32_t)req[6]; char timestamp[MAX_LINE_CHARS]; sprintf(timestamp, "%u", now); if(xsetlocaltime(timestamp) == 0) sw_log_info("[%s] %s set local time to %s with MCU clock", MODULE_NAME, log_prefix, timestamp); else sw_log_warn("[%s] %s failed to set local time to %s with MCU clock!", MODULE_NAME, log_prefix, timestamp); } crc3 = CRC16(tmpBuf3+1, 6); memcpy(&tmpBuf3[7], &crc3, 2); // 小端字节序 nSendBytes = sizeof(tmpBuf3); memcpy(sendBuf, tmpBuf3, nSendBytes); goto ret_p2; case 0x04: // MCU请求RTU, 同步参数 if(nReqDataLen != 4) { sw_log_error("[%s] %s received a request with invalid data length!!", MODULE_NAME, log_prefix); goto ret_p3; } if(RTU_LoadMCUParamsCfg() != 0) goto ret_p3; unsigned char tmpBuf4[16] = { 0x7D, 0x01, 0x04, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x7E }; tmpBuf4[3] = cfg_params.version[3]; tmpBuf4[4] = cfg_params.version[2]; tmpBuf4[5] = cfg_params.version[1]; tmpBuf4[6] = cfg_params.version[0]; tmpBuf4[7] = cfg_params.ctrl_mode; // 定时控制模式 tmpBuf4[8] = cfg_params.light_duration; // 光控定时时长 tmpBuf4[9] = cfg_params.start_hour; // 时控开始时间 tmpBuf4[10] = cfg_params.end_hour; // 时控结束时间 tmpBuf4[11] = cfg_params.tp_intvl_min[1]; // 拍照时间间隔 tmpBuf4[12] = cfg_params.tp_intvl_min[0]; // 拍照时间间隔 crc3 = CRC16(tmpBuf4+1, 12); memcpy(&tmpBuf4[13], &crc3, 2); // 小端字节序 nSendBytes = sizeof(tmpBuf4); memcpy(sendBuf, tmpBuf4, nSendBytes); goto ret_p2; case 0x06: // MCU通知RTU, 准备掉电 if(nReqDataLen != 1) { sw_log_error("[%s] %s received a request with invalid data length!!", MODULE_NAME, log_prefix); goto ret_p3; } unsigned char tmpBuf6[10] = { 0x7D, 0x01, 0x06, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x7E }; uint32_t work_state; sw_rwlock_rdlock(pComIO->state_rwlock, WAIT_FOREVER); memcpy(&work_state, pComIO->work_state, 4); // 保持小端序 for(int i = 0; i < 4; i++) { tmpBuf6[3+i] = pComIO->work_state[3-i]; } // 小端转大端 sw_rwlock_unlock(pComIO->state_rwlock); crc3 = CRC16(tmpBuf6+1, 6); memcpy(&tmpBuf6[7], &crc3, 2); // 小端字节序 if(work_state == 0x00000000 || work_state == 0x00000001) { // 空闲状态, 通知RTU准备掉电, 非空闲状态时MCU应继续查询等待 RTU_NotifyPwrWillOff(); sw_log_warn("[%s] %s notified \"RTU\" to power off!", MODULE_NAME, log_prefix); } nSendBytes = sizeof(tmpBuf6); memcpy(sendBuf, tmpBuf6, nSendBytes); goto ret_p2; default: sw_log_warn("[%s] %s unknown a data req(type=0x%02x)!", MODULE_NAME, log_prefix, req[2]); goto ret_p3; } // 2, 回复该请求帧的应答包 ret_p2: ret = frame_doEscapeChars(sendBuf, nSendBytes, &rsp); if(ret < MIN_FRAME_LEN) { sw_log_error("[%s] %s failed to escape the response(%d)!!", MODULE_NAME, log_prefix, ret); goto ret_p3; } nRspBytes = ret; xstrfromhex(rsp, nRspBytes, log_rsp, NULL, NULL); ret = serial_send_data(pComIO->h, rsp, nRspBytes); if(ret == nRspBytes) sw_log_debug("[%s] %s sent a response(%d bytes)(hex): %s", MODULE_NAME, log_prefix, nRspBytes, log_rsp); else sw_log_error("[%s] %s failed to send a response(%d bytes, ret=%d)(hex): %s!!", MODULE_NAME, log_prefix, nRspBytes, ret, log_rsp); // 3, 清空接收缓存区(重置) ret_p3: if(req) sw_heap_free(req); if(rsp) sw_heap_free(rsp); if(nRecvBytes > 0 && bPrintRecvBuf) { serial_printf_recv_buffer(pSerial, LEVEL_TRACE); sw_log_warn("[%s] %s discarded %d bytes of meaningless data!", MODULE_NAME, log_prefix, nRecvBytes); } if(nRecvBytes > 0) serial_clear_recv_buffer(pSerial); // 4, 持续累加的接收新数据 ret_p4: return 1; } // 打开与MCU控制板的通讯, 返回: 0成功, <0时失败 int MCBComInit() { s_myCom.state_rwlock = sw_rwlock_create(); s_myCom.edata_rwlock = sw_rwlock_create(); if(!s_myCom.state_rwlock || !s_myCom.edata_rwlock) { sw_log_error("[%s] failed to create the rwlocks!!", MODULE_NAME); MCBComExit(); return -1; } #ifdef _DEBUG // 上位机单元测试时使用 const char *serialName = "/dev/ttyS0"; int baudrate = 115200; #else const char *serialName = "/dev/ttymxc2"; int baudrate = 115200; #endif const char *parityCheck = "none"; // 无校检 s_myCom.h = serial_open(serialName, baudrate, parityCheck, \ comio_data_recv_proc, comio_data_recv_proc, NULL); if(!s_myCom.h) { sw_log_error("[%s] failed to open the \"%s:%d(%s parity)\" device!!", \ MODULE_NAME, serialName, baudrate, parityCheck); MCBComExit(); return -2; } return 0; } // 关闭与MCU控制板的通讯, 返回: 0成功, <0时失败 int MCBComExit() { if(s_myCom.h) serial_close(s_myCom.h, WAITTHRD_SAFEEXIT_TIMEOUT); if(s_myCom.state_rwlock) sw_rwlock_destroy(s_myCom.state_rwlock); if(s_myCom.edata_rwlock) sw_rwlock_destroy(s_myCom.edata_rwlock); memset(&s_myCom, 0, sizeof(s_myCom)); return 0; } // 配置单片机控制板-运行参数, 返回: 0成功, <0时失败 int MCBConfParameters(uint32_t version, uint8_t ctrl_mode, uint8_t light_duration, uint8_t start_hour, uint8_t end_hour, uint16_t tp_intvl_min) { memcpy(cfg_params.version, &version, 4); cfg_params.ctrl_mode = ctrl_mode; cfg_params.light_duration = light_duration; cfg_params.start_hour = start_hour; cfg_params.end_hour = end_hour; memcpy(cfg_params.tp_intvl_min, &tp_intvl_min, 2); return 0; } // 设置工作状态位图-固件运维, 返回: 0成功, <0时失败 int MCBSetMNTStateBit(uint8_t state/*1bit位, 0x00:空闲, 0x01:运维中*/) { if(!s_myCom.state_rwlock || (state != 0x00 && state != 0x01)) return -1; sw_rwlock_wrlock(s_myCom.state_rwlock, WAIT_FOREVER); if(state == 0x01) sw_chararry_setBit(s_myCom.work_state, 4, 1, 0x01); // 升级程序 else sw_chararry_setBit(s_myCom.work_state, 4, 1, 0x00); // 正常工作 sw_rwlock_unlock(s_myCom.state_rwlock); return 0; } // 设置工作状态位图-相机拍照, 返回: 0成功, <0时失败 int MCBSetCamStateBit(uint8_t state/*2bit位, 0x00:空闲, 0x01:拍照中, 0x10:上传中, 0x11:保留*/) { if(!s_myCom.state_rwlock || (state != 0x00 && state != 0x01 && state != 0x10)) return -1; sw_rwlock_wrlock(s_myCom.state_rwlock, WAIT_FOREVER); switch(state) { case 0x00: // 空闲 sw_chararry_setBit(s_myCom.work_state, 4, 2, 0x00); sw_chararry_setBit(s_myCom.work_state, 4, 3, 0x00); break; case 0x01: // 拍照中 sw_chararry_setBit(s_myCom.work_state, 4, 2, 0x01); sw_chararry_setBit(s_myCom.work_state, 4, 3, 0x00); break; case 0x10: // 上传中 sw_chararry_setBit(s_myCom.work_state, 4, 2, 0x00); sw_chararry_setBit(s_myCom.work_state, 4, 3, 0x01); break; } sw_rwlock_unlock(s_myCom.state_rwlock); return 0; } // 设置工作状态位图-环境数据, 返回: 0成功, <0时失败 int MCBSetEnvStateBit(uint8_t state/*2bit位, 0x00:空闲, 0x01:收集中, 0x10:上传中, 0x11:保留*/) { if(!s_myCom.state_rwlock || (state != 0x00 && state != 0x01 && state != 0x10)) return -1; sw_rwlock_wrlock(s_myCom.state_rwlock, WAIT_FOREVER); switch(state) { case 0x00: // 空闲 sw_chararry_setBit(s_myCom.work_state, 4, 4, 0x00); sw_chararry_setBit(s_myCom.work_state, 4, 5, 0x00); break; case 0x01: // 收集中 sw_chararry_setBit(s_myCom.work_state, 4, 4, 0x01); sw_chararry_setBit(s_myCom.work_state, 4, 5, 0x00); break; case 0x10: // 上传中 sw_chararry_setBit(s_myCom.work_state, 4, 4, 0x00); sw_chararry_setBit(s_myCom.work_state, 4, 5, 0x01); break; } sw_rwlock_unlock(s_myCom.state_rwlock); return 0; } // 请求获取当前实时-环境数据, 返回: 0成功, <0时失败 int MCBReqEnvCurData() { if(!s_myCom.h) return -1; unsigned char req[6] = { 0x7D, 0x01, 0x05, 0xC0, 0x23, 0x7E }; int ret = serial_send_data(s_myCom.h, req, sizeof(req)); if(ret != sizeof(req)) return -2; sw_log_debug("[%s] sent a request(6 bytes)(hex): %s", MODULE_NAME, "7d0105c0237e"); return 0; } #ifdef _DEBUG void *MCBGetSerialHandle() { return s_myCom.h; } #endif