概要
开源项目ser2net
在Linux/openwrt中作为tcp服务器转串口使用比较广泛,本文在物联网嵌入式产品中将其定制为一个完全透明的核心通信协议路由。
整体架构流程
1.设备端的通信板中ser2net使用本地串口与主板mcu进行通信,比如开机校验,
本地绑定, 维持心跳机制,工作和空闲时的安全机制。
2.控制器端使用tcp的连接方式与设备端局域网高速通信。
3.TV显示端使用udt的连接方式与多台设备端局域网高速通信。
技术细节
ser2net/
dataxfer.c中
handle_tcp_fd_read函数负责tcp连接的监听数据读取任务,handle_dev_fd_read函数负责串口设备的监听数据读取任务。
/* Data is ready to read on the TCP port. */
static void
handle_tcp_fd_read(int fd, void *data)
{
port_info_t *port = (port_info_t *) data;
int count;
port->tcp_to_dev.pos = 0;
//此时port->tcpfd可能还无效,所以不能直接使用port tcp回传
count = read(fd, port->tcp_to_dev.buf, port->tcp_to_dev.maxsize);
if (count < 0) {
if (errno == EAGAIN || errno == EWOULDBLOCK) {
/* Nothing to read, just return. */
return;
}
// Got an error on the read, shut down the port.
//syslog(LOG_ERR, "read error for port %s: %m", port->portname);
//shutdown_port(port, "tcp read error");
shutport_default_ops(port,"tcp read error");
is_client_connect_en = 0;
is_devio_default_read_thread_stop = 0;
return;
} else if (count == 0) {
// The other end closed the port, shut it down.
shutport_default_ops(port,"tcp read close");
is_client_connect_en = 0;
is_devio_default_read_thread_stop = 0;
return;
}
port->tcp_to_dev.cursize = count;
port->tcp_bytes_received += count;
//长度限制
UI8 buf_len = port->tcp_to_dev.cursize;
if(buf_len < PROTOCOL_MIN_LEN || buf_len > PROTOCOL_MAX_LEN) return;
//异或解密
UI8* decbuf = decode_protocol(port->tcp_to_dev.buf,buf_len);
//校验
UI32 sam = check_sam(decbuf,buf_len);
UI32 decbufsam = decbuf[PROTOCOL_SAMH_INDEX(buf_len)] << 8 | decbuf[PROTOCOL_SAML_INDEX(buf_len)];
if(sam != decbufsam) return;
//pointer backup
UI8*bmpointer = decbuf;
//benchmark 操作处理
if(handle_benchmark_opts(fd,bmpointer,buf_len) >= 0) return;
/**
防止通过发送心跳包直接穿透,特点是BENCHMARK未设置
*/
if(!is_benchmark_set || !is_client_connect_en) return;
//benchmark 时效性控制
int tmpbm = bmpointer[PROTOCOL_BENCHMARK_INDEX0(buf_len)] << 24 |
bmpointer[PROTOCOL_BENCHMARK_INDEX1(buf_len)] << 16 |
bmpointer[PROTOCOL_BENCHMARK_INDEX2(buf_len)] << 8 |
bmpointer[PROTOCOL_BENCHMARK_INDEX3(buf_len)];
//超时拒绝
if(abs(tmpbm - benchmark) > BENCHMARK_MAX_DIFF_CNT) return;
buf_len -= PROTOCOL_EXTRA_LEN;
/**
只要接收到了符合规范的协议即认为是接收到了心跳包
*/
is_heart_pulse_recv = 1;
//平台命令过滤
if(handle_platform_opts(fd,bmpointer,buf_len) >= 0) return;
//统一包尾
bmpointer[PROTOCOL_MAVLINK_SAMH_INDEX(buf_len)] = MAVLINK_TAIL_REPLACE_CKA;
bmpointer[PROTOCOL_MAVLINK_SAML_INDEX(buf_len)] = MAVLINK_TAIL_REPLACE_CKB;
memcpy(port->tcp_to_dev.buf,bmpointer,buf_len);
port->tcp_to_dev.cursize = buf_len;
/**
所谓设备锁定,就是不接受控制端的控制命令,
但保留读取命令
*/
if(is_std_or_business_version &&
is_robot_lock_en &&
IS_ROBOT_LOCK_CMD(port->tcp_to_dev.buf[5]))
{
return;
}
if(!is_full())
{
is_can_out_queue = 0;
pthread_mutex_lock(&inqueue_mutex);
in_queue(port->tcp_to_dev.buf,port->tcp_to_dev.cursize);
pthread_mutex_unlock(&inqueue_mutex);
is_can_out_queue = 1;
}
}
/* Data is ready to read on the serial port. */
void
handle_dev_fd_read(struct devio *io)
{
port_info_t *port = (port_info_t *) io->user_data;
int count;
port->dev_to_tcp.pos = 0;
if (port->enabled == PORT_TELNET) {
/* Leave room for IACs. */
count = port->io.f->read(&port->io, port->dev_to_tcp.buf,
port->tcp_to_dev.maxsize/2);
} else {
count = port->io.f->read(&port->io, port->dev_to_tcp.buf,
port->dev_to_tcp.maxsize);
}
if (port->dev_monitor != NULL) {
controller_write(port->dev_monitor,
(char *) port->dev_to_tcp.buf,
count);
}
if (count < 0) {
if (errno == EAGAIN || errno == EWOULDBLOCK) {
/* Nothing to read, just return. */
return;
}
/* Got an error on the read, shut down the port. */
//syslog(LOG_ERR, "dev read error for port %s: %m", port->portname);
shutdown_port(port, "dev read error");
return;
} else if (count == 0) {
/* The port got closed somehow, shut it down. */
shutdown_port(port, "closed port");
return;
}
port->dev_bytes_received += count;
port->dev_to_tcp.cursize = count;
/**参考Mavlink_Parse 解析出完整的数据包*/
UI8*databuf = port->dev_to_tcp.buf;
UI8 is_parse_complete = 0;
while(count--)
{
if(Mavlink_Frame_Char_Buffer(&rxmsg_dev,&status_dev,*databuf++)
== MAVLINK_FRAMING_OK)
{
//此阶段忽略CRC校验
port->dev_to_tcp.cursize = NON_PAYLOAD_LEN + rxmsg_dev.len;
memcpy(port->dev_to_tcp.buf,(UI8*)&rxmsg_dev + CHECKSUM_OFFSET_LEN,port->dev_to_tcp.cursize - CHECKSUM_OFFSET_LEN);
port->dev_to_tcp.buf[PROTOCOL_MAVLINK_SAMH_INDEX(port->dev_to_tcp.cursize)] = rxmsg_dev.cks & 0xFF;
port->dev_to_tcp.buf[PROTOCOL_MAVLINK_SAML_INDEX(port->dev_to_tcp.cursize)] = rxmsg_dev.cks >> 8;
is_parse_complete = 1;
}
}
if(!is_parse_complete) return;
if(handle_mainboard_opts(0,port->dev_to_tcp.buf,port->dev_to_tcp.cursize) >= 0) return;
//上报给udt服务器
if(pcuts != NULL){
pthread_mutex_lock(&udt_inqueue_mutex);
memcpy(gd_luc_frame,port->dev_to_tcp.buf,port->dev_to_tcp.cursize);
pcuts(gd_luc_frame,port->dev_to_tcp.cursize);
pthread_mutex_unlock(&udt_inqueue_mutex);
}
//入上传队列
UI8 sendatalen = port->dev_to_tcp.cursize + PROTOCOL_EXTRA_LEN;
port->dev_to_tcp.buf[PROTOCOL_BENCHMARK_INDEX0(sendatalen)] = benchmark >> 24 & 0xFF;
port->dev_to_tcp.buf[PROTOCOL_BENCHMARK_INDEX1(sendatalen)] = benchmark >> 16 & 0xFF;
port->dev_to_tcp.buf[PROTOCOL_BENCHMARK_INDEX2(sendatalen)] = benchmark >> 8 & 0xFF;
port->dev_to_tcp.buf[PROTOCOL_BENCHMARK_INDEX3(sendatalen)] = benchmark & 0xFF;
port->dev_to_tcp.buf[PROTOCOL_NR_INDEX(sendatalen)] = 0;
UI32 sam = check_sam(port->dev_to_tcp.buf,sendatalen);
port->dev_to_tcp.buf[PROTOCOL_SAMH_INDEX(sendatalen)] = sam >> 8 & 0xFF;
port->dev_to_tcp.buf[PROTOCOL_SAML_INDEX(sendatalen)] = sam & 0xFF;
UI8*enc_sendata = encode_protocol(port->dev_to_tcp.buf,sendatalen);
if(!is_full2())
{
is_can_out_queue2 = 0;
pthread_mutex_lock(&inqueue_mutex2);
in_queue2(enc_sendata,sendatalen);
pthread_mutex_unlock(&inqueue_mutex2);
is_can_out_queue2 = 1;
}
}
cmd_dispatcher_thread_func函数负责所有下发串口的任务,msg_upload_thread_func函数负责所有上传网络连接的任务。
//下发队列循环发送线程
void* cmd_dispatcher_thread_func(void* data)
{
UI8*sendata;
UI8 sendatalen;
while(TRUE)
{
if(pcutr != NULL && !pcutr(&sendata,&sendatalen))
{
if(!(sendata == NULL || sendatalen == 0)
&& !(gdevcfg_data == NULL || gdevcfg_data->devfd == -1))
{
if(handle_udt_platform_opts(sendata,sendatalen) < 0){
serial_data_io_write(gdevcfg_data->devfd,sendata,sendatalen);
}
}
}
if(is_can_out_queue && !is_empty())
{
pthread_mutex_lock(&inqueue_mutex);
out_queue(&sendata,&sendatalen);
pthread_mutex_unlock(&inqueue_mutex);
if(!(sendata == NULL || sendatalen == 0)
&& !(gdevcfg_data == NULL || gdevcfg_data->devfd == -1))
{
serial_data_io_write(gdevcfg_data->devfd,sendata,sendatalen);
}
}
else{
//有发送任务时快速完成发送低延时,没有时短暂休眠
usleep(USLEEP_TIME_1MS);
}
}
}
//上传队列循环发送线程
void* msg_upload_thread_func(void* data)
{
UI8*sendata;
UI8 sendatalen;
while(TRUE)
{
if(is_can_out_queue2)
{
if(!is_empty2())
{
pthread_mutex_lock(&inqueue_mutex2);
out_queue2(&sendata,&sendatalen);
pthread_mutex_unlock(&inqueue_mutex2);
if(sendata != NULL && sendatalen > 0 && current_listen_port != NULL &&
current_listen_port->tcp_to_dev_state != PORT_UNCONNECTED &&
current_listen_port->tcpfd != -1)
{
if(tcp_data_io_write(current_listen_port->tcpfd,sendata,sendatalen) < 0){
shutport_default_ops(current_listen_port,"msg_upload_thread_func close");
is_client_connect_en = 0;
is_devio_default_read_thread_stop = 0;
}
}
}
}
usleep(USLEEP_TIME_30MS);
}
}
local_udp_client_init函数和handle_udt_platform_opts函数负责与TV显示端的udt监听服务通信。
void local_udp_client_init(void)
{
if(!is_std_or_business_version) return;
//无需构建消息头,根据ip动态区分
//动态加载函数
lib_trans = dlopen("/usr/lib/libudt_trans.so", RTLD_LAZY);
if(lib_trans == NULL)
{
return;
}
pcuti = (c_udt_trans_init)dlsym(lib_trans, "udt_trans_init");
pcuts = (c_udt_trans_send)dlsym(lib_trans, "udt_trans_send");
pcutr = (c_udt_trans_recv)dlsym(lib_trans, "udt_trans_recv");
pcutc = (c_udt_trans_conf)dlsym(lib_trans, "udt_trans_conf");
if(pcuti == NULL ||
pcuts == NULL ||
pcutr == NULL ||
pcutc == NULL){
return;
}
//初始化
pcuti();
}
//UDT平台命令处理函数
I32 handle_udt_platform_opts(void*data,I32 datalen)
{
PBYTE cmd = (PBYTE)data;
cmd += MAVLINK_PKG_HEAD_LEN;
if(cmd[0] != PLATFORM_CTL) return -1;
//平台命令处理
switch(cmd[1])
{
case ROBOT_LOCK_OPTS:
switch(cmd[2])
{
case ROBOT_LOCK_GET_INFO:
robot_lock_get();
break;
case ROBOT_LOCK_SET_INFO:
robot_lock_set(cmd[3]);
break;
}
return 0;
case ROBOT_REMOVE_CURR_ROUTER:
robot_remove_curr_router();
return 0;
case ROBOT_ADDITIONAL_UDT_IPADDR_OPTS:
switch(cmd[2])
{
case UDT_IPADDR_GET_INFO:
udt_ipaddr_get(-1);
break;
case UDT_IPADDR_SET_INFO:
datalen -= NON_PAYLOAD_LEN + 2;
udt_ipaddr_set(-1,&cmd[3],datalen);
break;
}
return 0;
case ROBOT_BLE_TRANSMIT:
switch(cmd[2])
{
case BLE_TO_JOYSTICK:
handle_ble_to_joystick(data,datalen);
break;
case BLE_FROM_JOYSTICK:
break;
}
return 0;
default:
return 0;
}
return 0;
}
使用mavlink协议的状态机来连续接收和过滤串口的完整的每一帧数据包。
/************************************************************************/
/* Mavlink Frame:8--263 bytes
[STX][LEN][SEQ][SYS][COMP][MSG][PAYLOAD][CKA][CKB]
STX:标志起始位,在v1.0中以FE作为起始标志
LEN:表示payload长度,范围在0--255,可以在接受端验证有效载荷的长度是否正确
SEQ:表示本次消息的序号,每发送完一帧消息,这个字节的内容会加1,加到255会从0开始,
这个序号用于mavlink消息帧接受端计算消息丢失比例的,相当于信号强度.
SYS:本消息帧的设备的系统编号,用于mavlink消息接收端识别是哪个设备发来的
COMP:本消息帧的设备单元编号,用于mavlink消息接收端识别是设备的哪个单元发来的
MSG:表示有效载荷消息的编号,这个字节很重要,消息接收端用来识别payload是什么消息
CKA:16位校验位高8位
CKB:16位校验位低8位,校验码由crc16算法得到
***********************************************************************/
#ifndef __MAVLINK_MYTINY_H__
#define __MAVLINK_MYTINY_H__
#include <string.h>
typedef char MAVLINK_I8;
typedef unsigned char MAVLINK_UI8;
typedef unsigned short MAVLINK_UI16;
#define X25_INIT_CRC (0xFFFF)
#define X25_VALIDATE_CRC (0xF0B8)
#define CHECKSUM_OFFSET_LEN (2)
#define NON_PAYLOAD_LEN (8)
#define MAX_PAYLOAD_LEN (128)
#define MAVLINK_STK (0xFE)
#define MAVLINK_SYSID (0x03)
#define MAVLINK_COMPID (0x00)
#define MAVLINK_TAIL_REPLACE_CKA (0xFF)
#define MAVLINK_TAIL_REPLACE_CKB MAVLINK_TAIL_REPLACE_CKA
#define MAVLINK_PKG_HEAD_LEN (5)
#define MAVLINK_PKG_TAIL_LEN (2)
#ifdef __STM32__
# define MAVLINK_INLINE __INLINE
#endif
#define MAX_TXMSG_SIZE (100)
#define MAX_RXMSG_SIZE MAX_TXMSG_SIZE
#pragma pack(1)
typedef struct __mavlink_message
{
MAVLINK_UI16 cks; ///< checksum 强制转换为UI8会导致高低位颠倒,低位在前高位在后 [win32平台测试结果]
MAVLINK_UI8 stk; ///< protocol start flag
MAVLINK_UI8 len; ///< Length of payload
MAVLINK_UI8 seq; ///< Sequence of packet
MAVLINK_UI8 sysid; ///< ID of message sender system/aircraft
MAVLINK_UI8 compid; ///< ID of the message sender component
MAVLINK_UI8 msgid; ///< ID of message in payload
MAVLINK_UI8 payload[MAX_PAYLOAD_LEN];
} mavlink_message_t;
typedef enum
{
MAVLINK_PARSE_STATE_UNINIT=0,
MAVLINK_PARSE_STATE_IDLE,
MAVLINK_PARSE_STATE_GOT_STX,
MAVLINK_PARSE_STATE_GOT_SEQ,
MAVLINK_PARSE_STATE_GOT_LENGTH,
MAVLINK_PARSE_STATE_GOT_SYSID,
MAVLINK_PARSE_STATE_GOT_COMPID,
MAVLINK_PARSE_STATE_GOT_MSGID,
MAVLINK_PARSE_STATE_GOT_PAYLOAD,
MAVLINK_PARSE_STATE_GOT_CRC1,
MAVLINK_PARSE_STATE_GOT_BAD_CRC1
} mavlink_parse_state_t; ///< The state machine for the comm parser
typedef enum
{
MAVLINK_FRAMING_INCOMPLETE=0,
MAVLINK_FRAMING_OK=1,
MAVLINK_FRAMING_BAD_CRC=2
} mavlink_framing_t;
#pragma pack(1)
typedef struct __mavlink_status
{
MAVLINK_UI8 msg_received; ///< Number of received messages
MAVLINK_UI8 buffer_overrun; ///< Number of buffer overruns
MAVLINK_UI8 parse_error; ///< Number of parse errors
mavlink_parse_state_t parse_state; ///< Parsing state machine
MAVLINK_UI8 packet_idx; ///< Index in current packet
MAVLINK_UI8 current_rx_seq; ///< Sequence number of last packet received
MAVLINK_UI8 current_tx_seq; ///< Sequence number of last packet sent
MAVLINK_UI8 packet_rx_success_count; ///< Received packets
MAVLINK_UI16 packet_rx_drop_count; ///< Number of packet drops
} mavlink_status_t;
#define _MAV_PAYLOAD(msg) ((const MAVLINK_UI8 *)(&((msg)->payload[0])))
#define _MAV_PAYLOAD_NON_CONST(msg) ((MAVLINK_UI8 *)(&((msg)->payload[0])))
void Mavlink_Init(void);
void Mavlink_Build(MAVLINK_UI8 ind,MAVLINK_UI8 msgid);
void Mavlink_Build_Payload(MAVLINK_UI8 ind,MAVLINK_UI8 msgid,MAVLINK_UI8*data,MAVLINK_UI8 datalen);
MAVLINK_UI16 Mavlink_Checksum(MAVLINK_UI8*data,MAVLINK_UI8 len);
void Mavlink_GetBuff(MAVLINK_UI8 ind,MAVLINK_UI8**data,MAVLINK_UI8*len);
MAVLINK_UI8 Mavlink_GetFrameInd(void);
MAVLINK_UI8 Mavlink_Frame_Char_Buffer(mavlink_message_t* rxmsg, mavlink_status_t* status,MAVLINK_UI8 c);
void Mavlink_Parse(UI8*databuf,UI16 datalen);
#endif