CANopenSocket CANopenCommand.c hacking

/*****************************************************************************
 *                  CANopenSocket CANopenCommand.c hacking
 * 说明:
 *     分析一下CANopenSocket中的CANopenCommand部分是怎么工作的。
 *
 *                                          2017-3-23 深圳 南山平山村 曾剑锋
 ****************************************************************************/
/*
 * Client socket command interface for CANopenSocket.
 *
 * @file        CANopenCommand.c
 * @author      Janez Paternoster
 * @copyright   2015 Janez Paternoster
 *
 * This file is part of CANopenNode, an opensource CANopen Stack.
 * Project home page is <https://github.com/CANopenNode/CANopenNode>.
 * For more information on CANopen see <http://www.can-cia.org/>.
 *
 * CANopenNode is free and open source 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, see <http://www.gnu.org/licenses/>.
 */


#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/un.h>
#include <sys/socket.h>


#ifndef BUF_SIZE
#define BUF_SIZE            100000
#endif

/**
 * 错误退出函数
 */
/* Helper functions */
void errExit(char* msg) {
    perror(msg);
    exit(EXIT_FAILURE);
}


/**
 * 是否打印错误描述相关信息标志
 */
static int printErrorDescription = 0;

/**
 * 发送命令函数,fd是socket描述符
 */
static void sendCommand(int fd, char* command, size_t commandLength);


/**
 * 参数使用方法说明
 */
static void printUsage(char *progName) {
fprintf(stderr,
"Usage: %s [options] <command string>\n", progName);
fprintf(stderr,
"\n"
"Program reads arguments or standard input or file. It sends commands to\n"
"canopend via socket, line after line. Result is printed to standard output.\n"
"For more information see http://www.can-cia.org/, CiA 309 standard.\n"
"\n"
"Options:\n"
"  -f <input file path>  Path to the input file.\n"
"  -s <socket path>      Path to the socket (default '/tmp/CO_command_socket').\n"
"  -h                    Display description of error codes in case of error.\n"
"                        (Default, if command is passed by program arguments.)\n"
"  --help                Display this help.\n"
"  --helpall             Display this help, internal and SDO error codes.\n"
"\n"
"Command strings must start with \"[\"<sequence>\"]\" (except if from arguments):\n"
"  - SDO upload:   [[<net>] <node>] r[ead]  <index> <subindex> [<datatype>]\n"
"  - SDO download: [[<net>] <node>] w[rite] <index> <subindex>  <datatype> <value>\n"
"  - Configure SDO time-out: [<net>] set sdo_timeout <value>\n"
"  - Enable SDO block transfer: [<net>] set sdo_block <value>\n"
"  - Set default node: [<net>] set node <value>\n"
"\n"
"  - Start node:                  [[<net>] <node>] start\n"
"  - Stop node:                   [[<net>] <node>] stop\n"
"  - Set node to pre-operational: [[<net>] <node>] preop[erational]\n"
"  - Reset node:                  [[<net>] <node>] reset node\n"
"  - Reset communication:         [[<net>] <node>] reset comm[unication]\n"
"\n"
"Comments started with '#' are ignored. They may be on the beginning of the line\n"
"or after the command string. 'sdo_timeout' is in milliseconds, 500 by default.\n"
"If <node> is not specified within commands, then value defined by 'set node'\n"
"command is used.\n"
"\n"
"\n"
"Datatypes:\n"
"  - b                 - Boolean.\n"
"  - u8, u16, u32, u64 - Unsigned integers.\n"
"  - i8, i16, i32, i64 - Signed integers.\n"
"  - r32, r64          - Real numbers.\n"
"  - t, td             - Time of day, time difference.\n"
"  - vs                - Visible string (between double quotes).\n"
"  - os, us, d         - Octet string, unicode string, domain\n"
"                        (mime-base64 (RFC2045) should be used).\n"
"\n"
"\n"
"Response: \"[\"<sequence>\"]\" \\\n"
"    OK | <value> | ERROR: <SDO-abort-code> | ERROR: <internal-error-code>\n"
"\n"
"\n"
"LICENSE\n"
"    This program is part of CANopenSocket and can be downloaded from:\n"
"    https://github.com/CANopenNode/CANopenSocket\n"
"    Permission is granted to copy, distribute and/or modify this document\n"
"    under the terms of the GNU General Public License, Version 2.\n"
"\n"
);
}


/**
 * 错误描述对应的结构体
 */
/* Extract error description */
typedef struct {
    int code;
    char* desc;
} errorDescs_t;

/**
 * 这里列出所有的canopend返回的错误编号对应的显示的英文
 */
