串口转以太(三)

一、串口转以太ttl2tcp应用程序

1、 新建文件夹ttl2tcp

该文件夹下的Makefile

#
# Copyright (C) 2009 OpenWrt.org
#
# This is free software, licensed under the GNU General Public License v2.
# See /LICENSE for more information.
#

include $(TOPDIR)/rules.mk
include $(INCLUDE_DIR)/kernel.mk

PKG_NAME:=ttl2tcp
PKG_RELEASE:=0.5.0
PKG_BUILD_DIR := $(BUILD_DIR)/$(PKG_NAME)

include $(INCLUDE_DIR)/package.mk

define Package/ttl2tcp
  SECTION:=utils
  CATEGORY:=Utilities
  TITLE:=serial to tcp 
  DEPENDS:=+libuci +libpthread
endef

define Package/ttl2tcp/description
  A client of tcp to serial or serial to tcp
endef

define Build/Prepare
    mkdir -p $(PKG_BUILD_DIR)
    $(CP) ./src/* $(PKG_BUILD_DIR)/
endef

define Build/Configure
endef

define Build/Compile
    $(MAKE) -C $(PKG_BUILD_DIR) 
        CC="$(TARGET_CC)" 
        CFLAGS="$(TARGET_CFLAGS) -Wall -I$(LINUX_DIR)/user_headers/include" 
        LDFLAGS="$(TARGET_LDFLAGS)"
endef

define Package/ttl2tcp/install
    $(INSTALL_DIR) $(1)/usr/sbin
    $(INSTALL_BIN) $(PKG_BUILD_DIR)/ttl2tcp $(1)/usr/sbin/
    $(INSTALL_DIR) $(1)/etc/config
    $(INSTALL_DATA) ./files/$(PKG_NAME).config $(1)/etc/config/$(PKG_NAME)
    $(INSTALL_DIR) $(1)/etc/init.d
    $(INSTALL_BIN) ./files/$(PKG_NAME).init $(1)/etc/init.d/$(PKG_NAME)

endef

$(eval $(call BuildPackage,ttl2tcp))

2、 ttl2tcpsrc tl2tcp.c

/*
 * ttl2tcp
 *
 * tingpan<dktingpan@sina.cn> 2015-11-30
 *
 * this is a client of serial translate to tcp or tcp translate to serial.
 * serial read overtime is 0.5s
 * every server read overtime is 0.5s.
 */

#include    <stdio.h>
#include    <stdlib.h>
#include    <string.h>
#include    <errno.h>
#include    <sys/types.h>
#include    <sys/socket.h>
#include    <netinet/in.h>
#include     <unistd.h>  
#include     <fcntl.h> 
#include     <termios.h>
#include     <errno.h>
#include     <strings.h>
#include     <time.h> 
#include     <arpa/inet.h>
#include     <pthread.h>
#include     <uci.h>
#include    <semaphore.h>
#include    <signal.h>

#define SER_MAXLINE 128
#define CONF_MAXLINE 64
#define SEND_MAXLINE 64
#define RECV_MAXLINE 64

//为了应对缓存溢出,多分配一个字节,
//与请求的字节数有关,因为请求的是8个字节,而串口读每次请求1个字节,所以可以准确不超过缓冲区。
#define SOCK_MAXLINE 136    

#define TTL2TCP_DEBUG    0

#if (TTL2TCP_DEBUG == 1)
#define PRT(...) printf(__VA_ARGS__)
#else
#define PRT(...)
#endif


struct argument  
{  
    int fd; //串口文件描述符
    int sockfd;
}; 
struct _options {
    unsigned int enabled;
    char name[10];
    unsigned int baudrate;
    //unsigned int data;
    //unsigned int parity;
    //unsigned int stop;    
    unsigned int open;
    unsigned int sendhex;
    char send[SEND_MAXLINE];
    unsigned int recvhex;
    char recv[RECV_MAXLINE];        
    struct in_addr ipaddr;    
    unsigned int port;    
};
struct _options opt;
//pthread_mutex_t socket_lock;       //互斥锁

//为了保证用户输入的波特率是个正确的值,所以需要这两个数组验证,对于设置波特率时候,前面要加个B   
int speed_arr[] = { B115200, B57600, B38400, B19200, B9600, B4800, B2400, B1200, B300,  
    B115200, B57600, B38400, B19200, B9600, B4800, B2400, B1200, B300, };  
  
int name_arr[] = {115200, 57600, 38400, 19200, 9600, 4800, 2400, 1200, 300,  
    115200, 57600, 38400, 19200, 9600, 4800, 2400, 1200, 300, };  
  
/*
 * 函数名:      set_speed 
 * 参数:        int fd ,int speed 
 * 返回值:      void 
 * 描述:        设置fd表述符的串口波特率 
 */  
