Kaynağa Gözat

完成接收MCU串口JSON协议数据的解析框架

niujiuru 3 hafta önce
ebeveyn
işleme
74fc1e15c2

+ 6 - 1
Makefile

@@ -54,6 +54,10 @@ libair530z.a :
 libec200u.a :
 	$(MAKE) -C $(RTU_LINUX_MODULES_PATH)/ec200u target=$(target)  $@
 
+# 单片机MCU
+libmcu_ctrl_board.a :
+	$(MAKE) -C mcu_ctrl_board target=$(target) $@
+
 LIBS := -Wl,-Bstatic -L$(RTU_LINUX_MODULES_PATH)/swapi -lswapi -L$(RTU_LINUX_MODULES_PATH)/mvs_u_takephoto -lmvs_u_takephoto
 LIBS += -L$(RTU_LINUX_MODULES_PATH)/air720u -lair720u -L$(RTU_LINUX_MODULES_PATH)/air530z -lair530z -L$(RTU_LINUX_MODULES_PATH)/ec200u -lec200u
 ifeq ($(target),armv7hf)
@@ -63,7 +67,7 @@ else
 endif
 
 # 可执行程序
-rtu_xy_v.out : libswapi.a libmvs_u_takephoto.a libair720u.a libair530z.a libec200u.a ./main.go
+rtu_xy_v.out : libswapi.a libmvs_u_takephoto.a libair720u.a libair530z.a libec200u.a libmcu_ctrl_board.a ./main.go
 	mkdir -p ./build
 	$(GO) mod tidy
 	$(SETGO_ENV) CGO_LDFLAGS="$(LIBS)" $(GO_BUILD) $(GO_FLAGS) -o $@ ./main.go
@@ -76,4 +80,5 @@ clean :
 	make -C $(RTU_LINUX_MODULES_PATH)/air720u clean
 	make -C $(RTU_LINUX_MODULES_PATH)/air530z clean
 	make -C $(RTU_LINUX_MODULES_PATH)/ec200u  clean