static const errorDescs_t errorDescs[] = {
        {100, "Request not supported."},
        {101, "Syntax error."},
        {102, "Request not processed due to internal state."},
        {103, "Time-out (where applicable)."},
        {104, "No default net set."},
        {105, "No default node set."},
        {106, "Unsupported net."},
        {107, "Unsupported node."},
        {200, "Lost guarding message."},
        {201, "Lost connection."},
        {202, "Heartbeat started."},
        {203, "Heartbeat lost."},
        {204, "Wrong NMT state."},
        {205, "Boot-up."},
        {300, "Error passive."},
        {301, "Bus off."},
        {303, "CAN buffer overflow."},
        {304, "CAN init."},
        {305, "CAN active (at init or start-up)."},
        {400, "PDO already used."},
        {401, "PDO length exceeded."},
        {501, "LSS implementation- / manufacturer-specific error."},
        {502, "LSS node-ID not supported."},
        {503, "LSS bit-rate not supported."},
        {504, "LSS parameter storing failed."},
        {505, "LSS command failed because of media error."},
        {600, "Running out of memory."},
        {0x00000000, "No abort."},
        {0x05030000, "Toggle bit not altered."},
        {0x05040000, "SDO protocol timed out."},
        {0x05040001, "Command specifier not valid or unknown."},
        {0x05040002, "Invalid block size in block mode."},
        {0x05040003, "Invalid sequence number in block mode."},
        {0x05040004, "CRC error (block mode only)."},
        {0x05040005, "Out of memory."},
        {0x06010000, "Unsupported access to an object."},
        {0x06010001, "Attempt to read a write only object."},
        {0x06010002, "Attempt to write a read only object."},
        {0x06020000, "Object does not exist."},
        {0x06040041, "Object cannot be mapped to the PDO."},
        {0x06040042, "Number and length of object to be mapped exceeds PDO length."},
        {0x06040043, "General parameter incompatibility reasons."},
        {0x06040047, "General internal incompatibility in device."},
        {0x06060000, "Access failed due to hardware error."},
        {0x06070010, "Data type does not match, length of service parameter does not match."},
        {0x06070012, "Data type does not match, length of service parameter too high."},
        {0x06070013, "Data type does not match, length of service parameter too short."},
        {0x06090011, "Sub index does not exist."},
        {0x06090030, "Invalid value for parameter (download only)."},
        {0x06090031, "Value range of parameter written too high."},
        {0x06090032, "Value range of parameter written too low."},
        {0x06090036, "Maximum value is less than minimum value."},
        {0x060A0023, "Resource not available: SDO connection."},
        {0x08000000, "General error."},
        {0x08000020, "Data cannot be transferred or stored to application."},
        {0x08000021, "Data cannot be transferred or stored to application because of local control."},
        {0x08000022, "Data cannot be transferred or stored to application because of present device state."},
        {0x08000023, "Object dictionary not present or dynamic generation fails."},
        {0x08000024, "No data available."}
};

/**
 * 打印所有的错误描述文档内容,有一部分是十进制的,有一部分是十六进制的,注意for循环中的if判断和上面结构体数组中的数值就能体现出来了
 */
static void printErrorDescs(void) {
    int i, len;

    len = sizeof(errorDescs) / sizeof(errorDescs_t);

    fprintf(stderr, "Internal error codes:\n");

    for(i=0; i<len; i++) {
        const errorDescs_t *ed = &errorDescs[i];

        if(ed->code == 0) break;
        fprintf(stderr, "  - %d - %s\n", ed->code, ed->desc);
    }

    fprintf(stderr, "\n");
    fprintf(stderr, "SDO abort codes:\n");

    for(; i<len; i++) {
        const errorDescs_t *ed = &errorDescs[i];

        fprintf(stderr, "  - 0x%08X - %s\n", ed->code, ed->desc);
    }

    fprintf(stderr, "\n");
}


