OpenWrt – serialoverip的自启动脚本

serialoverip功能与ser2net相似,都能实现IP端口与串口间的数据转发,但不同点在于:

1. 官方发布的ser2net只支持TCP协议转发,增加UDP需要额外加补丁(详见:http://sourceforge.net/p/ser2net/patches/12/)。
2. serialoverip支持且只支持UDP协议转发,但也因此使得其程序非常小巧。后有我注释、替换变量名后的源代码,对此类软件的原理分析很有帮助。

在安装serialoverip后,尝试加入其启动命令到rc.local中,但发现开机并未成功启动serialoverip。几番分析,是由于执行rc.local中语句时OpenWrt的IP分配还未完成(即serialoverip无法绑定192.168.1.1端口),造成启动失败。

由于serialoverip源代码对此情况没有做考虑,所以笔者决定使用sleep来固定等待,修改了rc.local中命令:

sleep 10s && serialoverip -s 192.168.1.1 10002 -d /dev/ttyATH0 9600-8N1 &

先等待10秒再打开192.168.1.1:10002上UDP端口与/dev/ttyATH0串口的转发(加&让其后台运行)。

上例方法基本解决了serialoverip无法启动的问题,但是灵活性还是较低。苦苦搜索了sh命令规范后,本吊探索出一个更灵活的脚本(由于Linux虚拟机没装输入法,所以用英文注解了):

# Start data forwarding between UDP and Serial.
# If an instance is already running, then exit.
# The starting timeout is 30s.
timeout=30
while [ $timeout -ne 0 ]
do
    if [ `ps | grep serialoverip | wc -l` -eq 2 ]
    then
        break
    fi
    if [ `ifconfig br-lan | grep 192.168.1.1 | wc -l` -eq 1 ]
    then
        serialoverip -s 192.168.1.1 10002 -d /dev/ttyATH0 9600-8N1 &
        break
    fi
    let timeout=timeout-1
    sleep 1s
done

以上代码功能,检测是否已经完成IP分配,一旦完成分配则启动serialoverip并退出,否则每隔1秒再检测,直到30秒时间全部走完(当然你也可以改个更高的timeout初始值,不过我觉得30秒够长了)。

加到rc.local的exit 0之前就行。当然为了提高rc.local的可读性,还可以将以上代码连同exit 0写入到新的文件(比如udp2serial),放在/etc目录(或其他目录),之后在rc.local的exit 0前加入:

sh /etc/udp2serial

即当执行rc.local时会再去执行/etc下的udp2serial。由于这种方法最坏情况下会造成阻塞,故建议是放在exit 0之前,保证其他命令都执行后再处理(上面的例子均是放在exit 0前)。

以下是我使用的方案

/etc/rc.local文件末尾:

sh /etc/udp2serial

exit 0

/etc/udp2serial文件:

# Start data forwarding between UDP and Serial.
# If an instance is already running, then exit.
# The starting timeout is 30s.
timeout=30
while [ $timeout -ne 0 ]
do
    if [ `ps | grep serialoverip | wc -l` -eq 2 ]
    then
        break
    fi
    if [ `ifconfig br-lan | grep 192.168.1.1 | wc -l` -eq 1 ]
    then
        serialoverip -s 192.168.1.1 10002 -d /dev/ttyATH0 9600-8N1 &
        break
    fi
    let timeout=timeout-1
    sleep 1s
done

exit 0

欢迎跟我讨论更加有效的解决方法:)
我的邮箱:imjgz@qq.com
可以从http://sourceforge.net/projects/serialoverip/获取源代码。
这是serialoverip的源代码,我进行了变量替换和部分的注释(由于Linux虚拟机没装输入法,所以用英文注解了):

/*
* —————————————————————————-
* serialoverip
* Utility for transport of serial interfaces over UDP/IP
* Copyright (C) 2002 Stefan-Florin Nicola <sten@fx.ro>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place – Suite 330, Boston, MA 02111-1307, USA
*
*/

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <errno.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <fcntl.h>
#include <termios.h>
#include <signal.h>

#define MAXMESG 2048

char *programName;
int source[2], sourceType[2];