void set_speed(int fd ,int speed)  
{  
    struct termios opt;  
    int i;  
    int status;  
  
    tcgetattr(fd,&opt);  
    for(i = 0;i < sizeof(speed_arr)/sizeof(int);i++)  
    {  
        if(speed == name_arr[i])                        //找到标准的波特率与用户一致   
        {  
            tcflush(fd,TCIOFLUSH);                      //清除IO输入和输出缓存   
            cfsetispeed(&opt,speed_arr[i]);         //设置串口输入波特率   
            cfsetospeed(&opt,speed_arr[i]);         //设置串口输出波特率   
  
            status = tcsetattr(fd,TCSANOW,&opt);    //将属性设置到opt的数据结构中,并且立即生效   
            if(status != 0)  
                perror("tcsetattr fd:");                //设置失败   
            return ;  
        }  
        tcflush(fd,TCIOFLUSH);                          //每次清除IO缓存   
    }  
}  
/*
 * 函数名:      set_parity 
 * 参数:        int fd 
 * 返回值:      int 
 * 描述:        设置fd表述符的奇偶校验 
 */  
int set_parity(int fd)  
{  
    struct termios opt;  
  
    if(tcgetattr(fd,&opt) != 0)                 //或许原先的配置信息   
    {  
        perror("Get opt in parity error:");  
        return -1;  
    }  
  
    //通过设置opt数据结构,来配置相关功能,以下为八个数据位,不使能奇偶校验 
    opt.c_iflag &= ~(IGNBRK | BRKINT | PARMRK | ISTRIP  
                | INLCR | IGNCR | ICRNL | IXON);  
    opt.c_oflag &= ~OPOST;  
    opt.c_lflag &= ~(ECHO | ECHONL | ICANON | ISIG | IEXTEN);  
    opt.c_cflag &= ~(CSIZE | PARENB);  
    opt.c_cflag |= CS8;  
  
    tcflush(fd,TCIFLUSH);                           //清空输入缓存   
  
    if(tcsetattr(fd,TCSANOW,&opt) != 0)  
    {  
        perror("set attr parity error:");  
        return -1;  
    }  
  
    return 0;  
}  
/*
 * 函数名:      serial_init 
 * 参数:        char *dev_path,int speed,int is_block 
 * 返回值:      初始化成功返回打开的文件描述符 
 * 描述:        串口初始化,根据串口文件路径名,串口的速度,和串口是否阻塞,block为1表示阻塞 
 */  
int serial_init(char *dev_path,int speed,int is_block)  
{  
    int fd;  
    int flag;  
  
    flag = 0;  
    flag |= O_RDWR;                     //设置为可读写的串口属性文件   
    if(is_block == 0)  
        flag |=O_NONBLOCK;              //若为0则表示以非阻塞方式打开   
  
    fd = open(dev_path,flag);               //打开设备文件   
    if(fd < 0)  
    {  
        perror("Open device file err:");  
        close(fd);  
        return -1;  
    }  
  
    //打开设备文件后,下面开始设置波特率  
    set_speed(fd,speed);                //考虑到波特率可能被单独设置,所以独立成函数   
  
    //设置奇偶校验  
    if(set_parity(fd) != 0)  
    {  
        perror("set parity error:");  
        close(fd);                      //一定要关闭文件,否则文件一直为打开状态   
        return -1;  
    }  
  
    return fd;  
}  
/* 
 * 函数名:      serial_send 
 * 参数:        int fd,char *str,unsigned int len 
 * 返回值:      发送成功返回发送长度,否则返回小于0的值 
 * 描述:        向fd描述符的串口发送数据,长度为len,内容为str 
 * 备注:        因为无法发送含有0x00的字符串,所以将len相关注释,有待改进
 */  
int serial_send(int fd,char *str,unsigned int len) 
{  
    int ret;    
    
    //if(len > strlen(str)) //判断长度是否超过str的最大长度,以0x00为结束标志   
    //if(len > sizeof(str))   //判断长度是否超过str的最大长度
    //if(len > sizeof(str)) 
        //len = sizeof(str);  
    //printf("sizeof(str) is %d
",sizeof(str));
    //printf("strlen(str) is %d
",strlen(str));
    ret = write(fd,str,len);  
    if(ret < 0)  
    {  
        perror("serial send err:");  
        return -1;  
    } 
       
    return ret;  
}  
  
/*
 * 函数名:      serial_read 
 * 参数:        int fd,char *str,unsigned int len,unsigned int timeout 
 * 返回值:      在规定的时间内读取数据,超时则退出,超时时间为ms级别 
 * 描述:        向fd描述符的串口接收数据,长度为len,存入str,timeout 为超时时间 
 */  