/******************************************************************************/
int main (int argc, char *argv[]) {
    char *socketPath = "/tmp/CO_command_socket";  /* Name of the local domain socket, configurable by arguments. */
    char *inputFilePath = NULL;

    char buf[BUF_SIZE];
    int fd;
    struct sockaddr_un addr;
    int opt;
    int i;

    /**
     * 输出基本帮助信息
     */
    if(argc >= 2 && strcmp(argv[1], "--help") == 0) {
        printUsage(argv[0]);
        exit(EXIT_SUCCESS);
    }
    /**
     * 输出基本帮助信息和返回的错误码信息
     */
    if(argc >= 2 && strcmp(argv[1], "--helpall") == 0) {
        printUsage(argv[0]);
        printErrorDescs();
        exit(EXIT_SUCCESS);
    }

    /* Get program options */
    /**
     * 获取程序运行时命令行传递过来的参数,并解析
     */
    while((opt = getopt(argc, argv, "s:f:h")) != -1) {
        switch (opt) {
            case 'f':       // 将命令放置于文件中,通过读取文件并发送给canopend处理
                inputFilePath = optarg;
                break;
            case 's':       // 指定canopend的socket的path在哪里
                socketPath = optarg;
                break;
            case 'h':       // 输出错误描述
                printErrorDescription = 1;
                break;
            default:
                printUsage(argv[0]);
                exit(EXIT_FAILURE);
        }
    }

    /* Create and connect client socket */
    /**
     * 创建本地客户端socket
     */
    fd = socket(AF_UNIX, SOCK_STREAM, 0);
    if(fd == -1) {
        errExit("Socket creation failed");
    }

    /**
     * 设置本地socket
     */
    memset(&addr, 0, sizeof(struct sockaddr_un));
    addr.sun_family = AF_UNIX;
    strncpy(addr.sun_path, socketPath, sizeof(addr.sun_path) - 1);

    /**
     * 链接本地socket
     */
    if(connect(fd, (struct sockaddr *)&addr, sizeof(struct sockaddr_un)) == -1) {
        errExit("Socket connection failed");
    }


    /* get commands from input file, line after line */
    /**
     * 从文件中获取命令,这里不进行任何处理,直接全部传送,仅仅是将文件中的内容完整转给canopend处理
     */
    if(inputFilePath != NULL) {
        FILE *fp = fopen(inputFilePath, "r");
        if(fp == NULL) {
            errExit("Can't open input file");
        }

        while(fgets(buf, BUF_SIZE, fp) != NULL) {
            sendCommand(fd, buf, strlen(buf));
        }

        fclose(fp);
    }

    /* get command from arguments */
    /**
     * 从命令行的参数中获取命令
     */
    else if(optind < argc) {
        buf[0] = 0;
        size_t buflen = 0;

        /* Add sequence number if not present on command line arguments */
        /**
         * 如果CAN设备的序号没有提供,那么就是用默认的1号设备(can1)设备
         */
        if(argv[optind][0] != '[') {
            strcat(buf, "[1] ");
        }

        /**
         * 将命令行参数合成命令
         */
        for(i=optind; i<argc; i++) {
            strncat(buf, argv[i], (BUF_SIZE - 2) - buflen);
            strcat(buf, " ");
            buflen = strlen(buf);
            if(buflen >= (BUF_SIZE - 1)) {
                fprintf(stderr, "String too long!\n");
                exit(EXIT_FAILURE);
            }
        }
        /**
         * 每一条命令都是使用'\n'结束,表示一行
         */
        buf[buflen - 1] = '\n'; /* replace last space with newline */

        /**
         * 将合成的命令发送给canopend
         */
        printErrorDescription = 1;
        sendCommand(fd, buf, buflen);
    }

    /* get commands from stdin, line after line */
    /**
     * 从终端的标准输入中一行一行的获取命令
     */
    else {
        while(fgets(buf, BUF_SIZE, stdin) != NULL) {
            sendCommand(fd, buf, strlen(buf));
        }
    }

    close(fd);

    exit(EXIT_SUCCESS);
}


static void sendCommand(int fd, char* command, size_t commandLength) {
    size_t n;
    char buf[BUF_SIZE];

    /**
     * 发送命令
     */
    if (write(fd, command, commandLength) != commandLength) {
        errExit("Socket write failed");
    }

    /**
     * 接收命令返回信息
     */
    n = read(fd, buf, sizeof(buf));

    if(n == -1) {
        errExit("Socket read failed");
    }

    /**
     * 是否处理返回的信息
     */
    if(printErrorDescription == 1) {
        char *errLoc = strstr(buf, "ERROR:");
        char *endLoc = strstr(buf, "\r\n");

        if(errLoc != NULL && endLoc != NULL) {
            int num;
            char *sRet = NULL;

            errLoc += 6;

            num = strtol(errLoc, &sRet, 0);
            if(strlen(errLoc) != 0 && sRet == strchr(errLoc, '\r')) {
                int i, len;

                len = sizeof(errorDescs) / sizeof(errorDescs_t);

                /**
                 * 查找并输出信息
                 */
                for(i=0; i<len; i++) {
                    const errorDescs_t *ed = &errorDescs[i];
                    if(ed->code == num) {
                        sprintf(endLoc, " - %s\r\n", ed->desc);
                        break;
                    }
                }
            }
        }
    }

    /**
     * 输出返回原始信息
     */
    printf("%s", buf);
}

 

posted on 2017-03-23 10:37  zengjf  阅读(1144)  评论(1编辑  收藏  举报

导航