// Display the help.
void PrintHelp()
{
fprintf(stderr,"\
SerialOverIP version 1.0, Copyright (C) 2002 Stefan-Florin Nicola <sten@fx.ro>
SerialOverIP comes with ABSOLUTELY NO WARRANTY. This is free software, and you
are welcome to redistribute it under GNU General Public License.
Usage: %s <source1> <source2>
where <source1> and <source2> are one of the folowing:
-s <IP> <port>                 UDP server on IP:port
-c <IP> <port>                 UDP client for server IP:port
-d <device> sss-dps            local serial device
sss is speed (50,..,230400)
d is data bits (5,6,7,8)
p is parity type (N,E,O)
s is stop bits (1,2)
",programName);
return;
}

// Set the serial terminal and return the result.
int SetSerial(int source, struct termios *cfg, int baudRate, int dataBits, unsigned char parityType, int stopBits)
{
    // Reset the serial terminal.
    cfmakeraw(cfg);
    // Set the serial baud rate.
    switch (baudRate)
    {
    case 50:
    {
        cfsetispeed(cfg, B50);
        cfsetospeed(cfg, B50);
        break;
    }
    case 75:
    {
        cfsetispeed(cfg, B75);
        cfsetospeed(cfg, B75);
        break;
    }
    case 110:
    {
        cfsetispeed(cfg, B110);
        cfsetospeed(cfg, B110);
        break;
    }
    case 134:
    {
        cfsetispeed(cfg, B134);
        cfsetospeed(cfg, B134);
        break;
    }
    case 150:
    {
        cfsetispeed(cfg, B150);
        cfsetospeed(cfg, B150);
        break;
    }
    case 200:
    {
        cfsetispeed(cfg, B200);
        cfsetospeed(cfg, B200);
        break;
    }
    case 300:
    {
        cfsetispeed(cfg, B300);
        cfsetospeed(cfg, B300);
        break;
    }
    case 600:
    {
        cfsetispeed(cfg, B600);
        cfsetospeed(cfg, B600);
        break;
    }
    case 1200:
    {
        cfsetispeed(cfg, B1200);
        cfsetospeed(cfg, B1200);
        break;
    }
    case 1800:
    {
        cfsetispeed(cfg, B1800);
        cfsetospeed(cfg, B1800);
        break;
    }
    case 2400:
    {
        cfsetispeed(cfg, B2400);
        cfsetospeed(cfg, B2400);
        break;
    }
    case 4800:
    {
        cfsetispeed(cfg, B4800);
        cfsetospeed(cfg, B4800);
        break;
    }
    case 9600:
    {
        cfsetispeed(cfg, B9600);
        cfsetospeed(cfg, B9600);
        break;
    }
    case 19200:
    {
        cfsetispeed(cfg, B19200);
        cfsetospeed(cfg, B19200);
        break;
    }
    case 38400:
    {
        cfsetispeed(cfg, B38400);
        cfsetospeed(cfg, B38400);
        break;
    }
    case 57600:
    {
        cfsetispeed(cfg, B57600);
        cfsetospeed(cfg, B57600);
        break;
    }
    case 115200:
    {
        cfsetispeed(cfg, B115200);
        cfsetospeed(cfg, B115200);
        break;
    }
    case 230400:
    {
        cfsetispeed(cfg, B230400);
        cfsetospeed(cfg, B230400);
        break;
    }
    }
    // Set the parity type.
    switch (parityType | 32)
    {
    case 'n':
    {
        cfg->c_cflag &= ~PARENB;
        break;
    }
    case 'e':
    {
        cfg->c_cflag |= PARENB;
        cfg->c_cflag &= ~PARODD;
        break;
    }
    case 'o':
    {
        cfg->c_cflag |= PARENB;
        cfg->c_cflag |= PARODD;
        break;
    }
    }
    cfg->c_cflag &= ~CSIZE;
    // Set the data bits.
    switch (dataBits)
    {
    case 5:
    {
        cfg->c_cflag |= CS5;
        break;
    }
    case 6:
    {
        cfg->c_cflag |= CS6;
        break;
    }
    case 7:
    {
        cfg->c_cflag |= CS7;
        break;
    }
    case 8:
    {
        cfg->c_cflag |= CS8;
        break;
    }
    }
    // Set the stop bits.
    if (stopBits == 1)
        cfg->c_cflag &= ~CSTOPB;
    else
        cfg->c_cflag |= CSTOPB;
    // Set the serial terminal and return.
    return tcsetattr(source, TCSANOW, cfg);
}

// Close serial terminal then exit the program.
void ExitProgram(int x)
{
    if (sourceType[0] & 2)
    {
        tcflush(source[0], TCIOFLUSH);
        close(source[0]);
    }
    if (sourceType[1] & 2)
    {
        tcflush(source[1], TCIOFLUSH);
        close(source[1]);
    }
    printf("%s exiting.\n", programName);
    exit(1);
}

int main(int argc, char **argv)
{
    int i, n, w, clen[2], nonblock[2], baudRate, dataBits, stopBits;
    unsigned char c, buf[MAXMESG], *dataPointer, parityType;
    struct termios cfg;
    struct sockaddr_in addr[4][4];
    struct sigaction newact, oldact;

    programName = argv[0];
    if (argc != 7)
    {
        PrintHelp();
        return 1;
    }
    for (i = 0; i < 2; i++)
    {
        sourceType[i] = 0;
        switch (argv[3 * i + 1][1])
        {
        case 's':
            sourceType[i] = 1;
        case 'c':
            bzero((char *)&(addr[i][0]), sizeof(addr[i][0]));
            addr[i][0].sin_family = AF_INET;
            addr[i][0].sin_addr.s_addr = inet_addr(argv[3 * i + 2]);
            addr[i][0].sin_port = htons(atoi(argv[3 * i + 3]));
            bzero((char *)&(addr[i][1]), sizeof(addr[i][1]));
            addr[i][1].sin_family = AF_INET;
            addr[i][1].sin_addr.s_addr = 0;
            addr[i][1].sin_port = htons(0);
            if ((source[i] = socket(AF_INET, SOCK_DGRAM, 0)) < 0)
            {
                fprintf(stderr, "%s: can't open datagram socket", programName);
                return 3;
            }
            if (bind(source[i], (struct sockaddr *)&addr[i][!sourceType[i]], sizeof(addr[i][!sourceType[i]])) < 0)
            {
                fprintf(stderr, "%s: can't bind local address", programName);
                return 4;
            }
            break;
        case 'd':
            sourceType[i] = 2;
            if ((source[i] = open(argv[3 * i + 2], O_RDWR | O_NDELAY)) < 0)
            {
                fprintf(stderr, "%s: could not open device %s\n",
                        programName, argv[3 * i + 2]);
                return -1;
            }
            n = sscanf(argv[3 * i + 3], "%d-%d%c%d", &baudRate, &dataBits, &parityType, &stopBits);
            if (n < 4)
            {
                fprintf(stderr, "%s: invalid argument %1d from %s\n",
                        programName, read + 1, argv[3 * i + 3]);
                return 3;
            }
            if (SetSerial(source[i], &cfg, baudRate, dataBits, parityType, stopBits) < 0)
            {
                fprintf(stderr, "%s: could not initialize device %s\n",
                        programName, argv[3 * i + 2]);
                return 7;
            }
            break;
        default:
            PrintHelp();
            return 2;
        }
        clen[i] = sizeof(addr[i][1]);
        nonblock[i] = !(sourceType[i] & 1);
    }
    // Let ExitProgram function handle the SIGINT message(Generated by Ctrl+C).
    signal(SIGINT, ExitProgram);
    i = 0;
    // Keep data forwarding until SIGINT message occurred.
    while (1)
    {
        if (sourceType[i] & 2)
            n = read(source[i], buf, MAXMESG);
        else
        {
            n = recvfrom(source[i], buf, MAXMESG, nonblock[i] * MSG_DONTWAIT,
                         (struct sockaddr *)&addr[i][sourceType[i]], &clen[i]);
            nonblock[i] = 1;
        }
        dataPointer = buf;
        while (n > 0)
        {
            if (sourceType[!i] & 2)
                w = write(source[!i], dataPointer, n);
            else
                w = sendto(source[!i], dataPointer, n, 0,
                           (struct sockaddr *)&addr[!i][sourceType[!i]], clen[!i]);
            if (w > 0)
            {
                n -= w;
                dataPointer += w;
            }
            else
            {
                fprintf(stderr, "%s: write error\n", programName);
                break;
            }
        }
        i = !i;
    }
    return 0;
}