560 lines
19 KiB
C
Executable File
560 lines
19 KiB
C
Executable File
/*
|
|
* Client socket command interface for CANopenSocket.
|
|
*
|
|
* @file cocomm.c
|
|
* @author Janez Paternoster
|
|
* @copyright 2020 Janez Paternoster
|
|
*
|
|
* This file is part of CANopenSocket, a Linux implementation of CANopen
|
|
* stack with master functionality. Project home page is
|
|
* <https://github.com/CANopenNode/CANopenSocket>. CANopenSocket is based
|
|
* on CANopenNode: <https://github.com/CANopenNode/CANopenNode>.
|
|
*
|
|
* 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
|
|
*
|
|
* Unless required by applicable law or agreed to in writing, software
|
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
* See the License for the specific language governing permissions and
|
|
* limitations under the License.
|
|
*/
|
|
|
|
#include <stdio.h>
|
|
#include <stdlib.h>
|
|
#include <stdint.h>
|
|
#include <unistd.h>
|
|
#include <bits/getopt_core.h>
|
|
#include <string.h>
|
|
#include <netdb.h>
|
|
#include <limits.h>
|
|
#include <net/if.h>
|
|
#include <sys/types.h>
|
|
#include <sys/socket.h>
|
|
#include <sys/un.h>
|
|
#include <linux/can.h>
|
|
#include <signal.h>
|
|
|
|
#ifndef BUF_SIZE
|
|
#define BUF_SIZE 1000
|
|
#endif
|
|
#define BUF_LAG 100 /* max size of error response */
|
|
|
|
/* colors and stream for printing status */
|
|
char *greenC, *redC, *resetC;
|
|
FILE *errStream;
|
|
|
|
|
|
static void printUsage(char *progName) {
|
|
fprintf(errStream,
|
|
"Usage: %s [options] ['<command string>' ['<command string>'] ...]\n"
|
|
"\n"
|
|
"Program reads CANopen gateway command strings from arguments, standard input or\n"
|
|
"file. It sends commands to canopend via socket, line after line. Response status\n"
|
|
"is printed to standard error and response data (for example, value from write\n"
|
|
"command) is printed to standard output. Command strings from arguments must\n"
|
|
"be quoted. Socket is either unix domain socket (default) or a remote tcp socket\n"
|
|
"(option -t). For more information see http://www.can-cia.org/, CiA 309 standard.\n"
|
|
"\n"
|
|
"Options:\n"
|
|
" -f <input file> Path to the input file.\n"
|
|
" -s <socket path> Path to the unix socket. If not specified, path is obtained\n"
|
|
" from environmental variable, configured with:\n"
|
|
" 'export cocomm_socket=<socket path>'. If latter is not\n"
|
|
" specified, default value is used: '/tmp/CO_command_socket'.\n"
|
|
" -t <host> Connect via tcp to remote <host>. Set also with\n"
|
|
" 'export cocomm_host=<host>'. Unix socket is used by default.\n"
|
|
" -p <port> Tcp port to connect to when using -t. Set also with\n"
|
|
" 'export cocomm_port=<port>'. Default is 60000.\n"
|
|
" -i If set, then standard input will be read after each command\n"
|
|
" string from arguments. Useful with write commands.\n"
|
|
" -o all|data|flat By defult (setting 'all') outupt is split to colored stderr\n"
|
|
" and stdout. 'data' prints data only to stdout. 'flat' prints\n"
|
|
" all to stdout, set also with 'export cocomm_flat=<0|1>'.\n"
|
|
" -d <can device> If specified, then candump of specified CAN device will be\n"
|
|
" printed after the command response. Set also with\n"
|
|
" 'export cocomm_candump=<can device>'. Not used by default.\n"
|
|
" -n <count> Print <count> of candump messages, then exit. Set also with\n"
|
|
" 'export cocomm_candump_count=<count>'. Default is 10.\n"
|
|
" -T <msec> Exit candump after <msec> without reception. Set also with\n"
|
|
" 'export cocomm_candump_timeout=<msec>'. Default is 1000.\n"
|
|
" --help Display this help.\n"
|
|
"\n"
|
|
"For help on command strings type '%s \"help\"'.\n"
|
|
"\n"
|
|
"See also: https://github.com/CANopenNode/CANopenSocket\n"
|
|
"\n", progName, progName);
|
|
}
|
|
|
|
/* print reply, status to errStream (red or green), value to stdout */
|
|
static int printReply(int fd_gtw) {
|
|
char* replyBuf = malloc(BUF_SIZE + BUF_LAG + 1);
|
|
size_t count = 0; /* count of bytes in replyBuf */
|
|
int firstPass = 1;
|
|
int ret = EXIT_SUCCESS;
|
|
|
|
if(replyBuf == NULL) {
|
|
perror("replyBuf malloc");
|
|
exit(EXIT_FAILURE);
|
|
}
|
|
|
|
for (;;) {
|
|
ssize_t nRead = read(fd_gtw, &replyBuf[count], BUF_SIZE); /* blocking*/
|
|
|
|
if(nRead > 0) {
|
|
count += nRead;
|
|
replyBuf[count] = 0;
|
|
|
|
if (firstPass == 1) {
|
|
/* check for response type. Only response value goes to stdout*/
|
|
if (strstr(replyBuf, "] ERROR:") != NULL
|
|
&& strstr(replyBuf, "\r\n") != NULL
|
|
) {
|
|
fprintf(errStream, "%s%s%s", redC, replyBuf, resetC);
|
|
ret = EXIT_FAILURE;
|
|
break;
|
|
}
|
|
else if (strstr(replyBuf, "] OK\r\n") != NULL) {
|
|
fprintf(errStream, "%s%s%s", greenC, replyBuf, resetC);
|
|
break;
|
|
}
|
|
else {
|
|
char *replyBufTrimmed = replyBuf;
|
|
char *seq = strstr(replyBuf, "] ");
|
|
char *end = strstr(replyBuf, "\r\n");
|
|
if (seq != NULL && (size_t)(seq - replyBuf) < 15) {
|
|
replyBufTrimmed = seq + 2;
|
|
seq[1] = 0;
|
|
count -= strlen(replyBuf) + 1;
|
|
fprintf(errStream, "%s%s%s ", greenC, replyBuf, resetC);
|
|
}
|
|
if (end != NULL) {
|
|
end[0] = 0;
|
|
fputs(replyBufTrimmed, stdout);
|
|
fflush(stdout);
|
|
fputs("\r\n", errStream);
|
|
break;
|
|
}
|
|
else {
|
|
/* print replyBuf to stdout, except last BUF_LAG bytes.
|
|
* move them to the beginning of the replyBuf */
|
|
if (count > BUF_LAG) {
|
|
size_t nWrite = count - BUF_LAG;
|
|
fwrite(replyBufTrimmed, 1, nWrite, stdout);
|
|
replyBufTrimmed += nWrite;
|
|
count = BUF_LAG;
|
|
}
|
|
memmove(replyBuf, replyBufTrimmed, count);
|
|
}
|
|
}
|
|
firstPass = 0;
|
|
}
|
|
else {
|
|
char *end = strstr(replyBuf, "\r\n");
|
|
if (end != NULL) {
|
|
char *errResp = strstr(replyBuf, "\n...ERROR:0x");
|
|
if (errResp != NULL) {
|
|
errResp[0] = 0;
|
|
fputs(replyBuf, stdout);
|
|
fflush(stdout);
|
|
fprintf(errStream, "\n%s%s%s", redC, &errResp[1], resetC);
|
|
ret = EXIT_FAILURE;
|
|
}
|
|
else {
|
|
end[0] = 0;
|
|
fputs(replyBuf, stdout);
|
|
fflush(stdout);
|
|
fprintf(errStream, "\n%s...success%s\r\n", greenC, resetC);
|
|
}
|
|
break;
|
|
}
|
|
/* print replyBuf to stdout, except last BUF_LAG bytes.
|
|
* move them to the beginning of the replyBuf */
|
|
if (count > BUF_LAG) {
|
|
size_t nWrite = count - BUF_LAG;
|
|
fwrite(replyBuf, 1, nWrite, stdout);
|
|
count = BUF_LAG;
|
|
memmove(replyBuf, &replyBuf[nWrite], count);
|
|
}
|
|
}
|
|
}
|
|
else if (nRead == 0) {
|
|
fprintf(errStream, "%sError, zero response%s\n", redC, resetC);
|
|
ret = EXIT_FAILURE;
|
|
break;
|
|
}
|
|
else {
|
|
perror("Socket read failed");
|
|
free(replyBuf);
|
|
exit(EXIT_FAILURE);
|
|
}
|
|
fflush(stdout);
|
|
}
|
|
free(replyBuf);
|
|
|
|
return ret;
|
|
}
|
|
|
|
|
|
/******************************************************************************/
|
|
int main (int argc, char *argv[]) {
|
|
/* configurable options */
|
|
enum {out_all, out_data, out_flat} outputType = out_all;
|
|
char *inputFilePath = NULL;
|
|
char *socketPath = "/tmp/CO_command_socket"; /* Name of the local domain socket */
|
|
char hostname[HOST_NAME_MAX]; /* name of the remote TCP host */
|
|
char tcpPort[20] = "60000"; /* default port when used in tcp mode */
|
|
int additionalReadStdin = 0;
|
|
char *candump = NULL;
|
|
long candumpCount = 10;
|
|
long candumpTmo = 1000;
|
|
|
|
char* commBuf;
|
|
int fd_gtw;
|
|
int fd_candump;
|
|
int opt;
|
|
struct sockaddr_un addr_un;
|
|
sa_family_t addrFamily = AF_UNIX;
|
|
errStream = stderr;
|
|
|
|
if(argc >= 2 && strcmp(argv[1], "--help") == 0) {
|
|
printUsage(argv[0]);
|
|
exit(EXIT_SUCCESS);
|
|
}
|
|
|
|
/* Get program options from environment variables */
|
|
char *env;
|
|
if ((env = getenv("cocomm_host")) != NULL) {
|
|
strncpy(hostname, env, sizeof(hostname));
|
|
addrFamily = AF_INET;
|
|
}
|
|
if ((env = getenv("cocomm_port")) != NULL) {
|
|
strncpy(tcpPort, env, sizeof(tcpPort));
|
|
}
|
|
if ((env = getenv("cocomm_socket")) != NULL) {
|
|
socketPath = env;
|
|
addrFamily = AF_UNIX;
|
|
}
|
|
if ((env = getenv("cocomm_flat")) != NULL) {
|
|
if (strcmp(env, "1") == 0) {
|
|
outputType = out_flat;
|
|
}
|
|
}
|
|
if ((env = getenv("cocomm_candump")) != NULL) {
|
|
candump = env;
|
|
}
|
|
if ((env = getenv("cocomm_candump_count")) != NULL) {
|
|
candumpCount = atol(env);
|
|
}
|
|
if ((env = getenv("cocomm_candump_timeout")) != NULL) {
|
|
candumpTmo = atol(env);
|
|
}
|
|
|
|
/* Get program options from arguments */
|
|
while((opt = getopt(argc, argv, "f:s:t:p:io:d:n:T:")) != -1) {
|
|
switch (opt) {
|
|
case 'f':
|
|
inputFilePath = optarg;
|
|
break;
|
|
case 's':
|
|
addrFamily = AF_UNIX;
|
|
socketPath = optarg;
|
|
break;
|
|
case 't':
|
|
addrFamily = AF_INET;
|
|
strncpy(hostname, optarg, sizeof(hostname));
|
|
break;
|
|
case 'p':
|
|
strncpy(tcpPort, optarg, sizeof(tcpPort));
|
|
break;
|
|
case 'i':
|
|
additionalReadStdin = 1;
|
|
break;
|
|
case 'o':
|
|
if (strcmp(optarg, "data") == 0)
|
|
outputType = out_data;
|
|
else if (strcmp(optarg, "flat") == 0)
|
|
outputType = out_flat;
|
|
break;
|
|
case 'd':
|
|
candump = optarg;
|
|
break;
|
|
case 'n':
|
|
candumpCount = atol(optarg);
|
|
break;
|
|
case 'T':
|
|
candumpTmo = atol(optarg);
|
|
break;
|
|
default:
|
|
printUsage(argv[0]);
|
|
exit(EXIT_FAILURE);
|
|
}
|
|
}
|
|
|
|
switch (outputType) {
|
|
default:
|
|
case out_all:
|
|
greenC = "\033[32m";
|
|
redC = "\033[31m";
|
|
resetC = "\033[0m";
|
|
errStream = stderr;
|
|
break;
|
|
case out_data:
|
|
greenC = redC = resetC = "";
|
|
errStream = fopen("/dev/null", "w+");
|
|
break;
|
|
case out_flat:
|
|
greenC = redC = resetC = "";
|
|
errStream = stdout;
|
|
break;
|
|
}
|
|
|
|
/* Ignore the SIGPIPE signal, which may happen, if gateway broke
|
|
* the connection. Program will exit with error message anyway.
|
|
* Signal may be triggered by socket write call. */
|
|
if (signal(SIGPIPE, SIG_IGN) == SIG_ERR) {
|
|
perror("signal");
|
|
exit(EXIT_FAILURE);
|
|
}
|
|
|
|
/* Create and connect client socket */
|
|
if(addrFamily == AF_INET) {
|
|
|
|
struct addrinfo hints, *res, *rp;
|
|
int errcode;
|
|
|
|
memset(&hints, 0, sizeof(hints));
|
|
hints.ai_family = AF_INET;
|
|
hints.ai_socktype = SOCK_STREAM;
|
|
hints.ai_flags |= AI_CANONNAME;
|
|
|
|
errcode = getaddrinfo(hostname, tcpPort, &hints, &res);
|
|
if (errcode != 0) {
|
|
fprintf(stderr, "Error! Getaddrinfo for host %s failed\n", hostname);
|
|
exit(EXIT_FAILURE);
|
|
}
|
|
|
|
/* getaddrinfo() returns a list of address structures. Try each address
|
|
* until we successfully connect. If socket (or connect) fails,
|
|
* we (close the socket and) try the next address. */
|
|
|
|
for (rp = res; rp != NULL; rp = rp->ai_next) {
|
|
fd_gtw = socket(rp->ai_family, rp->ai_socktype, rp->ai_protocol);
|
|
if (fd_gtw == -1) {
|
|
continue;
|
|
}
|
|
|
|
if (connect(fd_gtw, rp->ai_addr, rp->ai_addrlen) != -1) {
|
|
break; /* Success */
|
|
}
|
|
|
|
close(fd_gtw);
|
|
perror("Socket connection failed");
|
|
exit(EXIT_FAILURE);
|
|
}
|
|
}
|
|
else { // addrFamily == AF_UNIX
|
|
fd_gtw = socket(AF_UNIX, SOCK_STREAM, 0);
|
|
if(fd_gtw == -1) {
|
|
perror("Socket creation failed");
|
|
exit(EXIT_FAILURE);
|
|
}
|
|
|
|
memset(&addr_un, 0, sizeof(struct sockaddr_un));
|
|
addr_un.sun_family = addrFamily;
|
|
strncpy(addr_un.sun_path, socketPath, sizeof(addr_un.sun_path) - 1);
|
|
|
|
if(connect(fd_gtw, (struct sockaddr *)&addr_un, sizeof(struct sockaddr_un)) == -1) {
|
|
fprintf(stderr, "Socket connection failed \"%s\": ", socketPath);
|
|
perror(NULL);
|
|
exit(EXIT_FAILURE);
|
|
}
|
|
}
|
|
|
|
/* Prepare candump */
|
|
if (candump != NULL && candumpCount > 0 && candumpTmo > 0) {
|
|
struct sockaddr_can sockAddr;
|
|
|
|
fd_candump = socket(PF_CAN, SOCK_RAW, CAN_RAW);
|
|
if (fd_candump < 0) {
|
|
perror("CAN socket creation failed");
|
|
exit(EXIT_FAILURE);
|
|
}
|
|
|
|
struct timeval tv;
|
|
tv.tv_sec = candumpTmo / 1000;
|
|
tv.tv_usec = (candumpTmo % 1000) * 1000;
|
|
setsockopt(fd_candump, SOL_SOCKET, SO_RCVTIMEO, (const char*)&tv, sizeof tv);
|
|
|
|
memset(&sockAddr, 0, sizeof(sockAddr));
|
|
sockAddr.can_family = AF_CAN;
|
|
sockAddr.can_ifindex = if_nametoindex(candump);
|
|
int ret = bind(fd_candump, (struct sockaddr*)&sockAddr, sizeof(sockAddr));
|
|
if (ret < 0) {
|
|
fprintf(stderr, "CAN Socket binding failed \"%s\": ", candump);
|
|
perror(NULL);
|
|
exit(EXIT_FAILURE);
|
|
}
|
|
}
|
|
else {
|
|
candumpCount = 0;
|
|
}
|
|
|
|
/* get commands from input file, line after line */
|
|
int ret = EXIT_SUCCESS;
|
|
|
|
commBuf = malloc(BUF_SIZE);
|
|
if(commBuf == NULL) {
|
|
perror("commBuf malloc");
|
|
exit(EXIT_FAILURE);
|
|
}
|
|
|
|
if(inputFilePath != NULL) {
|
|
FILE *fp = fopen(inputFilePath, "r");
|
|
if(fp == NULL) {
|
|
perror("Can't open input file");
|
|
free(commBuf);
|
|
exit(EXIT_FAILURE);
|
|
}
|
|
|
|
while(fgets(commBuf, BUF_SIZE, fp) != NULL) {
|
|
size_t len = strlen(commBuf);
|
|
if (len < 1) continue;
|
|
|
|
// send command
|
|
if (write(fd_gtw, commBuf, len) != len) { /* blocking function */
|
|
perror("Socket write failed");
|
|
free(commBuf);
|
|
exit(EXIT_FAILURE);
|
|
}
|
|
|
|
// print reply, if command is complete
|
|
if (commBuf[len - 1] == '\n') {
|
|
if (printReply(fd_gtw) == EXIT_FAILURE) {
|
|
ret = EXIT_FAILURE;
|
|
}
|
|
}
|
|
}
|
|
|
|
fclose(fp);
|
|
}
|
|
|
|
/* get command from arguments */
|
|
else if(optind < argc) {
|
|
for(int i = optind; i < argc; i++) {
|
|
char *comm = argv[i];
|
|
commBuf[0] = 0;
|
|
|
|
/* Add sequence number if not present on command line argument */
|
|
if(comm[0] != '[' && comm[0] != '#') {
|
|
sprintf(commBuf, "[%d] ", i - optind + 1);
|
|
}
|
|
|
|
if((strlen(commBuf) + strlen(comm)) >= (BUF_SIZE - 2)) {
|
|
fprintf(errStream, "%sCommand string too long!%s\n", redC, greenC);
|
|
continue;
|
|
}
|
|
|
|
strcat(commBuf, comm);
|
|
|
|
if (additionalReadStdin == 0) {
|
|
strcat(commBuf, "\n");
|
|
size_t len = strlen(commBuf);
|
|
if (write(fd_gtw, commBuf, len) != len) { /* blocking function */
|
|
perror("Socket write failed");
|
|
free(commBuf);
|
|
exit(EXIT_FAILURE);
|
|
}
|
|
}
|
|
else {
|
|
strcat(commBuf, " ");
|
|
size_t len = strlen(commBuf);
|
|
char lastChar;
|
|
|
|
do {
|
|
if (fgets(commBuf+len, BUF_SIZE-1-len, stdin) == NULL)
|
|
strcat(commBuf, "\n");
|
|
|
|
len = strlen(commBuf);
|
|
lastChar = commBuf[len - 1];
|
|
|
|
if (len < BUF_SIZE-2 && lastChar != '\n')
|
|
continue;
|
|
|
|
// send command
|
|
if (write(fd_gtw, commBuf, len) != len) { /* blocking f. */
|
|
perror("Socket write failed");
|
|
free(commBuf);
|
|
exit(EXIT_FAILURE);
|
|
}
|
|
|
|
commBuf[0] = 0;
|
|
len = 0;
|
|
} while (lastChar != '\n');
|
|
}
|
|
|
|
if (printReply(fd_gtw) == EXIT_FAILURE) {
|
|
ret = EXIT_FAILURE;
|
|
}
|
|
}
|
|
}
|
|
|
|
/* get commands from stdin, line after line */
|
|
else {
|
|
while(fgets(commBuf, BUF_SIZE, stdin) != NULL) {
|
|
size_t len = strlen(commBuf);
|
|
if (len < 1) continue;
|
|
|
|
// send command
|
|
if (write(fd_gtw, commBuf, len) != len) { /* blocking function */
|
|
perror("Socket write failed");
|
|
free(commBuf);
|
|
exit(EXIT_FAILURE);
|
|
}
|
|
|
|
// print reply, if command is complete
|
|
if (commBuf[len - 1] == '\n') {
|
|
if (printReply(fd_gtw) == EXIT_FAILURE) {
|
|
ret = EXIT_FAILURE;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
close(fd_gtw);
|
|
|
|
free(commBuf);
|
|
|
|
/* candump output */
|
|
if (candumpCount > 0) {
|
|
for (int i = 0; i < candumpCount; i++) {
|
|
const char hex_asc[] = "0123456789ABCDEF";
|
|
struct can_frame canFrame;
|
|
|
|
ssize_t nbytes = read(fd_candump, &canFrame, sizeof(struct can_frame));
|
|
|
|
if (nbytes < (ssize_t)sizeof(struct can_frame)) {
|
|
perror("CAN raw socket read timeout");
|
|
exit(EXIT_FAILURE);
|
|
}
|
|
|
|
char buf[17];
|
|
char* pbuf = &buf[0];
|
|
for (int j = 0; j < canFrame.can_dlc && j < sizeof(buf); j++) {
|
|
uint8_t byte = canFrame.data[j];
|
|
*pbuf++ = hex_asc[byte >> 4];
|
|
*pbuf++ = hex_asc[byte & 0x0F];
|
|
}
|
|
*pbuf = 0;
|
|
|
|
printf ("%03X#%s\n", canFrame.can_id, buf);
|
|
}
|
|
close(fd_candump);
|
|
}
|
|
|
|
exit(ret);
|
|
}
|