+	make -C ./mcu_ctrl_board clean
 	rm -rf ./*.a ./*.out ./build

+ 49 - 0
mcu_ctrl_board/Makefile

@@ -0,0 +1,49 @@
+.PHONY: build clean
+
+# 依赖库
+RTU_LINUX_MODULES_PATH := $(shell pwd)/../../rtu_linux_modules
+
+# 头文件
+INCS += -I.
+INCS += -I$(RTU_LINUX_MODULES_PATH)/swapi
+INCS += -I./include
+
+# 源文件
+SRCS += $(filter-out $(RTU_LINUX_MODULES_PATH)/swapi/testLib.c, $(wildcard $(RTU_LINUX_MODULES_PATH)/swapi/*.c))
+SRCS += $(filter-out ./lwjson/example_stream.c, $(wildcard ./lwjson/*.c))
+SRCS += $(RTU_LINUX_MODULES_PATH)/swapi/subjects/serial/serial.c mcu_ctrl_board.c
+
+# .o文件
+OBJS = $(SRCS:.c=.o)
+
+# 编译器
+CC = gcc
+CFLAGS = -Wall -fPIC -O2 -g
+DEFINS = -D_GNU_SOURCE
+
+# 单元测试时使用用, 开启调试模式, 正式编译时需要注释掉
+DEFINS += -D_DEBUG
+
+target ?= x86_64
+ifeq ($(target),armv7hf)
+  CC := arm-linux-gnueabihf-gcc
+	AR := arm-linux-gnueabihf-ar
+endif
+
+# 库文件
+LIBS += -Wl,-Bdynamic -lc -lm -ldl -lpthread
+
+# 编译和清理
+build : libmcu_ctrl_board.a mcb_test.out
+
+%.o : %.c
+	$(CC) $(DEFINS) $(CFLAGS) -c $< $(INCS) -o $@
+
+libmcu_ctrl_board.a : $(OBJS)
+	$(AR) -cr $@ $(OBJS)
+
+mcb_test.out : $(OBJS)
+	$(CC) $(DEFINS) $(CFLAGS) $(OBJS) mcb_test.c $(INCS) $(LIBS) -o $@
+
+clean :
+	rm -rf $(OBJS) *.out *.a config/ log/ status/ var/

+ 1 - 0
mcu_ctrl_board/lwjson/example_stream.c

@@ -63,6 +63,7 @@ example_stream_run(void) {
     for (const char* c = json_str; *c != '\0'; ++c) {
         res = lwjson_stream_parse(&stream_parser, *c);
         if (res == lwjsonSTREAMINPROG) {
+            printf("Parsing in progress\r\n");
         } else if (res == lwjsonSTREAMWAITFIRSTCHAR) {
             printf("Waiting first character\r\n");
         } else if (res == lwjsonSTREAMDONE) {

+ 20 - 0
mcu_ctrl_board/mcb_test.c

@@ -1,6 +1,26 @@
 #include "mcu_ctrl_board.h"
 
+char *RTU_ProcessCommand(const char *request)
+{
+  char *response = (char *)malloc(MAX_LINE_CHARS);
+  if (!response) return NULL;
+  snprintf(response, MAX_LINE_CHARS, "{\"jsonrpc\":\"2.0\",\"error\":{\"code\":-32601,\"message\":\"Method not found\"},\"id\":1}");
+  return response;
+}
+
 int main(int argc,char *argv[])
 {
+  int ret = MCBComInit();
+  if(ret < 0)
+  {
+    sw_log_error("an error occurred while calling the MCBComInit() function(%d)!!", ret);
+    goto end_p;
+  }
+
+  getchar();
+
+end_p:
+  MCBComExit();
+
   return 0;
 }

+ 66 - 6
mcu_ctrl_board/mcu_ctrl_board.c

@@ -1,5 +1,6 @@
 #include "mcu_ctrl_board.h"
 #include "../../rtu_linux_modules/swapi/subjects/serial/serial.h"
+#include "lwjson/lwjson.h"
 
 // 模块名称
 static const char MODULE_NAME[] = "MCUCtrlBoard";
@@ -8,27 +9,86 @@ static const char MODULE_NAME[] = "MCUCtrlBoard";
 typedef struct
 {
   void *h;
+  lwjson_stream_parser_t parser;
+  unsigned long jsonStartTick; // JSON数据包开始接收时的单调计时基准, 用于超时判断
 } SMCBCom;
 
 static SMCBCom s_myCom = { 0 };
 
-// 接收处理来自MCU控制板的数据报文帧, 串口-线程回调
+// 声明Go中处理串口请求并返回应答结果的函数(供C调用)
+extern char *RTU_ProcessCommand(const char *request);
+
+// 接收处理来自MCU控制板的数据报文帧, 串口-数据回调
 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); lwjsonr_t ret;
+
+  ret = lwjson_stream_parse(&pComIO->parser, pRecvBuf[nRecvBytes - 1]); // 把串口数据逐字节的喂给解析器去解析
+  switch(ret)
+  {
+  case lwjsonERRJSON:             // 1, 无效的字符  
+  case lwjsonSTREAMWAITFIRSTCHAR: // 2, 等待首字符
+    serial_printf_recv_buffer(pSerial, LEVEL_TRACE);
+    sw_log_warn("[%s] %s discarded %d bytes before JSON frame start!", MODULE_NAME, log_prefix, nRecvBytes);
+    serial_clear_recv_buffer(pSerial);
+    goto retp;
+  case lwjsonSTREAMINPROG:        // 3, 正在解析中
+    if(0 == pComIO->jsonStartTick) xgettickcount(&pComIO->jsonStartTick);
+    break;
+  case lwjsonSTREAMDONE:          // 4, 解析已完成
+    sw_log_debug("[%s] %s received a request(%d bytes): %s", MODULE_NAME, log_prefix, nRecvBytes, pRecvBuf);
+    char *pResponse = RTU_ProcessCommand((const char *)pRecvBuf);
+    if(pResponse)
+    {
+      int nRspBytes = strlen(pResponse);
+      int sendBytes = serial_send_data(pSerial, (const unsigned char *)pResponse, nRspBytes);
+      if(sendBytes == nRspBytes) sw_log_debug("[%s] %s send a response(%d bytes): %s", MODULE_NAME, log_prefix, nRspBytes, pResponse);
+      else sw_log_error("[%s] %s failed to send a response(%d bytes, ret=%d): %s!!", MODULE_NAME, log_prefix, nRspBytes, sendBytes, pResponse);
+      free(pResponse);
+    }
+    pComIO->jsonStartTick = 0; serial_clear_recv_buffer(pSerial); lwjson_stream_reset(&pComIO->parser);
+    goto retp;
+  case lwjsonERRMEM:             // 5, 嵌套的太深("LWJSON_CFG_STREAM_STACK_SIZE")
+    sw_log_error("[%s] %s parser stack overflow (ERRMEM), resetting parser!", MODULE_NAME, log_prefix);
+    pComIO->jsonStartTick = 0; serial_clear_recv_buffer(pSerial); lwjson_stream_reset(&pComIO->parser);
+    goto retp;
+  default: // 流式解析时, 其它的返回值, 都不应该出现
+    sw_log_fatal("[%s] %s internal error, lwjson_stream_parse() returned: %d, data receive thread will exit!!!", MODULE_NAME, log_prefix, ret);
+    return -1;
+  }
+
+retp:
+  return 1;
+}
+
+// 接收处理来自MCU控制板的数据报文帧, 串口-空闲回调
+static int comio_data_idle_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); unsigned long nowTick;
+
+  xgettickcount(&nowTick);
+  if(pComIO->jsonStartTick > 0 && (nowTick - pComIO->jsonStartTick) > 50/*空闲超时单位: ms*/)
+  {
+    sw_log_warn("[%s] %s timeout occurred while receiving a request(%d bytes): %s!", MODULE_NAME, log_prefix, nRecvBytes, pRecvBuf);
+    pComIO->jsonStartTick = 0; serial_clear_recv_buffer(pSerial); lwjson_stream_reset(&pComIO->parser);
+  }
+
   return 1;
 }
 
 // 打开与MCU控制板的串口通讯, 返回: 0成功, <0时失败
 int MCBComInit()
 {
-#ifdef _DEBUG // 上位机单元测试时使用
-  const char *serialName = "/dev/ttyS0";   int baudrate = 115200;
-#else
+  lwjson_stream_init(&s_myCom.parser, NULL);
+
   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);
+                          comio_data_recv_proc, comio_data_idle_proc, NULL);
   if(!s_myCom.h)
   {
     sw_log_error("[%s] failed to open the \"%s:%d(%s parity)\" device!!", \

+ 10 - 0
mcu_ctrl_board/mcu_ctrl_board.go

@@ -1,5 +1,10 @@
 package mcu_ctrl_board
 
+/*
+#include "mcu_ctrl_board.h"
+*/
+import "C"
+
 import (
 	"time"
 
@@ -37,3 +42,8 @@ func ModuleInit() bool {
 func ModuleExit() {
 	mcuCtrlBoard_ComExit()
 }
+
+//export RTU_ProcessCommand
+func RTU_ProcessCommand(req *C.char) *C.char {
+	return C.CString("")
+}