mirror of
https://github.com/eddyem/pusirobot.git
synced 2025-12-06 02:25:10 +03:00
processed
This commit is contained in:
parent
d311e1def3
commit
6f9c74f166
@ -102,3 +102,99 @@ char *find_device(){
|
|||||||
udev_enumerate_unref(enumerate);
|
udev_enumerate_unref(enumerate);
|
||||||
return path;
|
return path;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Cl_log *globlog = NULL;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Cl_createlog - create log file: init mutex, test file open ability
|
||||||
|
* @param logpath - path to log file
|
||||||
|
* @param level - lowest message level (e.g. LOGLEVEL_ERR won't allow to write warn/msg/dbg)
|
||||||
|
* @return allocated structure (should be free'd later by Cl_deletelog) or NULL
|
||||||
|
*/
|
||||||
|
Cl_log *Cl_createlog(const char *logpath, Cl_loglevel level){
|
||||||
|
if(level < LOGLEVEL_NONE || level > LOGLEVEL_ANY) return NULL;
|
||||||
|
if(!logpath) return NULL;
|
||||||
|
FILE *logfd = fopen(logpath, "a");
|
||||||
|
if(!logfd){
|
||||||
|
WARN("Can't open log file");
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
fclose(logfd);
|
||||||
|
Cl_log *log = MALLOC(Cl_log, 1);
|
||||||
|
if(pthread_mutex_init(&log->mutex, NULL)){
|
||||||
|
WARN("Can't init log mutex");
|
||||||
|
FREE(log);
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
log->logpath = strdup(logpath);
|
||||||
|
if(!log->logpath){
|
||||||
|
WARN("strdup()");
|
||||||
|
FREE(log);
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
log->loglevel = level;
|
||||||
|
return log;
|
||||||
|
}
|
||||||
|
|
||||||
|
void Cl_deletelog(Cl_log **log){
|
||||||
|
if(!log || !*log) return;
|
||||||
|
FREE((*log)->logpath);
|
||||||
|
FREE(*log);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Cl_putlog - put message to log file with/without timestamp
|
||||||
|
* @param timest - ==1 to put timestamp
|
||||||
|
* @param log - pointer to log structure
|
||||||
|
* @param lvl - message loglevel (if lvl > loglevel, message won't be printed)
|
||||||
|
* @param fmt - format and the rest part of message
|
||||||
|
* @return amount of symbols saved in file
|
||||||
|
*/
|
||||||
|
int Cl_putlogt(int timest, Cl_log *log, Cl_loglevel lvl, const char *fmt, ...){
|
||||||
|
if(!log || !log->logpath) return 0;
|
||||||
|
if(lvl > log->loglevel) return 0;
|
||||||
|
if(pthread_mutex_lock(&log->mutex)){
|
||||||
|
WARN("Can't lock log mutex");
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
int i = 0;
|
||||||
|
FILE *logfd = fopen(log->logpath, "a+");
|
||||||
|
if(!logfd) goto rtn;
|
||||||
|
if(timest){
|
||||||
|
char strtm[128];
|
||||||
|
time_t t = time(NULL);
|
||||||
|
struct tm *curtm = localtime(&t);
|
||||||
|
strftime(strtm, 128, "%Y/%m/%d-%H:%M:%S", curtm);
|
||||||
|
i = fprintf(logfd, "%s", strtm);
|
||||||
|
}
|
||||||
|
i += fprintf(logfd, "\t");
|
||||||
|
va_list ar;
|
||||||
|
va_start(ar, fmt);
|
||||||
|
i += vfprintf(logfd, fmt, ar);
|
||||||
|
va_end(ar);
|
||||||
|
fseek(logfd, -1, SEEK_CUR);
|
||||||
|
char c;
|
||||||
|
ssize_t r = fread(&c, 1, 1, logfd);
|
||||||
|
if(1 == r){ // add '\n' if there was no newline
|
||||||
|
if(c != '\n') i += fprintf(logfd, "\n");
|
||||||
|
}
|
||||||
|
fclose(logfd);
|
||||||
|
rtn:
|
||||||
|
pthread_mutex_unlock(&log->mutex);
|
||||||
|
return i;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief str2long - convert full string to long
|
||||||
|
* @param str - string
|
||||||
|
* @param l (o) - converted number
|
||||||
|
* @return 0 if OK, 1 if failed
|
||||||
|
*/
|
||||||
|
int str2long(char *str, long* l){
|
||||||
|
if(!str) return 1;
|
||||||
|
char *eptr = NULL;
|
||||||
|
long n = strtol(str, &eptr, 0);
|
||||||
|
if(*eptr) return 2; // wrong symbols in number
|
||||||
|
if(l) *l = n;
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|||||||
@ -22,7 +22,39 @@
|
|||||||
|
|
||||||
#include "cmdlnopts.h"
|
#include "cmdlnopts.h"
|
||||||
|
|
||||||
extern glob_pars *GP;
|
typedef enum{
|
||||||
|
LOGLEVEL_NONE, // no logs
|
||||||
|
LOGLEVEL_ERR, // only errors
|
||||||
|
LOGLEVEL_WARN, // only warnings and errors
|
||||||
|
LOGLEVEL_MSG, // all without debug
|
||||||
|
LOGLEVEL_DBG, // all messages
|
||||||
|
LOGLEVEL_ANY // all shit
|
||||||
|
} Cl_loglevel;
|
||||||
|
|
||||||
|
typedef struct{
|
||||||
|
char *logpath; // full path to logfile
|
||||||
|
Cl_loglevel loglevel; // loglevel
|
||||||
|
pthread_mutex_t mutex; // log mutex
|
||||||
|
} Cl_log;
|
||||||
|
|
||||||
|
extern Cl_log *globlog; // global log file
|
||||||
|
|
||||||
|
Cl_log *Cl_createlog(const char *logpath, Cl_loglevel level);
|
||||||
|
#define OPENLOG(nm, lvl) (globlog = Cl_createlog(nm, lvl))
|
||||||
|
void Cl_deletelog(Cl_log **log);
|
||||||
|
int Cl_putlogt(int timest, Cl_log *log, Cl_loglevel lvl, const char *fmt, ...);
|
||||||
|
// shortcuts for different log levels; ..ADD - add message without timestamp
|
||||||
|
#define LOGERR(...) do{Cl_putlogt(1, globlog, LOGLEVEL_ERR, __VA_ARGS__);}while(0)
|
||||||
|
#define LOGERRADD(...) do{Cl_putlogt(1, globlog, LOGLEVEL_ERR, __VA_ARGS__);}while(0)
|
||||||
|
#define LOGWARN(...) do{Cl_putlogt(1, globlog, LOGLEVEL_WARN, __VA_ARGS__);}while(0)
|
||||||
|
#define LOGWARNADD(...) do{Cl_putlogt(0, globlog, LOGLEVEL_WARN, __VA_ARGS__);}while(0)
|
||||||
|
#define LOGMSG(...) do{Cl_putlogt(1, globlog, LOGLEVEL_MSG, __VA_ARGS__);}while(0)
|
||||||
|
#define LOGMSGADD(...) do{Cl_putlogt(0, globlog, LOGLEVEL_MSG, __VA_ARGS__);}while(0)
|
||||||
|
#define LOGDBG(...) do{Cl_putlogt(1, globlog, LOGLEVEL_DBG, __VA_ARGS__);}while(0)
|
||||||
|
#define LOGDBGADD(...) do{Cl_putlogt(0, globlog, LOGLEVEL_DBG, __VA_ARGS__);}while(0)
|
||||||
|
|
||||||
char *find_device();
|
char *find_device();
|
||||||
|
|
||||||
|
int str2long(char *str, long* l);
|
||||||
|
|
||||||
#endif // AUX_H__
|
#endif // AUX_H__
|
||||||
|
|||||||
278
canserver/canbus.c
Normal file
278
canserver/canbus.c
Normal file
@ -0,0 +1,278 @@
|
|||||||
|
/*
|
||||||
|
* This file is part of the stepper project.
|
||||||
|
* Copyright 2020 Edward V. Emelianov <edward.emelianoff@gmail.com>.
|
||||||
|
*
|
||||||
|
* 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 3 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 <pthread.h>
|
||||||
|
#include <stdio.h>
|
||||||
|
#include <string.h>
|
||||||
|
#include <sys/select.h>
|
||||||
|
#include <usefull_macros.h>
|
||||||
|
|
||||||
|
#include "canbus.h"
|
||||||
|
|
||||||
|
#ifndef BUFLEN
|
||||||
|
#define BUFLEN 80
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#ifndef WAIT_TMOUT
|
||||||
|
#define WAIT_TMOUT 0.01
|
||||||
|
#endif
|
||||||
|
|
||||||
|
/*
|
||||||
|
This file should provide next functions:
|
||||||
|
int canbus_open(const char *devname) - calls @the beginning, return 0 if all OK
|
||||||
|
int canbus_setspeed(int speed) - set given speed (in Kbaud) @ CAN bus (return 0 if all OK)
|
||||||
|
void canbus_close() - calls @the end
|
||||||
|
int canbus_write(CANmesg *mesg) - write `data` with length `len` to ID `ID`, return 0 if all OK
|
||||||
|
int canbus_read(CANmesg *mesg) - blocking read (broadcast if ID==0 or only from given ID) from can bus, return 0 if all OK
|
||||||
|
*/
|
||||||
|
|
||||||
|
static TTY_descr *dev = NULL; // shoul be global to restore if die
|
||||||
|
static int serialspeed = 115200; // speed to open serial device
|
||||||
|
static pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;
|
||||||
|
|
||||||
|
static char *read_string();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief read_ttyX- read data from TTY with 10ms timeout WITH disconnect detection
|
||||||
|
* @param buff (o) - buffer for data read
|
||||||
|
* @param length - buffer len
|
||||||
|
* @return amount of bytes read
|
||||||
|
*/
|
||||||
|
static int read_ttyX(TTY_descr *d){
|
||||||
|
if(!d || d->comfd < 0) return -1;
|
||||||
|
size_t L = 0;
|
||||||
|
ssize_t l;
|
||||||
|
size_t length = d->bufsz;
|
||||||
|
char *ptr = d->buf;
|
||||||
|
fd_set rfds;
|
||||||
|
struct timeval tv;
|
||||||
|
int retval;
|
||||||
|
do{
|
||||||
|
l = 0;
|
||||||
|
FD_ZERO(&rfds);
|
||||||
|
FD_SET(d->comfd, &rfds);
|
||||||
|
tv.tv_sec = 0; tv.tv_usec = 500;
|
||||||
|
retval = select(d->comfd + 1, &rfds, NULL, NULL, &tv);
|
||||||
|
if (!retval) break;
|
||||||
|
if(FD_ISSET(d->comfd, &rfds)){
|
||||||
|
if((l = read(d->comfd, ptr, length)) < 1){
|
||||||
|
return -1; // disconnect or other error - close TTY & die
|
||||||
|
}
|
||||||
|
ptr += l; L += l;
|
||||||
|
length -= l;
|
||||||
|
}
|
||||||
|
}while(l && length);
|
||||||
|
d->buflen = L;
|
||||||
|
d->buf[L] = 0;
|
||||||
|
return (size_t)L;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// thread-safe writing, add trailing '\n'
|
||||||
|
static int ttyWR(const char *buff, int len){
|
||||||
|
FNAME();
|
||||||
|
pthread_mutex_lock(&mutex);
|
||||||
|
//canbus_clear();
|
||||||
|
read_string(); // clear RX buffer
|
||||||
|
DBG("Write 2tty %d bytes: ", len);
|
||||||
|
#ifdef EBUG
|
||||||
|
int _U_ n = write(STDERR_FILENO, buff, len);
|
||||||
|
fprintf(stderr, "\n");
|
||||||
|
double t0 = dtime();
|
||||||
|
#endif
|
||||||
|
int w = write_tty(dev->comfd, buff, (size_t)len);
|
||||||
|
if(!w) w = write_tty(dev->comfd, "\n", 1);
|
||||||
|
DBG("Written, dt=%g", dtime() - t0);
|
||||||
|
int errctr = 0;
|
||||||
|
while(1){
|
||||||
|
char *s = read_string(); // clear echo & check
|
||||||
|
if(!s || strncmp(s, buff, strlen(buff)) != 0){
|
||||||
|
if(++errctr > 3){
|
||||||
|
WARNX("wrong answer! Got '%s' instead of '%s'", s, buff);
|
||||||
|
w = 1;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}else break;
|
||||||
|
}
|
||||||
|
pthread_mutex_unlock(&mutex);
|
||||||
|
DBG("Success, dt=%g", dtime() - t0);
|
||||||
|
return w;
|
||||||
|
}
|
||||||
|
|
||||||
|
void canbus_close(){
|
||||||
|
if(dev) close_tty(&dev);
|
||||||
|
}
|
||||||
|
|
||||||
|
void setserialspeed(int speed){
|
||||||
|
serialspeed = speed;
|
||||||
|
}
|
||||||
|
|
||||||
|
void canbus_clear(){
|
||||||
|
while(read_ttyX(dev));
|
||||||
|
}
|
||||||
|
|
||||||
|
int canbus_open(const char *devname){
|
||||||
|
if(!devname){
|
||||||
|
WARNX("canbus_open(): need device name");
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
if(dev) close_tty(&dev);
|
||||||
|
dev = new_tty((char*)devname, serialspeed, BUFLEN);
|
||||||
|
if(dev){
|
||||||
|
if(!tty_open(dev, 1)) // blocking open
|
||||||
|
close_tty(&dev);
|
||||||
|
}
|
||||||
|
if(!dev){
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
int canbus_setspeed(int speed){
|
||||||
|
if(speed == 0) return 0; // default - not change
|
||||||
|
char buff[BUFLEN];
|
||||||
|
if(speed < 10 || speed > 3000){
|
||||||
|
WARNX("Wrong CAN bus speed value: %d", speed);
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
int len = snprintf(buff, BUFLEN, "b %d", speed);
|
||||||
|
if(len < 1) return 2;
|
||||||
|
int r = ttyWR(buff, len);
|
||||||
|
read_string(); // clear RX buf ('Reinit CAN bus with speed XXXXkbps')
|
||||||
|
return r;
|
||||||
|
}
|
||||||
|
|
||||||
|
int canbus_write(CANmesg *mesg){
|
||||||
|
FNAME();
|
||||||
|
char buf[BUFLEN];
|
||||||
|
if(!mesg || mesg->len > 8) return 1;
|
||||||
|
int rem = BUFLEN, len = 0;
|
||||||
|
int l = snprintf(buf, rem, "s %d", mesg->ID);
|
||||||
|
rem -= l; len += l;
|
||||||
|
for(uint8_t i = 0; i < mesg->len; ++i){
|
||||||
|
l = snprintf(&buf[len], rem, " %d", mesg->data[i]);
|
||||||
|
rem -= l; len += l;
|
||||||
|
if(rem < 0) return 2;
|
||||||
|
}
|
||||||
|
canbus_clear();
|
||||||
|
return ttyWR(buf, len);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* read strings from terminal (ending with '\n') with timeout
|
||||||
|
* @return NULL if nothing was read or pointer to static buffer
|
||||||
|
*/
|
||||||
|
static char *read_string(){
|
||||||
|
static char buf[1024];
|
||||||
|
int LL = 1023, r = 0, l;
|
||||||
|
char *ptr = NULL;
|
||||||
|
static char *optr = NULL;
|
||||||
|
if(optr && *optr){
|
||||||
|
ptr = optr;
|
||||||
|
optr = strchr(optr, '\n');
|
||||||
|
if(optr){
|
||||||
|
*optr = 0;
|
||||||
|
++optr;
|
||||||
|
}
|
||||||
|
return ptr;
|
||||||
|
}
|
||||||
|
ptr = buf;
|
||||||
|
double d0 = dtime();
|
||||||
|
do{
|
||||||
|
if((l = read_ttyX(dev))){
|
||||||
|
if(l < 0){
|
||||||
|
ERR("tty disconnected");
|
||||||
|
}
|
||||||
|
if(l > LL){ // buffer overflow
|
||||||
|
WARNX("read_string(): buffer overflow");
|
||||||
|
optr = NULL;
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
memcpy(ptr, dev->buf, dev->buflen);
|
||||||
|
r += l; LL -= l; ptr += l;
|
||||||
|
if(ptr[-1] == '\n'){
|
||||||
|
//DBG("Newline detected");
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
d0 = dtime();
|
||||||
|
}
|
||||||
|
}while(dtime() - d0 < WAIT_TMOUT && LL);
|
||||||
|
if(r){
|
||||||
|
buf[r] = 0;
|
||||||
|
optr = strchr(buf, '\n');
|
||||||
|
if(optr){
|
||||||
|
*optr = 0;
|
||||||
|
++optr;
|
||||||
|
}else{
|
||||||
|
WARNX("read_string(): no newline found");
|
||||||
|
DBG("buf: %s", buf);
|
||||||
|
optr = NULL;
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
DBG("buf: %s, time: %g", buf, dtime() - d0);
|
||||||
|
return buf;
|
||||||
|
}
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
CANmesg *parseCANmesg(const char *str){
|
||||||
|
static CANmesg m;
|
||||||
|
int l = sscanf(str, "%d #0x%hx 0x%hhx 0x%hhx 0x%hhx 0x%hhx 0x%hhx 0x%hhx 0x%hhx 0x%hhx", &m.timemark, &m.ID,
|
||||||
|
&m.data[0], &m.data[1], &m.data[2], &m.data[3], &m.data[4], &m.data[5], &m.data[6], &m.data[7]);
|
||||||
|
if(l < 2) return NULL;
|
||||||
|
m.len = l - 2;
|
||||||
|
return &m;
|
||||||
|
}
|
||||||
|
|
||||||
|
#ifdef EBUG
|
||||||
|
void showM(CANmesg *m){
|
||||||
|
printf("TS=%d, ID=0x%X", m->timemark, m->ID);
|
||||||
|
int l = m->len;
|
||||||
|
if(l) printf(", data=");
|
||||||
|
for(int i = 0; i < l; ++i) printf(" 0x%02X", m->data[i]);
|
||||||
|
printf("\n");
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
|
int canbus_read(CANmesg *mesg){
|
||||||
|
if(!mesg) return 1;
|
||||||
|
pthread_mutex_lock(&mutex);
|
||||||
|
double t0 = dtime();
|
||||||
|
int ID = mesg->ID;
|
||||||
|
char *ans;
|
||||||
|
CANmesg *m;
|
||||||
|
while(dtime() - t0 < T_POLLING_TMOUT){ // read answer
|
||||||
|
if((ans = read_string())){ // parse new data
|
||||||
|
if((m = parseCANmesg(ans))){
|
||||||
|
DBG("Got canbus message (dT=%g):", dtime() - t0);
|
||||||
|
#ifdef EBUG
|
||||||
|
showM(m);
|
||||||
|
#endif
|
||||||
|
if(ID && m->ID == ID){
|
||||||
|
memcpy(mesg, m, sizeof(CANmesg));
|
||||||
|
DBG("All OK");
|
||||||
|
pthread_mutex_unlock(&mutex);
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
pthread_mutex_unlock(&mutex);
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
48
canserver/canbus.h
Normal file
48
canserver/canbus.h
Normal file
@ -0,0 +1,48 @@
|
|||||||
|
/*
|
||||||
|
* This file is part of the stepper project.
|
||||||
|
* Copyright 2020 Edward V. Emelianov <edward.emelianoff@gmail.com>.
|
||||||
|
*
|
||||||
|
* 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 3 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/>.
|
||||||
|
*/
|
||||||
|
#pragma once
|
||||||
|
#ifndef CANBUS_H__
|
||||||
|
#define CANBUS_H__
|
||||||
|
|
||||||
|
#include <stdint.h>
|
||||||
|
|
||||||
|
#ifndef T_POLLING_TMOUT
|
||||||
|
#define T_POLLING_TMOUT (0.5)
|
||||||
|
#endif
|
||||||
|
|
||||||
|
typedef struct{
|
||||||
|
uint32_t timemark; // time since MCU run (ms)
|
||||||
|
uint16_t ID; // 11-bit identifier
|
||||||
|
uint8_t data[8]; // up to 8 bit data, data[0] is lowest
|
||||||
|
uint8_t len; // data length
|
||||||
|
} CANmesg;
|
||||||
|
|
||||||
|
// main (necessary) functions of canbus.c:
|
||||||
|
void canbus_close();
|
||||||
|
int canbus_open(const char *devname);
|
||||||
|
int canbus_write(CANmesg *mesg);
|
||||||
|
int canbus_read(CANmesg *mesg);
|
||||||
|
int canbus_setspeed(int speed);
|
||||||
|
void canbus_clear();
|
||||||
|
|
||||||
|
// auxiliary (not necessary) functions
|
||||||
|
void setserialspeed(int speed);
|
||||||
|
void showM(CANmesg *m);
|
||||||
|
CANmesg *parseCANmesg(const char *str);
|
||||||
|
|
||||||
|
#endif // CANBUS_H__
|
||||||
378
canserver/canopen.c
Normal file
378
canserver/canopen.c
Normal file
@ -0,0 +1,378 @@
|
|||||||
|
/*
|
||||||
|
* This file is part of the stepper project.
|
||||||
|
* Copyright 2020 Edward V. Emelianov <edward.emelianoff@gmail.com>.
|
||||||
|
*
|
||||||
|
* 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 3 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 <stdint.h>
|
||||||
|
#include <stdio.h>
|
||||||
|
#include <stdlib.h>
|
||||||
|
#include <string.h>
|
||||||
|
#include <usefull_macros.h>
|
||||||
|
|
||||||
|
#include "canopen.h"
|
||||||
|
|
||||||
|
typedef struct{
|
||||||
|
uint32_t code;
|
||||||
|
const char *errmsg;
|
||||||
|
} abortcodes;
|
||||||
|
|
||||||
|
|
||||||
|
static const abortcodes AC[] = {
|
||||||
|
//while read l; do N=$(echo $l|awk '{print $1 $2}'); R=$(echo $l|awk '{$1=$2=""; print substr($0,3)}'|sed 's/\.//'); echo -e "{0x$N, \"$R\"},"; done < codes.b
|
||||||
|
{0x05030000, "Toggle bit not alternated"},
|
||||||
|
{0x05040000, "SDO protocol timed out"},
|
||||||
|
{0x05040001, "Client/server command specifier not valid or unknown"},
|
||||||
|
{0x05040002, "Invalid block size (block mode only)"},
|
||||||
|
{0x05040003, "Invalid sequence number (block mode only)"},
|
||||||
|
{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 in the object dictionary"},
|
||||||
|
{0x06040041, "Object cannot be mapped to the PDO"},
|
||||||
|
{0x06040042, "The number and length of the objects to be mapped would exceed PDO length"},
|
||||||
|
{0x06040043, "General parameter incompatibility reason"},
|
||||||
|
{0x06040047, "General internal incompatibility in the device"},
|
||||||
|
{0x06060000, "Access failed due to a 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 low"},
|
||||||
|
{0x06090011, "Sub-index does not exist"},
|
||||||
|
{0x06090030, "Value range of parameter exceeded (only for write access)"},
|
||||||
|
{0x06090031, "Value of parameter written too high"},
|
||||||
|
{0x06090032, "Value of parameter written too low"},
|
||||||
|
{0x06090036, "Maximum value is less than minimum value"},
|
||||||
|
{0x08000000, "General error"},
|
||||||
|
{0x08000020, "Data cannot be transferred or stored to the application"},
|
||||||
|
{0x08000021, "Data cannot be transferred or stored to the application because of local control"},
|
||||||
|
{0x08000022, "Data cannot be transferred or stored to the application because of the present device state"},
|
||||||
|
{0x08000023, "Object dictionary dynamic generation fails or no object dictionary is present"},
|
||||||
|
};
|
||||||
|
|
||||||
|
static const int ACmax = sizeof(AC)/sizeof(abortcodes) - 1;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief abortcode_text - explanation of abort code
|
||||||
|
* @param abortcode - code
|
||||||
|
* @return text for error or NULL
|
||||||
|
*/
|
||||||
|
const char *abortcode_text(uint32_t abortcode){ //, int *n){
|
||||||
|
int idx = ACmax/2, min_ = 0, max_ = ACmax, newidx = 0, iter=0;
|
||||||
|
do{
|
||||||
|
++iter;
|
||||||
|
uint32_t c = AC[idx].code;
|
||||||
|
//printf("idx=%d, min=%d, max=%d\n", idx, min_, max_);
|
||||||
|
if(c == abortcode){
|
||||||
|
//if(n) *n = iter;
|
||||||
|
//DBG("got : %s", AC[idx].errmsg);
|
||||||
|
return AC[idx].errmsg;
|
||||||
|
}else if(c > abortcode){
|
||||||
|
newidx = (idx + min_)/2;
|
||||||
|
max_ = idx;
|
||||||
|
|
||||||
|
}else{
|
||||||
|
newidx = (idx + max_ + 1)/2;
|
||||||
|
min_ = idx;
|
||||||
|
}
|
||||||
|
if(newidx == idx || min_ < 0 || max_ > ACmax){
|
||||||
|
//if(n) *n = 0;
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
idx = newidx;
|
||||||
|
}while(1);
|
||||||
|
}
|
||||||
|
|
||||||
|
// make CAN message from sdo object; don't support more then one block/packet
|
||||||
|
static CANmesg *mkMesg(SDO *sdo){
|
||||||
|
static CANmesg mesg;
|
||||||
|
mesg.ID = RSDO_COBID + sdo->NID;
|
||||||
|
mesg.len = 8;
|
||||||
|
memset(mesg.data, 0, 8);
|
||||||
|
mesg.data[0] = SDO_CCS(sdo->ccs);
|
||||||
|
if(sdo->datalen){ // send N bytes of data
|
||||||
|
mesg.data[0] |= SDO_N(sdo->datalen) | SDO_E | SDO_S;
|
||||||
|
for(uint8_t i = 0; i < sdo->datalen; ++i) mesg.data[4+i] = sdo->data[i];
|
||||||
|
}
|
||||||
|
mesg.data[1] = sdo->index & 0xff; // l
|
||||||
|
mesg.data[2] = (sdo->index >> 8) & 0xff; // h
|
||||||
|
mesg.data[3] = sdo->subindex;
|
||||||
|
#if 0
|
||||||
|
FNAME();
|
||||||
|
green("Make message to 0x%X: ", mesg.ID);
|
||||||
|
for(uint8_t i = 0; i < 8; ++i) printf("0x%02X ", mesg.data[i]);
|
||||||
|
printf("\n");
|
||||||
|
#endif
|
||||||
|
return &mesg;
|
||||||
|
}
|
||||||
|
|
||||||
|
// transform CAN-message to SDO
|
||||||
|
SDO *parseSDO(CANmesg *mesg){
|
||||||
|
static SDO sdo;
|
||||||
|
if(mesg->len != 8){
|
||||||
|
WARNX("Wrong SDO data length");
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
uint16_t cobid = mesg->ID & COBID_MASK;
|
||||||
|
if(cobid != TSDO_COBID){
|
||||||
|
DBG("cobid=0x%X, not a TSDO!", cobid);
|
||||||
|
return NULL; // not a transmit SDO
|
||||||
|
}
|
||||||
|
sdo.NID = mesg->ID & NODEID_MASK;
|
||||||
|
uint8_t spec = mesg->data[0];
|
||||||
|
sdo.ccs = GET_CCS(spec);
|
||||||
|
sdo.index = (uint16_t)mesg->data[1] | ((uint16_t)mesg->data[2] << 8);
|
||||||
|
sdo.subindex = mesg->data[3];
|
||||||
|
if((spec & SDO_E) && (spec & SDO_S)) sdo.datalen = SDO_datalen(spec);
|
||||||
|
else if(sdo.ccs == CCS_ABORT_TRANSFER) sdo.datalen = 4; // error code
|
||||||
|
else sdo.datalen = 0; // no data in message
|
||||||
|
for(uint8_t i = 0; i < sdo.datalen; ++i) sdo.data[i] = mesg->data[4+i];
|
||||||
|
DBG("Got TSDO from NID=%d, ccs=%u, index=0x%X, subindex=0x%X, datalen=%d", sdo.NID, sdo.ccs, sdo.index, sdo.subindex, sdo.datalen);
|
||||||
|
return &sdo;
|
||||||
|
}
|
||||||
|
|
||||||
|
// send request to read SDO
|
||||||
|
static int ask2read(uint16_t idx, uint8_t subidx, uint8_t NID){
|
||||||
|
SDO sdo;
|
||||||
|
sdo.NID = NID;
|
||||||
|
sdo.ccs = CCS_INIT_UPLOAD;
|
||||||
|
sdo.datalen = 0;
|
||||||
|
sdo.index = idx;
|
||||||
|
sdo.subindex = subidx;
|
||||||
|
CANmesg *mesg = mkMesg(&sdo);
|
||||||
|
return canbus_write(mesg);
|
||||||
|
}
|
||||||
|
|
||||||
|
static SDO *getSDOans(uint16_t idx, uint8_t subidx, uint8_t NID){
|
||||||
|
FNAME();
|
||||||
|
CANmesg mesg;
|
||||||
|
SDO *sdo = NULL;
|
||||||
|
double t0 = dtime();
|
||||||
|
while(dtime() - t0 < SDO_ANS_TIMEOUT){
|
||||||
|
mesg.ID = TSDO_COBID | NID; // read only from given ID
|
||||||
|
if(canbus_read(&mesg)){
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
sdo = parseSDO(&mesg);
|
||||||
|
if(!sdo) continue;
|
||||||
|
if(sdo->index == idx && sdo->subindex == subidx) break;
|
||||||
|
}
|
||||||
|
if(!sdo || sdo->index != idx || sdo->subindex != subidx){
|
||||||
|
WARNX("No answer from SDO 0x%X/0x%X", idx, subidx);
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
return sdo;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief readSDOvalue - send request to SDO read
|
||||||
|
* @param idx - SDO index
|
||||||
|
* @param subidx - SDO subindex
|
||||||
|
* @param NID - target node ID
|
||||||
|
* @return SDO received or NULL if error
|
||||||
|
*/
|
||||||
|
SDO *readSDOvalue(uint16_t idx, uint8_t subidx, uint8_t NID){
|
||||||
|
FNAME();
|
||||||
|
if(ask2read(idx, subidx, NID)){
|
||||||
|
WARNX("readSDOvalue(): Can't initiate upload");
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
return getSDOans(idx, subidx, NID);
|
||||||
|
}
|
||||||
|
|
||||||
|
static inline uint32_t mku32(uint8_t data[4]){
|
||||||
|
return (uint32_t)(data[0] | (data[1]<<8) | (data[2]<<16) | (data[3]<<24));
|
||||||
|
}
|
||||||
|
|
||||||
|
static inline uint16_t mku16(uint8_t data[4]){
|
||||||
|
return (uint16_t)(data[0] | (data[1]<<8));
|
||||||
|
}
|
||||||
|
|
||||||
|
static inline uint8_t mku8(uint8_t data[4]){
|
||||||
|
return data[0];
|
||||||
|
}
|
||||||
|
|
||||||
|
static inline int32_t mki32(uint8_t data[4]){
|
||||||
|
return (int32_t)(data[0] | (data[1]<<8) | (data[2]<<16) | (data[3]<<24));
|
||||||
|
}
|
||||||
|
|
||||||
|
static inline int16_t mki16(uint8_t data[4]){
|
||||||
|
return (int16_t)(data[0] | (data[1]<<8));
|
||||||
|
}
|
||||||
|
|
||||||
|
static inline int8_t mki8(uint8_t data[4]){
|
||||||
|
return (int8_t)data[0];
|
||||||
|
}
|
||||||
|
|
||||||
|
// read SDO value, if error - return INT64_MIN
|
||||||
|
int64_t SDO_read(const SDO_dic_entry *e, uint8_t NID){
|
||||||
|
FNAME();
|
||||||
|
SDO *sdo = readSDOvalue(e->index, e->subindex, NID);
|
||||||
|
if(!sdo){
|
||||||
|
return INT64_MIN;
|
||||||
|
}
|
||||||
|
if(sdo->ccs == CCS_ABORT_TRANSFER){ // error
|
||||||
|
WARNX("Got error for SDO 0x%X", e->index);
|
||||||
|
uint32_t ac = mku32(sdo->data);
|
||||||
|
const char *etxt = abortcode_text(ac);
|
||||||
|
if(etxt) WARNX("Abort code 0x%X: %s", ac, etxt);
|
||||||
|
return INT64_MIN;
|
||||||
|
}
|
||||||
|
if(sdo->datalen != e->datasize){
|
||||||
|
WARNX("Got SDO with length %d instead of %d (as in dictionary)", sdo->datalen, e->datasize);
|
||||||
|
}
|
||||||
|
int64_t ans = 0;
|
||||||
|
if(e->issigned){
|
||||||
|
switch(sdo->datalen){
|
||||||
|
case 1:
|
||||||
|
ans = mki8(sdo->data);
|
||||||
|
break;
|
||||||
|
case 4:
|
||||||
|
ans = mki32(sdo->data);
|
||||||
|
break;
|
||||||
|
default: // can't be 3! 3->2
|
||||||
|
ans = mki16(sdo->data);
|
||||||
|
}
|
||||||
|
}else{
|
||||||
|
switch(sdo->datalen){
|
||||||
|
case 1:
|
||||||
|
ans = mku8(sdo->data);
|
||||||
|
break;
|
||||||
|
case 4:
|
||||||
|
ans = mku32(sdo->data);
|
||||||
|
break;
|
||||||
|
default: // can't be 3! 3->2
|
||||||
|
ans = mku16(sdo->data);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return ans;
|
||||||
|
}
|
||||||
|
|
||||||
|
// write SDO data, return 0 if all OK
|
||||||
|
int SDO_writeArr(const SDO_dic_entry *e, uint8_t NID, const uint8_t *data){
|
||||||
|
FNAME();
|
||||||
|
if(!e || !data || e->datasize < 1 || e->datasize > 4){
|
||||||
|
WARNX("SDO_write(): bad datalen");
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
SDO sdo;
|
||||||
|
sdo.NID = NID;
|
||||||
|
sdo.ccs = CCS_INIT_DOWNLOAD;
|
||||||
|
sdo.datalen = e->datasize;
|
||||||
|
for(uint8_t i = 0; i < e->datasize; ++i) sdo.data[i] = data[i];
|
||||||
|
sdo.index = e->index;
|
||||||
|
sdo.subindex = e->subindex;
|
||||||
|
CANmesg *mesgp = mkMesg(&sdo);
|
||||||
|
DBG("Canbus write..");
|
||||||
|
if(canbus_write(mesgp)){
|
||||||
|
WARNX("SDO_write(): Can't initiate download");
|
||||||
|
return 2;
|
||||||
|
}
|
||||||
|
DBG("get answer");
|
||||||
|
SDO *sdop = getSDOans(e->index, e->subindex, NID);
|
||||||
|
if(!sdop){
|
||||||
|
WARNX("SDO_write(): SDO read error");
|
||||||
|
return 3;
|
||||||
|
}
|
||||||
|
if(sdop->ccs == CCS_ABORT_TRANSFER){ // error
|
||||||
|
WARNX("SDO_write(): Got error for SDO 0x%X", e->index);
|
||||||
|
uint32_t ac = mku32(sdop->data);
|
||||||
|
const char *etxt = abortcode_text(ac);
|
||||||
|
if(etxt) WARNX("Abort code 0x%X: %s", ac, etxt);
|
||||||
|
return 4;
|
||||||
|
}
|
||||||
|
if(sdop->datalen != 0){
|
||||||
|
WARNX("SDO_write(): got answer with non-zero length");
|
||||||
|
return 5;
|
||||||
|
}
|
||||||
|
if(sdop->ccs != CCS_SEG_UPLOAD){
|
||||||
|
WARNX("SDO_write(): got wrong answer");
|
||||||
|
return 6;
|
||||||
|
}
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
int SDO_write(const SDO_dic_entry *e, uint8_t NID, int64_t data){
|
||||||
|
if(!e) return 1;
|
||||||
|
uint8_t arr[4] = {0};
|
||||||
|
uint32_t U;
|
||||||
|
int32_t I;
|
||||||
|
uint16_t U16;
|
||||||
|
int16_t I16;
|
||||||
|
if(e->issigned){
|
||||||
|
switch(e->datasize){
|
||||||
|
case 1:
|
||||||
|
arr[0] = (uint8_t) data;
|
||||||
|
break;
|
||||||
|
case 4:
|
||||||
|
I = (int32_t) data;
|
||||||
|
arr[0] = I&0xff;
|
||||||
|
arr[1] = (I>>8)&0xff;
|
||||||
|
arr[2] = (I>>16)&0xff;
|
||||||
|
arr[3] = (I>>24)&0xff;
|
||||||
|
break;
|
||||||
|
default: // can't be 3! 3->2
|
||||||
|
I16 = (int16_t) data;
|
||||||
|
arr[0] = I16&0xff;
|
||||||
|
arr[1] = (I16>>8)&0xff;
|
||||||
|
}
|
||||||
|
}else{
|
||||||
|
switch(e->datasize){
|
||||||
|
case 1:
|
||||||
|
arr[0] = (uint8_t) data;
|
||||||
|
break;
|
||||||
|
case 4:
|
||||||
|
U = (uint32_t) data;
|
||||||
|
arr[0] = U&0xff;
|
||||||
|
arr[1] = (U>>8)&0xff;
|
||||||
|
arr[2] = (U>>16)&0xff;
|
||||||
|
arr[3] = (U>>24)&0xff;
|
||||||
|
break;
|
||||||
|
default: // can't be 3! 3->2
|
||||||
|
U16 = (uint16_t) data;
|
||||||
|
arr[0] = U16&0xff;
|
||||||
|
arr[1] = (U16>>8)&0xff;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
/*
|
||||||
|
DBG("DATA:");
|
||||||
|
for(int i = 0; i < e->datasize; ++i) printf("0x%X ", arr[i]);
|
||||||
|
printf("\n");
|
||||||
|
return 0;*/
|
||||||
|
return SDO_writeArr(e, NID, arr);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
// read one byte of data
|
||||||
|
/*int SDO_readByte(uint16_t idx, uint8_t subidx, uint8_t *data, uint8_t NID){
|
||||||
|
SDO *sdo = readSDOvalue(idx, subidx, NID);
|
||||||
|
if(!sdo || sdo->datalen != 1){
|
||||||
|
WARNX("Got SDO with wrong data length: %d instead of 1", sdo->datalen);
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
if(data) *data = sdo->data[0];
|
||||||
|
return 0;
|
||||||
|
}*/
|
||||||
|
|
||||||
|
#if 0
|
||||||
|
// write uint8_t to SDO with index idx & subindex subidx to NID
|
||||||
|
void SDO_writeU8(uint16_t idx, uint8_t subidx, uint8_t data, uint8_t NID){
|
||||||
|
SDO sdo;
|
||||||
|
sdo.NID = NID;
|
||||||
|
sdo.ccs = CCS_INIT_DOWNLOAD;
|
||||||
|
}
|
||||||
|
#endif
|
||||||
90
canserver/canopen.h
Normal file
90
canserver/canopen.h
Normal file
@ -0,0 +1,90 @@
|
|||||||
|
/*
|
||||||
|
* This file is part of the stepper project.
|
||||||
|
* Copyright 2020 Edward V. Emelianov <edward.emelianoff@gmail.com>.
|
||||||
|
*
|
||||||
|
* 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 3 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/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
#ifndef CANOPEN_H__
|
||||||
|
#define CANOPEN_H__
|
||||||
|
|
||||||
|
#include "canbus.h"
|
||||||
|
#include "pusirobot.h"
|
||||||
|
|
||||||
|
// timeout for answer from the SDO, seconds
|
||||||
|
#define SDO_ANS_TIMEOUT (1.)
|
||||||
|
|
||||||
|
// COB-ID base:
|
||||||
|
#define NMT_COBID 0
|
||||||
|
#define EMERG_COBID 0x80
|
||||||
|
#define TIMESTAMP_COBID 0x100
|
||||||
|
#define TPDO1_COBID 0x180
|
||||||
|
#define RPDO1_COBID 0x200
|
||||||
|
#define TPDO2_COBID 0x280
|
||||||
|
#define RPDO2_COBID 0x300
|
||||||
|
#define TPDO3_COBID 0x380
|
||||||
|
#define RPDO3_COBID 0x400
|
||||||
|
#define TPDO4_COBID 0x480
|
||||||
|
#define RPDO4_COBID 0x500
|
||||||
|
#define TSDO_COBID 0x580
|
||||||
|
#define RSDO_COBID 0x600
|
||||||
|
#define HEARTB_COBID 0x700
|
||||||
|
// mask to select COB-ID base from ID
|
||||||
|
#define COBID_MASK 0x780
|
||||||
|
// mask to select node ID from ID
|
||||||
|
#define NODEID_MASK 0x7F
|
||||||
|
|
||||||
|
// SDO client command specifier field
|
||||||
|
typedef enum{
|
||||||
|
CCS_SEG_DOWNLOAD = 0,
|
||||||
|
CCS_INIT_DOWNLOAD,
|
||||||
|
CCS_INIT_UPLOAD,
|
||||||
|
CCS_SEG_UPLOAD,
|
||||||
|
CCS_ABORT_TRANSFER,
|
||||||
|
CCS_BLOCK_UPLOAD,
|
||||||
|
CCS_BLOCK_DOWNLOAD
|
||||||
|
} SDO_CCS_fields;
|
||||||
|
// make number from CSS field
|
||||||
|
#define SDO_CCS(f) (f<<5)
|
||||||
|
// make CCS from number
|
||||||
|
#define GET_CCS(f) (f>>5)
|
||||||
|
// make number from data amount
|
||||||
|
#define SDO_N(n) ((4-n)<<2)
|
||||||
|
// make data amount from number
|
||||||
|
#define SDO_datalen(n) (4-((n&0xC)>>2))
|
||||||
|
// SDO e & s fields:
|
||||||
|
#define SDO_E (1<<1)
|
||||||
|
#define SDO_S (1<<0)
|
||||||
|
|
||||||
|
typedef struct{
|
||||||
|
uint8_t NID; // node ID in CANopen
|
||||||
|
uint16_t index; // SDO index
|
||||||
|
uint8_t subindex; // SDO subindex
|
||||||
|
uint8_t data[4]; // 1..4 bytes of data (data[0] is lowest)
|
||||||
|
uint8_t datalen; // length of data
|
||||||
|
uint8_t ccs; // Client command specifier
|
||||||
|
} SDO;
|
||||||
|
|
||||||
|
const char *abortcode_text(uint32_t abortcode);
|
||||||
|
SDO *parseSDO(CANmesg *mesg);
|
||||||
|
SDO *readSDOvalue(uint16_t idx, uint8_t subidx, uint8_t NID);
|
||||||
|
|
||||||
|
int64_t SDO_read(const SDO_dic_entry *e, uint8_t NID);
|
||||||
|
|
||||||
|
int SDO_writeArr(const SDO_dic_entry *e, uint8_t NID, const uint8_t *data);
|
||||||
|
int SDO_write(const SDO_dic_entry *e, uint8_t NID, int64_t data);
|
||||||
|
|
||||||
|
//int SDO_readByte(uint16_t idx, uint8_t subidx, uint8_t *data, uint8_t NID);
|
||||||
|
#endif // CANOPEN_H__
|
||||||
@ -89,84 +89,3 @@ glob_pars *parse_args(int argc, char **argv){
|
|||||||
}
|
}
|
||||||
return &G;
|
return &G;
|
||||||
}
|
}
|
||||||
|
|
||||||
Cl_log *globlog = NULL;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief Cl_createlog - create log file: init mutex, test file open ability
|
|
||||||
* @param logpath - path to log file
|
|
||||||
* @param level - lowest message level (e.g. LOGLEVEL_ERR won't allow to write warn/msg/dbg)
|
|
||||||
* @return allocated structure (should be free'd later by Cl_deletelog) or NULL
|
|
||||||
*/
|
|
||||||
Cl_log *Cl_createlog(const char *logpath, Cl_loglevel level){
|
|
||||||
if(level < LOGLEVEL_NONE || level > LOGLEVEL_ANY) return NULL;
|
|
||||||
if(!logpath) return NULL;
|
|
||||||
FILE *logfd = fopen(logpath, "a");
|
|
||||||
if(!logfd){
|
|
||||||
WARN("Can't open log file");
|
|
||||||
return NULL;
|
|
||||||
}
|
|
||||||
fclose(logfd);
|
|
||||||
Cl_log *log = MALLOC(Cl_log, 1);
|
|
||||||
if(pthread_mutex_init(&log->mutex, NULL)){
|
|
||||||
WARN("Can't init log mutex");
|
|
||||||
FREE(log);
|
|
||||||
return NULL;
|
|
||||||
}
|
|
||||||
log->logpath = strdup(logpath);
|
|
||||||
if(!log->logpath){
|
|
||||||
WARN("strdup()");
|
|
||||||
FREE(log);
|
|
||||||
return NULL;
|
|
||||||
}
|
|
||||||
log->loglevel = level;
|
|
||||||
return log;
|
|
||||||
}
|
|
||||||
|
|
||||||
void Cl_deletelog(Cl_log **log){
|
|
||||||
if(!log || !*log) return;
|
|
||||||
FREE((*log)->logpath);
|
|
||||||
FREE(*log);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief Cl_putlog - put message to log file with/without timestamp
|
|
||||||
* @param timest - ==1 to put timestamp
|
|
||||||
* @param log - pointer to log structure
|
|
||||||
* @param lvl - message loglevel (if lvl > loglevel, message won't be printed)
|
|
||||||
* @param fmt - format and the rest part of message
|
|
||||||
* @return amount of symbols saved in file
|
|
||||||
*/
|
|
||||||
int Cl_putlogt(int timest, Cl_log *log, Cl_loglevel lvl, const char *fmt, ...){
|
|
||||||
if(!log || !log->logpath) return 0;
|
|
||||||
if(lvl > log->loglevel) return 0;
|
|
||||||
if(pthread_mutex_lock(&log->mutex)){
|
|
||||||
WARN("Can't lock log mutex");
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
int i = 0;
|
|
||||||
FILE *logfd = fopen(log->logpath, "a+");
|
|
||||||
if(!logfd) goto rtn;
|
|
||||||
if(timest){
|
|
||||||
char strtm[128];
|
|
||||||
time_t t = time(NULL);
|
|
||||||
struct tm *curtm = localtime(&t);
|
|
||||||
strftime(strtm, 128, "%Y/%m/%d-%H:%M:%S", curtm);
|
|
||||||
i = fprintf(logfd, "%s", strtm);
|
|
||||||
}
|
|
||||||
i += fprintf(logfd, "\t");
|
|
||||||
va_list ar;
|
|
||||||
va_start(ar, fmt);
|
|
||||||
i += vfprintf(logfd, fmt, ar);
|
|
||||||
va_end(ar);
|
|
||||||
fseek(logfd, -1, SEEK_CUR);
|
|
||||||
char c;
|
|
||||||
ssize_t r = fread(&c, 1, 1, logfd);
|
|
||||||
if(1 == r){ // add '\n' if there was no newline
|
|
||||||
if(c != '\n') i += fprintf(logfd, "\n");
|
|
||||||
}
|
|
||||||
fclose(logfd);
|
|
||||||
rtn:
|
|
||||||
pthread_mutex_unlock(&log->mutex);
|
|
||||||
return i;
|
|
||||||
}
|
|
||||||
|
|||||||
@ -44,35 +44,6 @@ typedef struct{
|
|||||||
|
|
||||||
glob_pars *parse_args(int argc, char **argv);
|
glob_pars *parse_args(int argc, char **argv);
|
||||||
|
|
||||||
typedef enum{
|
extern glob_pars *GP;
|
||||||
LOGLEVEL_NONE, // no logs
|
|
||||||
LOGLEVEL_ERR, // only errors
|
|
||||||
LOGLEVEL_WARN, // only warnings and errors
|
|
||||||
LOGLEVEL_MSG, // all without debug
|
|
||||||
LOGLEVEL_DBG, // all messages
|
|
||||||
LOGLEVEL_ANY // all shit
|
|
||||||
} Cl_loglevel;
|
|
||||||
|
|
||||||
typedef struct{
|
|
||||||
char *logpath; // full path to logfile
|
|
||||||
Cl_loglevel loglevel; // loglevel
|
|
||||||
pthread_mutex_t mutex; // log mutex
|
|
||||||
} Cl_log;
|
|
||||||
|
|
||||||
// logging
|
|
||||||
extern Cl_log *globlog; // global log file
|
|
||||||
Cl_log *Cl_createlog(const char *logpath, Cl_loglevel level);
|
|
||||||
#define OPENLOG(nm, lvl) (globlog = Cl_createlog(nm, lvl))
|
|
||||||
void Cl_deletelog(Cl_log **log);
|
|
||||||
int Cl_putlogt(int timest, Cl_log *log, Cl_loglevel lvl, const char *fmt, ...);
|
|
||||||
// shortcuts for different log levels; ..ADD - add message without timestamp
|
|
||||||
#define LOGERR(...) do{Cl_putlogt(1, globlog, LOGLEVEL_ERR, __VA_ARGS__);}while(0)
|
|
||||||
#define LOGERRADD(...) do{Cl_putlogt(1, globlog, LOGLEVEL_ERR, __VA_ARGS__);}while(0)
|
|
||||||
#define LOGWARN(...) do{Cl_putlogt(1, globlog, LOGLEVEL_WARN, __VA_ARGS__);}while(0)
|
|
||||||
#define LOGWARNADD(...) do{Cl_putlogt(0, globlog, LOGLEVEL_WARN, __VA_ARGS__);}while(0)
|
|
||||||
#define LOGMSG(...) do{Cl_putlogt(1, globlog, LOGLEVEL_MSG, __VA_ARGS__);}while(0)
|
|
||||||
#define LOGMSGADD(...) do{Cl_putlogt(0, globlog, LOGLEVEL_MSG, __VA_ARGS__);}while(0)
|
|
||||||
#define LOGDBG(...) do{Cl_putlogt(1, globlog, LOGLEVEL_DBG, __VA_ARGS__);}while(0)
|
|
||||||
#define LOGDBGADD(...) do{Cl_putlogt(0, globlog, LOGLEVEL_DBG, __VA_ARGS__);}while(0)
|
|
||||||
|
|
||||||
#endif // __CMDLNOPTS_H__
|
#endif // __CMDLNOPTS_H__
|
||||||
|
|||||||
143
canserver/dicentries.in
Normal file
143
canserver/dicentries.in
Normal file
@ -0,0 +1,143 @@
|
|||||||
|
/*
|
||||||
|
* This file is part of the stepper project.
|
||||||
|
* Copyright 2020 Edward V. Emelianov <edward.emelianoff@gmail.com>.
|
||||||
|
*
|
||||||
|
* 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 3 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/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
// this file can be included more than once!
|
||||||
|
|
||||||
|
// variable name / index / subindex / datasize / issigned / name
|
||||||
|
|
||||||
|
// heartbeat time
|
||||||
|
DICENTRY(HEARTBTTIME, 0x1017, 0, 2, 0, "heartbeat time")
|
||||||
|
|
||||||
|
// receive PDO parameter 0
|
||||||
|
// largest subindex supported
|
||||||
|
DICENTRY(RPDOP0LS, 0x1400, 0, 1, 0, "receive PDO parameter 0, largest subindex supported")
|
||||||
|
// COB-ID used by PDO
|
||||||
|
DICENTRY(RPDOP0CI, 0x1400, 1, 4, 0, "receive PDO parameter 0, COB-ID used by PDO")
|
||||||
|
// transmission type
|
||||||
|
DICENTRY(RPDOP0TT, 0x1400, 2, 1, 0, "receive PDO parameter 0, transmission type")
|
||||||
|
// inhibit time
|
||||||
|
DICENTRY(RPDOP0IT, 0x1400, 3, 2, 0, "receive PDO parameter 0, inhibit time")
|
||||||
|
// compatibility entry
|
||||||
|
DICENTRY(RPDOP0CE, 0x1400, 4, 1, 0, "receive PDO parameter 0, compatibility entry")
|
||||||
|
// event timer
|
||||||
|
DICENTRY(RPDOP0ET, 0x1400, 5, 2, 0, "receive PDO parameter 0, event timer")
|
||||||
|
|
||||||
|
// receive PDO mapping 0
|
||||||
|
// number of mapped application objects
|
||||||
|
DICENTRY(RPDOM0N, 0x1600, 0, 1, 0, "receive PDO mapping 0, number of objects")
|
||||||
|
// first map
|
||||||
|
DICENTRY(RPDOM0O1, 0x1600, 1, 4, 0, "receive PDO mapping 0, mapping for 1st object")
|
||||||
|
|
||||||
|
// transmit PDO parameter 0
|
||||||
|
// largest subindex supported
|
||||||
|
DICENTRY(TPDOP0LS, 0x1800, 0, 1, 0, "transmit PDO parameter 0, largest subindex supported")
|
||||||
|
// COB-ID used by PDO
|
||||||
|
DICENTRY(TPDOP0CI, 0x1800, 1, 4, 0, "transmit PDO parameter 0, COB-ID used by PDO")
|
||||||
|
// transmission type
|
||||||
|
DICENTRY(TPDOP0TT, 0x1800, 2, 1, 0, "transmit PDO parameter 0, transmission type")
|
||||||
|
// inhibit time
|
||||||
|
DICENTRY(TPDOP0IT, 0x1800, 3, 2, 0, "transmit PDO parameter 0, inhibit time")
|
||||||
|
// reserved
|
||||||
|
DICENTRY(TPDOP0R, 0x1800, 4, 1, 0, "transmit PDO parameter 0, reserved")
|
||||||
|
// event timer
|
||||||
|
DICENTRY(TPDOP0ET, 0x1800, 5, 2, 0, "transmit PDO parameter 0, event timer")
|
||||||
|
|
||||||
|
// transmit PDO mapping 0
|
||||||
|
// number of mapped application objects
|
||||||
|
DICENTRY(TPDOM0N, 0x1A00, 0, 1, 0, "transmit PDO mapping 0, number of objects")
|
||||||
|
// first map
|
||||||
|
DICENTRY(TPDOM0O1, 0x1A00, 1, 4, 0, "transmit PDO mapping 0, mapping for 1st object")
|
||||||
|
DICENTRY(TPDOM0O2, 0x1A00, 2, 4, 0, "transmit PDO mapping 0, mapping for 2nd object")
|
||||||
|
DICENTRY(TPDOM0O3, 0x1A00, 3, 4, 0, "transmit PDO mapping 0, mapping for 3rd object")
|
||||||
|
DICENTRY(TPDOM0O4, 0x1A00, 4, 4, 0, "transmit PDO mapping 0, mapping for 4th object")
|
||||||
|
DICENTRY(TPDOM0O5, 0x1A00, 5, 4, 0, "transmit PDO mapping 0, mapping for 5th object")
|
||||||
|
|
||||||
|
// node ID
|
||||||
|
DICENTRY(NODEID, 0x2002, 0, 1, 0, "node ID")
|
||||||
|
// baudrate
|
||||||
|
DICENTRY(BAUDRATE, 0x2003, 0, 1, 0, "baudrate")
|
||||||
|
// system control: 1- bootloader, 2 - save parameters, 3 - reset factory settings
|
||||||
|
DICENTRY(SYSCONTROL, 0x2007, 0, 1, 0, "system control: 1- bootloader, 2 - save parameters, 3 - reset factory settings")
|
||||||
|
// error status
|
||||||
|
DICENTRY(ERRSTATE, 0x6000, 0, 1, 0, "error status")
|
||||||
|
// controller status
|
||||||
|
DICENTRY(DEVSTATUS, 0x6001, 0, 1, 0, "controller status")
|
||||||
|
// rotation direction
|
||||||
|
DICENTRY(ROTDIR, 0x6002, 0, 1, 0, "rotation direction")
|
||||||
|
// maximal speed
|
||||||
|
DICENTRY(MAXSPEED, 0x6003, 0, 4, 1, "maximal speed")
|
||||||
|
// relative displacement
|
||||||
|
DICENTRY(RELSTEPS, 0x6004, 0, 4, 0, "relative displacement")
|
||||||
|
// operation mode
|
||||||
|
DICENTRY(OPMODE, 0x6005, 0, 1, 0, "operation mode")
|
||||||
|
// start speed
|
||||||
|
DICENTRY(STARTSPEED, 0x6006, 0, 2, 0, "start speed")
|
||||||
|
// stop speed
|
||||||
|
DICENTRY(STOPSPEED, 0x6007, 0, 2, 0, "stop speed")
|
||||||
|
// acceleration coefficient
|
||||||
|
DICENTRY(ACCELCOEF, 0x6008, 0, 1, 0, "acceleration coefficient")
|
||||||
|
// deceleration coefficient
|
||||||
|
DICENTRY(DECELCOEF, 0x6009, 0, 1, 0, "deceleration coefficient")
|
||||||
|
// microstepping
|
||||||
|
DICENTRY(MICROSTEPS, 0x600A, 0, 2, 0, "microstepping")
|
||||||
|
// max current
|
||||||
|
DICENTRY(MAXCURNT, 0x600B, 0, 2, 0, "maximum phase current")
|
||||||
|
// current position
|
||||||
|
DICENTRY(POSITION, 0x600C, 0, 4, 0, "current position")
|
||||||
|
// current reduction
|
||||||
|
DICENTRY(CURRREDUCT, 0x600D, 0, 1, 0, "current reduction")
|
||||||
|
// motor enable
|
||||||
|
DICENTRY(ENABLE, 0x600E, 0, 1, 0, "motor enable")
|
||||||
|
// EXT emergency stop enable
|
||||||
|
DICENTRY(EXTENABLE, 0x600F, 1, 1, 0, "EXT emergency stop enable")
|
||||||
|
// EXT emergency stop trigger mode
|
||||||
|
DICENTRY(EXTTRIGMODE, 0x600F, 2, 1, 0, "EXT emergency stop trigger mode")
|
||||||
|
// EXT emergency sensor type
|
||||||
|
DICENTRY(EXTSENSTYPE, 0x600F, 3, 1, 0, "EXT emergency sensor type")
|
||||||
|
// GPIO direction
|
||||||
|
DICENTRY(GPIODIR, 0x6011, 1, 2, 0, "GPIO direction")
|
||||||
|
// GPIO configuration
|
||||||
|
DICENTRY(GPIOCONF, 0x6011, 2, 4, 0, "GPIO configuration")
|
||||||
|
// GPIO value
|
||||||
|
DICENTRY(GPIOVAL, 0x6012, 0, 2, 0, "GPIO value")
|
||||||
|
// stall parameters
|
||||||
|
DICENTRY(STALLPARS, 0x6017, 0, 2, 0, "stall parameters (open loop)")
|
||||||
|
// offline operation
|
||||||
|
DICENTRY(OFFLNMBR, 0x6018, 1, 1, 0, "Number of offline programming command")
|
||||||
|
DICENTRY(OFFLENBL, 0x6018, 2, 1, 0, "Offline automatic operation enable")
|
||||||
|
// stall set
|
||||||
|
DICENTRY(STALLSET, 0x601B, 0, 1, 0, "stall set (open loop)")
|
||||||
|
// absolute displacement
|
||||||
|
DICENTRY(ABSSTEPS, 0x601C, 0, 4, 1, "absolute displacement")
|
||||||
|
// stop motor
|
||||||
|
DICENTRY(STOP, 0x6020, 0, 1, 0, "stop motor")
|
||||||
|
// encoder resolution
|
||||||
|
DICENTRY(ENCRESOL, 0x6021, 0, 2, 0, "encoder resolution (closed loop)")
|
||||||
|
// stall length parameter
|
||||||
|
DICENTRY(STALLLEN, 0x6028, 0, 2, 0, "stall length parameter (closed loop)")
|
||||||
|
// torque ring enable
|
||||||
|
DICENTRY(TORQRING, 0x6029, 0, 1, 0, "torque ring enable (closed loop)")
|
||||||
|
// autosave position
|
||||||
|
DICENTRY(POSAUTOSAVE, 0x602A, 0, 1, 0, "autosave position (closed loop)")
|
||||||
|
// real time speed
|
||||||
|
DICENTRY(REALTIMESPD, 0x6030, 0, 2, 1, "real time speed (closed loop)")
|
||||||
|
// calibration zero
|
||||||
|
DICENTRY(CALIBZERO, 0x6034, 0, 4, 1, "calibration zero")
|
||||||
|
// encoder position
|
||||||
|
DICENTRY(ENCPOS, 0x6035, 0, 4, 1, "encoder position")
|
||||||
|
|
||||||
@ -54,7 +54,6 @@ int main(int argc, char **argv){
|
|||||||
return parse_data_file(GP->checkfile, 0);
|
return parse_data_file(GP->checkfile, 0);
|
||||||
}*/
|
}*/
|
||||||
char *dev = find_device();
|
char *dev = find_device();
|
||||||
red("dev = %s\n", dev);
|
|
||||||
if(!dev) ERRX("Serial device not found!");
|
if(!dev) ERRX("Serial device not found!");
|
||||||
FREE(dev);
|
FREE(dev);
|
||||||
signal(SIGTERM, signals); // kill (-15) - quit
|
signal(SIGTERM, signals); // kill (-15) - quit
|
||||||
@ -69,7 +68,6 @@ int main(int argc, char **argv){
|
|||||||
while(v--){ // increase loglevel
|
while(v--){ // increase loglevel
|
||||||
if(++lvl == LOGLEVEL_ANY) break;
|
if(++lvl == LOGLEVEL_ANY) break;
|
||||||
}
|
}
|
||||||
green("Log level: %d\n", lvl);
|
|
||||||
OPENLOG(GP->logfile, lvl);
|
OPENLOG(GP->logfile, lvl);
|
||||||
}
|
}
|
||||||
#ifndef EBUG
|
#ifndef EBUG
|
||||||
@ -81,10 +79,10 @@ int main(int argc, char **argv){
|
|||||||
while(1){ // guard for dead processes
|
while(1){ // guard for dead processes
|
||||||
pid_t childpid = fork();
|
pid_t childpid = fork();
|
||||||
if(childpid){
|
if(childpid){
|
||||||
LOGDBG("create child with PID %d", childpid);
|
LOGDBG("Create child with PID %d", childpid);
|
||||||
DBG("Created child with PID %d\n", childpid);
|
DBG("Created child with PID %d\n", childpid);
|
||||||
wait(NULL);
|
wait(NULL);
|
||||||
LOGDBG("child %d died", childpid);
|
LOGDBG("Child %d died", childpid);
|
||||||
WARNX("Child %d died\n", childpid);
|
WARNX("Child %d died\n", childpid);
|
||||||
sleep(1);
|
sleep(1);
|
||||||
}else{
|
}else{
|
||||||
|
|||||||
26
canserver/processmotors.c
Normal file
26
canserver/processmotors.c
Normal file
@ -0,0 +1,26 @@
|
|||||||
|
/*
|
||||||
|
* This file is part of the CANserver project.
|
||||||
|
* Copyright 2020 Edward V. Emelianov <edward.emelianoff@gmail.com>.
|
||||||
|
*
|
||||||
|
* 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 3 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 "aux.h"
|
||||||
|
#include "canbus.h"
|
||||||
|
#include "canopen.h"
|
||||||
|
#include "cmdlnopts.h"
|
||||||
|
#include "processmotors.h"
|
||||||
|
#include "pusirobot.h"
|
||||||
|
|
||||||
|
#include <usefull_macros.h>
|
||||||
25
canserver/processmotors.h
Normal file
25
canserver/processmotors.h
Normal file
@ -0,0 +1,25 @@
|
|||||||
|
/*
|
||||||
|
* This file is part of the CANserver project.
|
||||||
|
* Copyright 2020 Edward V. Emelianov <edward.emelianoff@gmail.com>.
|
||||||
|
*
|
||||||
|
* 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 3 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/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
#ifndef PROCESSMOTORS_H__
|
||||||
|
#define PROCESSMOTORS_H__
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
#endif // PROCESSMOTORS_H__
|
||||||
106
canserver/proto.c
Normal file
106
canserver/proto.c
Normal file
@ -0,0 +1,106 @@
|
|||||||
|
/*
|
||||||
|
* This file is part of the CANserver project.
|
||||||
|
* Copyright 2020 Edward V. Emelianov <edward.emelianoff@gmail.com>.
|
||||||
|
*
|
||||||
|
* 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 3 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 "aux.h"
|
||||||
|
#include "cmdlnopts.h"
|
||||||
|
#include "proto.h"
|
||||||
|
|
||||||
|
#include <stdio.h>
|
||||||
|
#include <string.h>
|
||||||
|
#include <usefull_macros.h>
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief sendraw - send raw data to CANbus
|
||||||
|
* @param id - CANid (in string format)
|
||||||
|
* @param data - data to send (delimeters are: space, tab, comma or semicolon)
|
||||||
|
* WARNING! parameter `data` will be broken after this function
|
||||||
|
* id & data can be decimal, hexadecimal or octal
|
||||||
|
* @return answer to client
|
||||||
|
*/
|
||||||
|
char *sendraw(char *id, char *data){
|
||||||
|
char buf[128], *s, *saveptr;
|
||||||
|
if(!id) return strdup("Need CAN ID\n");
|
||||||
|
long ID, info[8]={0};
|
||||||
|
int i;
|
||||||
|
if(str2long(id, &ID)){
|
||||||
|
snprintf(buf, 128, "Wrong ID: %s\n", id);
|
||||||
|
return strdup(buf);
|
||||||
|
}
|
||||||
|
for(s = data, i = 0; ; s = NULL, ++i){
|
||||||
|
char *nxt = strtok_r(s, " \t,;\r\n", &saveptr);
|
||||||
|
if(!nxt) break;
|
||||||
|
if(str2long(nxt, &info[i])) break;
|
||||||
|
}
|
||||||
|
if(i > 8) return strdup("Not more than 8 data bytes\n");
|
||||||
|
snprintf(buf, 128, "ID=%ld, datalen=%d, data={%ld, %ld, %ld, %ld, %ld, %ld, %ld, %ld}\n",
|
||||||
|
ID, i, info[0], info[1], info[2], info[3], info[4], info[5], info[6], info[7]);
|
||||||
|
return strdup(buf);
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Commands format:
|
||||||
|
* function [[NAME] data]
|
||||||
|
* where:
|
||||||
|
* - function - function name (register, help, unregister, stop, absmove etc),
|
||||||
|
* - NAME - thread name,
|
||||||
|
* - data - function data (e.g. relmove turret1 150)
|
||||||
|
* you can get full list of functions by function `help`
|
||||||
|
*/
|
||||||
|
|
||||||
|
typedef struct{
|
||||||
|
char *fname;
|
||||||
|
char *(*handler)(char *arg1, char *arg2);
|
||||||
|
} cmditem;
|
||||||
|
|
||||||
|
// array with known functions
|
||||||
|
static cmditem functions[] = {
|
||||||
|
{"raw", sendraw},
|
||||||
|
{NULL, NULL}
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief processCommand - parse command received by socket
|
||||||
|
* @param cmd (io) - text command (after this function its content will be broken!)
|
||||||
|
* @return answer to user (or NULL if none) !!!ALLOCATED HERE, should be FREEd!!!
|
||||||
|
*/
|
||||||
|
char *processCommand(char *cmd){
|
||||||
|
if(!cmd) return NULL;
|
||||||
|
char *saveptr = NULL, *fname = NULL, *procname = NULL, *data = NULL;
|
||||||
|
DBG("Got %s", cmd);
|
||||||
|
fname = strtok_r(cmd, " \t\r\n", &saveptr);
|
||||||
|
DBG("fname: %s", fname);
|
||||||
|
if(fname){
|
||||||
|
procname = strtok_r(NULL, " \t\r\n", &saveptr);
|
||||||
|
DBG("procname: %s", procname);
|
||||||
|
if(procname){
|
||||||
|
data = saveptr;
|
||||||
|
DBG("data: %s", data);
|
||||||
|
}
|
||||||
|
}else return NULL;
|
||||||
|
for(cmditem *item = functions; item->fname; ++item){
|
||||||
|
if(0 == strcasecmp(item->fname, fname)) return item->handler(procname, data);
|
||||||
|
}
|
||||||
|
return strdup("Wrong command\n");
|
||||||
|
}
|
||||||
|
|
||||||
|
#if 0
|
||||||
|
static char buf[1024];
|
||||||
|
snprintf(buf, 1024, "FUNC=%s, PROC=%s, CMD=%s\n", fname, procname, data);
|
||||||
|
DBG("buf: %s", buf);
|
||||||
|
return buf;
|
||||||
|
#endif
|
||||||
25
canserver/proto.h
Normal file
25
canserver/proto.h
Normal file
@ -0,0 +1,25 @@
|
|||||||
|
/*
|
||||||
|
* This file is part of the CANserver project.
|
||||||
|
* Copyright 2020 Edward V. Emelianov <edward.emelianoff@gmail.com>.
|
||||||
|
*
|
||||||
|
* 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 3 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/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
#ifndef PROTO_H__
|
||||||
|
#define PROTO_H__
|
||||||
|
|
||||||
|
char *processCommand(char *cmd);
|
||||||
|
|
||||||
|
#endif // PROTO_H__
|
||||||
82
canserver/pusirobot.c
Normal file
82
canserver/pusirobot.c
Normal file
@ -0,0 +1,82 @@
|
|||||||
|
/*
|
||||||
|
* This file is part of the stepper project.
|
||||||
|
* Copyright 2020 Edward V. Emelianov <edward.emelianoff@gmail.com>.
|
||||||
|
*
|
||||||
|
* 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 3 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 <stdlib.h> // for NULL
|
||||||
|
|
||||||
|
#include "pusirobot.h"
|
||||||
|
|
||||||
|
// we should init constants here!
|
||||||
|
#undef DICENTRY
|
||||||
|
#define DICENTRY(name, idx, sidx, sz, s, n) const SDO_dic_entry name = {idx, sidx, sz, s, n};
|
||||||
|
#include "dicentries.in"
|
||||||
|
|
||||||
|
// now init array with all dictionary
|
||||||
|
#undef DICENTRY
|
||||||
|
#define nnn(nm) nm
|
||||||
|
#define lnk(nm) & ## nnn(nm)
|
||||||
|
#define DICENTRY(name, idx, sidx, sz, s, n) &name,
|
||||||
|
const SDO_dic_entry* allrecords[] = {
|
||||||
|
#include "dicentries.in"
|
||||||
|
};
|
||||||
|
const int DEsz = sizeof(allrecords) / sizeof(SDO_dic_entry*);
|
||||||
|
|
||||||
|
// controller status for bits
|
||||||
|
static const char *DevStatus[] = {
|
||||||
|
"External stop 1",
|
||||||
|
"External stop 2",
|
||||||
|
"Stall state",
|
||||||
|
"Busy state",
|
||||||
|
"External stop 3",
|
||||||
|
"The FIFO of PVT Mode 3 is empty",
|
||||||
|
"FIFO Lower bound of PVT Mode 3",
|
||||||
|
"FIFO upper limit of PVT mode 3"
|
||||||
|
};
|
||||||
|
|
||||||
|
// controller error statuses
|
||||||
|
static const char *DevError[] = {
|
||||||
|
"TSD, over temperature shutdown",
|
||||||
|
"AERR, coil A error",
|
||||||
|
"BERR, coil B error",
|
||||||
|
"AOC, A over current",
|
||||||
|
"BOC, B over current",
|
||||||
|
"UVLO, low voltage fault"
|
||||||
|
};
|
||||||
|
|
||||||
|
// return status message for given bit in status
|
||||||
|
const char *devstatus(uint8_t status, uint8_t bit){
|
||||||
|
if(bit > 7) return NULL;
|
||||||
|
if(status & (1<<bit)) return DevStatus[bit];
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
// error codes explanation
|
||||||
|
const char *errname(uint8_t error, uint8_t bit){
|
||||||
|
if(bit > 5) return NULL;
|
||||||
|
if(error & (1<<bit)) return DevError[bit];
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
// search if the object exists in dictionary - for config file parser
|
||||||
|
SDO_dic_entry *dictentry_search(uint16_t index, uint8_t subindex){
|
||||||
|
// the search is linear as dictionary can be unsorted!!!
|
||||||
|
for(int i = 0; i < DEsz; ++i){
|
||||||
|
const SDO_dic_entry *entry = allrecords[i];
|
||||||
|
if(entry->index == index && entry->subindex == subindex) return (SDO_dic_entry*)entry;
|
||||||
|
}
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
55
canserver/pusirobot.h
Normal file
55
canserver/pusirobot.h
Normal file
@ -0,0 +1,55 @@
|
|||||||
|
/*
|
||||||
|
* This file is part of the stepper project.
|
||||||
|
* Copyright 2020 Edward V. Emelianov <edward.emelianoff@gmail.com>.
|
||||||
|
*
|
||||||
|
* 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 3 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/>.
|
||||||
|
*/
|
||||||
|
#pragma once
|
||||||
|
#ifndef PUSIROBOT_H__
|
||||||
|
#define PUSIROBOT_H__
|
||||||
|
|
||||||
|
#include <stdint.h>
|
||||||
|
|
||||||
|
// entry of SDO dictionary
|
||||||
|
typedef struct{
|
||||||
|
uint16_t index; // SDO index
|
||||||
|
uint8_t subindex; // SDO subindex
|
||||||
|
uint8_t datasize; // data size: 1,2,3 or 4 bytes
|
||||||
|
uint8_t issigned; // signess: if issigned==1, then signed, else unsigned
|
||||||
|
const char *name; // dictionary entry name
|
||||||
|
} SDO_dic_entry;
|
||||||
|
|
||||||
|
#ifndef DICENTRY
|
||||||
|
#define DICENTRY(name, idx, sidx, sz, s, n) extern const SDO_dic_entry name;
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#include "dicentries.in"
|
||||||
|
|
||||||
|
extern const int DEsz;
|
||||||
|
extern const SDO_dic_entry* allrecords[];
|
||||||
|
|
||||||
|
#define MAX_SPEED_MIN -200000
|
||||||
|
#define MAX_SPEED_MAX 200000
|
||||||
|
|
||||||
|
// limit switches mask in GPIO status register (x=1,2,3)
|
||||||
|
#define EXTMASK(x) (1<<(6+x))
|
||||||
|
|
||||||
|
#define EXTACTIVE(x, reg) ((reg&EXTMASK(x)) ? 1:0)
|
||||||
|
|
||||||
|
// unclearable status
|
||||||
|
#define BUSY_STATE (1<<3)
|
||||||
|
const char *devstatus(uint8_t status, uint8_t bit);
|
||||||
|
const char *errname(uint8_t error, uint8_t bit);
|
||||||
|
SDO_dic_entry *dictentry_search(uint16_t index, uint8_t subindex);
|
||||||
|
#endif // PUSIROBOT_H__
|
||||||
@ -18,6 +18,7 @@
|
|||||||
|
|
||||||
#include "aux.h"
|
#include "aux.h"
|
||||||
#include "cmdlnopts.h" // glob_pars
|
#include "cmdlnopts.h" // glob_pars
|
||||||
|
#include "proto.h"
|
||||||
#include "socket.h"
|
#include "socket.h"
|
||||||
#include "term.h"
|
#include "term.h"
|
||||||
|
|
||||||
@ -34,7 +35,8 @@
|
|||||||
#include <unistd.h> // daemon
|
#include <unistd.h> // daemon
|
||||||
#include <usefull_macros.h>
|
#include <usefull_macros.h>
|
||||||
|
|
||||||
#define BUFLEN (10240)
|
// buffer size for received data
|
||||||
|
#define BUFLEN (1024)
|
||||||
// Max amount of connections
|
// Max amount of connections
|
||||||
#define BACKLOG (30)
|
#define BACKLOG (30)
|
||||||
|
|
||||||
@ -75,21 +77,21 @@ static int waittoread(int sock){
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**************** SERVER FUNCTIONS ****************/
|
/**************** SERVER FUNCTIONS ****************/
|
||||||
pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;
|
//pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;
|
||||||
/**
|
/**
|
||||||
* Send data over socket
|
* Send data over socket
|
||||||
* @param sock - socket fd
|
* @param sock - socket fd
|
||||||
* @param textbuf - zero-trailing buffer with data to send
|
* @param textbuf - zero-trailing buffer with data to send
|
||||||
* @return 1 if all OK
|
* @return amount of sent bytes
|
||||||
*/
|
*/
|
||||||
static int send_data(int sock, char *textbuf){
|
static size_t send_data(int sock, char *textbuf){
|
||||||
ssize_t Len = strlen(textbuf);
|
ssize_t Len = strlen(textbuf);
|
||||||
if(Len != write(sock, textbuf, Len)){
|
if(Len != write(sock, textbuf, Len)){
|
||||||
WARN("write()");
|
WARN("write()");
|
||||||
LOGERR("send_data(): write() failed");
|
LOGERR("send_data(): write() failed");
|
||||||
return 0;
|
return 0;
|
||||||
}else LOGDBG("send_data(): sent '%s'", textbuf);
|
}else LOGDBG("send_data(): sent '%s'", textbuf);
|
||||||
return 1;
|
return (size_t)Len;
|
||||||
}
|
}
|
||||||
|
|
||||||
#if 0
|
#if 0
|
||||||
@ -109,17 +111,11 @@ static char* stringscan(char *str, char *needle){
|
|||||||
#endif
|
#endif
|
||||||
|
|
||||||
static void *handle_socket(void *asock){
|
static void *handle_socket(void *asock){
|
||||||
//putlog("handle_socket(): getpid: %d, pthread_self: %lu, tid: %lu",getpid(), pthread_self(), syscall(SYS_gettid));
|
|
||||||
FNAME();
|
FNAME();
|
||||||
int sock = *((int*)asock);
|
int sock = *((int*)asock);
|
||||||
char buff[BUFLEN];
|
char buff[BUFLEN];
|
||||||
ssize_t rd;
|
ssize_t rd;
|
||||||
double t0 = dtime();
|
while(1){
|
||||||
/*
|
|
||||||
* INSERT CODE HERE
|
|
||||||
* change to while(1) if socket shouldn't be closed after data transmission
|
|
||||||
*/
|
|
||||||
while(dtime() - t0 < SOCKET_TIMEOUT){
|
|
||||||
if(!waittoread(sock)){ // no data incoming
|
if(!waittoread(sock)){ // no data incoming
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
@ -138,28 +134,23 @@ static void *handle_socket(void *asock){
|
|||||||
buff[rd] = 0;
|
buff[rd] = 0;
|
||||||
// now we should check what do user want
|
// now we should check what do user want
|
||||||
// here we can process user data
|
// here we can process user data
|
||||||
DBG("user send: %s", buff);
|
DBG("user send '%s'", buff);
|
||||||
LOGDBG("user send %s", buff);
|
LOGDBG("user send '%s'", buff);
|
||||||
if(GP->echo){
|
if(GP->echo){
|
||||||
if(!send_data(sock, buff)){
|
if(!send_data(sock, buff)){
|
||||||
LOGWARN("Can't send data to user, some error occured");
|
WARN("Can't send data to user, some error occured");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
pthread_mutex_lock(&mutex);
|
//pthread_mutex_lock(&mutex);
|
||||||
/*
|
char *ans = processCommand(buff); // run command parser
|
||||||
* INSERT CODE HERE
|
if(ans){
|
||||||
* Process user commands here & send him an answer
|
send_data(sock, ans); // send answer
|
||||||
*/
|
FREE(ans);
|
||||||
pthread_mutex_unlock(&mutex);
|
}
|
||||||
t0 = dtime();
|
//pthread_mutex_unlock(&mutex);
|
||||||
}
|
|
||||||
if(dtime() - t0 > SOCKET_TIMEOUT){
|
|
||||||
LOGDBG("Socket %d closed on timeout", sock);
|
|
||||||
DBG("Closed on timeout");
|
|
||||||
}else{
|
|
||||||
LOGDBG("Socket %d closed", sock);
|
|
||||||
DBG("Socket closed");
|
|
||||||
}
|
}
|
||||||
|
LOGDBG("Socket %d closed", sock);
|
||||||
|
DBG("Socket closed");
|
||||||
close(sock);
|
close(sock);
|
||||||
pthread_exit(NULL);
|
pthread_exit(NULL);
|
||||||
return NULL;
|
return NULL;
|
||||||
@ -201,17 +192,17 @@ static void *server(void *asock){
|
|||||||
// data gathering & socket management
|
// data gathering & socket management
|
||||||
static void daemon_(int sock){
|
static void daemon_(int sock){
|
||||||
if(sock < 0) return;
|
if(sock < 0) return;
|
||||||
pthread_t sock_thread;
|
|
||||||
if(pthread_create(&sock_thread, NULL, server, (void*) &sock)){
|
|
||||||
LOGERR("daemon_(): pthread_create() failed");
|
|
||||||
ERR("pthread_create()");
|
|
||||||
}
|
|
||||||
double tgot = 0.;
|
double tgot = 0.;
|
||||||
char *devname = find_device();
|
char *devname = find_device();
|
||||||
if(!devname){
|
if(!devname){
|
||||||
LOGERR("Can't find serial device");
|
LOGERR("Can't find serial device");
|
||||||
ERRX("Can't find serial device");
|
ERRX("Can't find serial device");
|
||||||
}
|
}
|
||||||
|
pthread_t sock_thread;
|
||||||
|
if(pthread_create(&sock_thread, NULL, server, (void*) &sock)){
|
||||||
|
LOGERR("daemon_(): pthread_create() failed");
|
||||||
|
ERR("pthread_create()");
|
||||||
|
}
|
||||||
do{
|
do{
|
||||||
if(pthread_kill(sock_thread, 0) == ESRCH){ // died
|
if(pthread_kill(sock_thread, 0) == ESRCH){ // died
|
||||||
WARNX("Sockets thread died");
|
WARNX("Sockets thread died");
|
||||||
@ -230,7 +221,7 @@ static void daemon_(int sock){
|
|||||||
* Gather data (poll_device)
|
* Gather data (poll_device)
|
||||||
*/
|
*/
|
||||||
// copy temporary buffers to main
|
// copy temporary buffers to main
|
||||||
pthread_mutex_lock(&mutex);
|
//pthread_mutex_lock(&mutex);
|
||||||
int fd = open(devname, O_RDONLY);
|
int fd = open(devname, O_RDONLY);
|
||||||
if(fd == -1){
|
if(fd == -1){
|
||||||
WARN("open()");
|
WARN("open()");
|
||||||
@ -250,7 +241,7 @@ static void daemon_(int sock){
|
|||||||
* INSERT CODE HERE
|
* INSERT CODE HERE
|
||||||
* fill global data buffers
|
* fill global data buffers
|
||||||
*/
|
*/
|
||||||
pthread_mutex_unlock(&mutex);
|
//pthread_mutex_unlock(&mutex);
|
||||||
}while(1);
|
}while(1);
|
||||||
LOGERR("daemon_(): UNREACHABLE CODE REACHED!");
|
LOGERR("daemon_(): UNREACHABLE CODE REACHED!");
|
||||||
}
|
}
|
||||||
|
|||||||
324
canserver/threadlist.c
Normal file
324
canserver/threadlist.c
Normal file
@ -0,0 +1,324 @@
|
|||||||
|
/*
|
||||||
|
* This file is part of the CANserver project.
|
||||||
|
* Copyright 2020 Edward V. Emelianov <edward.emelianoff@gmail.com>.
|
||||||
|
*
|
||||||
|
* 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 3 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 "threadlist.h"
|
||||||
|
|
||||||
|
#include <stdio.h>
|
||||||
|
#include <stdlib.h>
|
||||||
|
#include <string.h>
|
||||||
|
#include <usefull_macros.h>
|
||||||
|
|
||||||
|
// global thread list
|
||||||
|
static threadlist *thelist = NULL;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* push data into the tail of a list (FIFO)
|
||||||
|
* @param lst (io) - list
|
||||||
|
* @param v (i) - data to push
|
||||||
|
* @return pointer to just pushed node
|
||||||
|
*/
|
||||||
|
static msglist *pushmessage(msglist **lst, char *v){
|
||||||
|
if(!lst || !v) return NULL;
|
||||||
|
msglist *node;
|
||||||
|
if((node = MALLOC(msglist, 1)) == 0)
|
||||||
|
return NULL; // allocation error
|
||||||
|
node->data = strdup(v);
|
||||||
|
if(!node->data){
|
||||||
|
FREE(node);
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
if(!*lst){
|
||||||
|
*lst = node;
|
||||||
|
(*lst)->last = node;
|
||||||
|
return node;
|
||||||
|
}
|
||||||
|
(*lst)->last->next = node;
|
||||||
|
(*lst)->last = node;
|
||||||
|
return node;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief popmessage - get data from head of list
|
||||||
|
* @param lst (io) - list
|
||||||
|
* @return data from first node or NULL if absent (SHOULD BE FREEd AFER USAGE!)
|
||||||
|
*/
|
||||||
|
static char *popmessage(msglist **lst){
|
||||||
|
if(!lst || !*lst) return NULL;
|
||||||
|
char *ret;
|
||||||
|
msglist *node = *lst;
|
||||||
|
ret = (*lst)->data;
|
||||||
|
*lst = (*lst)->next;
|
||||||
|
FREE(node);
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief getlast - find last element in thread list
|
||||||
|
* @return pointer to the last element or NULL if list is empty
|
||||||
|
*/
|
||||||
|
static threadlist *getlast(){
|
||||||
|
if(!thelist) return NULL;
|
||||||
|
threadlist *t = thelist;
|
||||||
|
while(t->next) t = t->next;
|
||||||
|
return t;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief findThreadByName - find thread by name
|
||||||
|
* @param name - thread name
|
||||||
|
* @return thread found or NULL if absent
|
||||||
|
*/
|
||||||
|
threadinfo *findThreadByName(char *name){
|
||||||
|
if(!name) return NULL;
|
||||||
|
if(!thelist) return NULL; // thread list is empty
|
||||||
|
size_t l = strlen(name);
|
||||||
|
if(l < 1 || l > THREADNAMEMAXLEN) return NULL;
|
||||||
|
DBG("Try to find thread '%s'", name);
|
||||||
|
threadlist *lptr = thelist;
|
||||||
|
while(lptr){
|
||||||
|
DBG("Check '%s'", lptr->ti.name);
|
||||||
|
if(strcmp(lptr->ti.name, name) == 0) return &lptr->ti;
|
||||||
|
lptr = lptr->next;
|
||||||
|
}
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief findThreadByID - find thread by numeric ID
|
||||||
|
* @param ID - thread ID
|
||||||
|
* @return thread found or NULL if absent
|
||||||
|
*/
|
||||||
|
threadinfo *findThreadByID(int ID){
|
||||||
|
if(!thelist) return NULL; // thread list is empty
|
||||||
|
DBG("Try to find thread with ID=%d", ID);
|
||||||
|
threadlist *lptr = thelist;
|
||||||
|
while(lptr){
|
||||||
|
DBG("Check %d", lptr->ti.ID);
|
||||||
|
if(ID == lptr->ti.ID) return &lptr->ti;
|
||||||
|
lptr = lptr->next;
|
||||||
|
}
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief addmesg - add message to thread's queue
|
||||||
|
* @param idx - index (MOSI/MISO)
|
||||||
|
* @param msg - message itself
|
||||||
|
* @param txt - data to add
|
||||||
|
* @return data added or NULL if failed
|
||||||
|
*/
|
||||||
|
char *addmesg(msgidx idx, message *msg, char *txt){
|
||||||
|
if(idx < 0 || idx >= idxNUM){
|
||||||
|
WARNX("Wrong message index");
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
if(!msg) return NULL;
|
||||||
|
size_t L = strlen(txt);
|
||||||
|
if(L < 1) return NULL;
|
||||||
|
DBG("Want to add mesg '%s' with length %zd", txt, L);
|
||||||
|
if(pthread_mutex_lock(&msg->mutex[idx])) return NULL;
|
||||||
|
if(!pushmessage(&msg->text[idx], txt)) return NULL;
|
||||||
|
pthread_mutex_unlock(&msg->mutex[idx]);
|
||||||
|
return msg->text[idx]->data;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief getmesg - get first message from queue (allocates data, should be free'd after usage!)
|
||||||
|
* @param idx - index (MOSI/MISO)
|
||||||
|
* @param msg - message itself
|
||||||
|
* @return data or NULL if empty
|
||||||
|
*/
|
||||||
|
char *getmesg(msgidx idx, message *msg){
|
||||||
|
if(idx < 0 || idx >= idxNUM){
|
||||||
|
WARNX("Wrong message index");
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
if(!msg) return NULL;
|
||||||
|
char *text = NULL;
|
||||||
|
if(pthread_mutex_lock(&msg->mutex[idx])) return NULL;
|
||||||
|
text = popmessage(&msg->text[idx]);
|
||||||
|
pthread_mutex_unlock(&msg->mutex[idx]);
|
||||||
|
return text;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief registerThread - register new thread
|
||||||
|
* @param name - thread name
|
||||||
|
* @param ID - thread numeric ID
|
||||||
|
* @param handler - thread handler
|
||||||
|
* @return pointer to new threadinfo struct or NULL if failed
|
||||||
|
*/
|
||||||
|
threadinfo *registerThread(char *name, int ID, void *(*handler)(void *)){
|
||||||
|
if(!name || strlen(name) < 1 || !handler) return NULL;
|
||||||
|
threadinfo *ti = findThreadByName(name);
|
||||||
|
DBG("Register new thread with name '%s' and ID=%d", name, ID);
|
||||||
|
if(ti){
|
||||||
|
WARNX("Thread named '%s' exists!", name);
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
ti = findThreadByID(ID);
|
||||||
|
if(ti){
|
||||||
|
WARNX("Thread with ID=%d exists!", ID);
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
if(!thelist){ // the first element
|
||||||
|
thelist = MALLOC(threadlist, 1);
|
||||||
|
ti = &thelist->ti;
|
||||||
|
}else{
|
||||||
|
threadlist *last = getlast();
|
||||||
|
last->next = MALLOC(threadlist, 1);
|
||||||
|
ti = &last->next->ti;
|
||||||
|
}
|
||||||
|
ti->handler = handler;
|
||||||
|
snprintf(ti->name, THREADNAMEMAXLEN+1, "%s", name);
|
||||||
|
ti->ID = ID;
|
||||||
|
memset(&ti->mesg, 0, sizeof(ti->mesg));
|
||||||
|
for(int i = 0; i < 2; ++i)
|
||||||
|
pthread_mutex_init(&ti->mesg.mutex[i], NULL);
|
||||||
|
if(pthread_create(&ti->thread, NULL, handler, (void*)ti)){
|
||||||
|
WARN("pthread_create()");
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
return ti;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief killThread - kill and unregister thread with given name
|
||||||
|
* @param name - thread's name
|
||||||
|
* @return 0 if all OK
|
||||||
|
*/
|
||||||
|
int killThread(const char *name){
|
||||||
|
if(!name || !thelist) return 1;
|
||||||
|
threadlist *lptr = thelist, *prev = NULL;
|
||||||
|
for(; lptr; lptr = lptr->next){
|
||||||
|
if(strcmp(lptr->ti.name, name)){
|
||||||
|
prev = lptr;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
DBG("Found '%s', prev: '%s', delete", name, prev->ti.name);
|
||||||
|
threadlist *next = lptr->next;
|
||||||
|
if(lptr == thelist) thelist = next;
|
||||||
|
else if(prev) prev->next = next;
|
||||||
|
for(int i = 0; i < 2; ++i){
|
||||||
|
pthread_mutex_lock(&lptr->ti.mesg.mutex[i]);
|
||||||
|
char *txt;
|
||||||
|
while((txt = popmessage(&lptr->ti.mesg.text[i]))) FREE(txt);
|
||||||
|
pthread_mutex_destroy(&lptr->ti.mesg.mutex[i]);
|
||||||
|
}
|
||||||
|
if(pthread_cancel(lptr->ti.thread)) WARN("Can't kill thread '%s'", name);
|
||||||
|
FREE(lptr);
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
return 2; // not found
|
||||||
|
}
|
||||||
|
|
||||||
|
#if 0
|
||||||
|
static void *handler(void *data){
|
||||||
|
threadinfo *ti = (threadinfo*)data;
|
||||||
|
while(1){
|
||||||
|
char *got = getmesg(idxMOSI, &ti->mesg);
|
||||||
|
if(got){
|
||||||
|
green("%s got: %s\n", ti->name, got);
|
||||||
|
FREE(got);
|
||||||
|
addmesg(idxMISO, &ti->mesg, "received");
|
||||||
|
addmesg(idxMISO, &ti->mesg, "need more");
|
||||||
|
}
|
||||||
|
usleep(100);
|
||||||
|
}
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void dividemessages(message *msg, char *longtext){
|
||||||
|
char *copy = strdup(longtext), *saveptr = NULL;
|
||||||
|
for(char *s = copy; ; s = NULL){
|
||||||
|
char *nxt = strtok_r(s, " ", &saveptr);
|
||||||
|
if(!nxt) break;
|
||||||
|
addmesg(idxMOSI, msg, nxt);
|
||||||
|
}
|
||||||
|
FREE(copy);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void procmesg(char *text){
|
||||||
|
if(!text) return;
|
||||||
|
char *nxt = strchr(text, ' ');
|
||||||
|
if(!nxt){
|
||||||
|
WARNX("Usage: cmd data, where cmd:\n"
|
||||||
|
"\tnew threadname - create thread\n"
|
||||||
|
"\tdel threadname - delete thread\n"
|
||||||
|
"\tsend threadname data - send data to thread\n"
|
||||||
|
"\tsend all data - send data to all\n");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
*nxt++ = 0;
|
||||||
|
if(strcasecmp(text, "new") == 0){
|
||||||
|
registerThread(nxt, handler);
|
||||||
|
}else if(strcasecmp(text, "del") == 0){
|
||||||
|
if(killThread(nxt)) WARNX("Can't delete '%s'", nxt);
|
||||||
|
}else if(strcasecmp(text, "send") == 0){
|
||||||
|
text = strchr(nxt, ' ');
|
||||||
|
if(!text){
|
||||||
|
WARNX("send all/threadname data");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
*text++ = 0;
|
||||||
|
if(strcasecmp(nxt, "all") == 0){ // bcast
|
||||||
|
threadlist *lptr = thelist;
|
||||||
|
while(lptr){
|
||||||
|
threadinfo *ti = &lptr->ti;
|
||||||
|
lptr = lptr->next;
|
||||||
|
green("Bcast send '%s' to thread '%s'\n", text, ti->name);
|
||||||
|
dividemessages(&ti->mesg, text);
|
||||||
|
}
|
||||||
|
}else{ // single
|
||||||
|
threadinfo *ti = findthread(nxt);
|
||||||
|
if(!ti){
|
||||||
|
WARNX("Thread '%s' not found", nxt);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
green("Send '%s' to thread '%s'\n", text, nxt);
|
||||||
|
dividemessages(&ti->mesg, text);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
int main(){
|
||||||
|
using_history();
|
||||||
|
while(1){
|
||||||
|
threadlist *lptr = thelist;
|
||||||
|
while(lptr){
|
||||||
|
threadinfo *ti = &lptr->ti;
|
||||||
|
lptr = lptr->next;
|
||||||
|
char *got;
|
||||||
|
while((got = getmesg(idxMISO, &ti->mesg))){
|
||||||
|
red("got from '%s': %s\n", ti->name, got);
|
||||||
|
fflush(stdout);
|
||||||
|
FREE(got);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
char *text = readline("mesg > ");
|
||||||
|
if(!text) break; // ^D
|
||||||
|
if(strlen(text) < 1) continue;
|
||||||
|
add_history(text);
|
||||||
|
procmesg(text);
|
||||||
|
FREE(text);
|
||||||
|
}
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
#endif
|
||||||
|
|
||||||
68
canserver/threadlist.h
Normal file
68
canserver/threadlist.h
Normal file
@ -0,0 +1,68 @@
|
|||||||
|
/*
|
||||||
|
* This file is part of the CANserver project.
|
||||||
|
* Copyright 2020 Edward V. Emelianov <edward.emelianoff@gmail.com>.
|
||||||
|
*
|
||||||
|
* 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 3 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/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
#ifndef THREADLIST_H__
|
||||||
|
#define THREADLIST_H__
|
||||||
|
|
||||||
|
#include <pthread.h>
|
||||||
|
|
||||||
|
// max length (in symbols) of thread name (any zero-terminated string)
|
||||||
|
#define THREADNAMEMAXLEN (31)
|
||||||
|
|
||||||
|
// messages FIFO
|
||||||
|
typedef struct msglist_{
|
||||||
|
char *data; // message itself
|
||||||
|
struct msglist_ *next, *last; // other elements of list
|
||||||
|
} msglist;
|
||||||
|
|
||||||
|
typedef enum{
|
||||||
|
idxMOSI = 0, // master out, slave in
|
||||||
|
idxMISO = 1, // master in, slave out
|
||||||
|
idxNUM = 2 // amount of indexes
|
||||||
|
} msgidx;
|
||||||
|
|
||||||
|
// interthread messages; index 0 - MOSI, index 1 - MISO
|
||||||
|
typedef struct{
|
||||||
|
msglist *text[idxNUM]; // stringified text messages
|
||||||
|
pthread_mutex_t mutex[idxNUM]; // text changing mutex
|
||||||
|
} message;
|
||||||
|
|
||||||
|
// thread information
|
||||||
|
typedef struct{
|
||||||
|
char name[THREADNAMEMAXLEN+1]; // thread name
|
||||||
|
int ID; // numeric ID (canopen ID)
|
||||||
|
message mesg; // inter-thread messages
|
||||||
|
pthread_t thread; // thread descriptor
|
||||||
|
void *(*handler)(void *); // handler function
|
||||||
|
} threadinfo;
|
||||||
|
|
||||||
|
// list of threads member
|
||||||
|
typedef struct thread_list_{
|
||||||
|
threadinfo ti; // base thread information
|
||||||
|
struct thread_list_ *next; // next element
|
||||||
|
} threadlist;
|
||||||
|
|
||||||
|
threadinfo *findThreadByName(char *name);
|
||||||
|
threadinfo *findThreadByID(int ID);
|
||||||
|
threadinfo *registerThread(char *name, int ID, void *(*handler)(void *));
|
||||||
|
int killThread(const char *name);
|
||||||
|
char *getmesg(msgidx idx, message *msg);
|
||||||
|
char *addmesg(msgidx idx, message *msg, char *txt);
|
||||||
|
|
||||||
|
#endif // THREADLIST_H__
|
||||||
Loading…
x
Reference in New Issue
Block a user