int serial_read(int fd, char *str, unsigned int len, unsigned int timeout)  
{  
    fd_set rfds;  
    struct timeval tv;  
    int ret;                                //每次读的结果   
    int sret;                               //select监控结果   
    int readlen = 0;                        //实际读到的字节数   
    char * ptr;  
  
    ptr = str;                          //读指针,每次移动,因为实际读出的长度和传入参数可能存在差异   
  
    FD_ZERO(&rfds);                     //清除文件描述符集合   
    FD_SET(fd,&rfds);                   //将fd加入fds文件描述符,以待下面用select方法监听   
  
    //传入的timeout是ms级别的单位,这里需要转换为struct timeval 结构的  
    tv.tv_sec  = timeout / 1000;  
    tv.tv_usec = (timeout%1000)*1000;  
  
    //开始读  
    while(readlen < len)  
    {  
        sret = select(fd+1,&rfds,NULL,NULL,&tv);        //检测串口是否可读   
  
        if(sret == -1)                              //检测失败   
        {  
            perror("serial_read select");  //自动添加冒号
            break;  
        }  
        else if(sret > 0)                        //<SPAN style="WHITE-SPACE: pre"> </SPAN>//检测成功可读   
        {  
            ret = read(fd,ptr,1); //第三个参数为请求读取的字节数 
            if(ret < 0)  
            {  
                perror("read err:");  
                break;  
            }  
            else if(ret == 0)  
                break;  
  
            readlen += ret;                             //更新读的长度   
            ptr     += ret;                             //更新读的位置   
        }  
        else                                          //超时   sret == 0 ,没填满buf也退出循环
        {  
            PRT("serial read timeout!
");  
            break;  
        }  
    }  
  
    return readlen;  
}  

/*
 * socket_read: 读取tcp数据 
 * @fd: socket文件描述符
 * @str:将读到的数据存放在该地址
 * @len:申请读取的字符长度
 * @timeout:超时时间,单位ms
 */ 
int socket_read(int fd, char *str, unsigned int len, unsigned int timeout)
{
    fd_set fdsr;
    struct timeval tv;
    int readlen = 0; 
    char * ptr; 
    int ret;
    ptr = str; 
    // initialize file descriptor set
    FD_ZERO(&fdsr);//每次循环都要清空
    FD_SET(fd, &fdsr);
    tv.tv_sec  = timeout / 1000;  
    tv.tv_usec = (timeout%1000)*1000;  
    while(readlen < len){
        ret = select(fd + 1, &fdsr, NULL, NULL, &tv);
        if (ret < 0) {
            perror("socket_read select"); //自动添加冒号
            sleep(1);
            break;
            //exit(-1);
        } else if (ret == 0) {
            PRT("socket read timeout
");
            sleep(1);
            break;
          }
        //每次申请读取8个字节,但事实上是按发送端每次发送的字符串长度来确定的,如果长度小于8,则每次读取实际长度,如果大于8,则读取8字节。
        //recv多少就从缓冲区中删除多少,剩下的数据可以在下次recv时得到
        //即使子线程挂起,也一直有数据可以读,数据不丢失,真正的接收数据是协议来完成的,存放在s的接收缓冲中。
        ret = recv(fd, ptr, 8, 0);//申请8个字节         
        if (ret <= 0) {//如果连接已中止,返回0。如果发生错误,返回-1  
            printf("client close
");
            close(fd);  //可以关闭文件描述符吗,如何主线程和子线程的同步, 执行了该句,子线程的文件描述符还是不变
            FD_CLR(fd, &fdsr);
            fd = 0; //会改变文件描述符吗
            //sleep(1);
            //break;//要跳出循环,否则只要send没发东西,就一直在这里面。断线了一定要重新连接,跳出了也没用。
        } else {        
           readlen +=ret;
           ptr += ret; 
           //printf("the ret length is:%d
",readlen); 
        }
    }
    return readlen;
}

/*
 * read_config: 读取配置文件 
 * @popt: 配置信息保存的结构体
 */ 
static int read_config(struct _options *popt)
{
    static struct uci_context *ctx;
    struct uci_ptr ptr;
    char a[CONF_MAXLINE];
    //int len = 0;
    
    ctx = uci_alloc_context();    

    //读取设备名称
    if ((strncpy(a, "ttl2tcp.device.name", sizeof(a)) == NULL) 
        || (uci_lookup_ptr(ctx, &ptr, a, true) != UCI_OK)) {
        printf("Read ttl2tcp.device.name failed! exit.
");
        exit(-1);
    }
    if (ptr.o) {
        strncpy(popt->name, ptr.o->v.string, sizeof(popt->name));
    } else {
        printf("ttl2tcp.device.name Not found!
");
    }

    //读取串口波特率
    if ((strncpy(a, "ttl2tcp.device.baudrate", sizeof(a)) == NULL) 
        || (uci_lookup_ptr(ctx, &ptr, a, true) != UCI_OK)) {
        printf("Read tttl2tcp.device.baudrate failed! exit.
");
        exit(-1);
    }
    if (ptr.o) {
        popt->baudrate = strtol(ptr.o->v.string, NULL, 10);
    } else {
        printf("ttl2tcp.device.baudrate Not found!
");
    }

    //Serial Probe
    //open
    if ((strncpy(a, "ttl2tcp.probe.open", sizeof(a)) == NULL) 
        || (uci_lookup_ptr(ctx, &ptr, a, true) != UCI_OK)) {
        printf("Read ttl2tcp.probe.open failed! exit.
");
        exit(-1);
    }
    if (ptr.o) {
        popt->open = strtol(ptr.o->v.string, NULL, 10);
    } else {
        printf("ttl2tcp.probe.open Not found!
");
    }
    //sendhex
    if ((strncpy(a, "ttl2tcp.probe.sendhex", sizeof(a)) == NULL) 
        || (uci_lookup_ptr(ctx, &ptr, a, true) != UCI_OK)) {
        printf("Read ttl2tcp.probe.sendhex failed! exit.
");
        exit(-1);
    }
    if (ptr.o) {
        popt->sendhex = strtol(ptr.o->v.string, NULL, 10);
    } else {
        printf("ttl2tcp.probe.sendhex Not found!
");
    }
    //send
    if ((strncpy(a, "ttl2tcp.probe.send", sizeof(a)) == NULL) 
        || (uci_lookup_ptr(ctx, &ptr, a, true) != UCI_OK)) {
        printf("Read ttl2tcp.device.send failed! exit.
");
        exit(-1);
    }
    if (ptr.o) {
        //if (sizeof(popt->send) > SEND_MAXLINE) {
            //len = SEND_MAXLINE;
        //} else {
            //len = sizeof(popt->send);
        //}        
        //strncpy(popt->send, ptr.o->v.string, len);
        strncpy(popt->send, ptr.o->v.string, sizeof(popt->send));
    } else {
        printf("ttl2tcp.probe.send Not found!
");
    }
    //recvhex
    if ((strncpy(a, "ttl2tcp.probe.recvhex", sizeof(a)) == NULL) 
        || (uci_lookup_ptr(ctx, &ptr, a, true) != UCI_OK)) {
        printf("Read ttl2tcp.probe.recvhex failed! exit.
");
        exit(-1);
    }
    if (ptr.o) {
        popt->recvhex = strtol(ptr.o->v.string, NULL, 10);
    } else {
        printf("ttl2tcp.probe.recvhex Not found!
");
    }
    //recv
    if ((strncpy(a, "ttl2tcp.probe.recv", sizeof(a)) == NULL) 
        || (uci_lookup_ptr(ctx, &ptr, a, true) != UCI_OK)) {
        printf("Read ttl2tcp.device.recv failed! exit.
");
        exit(-1);
    }
    if (ptr.o) {
        strncpy(popt->recv, ptr.o->v.string, sizeof(popt->recv));
    } else {
        printf("ttl2tcp.probe.send Not found!
");
    }

        
    //读取ip地址
    if (!snprintf(a,sizeof(a),"ttl2tcp.server.ipaddr")
        || (uci_lookup_ptr(ctx, &ptr, a, true) != UCI_OK)) {
        printf("Read ttl2tcp.server.ipaddr failed! exit.
");
        exit(-1);
    }
    if (ptr.o) {
        unsigned int laddr;
        laddr = inet_addr(ptr.o->v.string);//因为ipaddr为网络字节顺序,大端字节序,而输入的为主机字节序
        memcpy(&popt->ipaddr, &laddr, 4);    
    } else {
        printf("ttl2tcp.server.ipaddr Not found!
");//如果不存在对应的配置文件
    }        
    //读取port
    if (!snprintf(a,sizeof(a),"ttl2tcp.server.port")
        || (uci_lookup_ptr(ctx, &ptr, a, true) != UCI_OK)) {
        printf("Read ttl2tcp.server.port failed! exit.
");
        exit(-1);
    }
    if (ptr.o) {
        popt->port = strtol(ptr.o->v.string, NULL, 10);
    } else {
        printf("ttl2tcp.server.port Not found!
");//如果不存在对应的配置文件
    }
    uci_free_context(ctx);    
    return 0;
}

//tcp转串口
/*void *socket_thread(void *arg)
{
    char sockbuf[SOCK_MAXLINE];
    int sreadlen = 0;
    int freadlen = 0;
    struct argument thread_arg;
    thread_arg = *(struct argument *)arg;
    memset(sockbuf, 0, sizeof(sockbuf));
    while(1)
    {
        //pthread_mutex_lock(&socket_lock);//lock        
        sreadlen = socket_read(thread_arg.sockfd, sockbuf, SOCK_MAXLINE-8, 50);//为了防止缓存溢出,少读取一个字节
        //printf("thread sockfd is %d
",thread_arg.sockfd);
        PRT("the sockbuf is:%s
", sockbuf);//打印出数据
        PRT("the sockbuf length is:%d
",sreadlen); 
        freadlen = serial_send(thread_arg.fd,sockbuf,sreadlen); //发送含有0x00的十六进制数据有问题,会当作结束符号
        PRT("tcp to serial send %d bytes!
",freadlen);   
        memset(sockbuf, 0, sizeof(sockbuf));
        //pthread_mutex_unlock(&socket_lock);//unlock
        usleep(1);
    }
}*/

//str1和str2对比,比较两个字符串或者两组十六进制数是否一样,str1的长度大于str2,将被忽略
int strhexcmp(char *str1,char *str2,int len)
{
    int i;
    for (i = 0; i < len; i++) {
        if (*(str1+i) != *(str2+i)) return -1;
    }
    return 0;
}

//串口握手测试
void serial_probe(int fd)
{
    char serbuf[SER_MAXLINE];
    int readlen; 
    char *beginstr;
    int beginstr_len = 0;
    //char beginstr_hex[]={0x45,0x46,0x00,0x0D,//头部
        //            0x00,0x00,0x01,//源,第三个为类型,00无效;01主机;02基站;03为CS;04节点
        //            0x01,0x00,0x04,//目标,发送给节点,第三个为类型,
        //            0x88,0x01,0x00 , //示开始接收数据
        //            0x2E,0x20,0x0D,0x0A};//结束
    char beginstr_hex[22] = {0};
    int beginstr_hex_len = 0;
    char *ackstr;
    int ackstr_len = 0;
    char ackstr_hex[22] = {0};
    int ackstr_hex_len = 0;
    int i;
    
    beginstr = opt.send;
    ackstr = opt.recv;
    //为防止溢出,最大为SEN_MAXLIN-1,数组最后一个为''
    if (strlen(beginstr) >= SEND_MAXLINE) {
        beginstr_len = SEND_MAXLINE -1;
    } else {
        beginstr_len = strlen(beginstr);
    }

    if (strlen(ackstr) >= RECV_MAXLINE) {
        ackstr_len = RECV_MAXLINE -1;
    } else {
        ackstr_len = strlen(ackstr);
    }
    PRT("beginstr is %d: %s
", beginstr_len, beginstr);
    PRT("ackstr is %d: %s
", ackstr_len, ackstr);

    if (opt.sendhex == 1) {        
        //将字符串转换成十六进制
        beginstr_hex_len = (beginstr_len+1)/3;
        PRT("beginstr to hex len is %d
",beginstr_hex_len);
        for (i = 0; i < beginstr_hex_len; i++) {
            beginstr_hex[i] = (char)strtol(&opt.send[3*i], NULL, 16); 
            PRT("beginstr_hex[%d] is %hhx
",i,beginstr_hex[i]);
        }
        beginstr = beginstr_hex;
        beginstr_len = beginstr_hex_len;
        PRT("beginstr to hex is success
");
    } 
    if (opt.recvhex == 1) {
        //将字符串转换成十六进制
        ackstr_hex_len = (ackstr_len+1)/3;
        PRT("ackstr to hex len is %d
",ackstr_hex_len);
        for (i = 0; i < ackstr_hex_len; i++) {
            ackstr_hex[i] = (char)strtol(&opt.recv[3*i], NULL, 16); 
            PRT("ackstr_hex[%d] is %hhx
",i,ackstr_hex[i]);
        }
        ackstr = ackstr_hex;
        ackstr_len = ackstr_hex_len;
        PRT("ackstr to hex is success
");        
    } 
    printf("serial probing ...
");
    while(1) {
        memset(serbuf, 0, sizeof(serbuf));    
        serial_send(fd, beginstr, beginstr_len);        
        readlen = serial_read(fd,serbuf,SER_MAXLINE,1000);
        if (readlen > 0) {
            //PRT("the ack serbuf is %d: %s
",readlen,serbuf);
            PRT("the ack serbuf is :
");
            for (i = 0; i < readlen; i++) {
                PRT("%hhx ",*(serbuf+i));
            }
            PRT("
");
            //if( strcmp(serbuf,ackstr) == 0 )
            if( strhexcmp(serbuf,ackstr,ackstr_len) == 0 ) {
                PRT("begin print data
");
                break;
            }
        }    
    }
}

int main(int argc, char** argv)
{
    //串口变量定义
    int fd;    
    char str[]="hello linux serial!"; //字符串初始化 
    char serbuf[SER_MAXLINE]; 
    int readlen;
    char dev_path[20];
   
    // socket变量定义     
    int sockfd;
    struct sockaddr_in servaddr;
    char sockbuf[SOCK_MAXLINE];
    int sreadlen = 0;
    int freadlen = 0;
        
    //多线程
    //pthread_t thread;
    //int mret;
    //struct argument arg;
    
    //读取配置文件
    memset(&opt,0 ,sizeof(struct _options));
    read_config(&opt);
        
    //串口初始化
    if (!snprintf(dev_path,sizeof(dev_path),"/dev/%s",opt.name)
        || (fd = serial_init(dev_path,opt.baudrate,1)) < 0)
    {
        perror("serial init err:");  
        return -1;  
    } 
    memset(serbuf, 0, sizeof(serbuf));

    //socket始化
    if( (sockfd = socket(AF_INET, SOCK_STREAM, 0)) < 0)
    {
        printf("create socket2 error: %s(errno: %d)
", strerror(errno),errno);
        exit(-1);
        //return -1;
    }
    PRT("create sockfd is %d
",sockfd);
    //memset(&servaddr, 0, sizeof(servaddr));
    bzero(&servaddr, sizeof(struct sockaddr_in));
    servaddr.sin_family = AF_INET;
    servaddr.sin_port = htons(opt.port);        
    servaddr.sin_addr = opt.ipaddr;//与配置文件有关,注意配置文件要正确            
    PRT("socket init!
");
    
    while (-1 == connect(sockfd, (struct sockaddr*)&servaddr, sizeof(servaddr)))
    {
        printf("connect error: %s(errno: %d)
",strerror(errno),errno);
        sleep(5);
    }  
    PRT("send msg to server:
");    
    
    signal(SIGPIPE, SIG_IGN);    // prevent exiting when we send data to closed socket
        
    //socket发送
    //if( send(sockfd, str, strlen(str), 0) < 0)
    //{
        //printf("send msg error: %s(errno: %d)
", strerror(errno), errno);
        //sleep(1);
    //}
    
    ////pthread_mutex_init(&socket_lock, NULL);
 
    //创建多线程
    /*arg.fd=fd;
    arg.sockfd=sockfd;
    mret = pthread_create(&thread, NULL, socket_thread, (void *)(long)&arg);    
    if (mret != 0) 
    { 
        printf("Create thread failed
"); 
        exit(mret); 
    }
    printf("Create thread
"); */
    
    //发送消息告诉cc2530可以打印出数据
    if (opt.open != 1) {
        PRT("do not open serial probe!
");
    } else {
        PRT("open serial probe!
");
        serial_probe(fd);
    }
    
    printf("tcp to serial or serial to tcp start!
");        
    while(1)
    {    
        //tcp转串口    
        memset(sockbuf, 0, sizeof(sockbuf));
        sreadlen = socket_read(sockfd, sockbuf, SOCK_MAXLINE-8, 50);//为了防止缓存溢出,少读取一个字节
        //printf("thread sockfd is %d
",thread_arg.sockfd);
        PRT("the sockbuf is:%s
", sockbuf);//打印出数据
        PRT("the sockbuf length is:%d
",sreadlen); 
        freadlen = serial_send(fd,sockbuf,sreadlen); //发送含有0x00的十六进制数据有问题,会当作结束符号 
        PRT("tcp to serial send %d bytes!
",freadlen);   
        
        //串口转tcp        
        memset(serbuf, 0, sizeof(serbuf));    
        freadlen = serial_read(fd,serbuf,SER_MAXLINE,50);//50ms内如果数据没有装满buf,则读取完毕。 如果数据量大,则读取的速度也越快。 
        PRT("the serbuf is :%s
",serbuf); 
        PRT("the serbuf length is :%d
",freadlen); 

        if(send(sockfd, serbuf, freadlen, 0) < 0)//serbuf中有数据可以发送才会执行这条语句,没数据就执行成功
        {
            printf("serial to tcp send msg error: %s(errno: %d)
", strerror(errno), errno);//服务端断掉,则发送失败。
            //exit(0);
            
            //断线重连
            close(sockfd);
            sockfd = socket(AF_INET, SOCK_STREAM, 0);//重新创建一个套接子,但描述符可能重复
            if (sockfd == -1) 
            {
                printf("recreate socket Error!!! Error # is %d. Exit!
", errno);
                exit(-1);
            }
            PRT("recreate sockfd is %d
",sockfd);

            // connect to server
            //有两种方式,一种是用if,子线程可以close(fd)
            //另一种用while,子线程也不可以close(fd),貌似行不通
            //if (-1 == connect(sockfd,(struct sockaddr *)(&servaddr), sizeof(servaddr))) 
            while (-1 == connect(sockfd,(struct sockaddr *)(&servaddr), sizeof(servaddr))) 
            {
                printf("sockfd reconnect error: %s(errno: %d)
",strerror(errno),errno);
                sleep(5);
            }                
        }
    }
    //退出
    printf("ttl2tcp exit!
");
    close(fd); 
    close(sockfd);
    exit(0);
}

3、 tl2tcpsrcMakefile

CC = gcc
CFLAGS = -Wall
OBJS = ttl2tcp.o

all: ttl2tcp

%.o: %.c
    $(CC) $(CFLAGS) -c -o $@ $< 

ttl2tcp: $(OBJS)
    $(CC) -o $@ $(OBJS) -luci -lpthread

clean:
    rm -f rbcfg *.o

4、 ttl2tcpfiles tl2tcp.config

该文件为关于串口和服务器地址及端口的配置文件,在openwrt下的/etc/config文件夹下的ttl2tcp

package ttl2tcp

config serial device
    #option name ttyUSB0
    option name ttyS1
    #option name ttyATH0
    option baudrate 115200
    option data 8
    option parity None
    option stop 1
    option enabled 1
    
config serp probe
    option open 1
    option sendhex 1
    option send A2-E3-45-56-78-92
    option recvhex 1
    option recv A2-E3-45-56-78-9D-35-46-87
    
config server server
    option ipaddr 192.168.6.100
    option port 10001

5、 ttl2tcpfiles ttl2tcp.init

该文件为启动脚本,用于编写Luci时,点击save & Apply之后能够调用到该脚本。

#!/bin/sh /etc/rc.common

START=101

start_service() {
    local section="$1"
    config_get_bool "enabled" "$section" "enabled" '0'
    [ "$enabled" -gt 0 ] || return 1
    ttl2tcp &
    ttl2tcp_pid=$!
    echo "$ttl2tcp_pid" > /tmp/ttl2tcp_temp
    logger -s -t fqrouter ttl2tcp has started $ttl2tcp_pid.
}

start()
{
    config_load ttl2tcp
    config_foreach start_service serial
}

stop()
{
    read ttl2tcp_pid < /tmp/ttl2tcp_temp
    kill -9  $ttl2tcp_pid
    logger -s -t fqrouter ttl2tcp has stoped $ttl2tcp_pid.
}

注意:

(1)、start()中的“serial”对应的是配置文件ttl2tcp中的section(“节”)。

(2)、config_get_bool "enabled" "$section" "enabled" '0' 

这句语句表示如果配置文件中没有enabled这个option,那么久默认表示enabled等于0。

6、 将该文件夹ttl2tcp整个拷贝至barrier_breaker/package/utils文件夹下。

二、Luci配置界面

Luci的整个工作过程大致是这样的:当设置好配置文件后,点击Save & Apply之后,就会调用/etc/config/ucitrack文件,在该文件中就可以找到该配置文件对应的启动脚本/etc/init.d/ttl2tcp。调用该启动脚本的时候会先执行stop函数,接着执行start函数,保证关闭之前的进程,避免重复打开多个同样进程。

1、 新建luci-ttl2tcp文件夹,该文件夹目录下的Makefile文件如下,用于中文翻译

PO = ttl2tcp

include ../../build/config.mk
include ../../build/module.mk

2、 luci-ttl2tcpluasrccontroller ttl2tcp.lua

该文件为注册一个新的Luci模块

--[[
LuCI - Lua Configuration Interface

Copyright 2008 Steven Barth <steven@midlink.org>
Copyright 2008 Jo-Philipp Wich <xm@leipzig.freifunk.net>

Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at

    http://www.apache.org/licenses/LICENSE-2.0

$Id$
]]--

module("luci.controller.ttl2tcp", package.seeall)

function index()
    --entry({"admin", "services", "ttl_client"}, cbi("ttl_client"), _("serial service"),2)  --位置顺序有待考虑    
    entry({"admin", "services", "ttl2tcp"}, cbi("ttl2tcp"), _("Serial Service")) 
end

3、 luci-ttl2tcpluasrcmodelcbi ttl2tcp.lua

该文件为Luci具体页面的实现

--[[
LuCI - Lua Configuration Interface

Copyright 2015 tingpan<dktingpan@sina.cn>

Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
]]--

local fs  = require "nixio.fs"
local sys = require "luci.sys"
local uci = require "luci.model.uci".cursor()

--字符串串接,不能用"",因为这样无法表示空格
local m = Map("ttl2tcp", translate("Serial Service"), 
    translate("This service use default serial configuration include eight data bits, one stop bit, no parity. " ..
        "It use tcp network protocol. The serial probe is used to send one string to serial slave device, when the slave " ..
        "device receive the string, it will return an ack and then begin to print data to serial. " ..
        "Pay attention to don't over 64 bytes in the send and the receive."))
local s = m:section( TypedSection, "serial", translate("Serial Configuration"))
s.addremove = false
s.anonymous = true --不显示配置文件的名字

s:option(Flag, "enabled", translate("Enabled")) 

name = s:option(Value, "name", translate("Serial")) 
local device_suggestions = nixio.fs.glob("/dev/tty[A-Z]*")
if device_suggestions then
    local node
    for node in device_suggestions do
        --name:value(node)
        name:value(string.sub(node,6,string.len(node))) 
    end
end

baudrate = s:option(Value, "baudrate", translate("Baudrate"))
baudrate:value("115200", translate("115200"))
baudrate:value("57600", translate("57600"))
baudrate:value("38400", translate("38400"))
baudrate:value("19200", translate("19200"))
baudrate:value("9600", translate("9600"))
baudrate:value("4800", translate("4800"))
baudrate:value("2400", translate("2400"))
baudrate:value("1200", translate("1200"))
baudrate:value("300", translate("300"))

local s = m:section( TypedSection, "serp", translate("Serial Probe"))
s.addremove = false
s.anonymous = true --不显示配置文件的名字
s:option(Flag, "open", translate("Enabled")) 
s:option(Flag, "sendhex", translate("Send in hex"), translate("if send in hex,the Send should input like this "A2-E3-45-56-78-9D".")) 
s:option(Value, "send", translate("Send")) 
s:option(Flag, "recvhex", translate("Receive in hex"), translate("if receive in hex,the Receive should input like this "A2-E3-45-56-78-9D".")) 
s:option(Value, "recv", translate("Receive")) 

local s = m:section( TypedSection, "server", translate("Servers Configuration"))
s.addremove = false
s.anonymous = true --不显示配置文件的名字
--s.template = "cbi/tblsection" 
s:option(Value, "ipaddr", translate("IPv4 address")) 
s:option(Value, "port", translate("Port")) 

return m

4、 luci-ttl2tcp ootetcuci-defaults luci-ttl2tcp

该文件用于在openwrt中的/etc/config/ucitrack中加入新的关联,只要配置文件变化了,即点击了Save & Apply之后,就会执行/etc/init.d/ttl2tcp脚本文件。

#!/bin/sh

uci -q batch <<-EOF >/dev/null
    delete ucitrack.@ttl2tcp[-1]
    add ucitrack ttl2tcp
    set ucitrack.@ttl2tcp[-1].init=ttl2tcp
    commit ucitrack
EOF

rm -f /tmp/luci-indexcache
exit 0

1、 luci-ttl2tcpipkg postinst

5、 luci-ttl2tcpipkg postinst

#!/bin/sh
[ -n "${IPKG_INSTROOT}" ] || {
    ( . /etc/uci-defaults/luci-ttl2tcp ) && rm -f /etc/uci-defaults/luci-ttl2tcp
    /etc/init.d/ttl2tcp enabled || /etc/init.d/ttl2tcp enable
    exit 0
}

6、 将整个文件夹拷贝至barrier_breaker/feeds/luci/applications文件夹下

7、 进入barrier_breaker/feeds/luci/po/templates,新建ttl2tcp.pot文件

msgid ""
msgstr "Content-Type: text/plain; charset=UTF-8"

msgid "Serial Service"
msgstr ""

msgid ""
"This service use default serial configuration include eight data bits, one stop bit, no parity. "
"It use tcp network protocol. The serial probe is used to send one string to serial slave device, when the slave " 
"device receive the string, it will return an ack and then begin to print data to serial. "
"Pay attention to don't over 64 bytes in the send and the receive."
msgstr ""

msgid "Serial Configuration"
msgstr ""

msgid "Serial"
msgstr ""

msgid "Baudrate"
msgstr ""

msgid "Serial Probe"
msgstr ""

msgid "Send in hex"
msgstr ""

msgid "if send in hex,the Send should input like this "A2-E3-45-56-78-9D"."
msgstr ""

msgid "Send"
msgstr ""

msgid "Receive in hex"
msgstr ""

msgid "if receive in hex,the Receive should input like this "A2-E3-45-56-78-9D"."
msgstr ""

msgid "Receive"
msgstr ""

msgid "Servers Configuration"
msgstr ""

8、 进入barrier_breaker/feeds/luci/po/zh_CN文件夹,新建ttl2tcp.po

msgid "Serial Service"
msgstr "串口服务"

msgid ""
"This service use default serial configuration include eight data bits, one stop bit, no parity. "
"It use tcp network protocol. The serial probe is used to send one string to serial slave device, when the slave " 
"device receive the string, it will return an ack and then begin to print data to serial. "
"Pay attention to don't over 64 bytes in the send and the receive."
msgstr ""
"这个服务使用默认的串口配置,包括8位数据位,1位停止位,没有奇偶较验位。这个服务使用的是tcp的网络协议。串口探测功能是用于发送一串数据到串口从设备,"
"当从设备收到了这样的一串数据,它将反馈回一串数据给主设备,然后从设备的串口才开始打印数据。注意,无论是发送还是接收,数据长度不要超过64个字节。"

msgid "Serial Configuration"
msgstr "串口设置"

msgid "Serial"
msgstr "串口"

msgid "Baudrate"
msgstr "波特率"

msgid "Serial Probe"
msgstr "串口探测"

msgid "Send in hex"
msgstr "以十六进制发送"

msgid "if send in hex,the Send should input like this "A2-E3-45-56-78-9D"."
msgstr "如果以十六进制形式发送,必须输入如下“A2-E3-45-56-78-9D”。"

msgid "Send"
msgstr "发送"

msgid "Receive in hex"
msgstr "以十六进制接收"

msgid "if receive in hex,the Receive should input like this "A2-E3-45-56-78-9D"."
msgstr "如果以十六进制形式接收,必须输入如下“A2-E3-45-56-78-9D”。"

msgid "Receive"
msgstr "接收"

msgid "Servers Configuration"
msgstr "服务器设置"

这里要注意两点:

(1) 如果要翻译的内容比较多,可以写成多行,但要句子结束的引号前面不能有空格;

(2) 含有引号的内容用转义斜杠””,要和lua中的内容对应起来。

9、 在barrier_breaker/feeds/luci/contrib/package/luci-addons的Makefile文件的### Applications ###栏目下,约215行,添加

$(eval $(call application,ttl2tcp,LuCI Support for serial service,ttl2tcp))

三、编译

1、 make menuconfig,在Utils菜单下选中ttl2tcp应用程序,在Luci菜单下的Application子菜单下选中luci-app-ttl2tcp软件包;

2、 make V=s

3、 完成之后就可以重新烧写固件,或者单独安装ttl2tcp和luci-app-ttl2tcp软件包。如果单独安装,会提示缺少一些依赖包,只要根据提示安装对应的依赖包就好。

二、说明

对应luci-ttl2tcp文件夹,如果在编译了一次之后又更改了里面的内容,此时要把更改后的内容拷贝至barrier_breaker/build_dir/target-mipsel_24kec+dsp_uClibc-0.9.33.2/luci-addons所在目录。或者也可以用make clean重新编译。

参考:

Linux下串口通讯 - Linux设备驱动编程总结_Linux编程_Linux公社-Linux系统门户网站

非阻塞connect的实现 - The time is passing - ITeye技术网站

【Linux开发】Linux下的多线程编程 - gnuhpc - 博客园

请教Luci Save&Apply 如何工作的? - OPENWRT专版 - 恩山WIFI论坛 - Powered by Discuz!

源码下载:

http://pan.baidu.com/s/1mgZ1m2W

原文地址:https://www.cnblogs.com/smbx-ztbz/p/5137436.html