diff --git a/MMPP_control/new/main.c b/MMPP_control/new/main.c index 45ede30..6faf006 100644 --- a/MMPP_control/new/main.c +++ b/MMPP_control/new/main.c @@ -134,8 +134,7 @@ int main(int argc, char **argv){ signal(SIGTSTP, SIG_IGN); // ctrl+Z setbuf(stdout, NULL); G = parse_args(argc, argv); - char *self = strdup(argv[0]); - check4running(self, G->pidfile); + check4running(NULL, G->pidfile); DBG("Try to open serial %s", G->comdev); if(tty_tryopen(G->comdev, G->speed)){ ERR(_("Can't open %s with speed %d. Exit."), G->comdev, G->speed); diff --git a/MMPP_lib/CMake.readme b/MMPP_lib/CMake.readme new file mode 100644 index 0000000..e7c8ccd --- /dev/null +++ b/MMPP_lib/CMake.readme @@ -0,0 +1,4 @@ +cmake defines: +-DDEBUG=1 - debug mode +-DNOGETTEXT=1 - don't run xgettext +-DEXAMPLES=1 - to compile examples diff --git a/MMPP_lib/CMakeLists.txt b/MMPP_lib/CMakeLists.txt new file mode 100644 index 0000000..450d4ae --- /dev/null +++ b/MMPP_lib/CMakeLists.txt @@ -0,0 +1,161 @@ +cmake_minimum_required(VERSION 3.9) +set(PROJ mmpp) +set(MINOR_VERSION "1") +set(MID_VERSION "0") +set(MAJOR_VERSION "0") +set(PROJ_VERSION "${MAJOR_VERSION}.${MID_VERSION}.${MINOR_VERSION}") + +project(${PROJ} VERSION ${PROJ_VERSION} LANGUAGES C) + +# default flags +set(CMAKE_C_FLAGS "${CFLAGS} -O2") +set(CMAKE_C_FLAGS_RELEASE "${CMAKE_C_FLAGS}") +set(CMAKE_C_FLAGS_DEBUG "${CMAKE_C_FLAGS} -Wextra -Wall -Werror -W") +set(CMAKE_COLOR_MAKEFILE ON) + +# threads number definition +if(NOT DEFINED PROCESSOR_COUNT) + set(PROCESSOR_COUNT 2) # by default 2 cores + set(cpuinfo_file "/proc/cpuinfo") + if(EXISTS "${cpuinfo_file}") + file(STRINGS "${cpuinfo_file}" procs REGEX "^processor.: [0-9]+$") + list(LENGTH procs PROCESSOR_COUNT) + endif() +endif() +add_definitions(-DTHREAD_NUMBER=${PROCESSOR_COUNT}) +message("In multithreaded operations will use ${PROCESSOR_COUNT} threads") + +# cmake -DDEBUG=1 -> debugging +if(DEFINED DEBUG AND DEBUG EQUAL 1) + set(CMAKE_BUILD_TYPE "Debug") +else() + set(CMAKE_BUILD_TYPE "Release") +endif() + +if(CMAKE_BUILD_TYPE STREQUAL "Debug") + add_definitions(-DEBUG) + set(CMAKE_VERBOSE_MAKEFILE true) + if(CMAKE_INSTALL_PREFIX_INITIALIZED_TO_DEFAULT) + message("install to ${CMAKE_CURRENT_SOURCE_DIR}/install ") + set(CMAKE_INSTALL_PREFIX ${CMAKE_CURRENT_SOURCE_DIR}/install) + endif() + set(CMAKE_C_FLAGS ${CMAKE_C_FLAGS_DEBUG}) +else() + set(CMAKE_C_FLAGS ${CMAKE_C_FLAGS_RELEASE}) +endif() + +message("Build type: ${CMAKE_BUILD_TYPE}") + +# here is one of two variants: all .c in directory or .c files in list +aux_source_directory(${CMAKE_CURRENT_SOURCE_DIR} SOURCES) + +# directory should contain dir locale/ru for gettext translations +if(0) +set(LCPATH ${CMAKE_SOURCE_DIR}/locale/ru) +if(NOT DEFINED LOCALEDIR) + if(CMAKE_BUILD_TYPE STREQUAL "Debug") + set(LOCALEDIR ${CMAKE_CURRENT_SOURCE_DIR}/locale) + else() + set(LOCALEDIR ${CMAKE_INSTALL_PREFIX}/share/locale) + endif() +endif() +endif(0) + +###### pkgconfig ###### +# pkg-config modules (for pkg-check-modules) +#set(MODULES cfitsio fftw3) +# find packages: +#find_package(PkgConfig REQUIRED) +#pkg_check_modules(${PROJ} REQUIRED ${MODULES}) + +# external modules like OpenMP: +include(FindOpenMP) +if(OPENMP_FOUND) + set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} ${OpenMP_C_FLAGS}") + set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} ${OpenMP_EXE_LINKER_FLAGS}") + add_definitions(-DOMP_FOUND) +endif() +###### additional flags ###### +#list(APPEND ${PROJ}_LIBRARIES "-lfftw3_threads") + +# gettext files +set(PO_FILE ${LCPATH}/messages.po) +set(MO_FILE ${LCPATH}/LC_MESSAGES/${PROJ}.mo) +set(RU_FILE ${LCPATH}/ru.po) + +# exe file +#add_executable(${PROJ} ${SOURCES}) +# library +add_library(${PROJ} SHARED ${SOURCES}) +# library header files +set(LIBHEADER "usefull_macros.h") +# -I +include_directories(${${PROJ}_INCLUDE_DIRS}) +# -L +link_directories(${${PROJ}_LIBRARY_DIRS}) +# -D +add_definitions(-DLOCALEDIR=\"${LOCALEDIR}\" + -DPACKAGE_VERSION=\"${PROJ_VERSION}\" -DGETTEXT_PACKAGE=\"${PROJ}\" + -DMINOR_VERSION=\"${MINOR_VERSION}\" -DMID_VERSION=\"${MID_VERSION}\" + -DMAJOR_VERSION=\"${MAJOR_VESION}\") + +# -l +target_link_libraries(${PROJ} ${${PROJ}_LIBRARIES}) + +set(PCFILE "${CMAKE_BINARY_DIR}/${PROJ}.pc") +configure_file("${PROJ}.pc.in" ${PCFILE} @ONLY) + +set_target_properties(${PROJ} PROPERTIES VERSION ${PROJ_VERSION}) +set_target_properties(${PROJ} PROPERTIES PUBLIC_HEADER ${LIBHEADER}) + +# Installation of the program +include(GNUInstallDirs) +#install(TARGETS ${PROJ} DESTINATION "bin") +install(TARGETS ${PROJ} LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR} + PUBLIC_HEADER DESTINATION ${CMAKE_INSTALL_INCLUDEDIR}) +install(FILES ${PCFILE} DESTINATION ${CMAKE_INSTALL_DATAROOTDIR}/pkgconfig) + +# EXAMPLES +if(DEFINED EXAMPLES AND EXAMPLES EQUAL 1) + add_subdirectory(examples) +endif() + +###### gettext ###### +if(0) +if(NOT DEFINED NOGETTEXT) + add_definitions(-DGETTEXT) + if(CMAKE_BUILD_TYPE STREQUAL "Debug") + message("Generate locale files @ make") + find_package(Gettext REQUIRED) + find_program(GETTEXT_XGETTEXT_EXECUTABLE xgettext) + if(NOT GETTEXT_XGETTEXT_EXECUTABLE OR NOT GETTEXT_MSGFMT_EXECUTABLE) + message(FATAL_ERROR "xgettext not found") + endif() + file(MAKE_DIRECTORY ${LCPATH}) + file(MAKE_DIRECTORY ${LCPATH}/LC_MESSAGES) + + add_custom_command( + OUTPUT ${PO_FILE} + WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR} + COMMAND ${GETTEXT_XGETTEXT_EXECUTABLE} --from-code=utf-8 ${SOURCES} -c -k_ -kN_ -o ${PO_FILE} + COMMAND sed -i 's/charset=.*\\\\n/charset=koi8-r\\\\n/' ${PO_FILE} + COMMAND enconv ${PO_FILE} + DEPENDS ${SOURCES} + ) + # we need this to prevent ru.po & .mo from deleting by make clean + add_custom_target( + RU_FILE + COMMAND [ -f ${RU_FILE} ] && ${GETTEXT_MSGMERGE_EXECUTABLE} -Uis ${RU_FILE} ${PO_FILE} || cp ${PO_FILE} ${RU_FILE} + DEPENDS ${PO_FILE} ${SOURCES} + ) + add_custom_target( + MO_FILE + COMMAND make RU_FILE && ${GETTEXT_MSGFMT_EXECUTABLE} ${RU_FILE} -o ${MO_FILE} + DEPENDS ${RU_FILE} + ) + add_dependencies(${PROJ} MO_FILE) + else() # install .mo file + install(FILES ${MO_FILE} DESTINATION "${LOCALEDIR}/ru/LC_MESSAGES") + endif() +endif(NOT DEFINED NOGETTEXT) +endif(0) \ No newline at end of file diff --git a/MMPP_lib/examples/CMakeLists.txt b/MMPP_lib/examples/CMakeLists.txt new file mode 100644 index 0000000..9081b17 --- /dev/null +++ b/MMPP_lib/examples/CMakeLists.txt @@ -0,0 +1,10 @@ +cmake_minimum_required(VERSION 3.9) +project(examples) +link_libraries(mmpp) +include_directories(../) + +#target_link_libraries(hello -lm) +#add_executable(movestages movecmdlnopts.c move.c) +#target_link_libraries(movestages -lusefull_macros) +add_executable(testmove tmcmdlnopts.c tm.c) +target_link_libraries(testmove -lusefull_macros) diff --git a/MMPP_lib/examples/move.c b/MMPP_lib/examples/move.c new file mode 100644 index 0000000..b6234a2 --- /dev/null +++ b/MMPP_lib/examples/move.c @@ -0,0 +1,203 @@ +/* + * geany_encoding=koi8-r + * main.c + * + * Copyright 2018 Edward V. Emelianov + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, + * MA 02110-1301, USA. + * + */ + +#include "movecmdlnopts.h" +#include "signal.h" +#include +#include +#include +#include + +// All return states of main(): +enum{ + RET_ALLOK = 0, + RET_NOTFOUND, // none of MCU found or didn't found seeking MCU + RET_ONLYONE, // only one MCU found + RET_COMMERR, // communication error + RET_CANTINIT, // can't init motors: error during initiation or some of motors are moving + RET_WAITERR, // error occured during waiting procedure + RET_ERROR = 9, // uncoverable error - from libsnippets + RET_HELPCALL = 255 // user call help (or give wrong parameter[s]) - from libsnippets +}; + +static glob_pars *G; + +/** + * We REDEFINE the default WEAK function of signal processing + */ +void __attribute__((noreturn)) signals(int sig){ + if(sig){ + signal(sig, SIG_IGN); + DBG("Get signal %d, quit.\n", sig); + } + if(G->pidfile) // remove unnesessary PID file + unlink(G->pidfile); + restore_console(); + mmpp_close(); + exit(sig); +} + +void __attribute__((noreturn)) iffound_default(pid_t pid){ + ERRX("Another copy of this process found, pid=%d. Exit.", pid); +} + +static double convangle(double val){ + int X = (int)(val / 360.); + val -= 360. * (double)X; + return val; +} + +/** + * @return 1 if motor start moving else return 0 + */ +static int parsemotans(ttysend_status ans, const char *prefix){ + DBG("parse ans: %d (prefix: %s)", ans, prefix); + if(ans == SEND_ALLOK) return 1; + else if(ans == SEND_ERR) WARNX(_("%s moving error!"), prefix); + else if(ans == SEND_ESWITCH) WARNX(_("%s is on end-switch and can't move further"), prefix); + else if(ans == SEND_OTHER) WARNX(_("Can't move %s: bad steps amount or still moving"), prefix); + return 0; +} + +/** + * move motor + * @return 0 if motor can't move, else return 1 + */ +/** + * @brief movemotor - move motor + * @param mcu - MCU# (controller No, 1 or 2) + * @param motnum - motor# (0 or 1) + * @param steps - steps amount ( + * @param name + * @return 0 if motor can't move, else return 1 + */ +static int movemotor(int mcu, int motnum, int steps, const char *name){ + char buf[32]; + int curpos = mot_getpos(mcu, motnum); + if(curpos == INT_MIN){ + WARNX(_("Can't get current %s position"), name); + return 0; + } + if(curpos < 0){ // need to init + WARNX(_("Init of %s failed"), name); + return 0; + } + if(G->absmove){ + if(steps < 0){ + if(motnum == 1){ + steps += (mcu == 1) ? STEPSREV1 : STEPSREV2; // convert rotator angle to positive + }else{ + WARNX(_("Can't move to negative position")); + return 0; + } + } + steps -= curpos; + } + if(steps == 0){ + MSG(name, _("already at position")); + return 0; + } + DBG("try to move motor%d of mcu %d for %d steps", motnum, mcu, steps); + snprintf(buf, 32, "%dM%dM%d", mcu, motnum, steps); + ttysend_status ans = tty_sendcmd(buf); + return parsemotans(ans, name); +} + +int main(int argc, char **argv){ +// char cmd[32]; + int waitforstop = 0; + int rtn_status = RET_ALLOK; + initial_setup(); + signal(SIGTERM, signals); // kill (-15) + signal(SIGINT, signals); // ctrl+C + signal(SIGQUIT, SIG_IGN); // ctrl+\ . + signal(SIGTSTP, SIG_IGN); // ctrl+Z + setbuf(stdout, NULL); + G = parse_args(argc, argv); + check4running(NULL, G->pidfile); + DBG("Try to open serial %s", G->comdev); + if(mmpp_tryopen(G->comdev, G->speed)){ + ERRX(_("Can't open %s with speed %d. Exit."), G->comdev, G->speed); + } + + if(handshake()) signals(RET_NOTFOUND); // test connection & get all positions + if(G->waitold){ + if(tty_wait()) signals(RET_WAITERR); + handshake(); + } + if(G->showtemp){ + if(showtemp() != 2) rtn_status = RET_ONLYONE; + } + if(G->stopall){ // stop everything before analyze other commands + if(tty_stopall()) rtn_status = RET_COMMERR; + else MSG(_("All motors stopped"), NULL); + } + if(G->sendraw){ + MSG(_("Send raw string"), G->sendraw); + char *got = tty_sendraw(G->sendraw); + if(got){ + MSG(_("Receive"), got); + if(quiet) printf("%s", got); + }else WARNX(_("Nothing received")); + } + if(G->rot1angle > -999. || G->rot2angle > -999. || G->l1steps != INT_MAX || G->l2steps != INT_MAX){ + // all other commands are tied with moving, so check if motors are inited + if(init_motors()) rtn_status = RET_CANTINIT; + else{ + if(G->rot1angle > -999.){ + double angle = convangle(G->rot1angle); + int steps = (int)((STEPSREV1/360.) * angle); + waitforstop = movemotor(1, 1, steps, "polaroid"); + } + if(G->rot2angle > -999.){ + double angle = convangle(G->rot2angle); + int steps = (int)((STEPSREV2/360.) * angle); + waitforstop = movemotor(2, 1, steps, "lambda/4"); + } + if(G->l1steps != INT_MAX){ + waitforstop = movemotor(1, 0, G->l1steps, "polaroid stage"); + } + if(G->l2steps != INT_MAX){ + waitforstop = movemotor(2, 0, G->l2steps, "lambda/4 stage"); + } + } + } + if((waitforstop && !G->dontwait)) if(tty_wait()) rtn_status = RET_WAITERR; + if(G->getstatus) tty_getstatus(); + if(G->reset){ + int **N = G->reset; + while(*N){ + char cmd[3]; + if(**N < 1 || **N > 2){ + WARNX(_("Wrong MCU number (%d)"), **N); + }else{ + if(!quiet) green("Reset controller #%d\n", **N); + snprintf(cmd, 3, "%dR", **N); + ttysend_status _U_ rt = tty_sendcmd(cmd); + DBG("reset %d, result: %d", **N, rt); + } + ++N; + } + } + signals(rtn_status); +} diff --git a/MMPP_lib/examples/movecmdlnopts.c b/MMPP_lib/examples/movecmdlnopts.c new file mode 100644 index 0000000..a7395ab --- /dev/null +++ b/MMPP_lib/examples/movecmdlnopts.c @@ -0,0 +1,112 @@ +/* + * cmdlnopts.c - the only function that parse cmdln args and returns glob parameters + * + * Copyright 2013 Edward V. Emelianoff + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, + * MA 02110-1301, USA. + */ + +#include "movecmdlnopts.h" +#include +#include +#include +#include +#include +#include +#include + +/* + * here are global parameters initialisation + */ +static int help; +static glob_pars G; + +int quiet = 0; // less messages @ stdout + +// DEFAULTS +// default global parameters +glob_pars const Gdefault = { + .showtemp = 0 + ,.comdev = "/dev/ttyUSB0" + ,.pidfile = "/tmp/MMPP_control.pid" + ,.speed = BAUD_RATE + ,.rot1angle = -1000. + ,.rot2angle = -1000. + ,.l1steps = INT_MAX + ,.l2steps = INT_MAX +}; + +/* + * Define command line options by filling structure: + * name has_arg flag val type argptr help +*/ +static myoption cmdlnopts[] = { + {"help", NO_ARGS, NULL, 'h', arg_none, APTR(&help), N_("show this help")}, + {"quiet", NO_ARGS, NULL, 'q', arg_none, APTR(&quiet), N_("don't show anything @screen from stdout")}, + {"absmove", NO_ARGS, NULL, 'A', arg_none, APTR(&G.absmove), N_("absolute move (without this flag moving is relative)")}, + {"temp", NO_ARGS, NULL, 't', arg_none, APTR(&G.showtemp), N_("show temperature of both MCU")}, + {"comdev", NEED_ARG, NULL, 'd', arg_string, APTR(&G.comdev), N_("terminal device filename")}, + {"sendraw", NEED_ARG, NULL, 'a', arg_string, APTR(&G.sendraw), N_("send RAW string to port and read the answer")}, + {"reset", MULT_PAR, NULL, 'E', arg_int, APTR(&G.reset), N_("reset given mcu (may be included several times)")}, + {"rot1", NEED_ARG, NULL, 'R', arg_double, APTR(&G.rot1angle), N_("rotate polaroid to given angle")}, + {"rot2", NEED_ARG, NULL, 'r', arg_double, APTR(&G.rot2angle), N_("rotate lambda/4 to given angle")}, + {"status", NO_ARGS, NULL, 's', arg_none, APTR(&G.getstatus), N_("get device status")}, + {"baudrate",NEED_ARG, NULL, 'b', arg_int, APTR(&G.speed), N_("TTY baudrate")}, + {"wait", NO_ARGS, NULL, 'w', arg_none, APTR(&G.waitold), N_("wait while all previous moving ends")}, + {"async", NO_ARGS, NULL, 'y', arg_none, APTR(&G.dontwait), N_("asynchronous moving - don't wait")}, + {"lin1", NEED_ARG, NULL, 'L', arg_int, APTR(&G.l1steps), N_("move polaroid linear stage to N steps")}, + {"lin2", NEED_ARG, NULL, 'l', arg_int, APTR(&G.l2steps), N_("move wave-plate linear stage to N steps")}, + {"pidfile", NEED_ARG, NULL, 'p', arg_string, APTR(&G.pidfile), N_("PID-file name")}, + {"stop", NO_ARGS, NULL, 'S', arg_none, APTR(&G.stopall), N_("stop any moving")}, + end_option +}; + + +/** + * Parse command line options and return dynamically allocated structure + * to global parameters + * @param argc - copy of argc from main + * @param argv - copy of argv from main + * @return allocated structure with global parameters + */ +glob_pars *parse_args(int argc, char **argv){ + void *ptr; + ptr = memcpy(&G, &Gdefault, sizeof(G)); assert(ptr); + // format of help: "Usage: progname [args]\n" + // parse arguments + parseargs(&argc, &argv, cmdlnopts); + if(help) showhelp(-1, cmdlnopts); + if(argc > 0){ + WARNX("%d unused parameters:\n", argc); + for(int i = 0; i < argc; ++i) + printf("\t%4d: %s\n", i+1, argv[i]); + } + return &G; +} + +/** + * @brief MSG show coloured message if `quiet` not set + * !! This function adds trailing '\n' to message + * @param s1 - green part of message (may be null) + * @param s2 - normal colored part of messate (may be null) + */ +void MSG(const char *s1, const char *s2){ + if(quiet) return; + if(s1){ + green("%s%s", s1, s2 ? ": " : "\n"); + } + if(s2) printf("%s\n", s2); +} diff --git a/MMPP_lib/examples/movecmdlnopts.h b/MMPP_lib/examples/movecmdlnopts.h new file mode 100644 index 0000000..758c6dc --- /dev/null +++ b/MMPP_lib/examples/movecmdlnopts.h @@ -0,0 +1,57 @@ +/* + * cmdlnopts.h - comand line options for parceargs + * + * Copyright 2013 Edward V. Emelianoff + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, + * MA 02110-1301, USA. + */ + +#pragma once +#ifndef MOVECMDLNOPTS_H__ +#define MOVECMDLNOPTS_H__ + +#include +#include + +/* + * here are some typedef's for global data + */ +typedef struct{ + int showtemp; // show temperatures of both MCU + char *comdev; // TTY device + char *sendraw; // send RAW string + char *pidfile; // pid file name + double rot1angle; // rotator 1 angle + double rot2angle; // rotator 2 angle + int speed; // TTY speed + int getstatus; // get both MCU status + int waitold; // wait for previous moving ends + int dontwait; // don't wait for moving end + int l1steps; // move linear stage 1 (polaroid) for N steps + int l2steps; // move linear stage 2 (L/4) for N steps + int absmove; // absolute move (to given position from zero-esw) + int stopall; // stop all moving + int **reset; // reset given MCU's +} glob_pars; + +// default & global parameters +extern glob_pars const Gdefault; +extern int quiet; + +glob_pars *parse_args(int argc, char **argv); +void MSG(const char *s1, const char *s2); + +#endif // MOVECMDLNOPTS_H__ diff --git a/MMPP_lib/examples/tm.c b/MMPP_lib/examples/tm.c new file mode 100644 index 0000000..3865495 --- /dev/null +++ b/MMPP_lib/examples/tm.c @@ -0,0 +1,109 @@ +/* + * This file is part of the libmmpp project. + * Copyright 2019 Edward V. Emelianov . + * + * 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 . + */ + +#include "tmcmdlnopts.h" +#include +#include +#include +#include + +// All return states of main(): +enum{ + RET_ALLOK = 0, + RET_NOTFOUND, // none of MCU found or didn't found seeking MCU + RET_ONLYONE, // only one MCU found + RET_COMMERR, // communication error + RET_CANTINIT, // can't init motors: error during initiation or some of motors are moving + RET_WAITERR, // error occured during waiting procedure + RET_ERROR = 9, // uncoverable error - from libsnippets + RET_HELPCALL = 255 // user call help (or give wrong parameter[s]) - from libsnippets +}; +static glob_pars *G; + +/** + * We REDEFINE the default WEAK function of signal processing + */ +void __attribute__((noreturn)) signals(int sig){ + if(sig){ + signal(sig, SIG_IGN); + DBG("Get signal %d, quit.\n", sig); + } + if(G->pidfile) // remove unnesessary PID file + unlink(G->pidfile); + restore_console(); + mmpp_close(); + exit(sig); +} + +int main(int argc, char **argv){ + initial_setup(); + signal(SIGTERM, signals); // kill (-15) + signal(SIGINT, signals); // ctrl+C + signal(SIGQUIT, SIG_IGN); // ctrl+\ . + signal(SIGTSTP, SIG_IGN); // ctrl+Z + setbuf(stdout, NULL); + G = parse_args(argc, argv); + check4running(NULL, G->pidfile); + DBG("Try to open serial %s", G->comdev); + if(mmpp_tryopen(G->comdev, G->speed)){ + ERRX(_("Can't open %s with speed %d. Exit."), G->comdev, G->speed); + } + if(mot_handshake()){ + WARNX(_("Controllers not found")); + signals(RET_NOTFOUND); + } + for(int i = 1; i < 3; ++i){ + if(get_alive(i)){ + green("MCU #%d found\n", i); + if(get_rst(i, true)) + red("Controller #%d was in reset state\n", i); + }else red("MCU #%d not found\n", i); + } + if(G->stopall){ + int r = stop_all(); + if(r) red("Error for %d motors of 4\n", r); + else green("Successfully send command to stop all\n"); + } + if(G->gettemp){ + double t1, t2; + if(get_temp(&t1, &t2)){ + green("Got MCU temp:\n"); + if(t1 > -300.) printf("\tMCU#1 - %gdegrC\n", t1); + if(t2 > -300.) printf("\tMCU#2 - %gdegrC\n", t2); + }else red("Can't get MCU temp\n"); + } + if(G->getstatus){ + for(int N = 1; N < 3; ++N){ + motor_state s; + if(mot_getstatus(N, &s)){ + green("MCU#%d state:\n", N); + for(int i = 0; i < 2; ++i){ + printf("\tstate[%d]=%d\n",i, s.state[i]); + printf("\tstepslefs[%d]=%d\n", i, s.stepsleft[i]); + printf("\tcurpos[%d]=%d\n", i, s.curpos[i]); + for(int j = 0; j < 2; ++j) + printf("\tESW_status[%d][%d]=%d\n", i, j, s.ESW_status[i][j]); + } + } + } + } + int ini = init_motors(); + if(ini) red("Can't init motors: %d\n", ini); + else green("Motors are ready!\n"); + signals(0); +} diff --git a/MMPP_lib/examples/tmcmdlnopts.c b/MMPP_lib/examples/tmcmdlnopts.c new file mode 100644 index 0000000..9cbfe2f --- /dev/null +++ b/MMPP_lib/examples/tmcmdlnopts.c @@ -0,0 +1,92 @@ +/* + * This file is part of the libmmpp project. + * Copyright 2019 Edward V. Emelianov . + * + * 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 . + */ +#include "tmcmdlnopts.h" +#include +#include +#include +#include +#include + +/* + * here are global parameters initialisation + */ +static int help; +static glob_pars G; + +int quiet = 0; // less messages @ stdout + +// DEFAULTS +// default global parameters +glob_pars const Gdefault = { + .comdev = "/dev/ttyUSB0" + ,.pidfile = "/tmp/MMPP_con.pid" + ,.speed = BAUD_RATE +}; + +/* + * Define command line options by filling structure: + * name has_arg flag val type argptr help +*/ +static myoption cmdlnopts[] = { + {"help", NO_ARGS, NULL, 'h', arg_none, APTR(&help), N_("show this help")}, + {"quiet", NO_ARGS, NULL, 'q', arg_none, APTR(&quiet), N_("don't show anything @screen from stdout")}, + {"comdev", NEED_ARG, NULL, 'd', arg_string, APTR(&G.comdev), N_("terminal device filename")}, + {"baudrate",NEED_ARG, NULL, 'b', arg_int, APTR(&G.speed), N_("TTY baudrate")}, + {"pidfile", NEED_ARG, NULL, 'p', arg_string, APTR(&G.pidfile), N_("PID-file name")}, + {"stopall", NO_ARGS, NULL, 's', arg_none, APTR(&G.stopall), N_("stop all motors")}, + {"gettemp", NO_ARGS, NULL, 't', arg_none, APTR(&G.gettemp), N_("get MCU temperature")}, + {"status", NO_ARGS, NULL, 'S', arg_none, APTR(&G.getstatus), N_("get device status")}, + end_option +}; + + +/** + * Parse command line options and return dynamically allocated structure + * to global parameters + * @param argc - copy of argc from main + * @param argv - copy of argv from main + * @return allocated structure with global parameters + */ +glob_pars *parse_args(int argc, char **argv){ + void *ptr; + ptr = memcpy(&G, &Gdefault, sizeof(G)); assert(ptr); + // format of help: "Usage: progname [args]\n" + // parse arguments + parseargs(&argc, &argv, cmdlnopts); + if(help) showhelp(-1, cmdlnopts); + if(argc > 0){ + WARNX("%d unused parameters:\n", argc); + for(int i = 0; i < argc; ++i) + printf("\t%4d: %s\n", i+1, argv[i]); + } + return &G; +} + +/** + * @brief MSG show coloured message if `quiet` not set + * !! This function adds trailing '\n' to message + * @param s1 - green part of message (may be null) + * @param s2 - normal colored part of messate (may be null) + */ +void MSG(const char *s1, const char *s2){ + if(quiet) return; + if(s1){ + green("%s%s", s1, s2 ? ": " : "\n"); + } + if(s2) printf("%s\n", s2); +} diff --git a/MMPP_lib/examples/tmcmdlnopts.h b/MMPP_lib/examples/tmcmdlnopts.h new file mode 100644 index 0000000..cfa1d12 --- /dev/null +++ b/MMPP_lib/examples/tmcmdlnopts.h @@ -0,0 +1,43 @@ +/* + * This file is part of the libmmpp project. + * Copyright 2019 Edward V. Emelianov . + * + * 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 . + */ +#pragma once +#ifndef TMCMDLNOPTS_H__ +#define TMCMDLNOPTS_H__ + +#include + +/* + * here are some typedef's for global data + */ +typedef struct{ + char *comdev; // TTY device + char *pidfile; // pid file name + int gettemp; // get MCU temperature + int stopall; // stop all motors + int speed; // TTY speed + int getstatus; // get status of all devices +} glob_pars; + +// default & global parameters +extern glob_pars const Gdefault; +extern int quiet; + +glob_pars *parse_args(int argc, char **argv); +void MSG(const char *s1, const char *s2); + +#endif // TMCMDLNOPTS_H__ diff --git a/MMPP_lib/libmmpp.c b/MMPP_lib/libmmpp.c new file mode 100644 index 0000000..7eb7377 --- /dev/null +++ b/MMPP_lib/libmmpp.c @@ -0,0 +1,375 @@ +/* + * This file is part of the libmmpp project. + * Copyright 2019 Edward V. Emelianov . + * + * 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 . + */ + +#include "libmmpp.h" +#include "tty_procs.h" +#include +#include +#include + +#if defined GETTEXT +#include +#define _(String) gettext(String) +#define gettext_noop(String) String +#define N_(String) gettext_noop(String) +#else +#define _(String) (String) +#define N_(String) (String) +#endif + +// tty device +static TTYdescr *dev = NULL; +static bool alive[3] = {false,true,true}; // ==true if controller answers, false if no +static bool reset[3] = {false,false,false}; // reset occured + +/** + * @brief tty_tryopen - try to open serial device + * @param devnm - path to device + * @param spd - speed (number) + * @return 0 if all OK + */ +int mmpp_tryopen(char *devnm, int spd){ + dev = new_tty(devnm, spd, 256); + if(!dev) return 1; + if(!tty_open(dev, true)) return 1; + return 0; +} + +/** + * @brief tty_close - close TTY device + */ +void mmpp_close(){ + close_tty(&dev); +} + +/** + * test connection (1,2 -> ALIVE) + * @return 1 if none of MCU found, 0 if at least 1 found + */ +int mot_handshake(){ + char buff[32], *ret; + int mcu; + FNAME(); + for(mcu = 1; mcu < 3; ++mcu){ + DBG("MCU #%d", mcu); + // check if MCU alive + sprintf(buff, "%d", mcu); + int notresp = 1; + alive[mcu] = false; + // make HANDSHAKE_TRIES tries + for(int tr = 0; tr < HANDSHAKE_TRIES; ++tr){ + ret = tty_sendraw(buff); + if(ret && 0 == strcmp(ret, "ALIVE\n")){ + notresp = 0; + break; + } + } + if(notresp){ + continue; + } + alive[mcu] = true; + } + if(alive[1] == false && alive[2] == false) return 1; + return 0; +} + +/** + * Get temperature of both MCU + * @param t1, t2 (o) - temperatures of first and second MCUs (==-300 if error) + * @return amount of successful calls + */ +int get_temp(double *t1, double *t2){ + char *val, buff[] = "xGT\n", *ans; + int ret = 0; + if(t1) *t1 = -300.; + if(t2) *t2 = -300.; + for(int i = 1; i < 3; ++i){ + if(!alive[i]){ + DBG("MCU %d didn't respond!", i); + continue; + } + buff[0] = '0' + (char)i; + if((ans = tty_sendraw(buff))){ + val = keyval("TEMP", ans); + DBG("val: %s", val); + if(val){ + ++ret; + double t; + if(str2double(&t, val)){ + if(i == 1){if(t1) *t1 = t/10.;} + else{if(t2) *t2 = t/10.;} + } + } + } + } + return ret; +} + +/** + * @brief stop_all - send commands to stop all motors + * @return 0 if all OK, else return amount of motors failed to stop + */ +int stop_all(){ + int ret = 4; + if(alive[1]){ + if(SEND_ALLOK == tty_sendcmd("1M0S")) --ret; + if(SEND_ALLOK == tty_sendcmd("1M1S")) --ret; + } + if(alive[2]){ + if(SEND_ALLOK == tty_sendcmd("2M0S")) --ret; + if(SEND_ALLOK == tty_sendcmd("2M1S")) --ret; + } + return ret; +} + +/** + * @brief init_motors - BLOCKING (!!!) init all motors simultaneously (if they need to) + * @return 0 if all OK, or Nmcu*10+motnum for problem motor (motnum == 2 for problems with MCU) + * !!! in case of non-zero returning you should repeat initialisation + */ +int init_motors(){ + FNAME(); + char buf[32]; + motor_state S[3]; +#define RETVAL() (Nmcu*10+motnum) + int Nmcu, motnum, needinit = 0; + for(Nmcu = 1; Nmcu < 3; ++Nmcu){ + if(!mot_getstatus(Nmcu, &S[Nmcu])) return 10*Nmcu+2; + for(motnum = 0; motnum < 2; ++motnum){ + // check position + if(S[Nmcu].curpos[motnum] < 0) needinit = 1; + } + } + if(!needinit) return 0; + DBG("Need to init, start!"); + for(Nmcu = 1; Nmcu < 3; ++Nmcu){ + for(motnum = 0; motnum < 2; ++motnum){ + int pos = S[Nmcu].curpos[motnum]; + if(pos >= 0) continue; + // check if we are on zero endswitch + if(S[Nmcu].ESW_status[motnum][0] == ESW_HALL){ // move a little from zero esw + sprintf(buf, "%dM%dM100", Nmcu, motnum); + if(SEND_ERR == tty_sendcmd(buf)){ + return RETVAL(); + } + mot_wait(); + } + sprintf(buf, "%dM%dM-400", Nmcu, motnum); + if(SEND_ALLOK != tty_sendcmd(buf)){ + return RETVAL(); + } + }} + mot_wait(); + for(Nmcu = 1; Nmcu < 3; ++Nmcu){ + if(!mot_getstatus(Nmcu, &S[Nmcu])) return 10*Nmcu+2; + for(motnum = 0; motnum < 2; ++motnum){ + if(S[Nmcu].curpos[motnum]){ + return RETVAL(); + } + } + } + return 0; +#undef RETVAL +} + +/** + * Wait while all motors are stopped + * @return 0 if all OK + */ +int mot_wait(){ + int failcount = 0; + bool mov[3] = {false,true, true}; + if(!alive[1] && !alive[2]) return 0; // all are dead here + while(failcount < FAIL_TRIES && (mov[1] || mov[2])){ + for(int Nmcu = 1; Nmcu < 3; ++Nmcu){ + usleep(10000); + DBG("alive=%d/%d, mov=%d/%d", alive[1],alive[2],mov[1],mov[2]); + if(!alive[Nmcu] || !mov[Nmcu]) continue; + motor_state S; + if(!mot_getstatus(Nmcu, &S)){ + ++failcount; + } + if(S.state[0] == STP_SLEEP && S.state[1] == STP_SLEEP) + mov[Nmcu] = false; + } + } + if(failcount >= FAIL_TRIES){ + return 1; + } + return 0; +} + +/** + * read data from TTY + * WARNING! Not thread-safe!!! + * @return static buffer with data read or NULL + */ +char *tty_get(){ + static char buf[TBUFLEN]; + char *ptr = buf; + size_t L = 0, l = TBUFLEN; + double t0 = dtime(); + *ptr = 0; + while(dtime() - t0 < TTYTIMEOUT && l){ + size_t r = read_tty(dev); + if(!r) continue; + t0 = dtime(); + if(r > l) r = l; + DBG("got %zd bytes: %s", r, dev->buf); + strncpy(ptr, dev->buf, r); + L += r; l -= r; ptr += r; + } + buf[L] = 0; + if(L){ + return buf; + } + DBG("no answer"); + return NULL; +} + +/** + * Send given string command to port + * @return 0 if failed + */ +int tty_send(char *cmd){ + size_t l = 0; + char *s = cpy2buf(cmd, &l); + if(!s) return 0; + if(write_tty(dev->comfd, s, l)) return 0; + return 1; +} + +/** + * send RAW string to port device + * @param string - string to send + * @return string received or NULL in case of error + */ +char* tty_sendraw(char *string){ + DBG("sendraw %s", string); + if(!tty_send(string)) return NULL; + return tty_get(); +} + +/** + * Send given string command to port with answer analysis + * @return status + */ +ttysend_status tty_sendcmd(char *cmd){ + DBG("SEND: %s", cmd); + if(!tty_send(cmd)) return SEND_ERR; + char *got = tty_get(); + if(!got) return SEND_ERR; + if(strcmp(got, "ALLOK\n") == 0) return SEND_ALLOK; + else if(strcmp(got, "IsMoving") == 0) return SEND_ACTIVE; + else if(strcmp(got, "OnEndSwitch\n") == 0) return SEND_ESWITCH; + else if(strcmp(got, "ZeroMove") == 0) return SEND_ZEROMOVE; + else if(strcmp(got, "TooBigNumber") == 0) return SEND_TOOBIG; + return SEND_OTHER; +} + +/** + * @brief mot_getstatus - get status of motors for given controller + * @param Nmcu - (1 or 2) MCU number + * @param s (o) - status + * @return true if all OK + */ +bool mot_getstatus(int Nmcu, motor_state *s){ + char buff[32], *ans, cmd[4] = "xGS", *val; + if(Nmcu < 1 || Nmcu > 2) return false; + cmd[0] = '0' + Nmcu; + ans = tty_sendraw(cmd); + if(!ans){ + alive[Nmcu] = false; + return false; + } + alive[Nmcu] = true; + motor_state S; + memset(&S, 0, sizeof(S)); + val = keyval("WDGRESET", ans); + if(val) s->rst = RESET_WDG; + else{ + val = keyval("SOFTRESET", ans); + if(val) s->rst = RESET_SW; + } + if(s->rst != RESET_NONE) reset[Nmcu] = true; + for(int i = 0; i < 2; ++i){ + sprintf(buff, "POS%d", i); + val = keyval(buff, ans); + if(val){ + S.curpos[i] = atoi(val); + } + sprintf(buff, "MOTOR%d", i); + val = keyval(buff, ans); + stp_state ms = STP_UNKNOWN; + if(val){ + if(strcmp(val, "ACCEL") == 0) ms = STP_ACCEL; + else if(strcmp(val, "DECEL") == 0) ms = STP_DECEL; + else if(strcmp(val, "MOVE") == 0) ms = STP_MOVE; + else if(strcmp(val, "MOVETO0") == 0) ms = STP_MOVE0; + else if(strcmp(val, "MOVETO1") == 0) ms = STP_MOVE1; + else if(strcmp(val, "MVSLOW") == 0) ms = STP_MVSLOW; + else if(strcmp(val, "STOP") == 0) ms = STP_STOP; + else if(strcmp(val, "STOPZERO") == 0) ms = STP_STOPZERO; + else if(strcmp(val, "SLEEP") == 0) ms = STP_SLEEP; + S.state[i] = ms; + } + if(ms != STP_UNKNOWN && ms != STP_SLEEP){ // moving + sprintf(buff, "STEPSLEFT%d", i); + val = keyval(buff, ans); + if(val){ + S.stepsleft[i] = atoi(val); + } + } + // end-switches + for(int j = 0; j < 2; ++j){ + sprintf(buff, "ESW%d%d", i, j); + val = keyval(buff, ans); + if(val){ + ESW_status s = ESW_ERROR; + if(strcmp(val, "RLSD") == 0) s = ESW_RELEASED; + else if(strcmp(val, "BTN") == 0) s = ESW_BUTTON; + else if(strcmp(val, "HALL") == 0) s = ESW_HALL; + S.ESW_status[i][j] = s; + } + } + } + if(s) *s = S; + return true; +} + +/** + * @brief get_rst - get reset state for mcu #N + * @param N - number of MCU + * @param clear - true to clear reset state + * @return + */ +bool get_rst(int N, bool clear){ + if(N < 1 || N > 2) return false; + bool state = reset[N]; + if(clear) reset[N] = 0; + return state; +} + +/** + * @brief get_alive - return true if MCU #N is alive + * @param N - number of MCU (1 or 2) + * @return alive[N] + */ +bool get_alive(int N){ + return alive[N]; +} diff --git a/MMPP_lib/libmmpp.h b/MMPP_lib/libmmpp.h new file mode 100644 index 0000000..88852ac --- /dev/null +++ b/MMPP_lib/libmmpp.h @@ -0,0 +1,100 @@ +/* + * This file is part of the libmmpp project. + * Copyright 2019 Edward V. Emelianov . + * + * 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 . + */ +#pragma once +#ifndef LIBMMPP_H__ +#define LIBMMPP_H__ + +#include + +// default baudrate for communication +#define BAUD_RATE (9600) +// amount of tries to establish handshake +#define HANDSHAKE_TRIES (10) +// how many fails can we omit when waiting for moving end +#define FAIL_TRIES (5) +// steps per full revolution for both rotation stages (1 - polaroid, 2 - waveplate) +#define STEPSREV1 (36000) +#define STEPSREV2 (28800) + +/* + * MMPP controllers state + */ +// motors: +typedef enum{ + STP_UNKNOWN, // wrong state + STP_SLEEP, // don't moving + STP_ACCEL, // start moving with acceleration + STP_MOVE, // moving with constant speed + STP_MVSLOW, // moving with slowest constant speed + STP_DECEL, // moving with deceleration + STP_STOP, // stop motor right now (by demand) + STP_STOPZERO, // stop motor and zero its position (on end-switch) + STP_MOVE0, // move towards 0 endswitch (negative direction) + STP_MOVE1 // move towards 1 endswitch (positive direction) +} stp_state; +// answers on motor commands +typedef enum{ + SEND_ERR, // communication error + SEND_ALLOK, // no errors + SEND_ACTIVE, // motor is still moving + SEND_TOOBIG, // amount of steps too big + SEND_ZEROMOVE, // give 0 steps to move + SEND_ESWITCH, // staying on end-switch & try to move further + SEND_OTHER +} ttysend_status; +// end-switch state +typedef enum{ + ESW_ERROR, // wrong value + ESW_RELEASED, // opened + ESW_HALL, // hall sensor + ESW_BUTTON // user button +} ESW_status; +// reset state +typedef enum{ + RESET_NONE, // no sw/wd reset occured + RESET_SW, // software reset have been before last status call + RESET_WDG // watchdog reset -//- +} reset_status; +// motor state +typedef struct{ + reset_status rst; // reset status (was MCU reseted by watchdog or software?) + stp_state state[2]; // status of stepper motor + int stepsleft[2]; // steps left to reach target position + int curpos[2]; // current position (negative for non-initialized state or error) + ESW_status ESW_status[2][2];// End-switches status, [i][j], i - motor, j - esw 0 or 1 +} motor_state; + +int mmpp_tryopen(char *dev, int spd); +void mmpp_close(); +int mot_handshake(); +bool get_rst(int N, bool clear); +bool get_alive(int N); +int stop_all(); +int get_temp(double *t1, double *t2); +bool mot_getstatus(int Nmcu, motor_state *s); +int init_motors(); +int mot_wait(); + +char *tty_get(); +int tty_send(char *cmd); +ttysend_status tty_sendcmd(char *cmd); +char* tty_sendraw(char *string); + + + +#endif // LIBMMPP_H__ diff --git a/MMPP_lib/mmpp.pc.in b/MMPP_lib/mmpp.pc.in new file mode 100644 index 0000000..441b10e --- /dev/null +++ b/MMPP_lib/mmpp.pc.in @@ -0,0 +1,10 @@ +prefix=@CMAKE_INSTALL_PREFIX@ +exec_prefix=${prefix} +libdir=${exec_prefix}/lib +includedir=${prefix}/include + +Name: @PROJ@ +Description: Library with a lot of usefull snippets +Version: @VERSION@ +Libs: -L${libdir} -l@PROJ@ +Cflags: -I${includedir} diff --git a/MMPP_lib/tty_procs.c b/MMPP_lib/tty_procs.c new file mode 100644 index 0000000..03ed052 --- /dev/null +++ b/MMPP_lib/tty_procs.c @@ -0,0 +1,310 @@ +/* + * This file is part of the libmmpp project. + * Copyright 2019 Edward V. Emelianov . + * + * 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 . + */ + +#include "tty_procs.h" +#include +#include // read +#include +#include +#include +#include // ioctl +#include // read +#include // gettimeofday +#include // close + +// buffer for data +static char bufo[TBUFLEN+1]; + +/** + * @brief my_alloc - safe memory allocation for macro ALLOC + * @param N - number of elements to allocate + * @param S - size of single element (typically sizeof) + * @return pointer to allocated memory area + */ +void *my_alloc(size_t N, size_t S){ + void *p = calloc(N, S); + if(!p){ + perror("malloc()"); + exit(-1); + } + return p; +} + +/** + * @brief dtime - function for different purposes that need to know time intervals + * @return double value: UNIX time in seconds + */ +double dtime(){ + double t; + struct timeval tv; + gettimeofday(&tv, NULL); + t = tv.tv_sec + ((double)tv.tv_usec)/1e6; + return t; +} + +typedef struct { + int speed; // communication speed in bauds/s + tcflag_t bspeed; // baudrate from termios.h +} spdtbl; + +static const spdtbl speeds[] = { + {50, B50}, + {75, B75}, + {110, B110}, + {134, B134}, + {150, B150}, + {200, B200}, + {300, B300}, + {600, B600}, + {1200, B1200}, + {1800, B1800}, + {2400, B2400}, + {4800, B4800}, + {9600, B9600}, + {19200, B19200}, + {38400, B38400}, + {57600, B57600}, + {115200, B115200}, + {230400, B230400}, + {460800, B460800}, + {500000, B500000}, + {576000, B576000}, + {921600, B921600}, + {1000000, B1000000}, + {1152000, B1152000}, + {1500000, B1500000}, + {2000000, B2000000}, + {2500000, B2500000}, + {3000000, B3000000}, + {3500000, B3500000}, + {4000000, B4000000}, + {0,0} +}; + +/** + * @brief conv_spd - test if `speed` is in .speed of `speeds` array + * @param speed - integer speed (bps) + * @return 0 if error, Bxxx if all OK + */ +static tcflag_t conv_spd(int speed){ + const spdtbl *spd = speeds; + int curspeed = 0; + do{ + curspeed = spd->speed; + if(curspeed == speed) + return spd->bspeed; + ++spd; + }while(curspeed); + return 0; +} + +/** + * @brief new_tty - create new TTY structure with partially filled fields + * @param comdev - TTY device filename + * @param speed - speed (number) + * @param bufsz - size of buffer for input data (or 0 if opened only to write) + * @return pointer to TTY structure if all OK + */ +TTYdescr *new_tty(char *comdev, int speed, size_t bufsz){ + tcflag_t spd = conv_spd(speed); + if(!spd) return NULL; + DBG("create %s with speed %d and buffer size %zd", comdev, speed, bufsz); + TTYdescr *descr = MALLOC(TTYdescr, 1); + descr->portname = strdup(comdev); + descr->baudrate = spd; + descr->speed = speed; + if(descr->portname && bufsz){ + descr->buf = MALLOC(char, bufsz+1); + descr->bufsz = bufsz; + DBG("allocate buffer with size %zd", bufsz); + return descr; + } + FREE(descr->portname); + FREE(descr); + return NULL; +} + +/** + * @brief tty_init - open & setup terminal + * @param descr (io) - port descriptor + * @return 0 if all OK or error code + */ +int tty_init(TTYdescr *descr){ + DBG("\nOpen port..."); // |O_NONBLOCK + if ((descr->comfd = open(descr->portname, O_RDWR|O_NOCTTY)) < 0){ + return errno ? errno : 1; + } + DBG("OK\nGet current settings..."); + if(tcgetattr(descr->comfd, &descr->oldtty) < 0){ // Get settings + return errno ? errno : 1; + } + descr->tty = descr->oldtty; + descr->tty.c_lflag = 0; // ~(ICANON | ECHO | ECHOE | ISIG) + descr->tty.c_oflag = 0; + descr->tty.c_cflag = descr->baudrate|CS8|CREAD|CLOCAL; // 9.6k, 8N1, RW, ignore line ctrl + descr->tty.c_cc[VMIN] = 0; // non-canonical mode + descr->tty.c_cc[VTIME] = 5; + if(tcsetattr(descr->comfd, TCSANOW, &descr->tty) < 0){ + return errno ? errno : 1; + } + // make exclusive open + if(descr->exclusive){ + if(ioctl(descr->comfd, TIOCEXCL)){ + perror("ioctl(TIOCEXCL)"); + }} + DBG("OK"); + return 0; +} + + +/** + * @brief tty_open - init & open tty device + * @param d - already filled structure (with new_tty or by hands) + * @param exclusive - == 1 to make exclusive open + * @return pointer to TTY structure if all OK + */ +TTYdescr *tty_open(TTYdescr *d, bool exclusive){ + DBG("open %s with speed %d%s", d->portname, d->speed, exclusive ? "" : " (exclusive)"); + if(!d || !d->portname || !d->baudrate) return NULL; + if(exclusive) d->exclusive = true; + else d->exclusive = false; + if(tty_init(d)) return NULL; + return d; +} + +/** + * @brief restore_tty - restore opened TTY to previous state and close it + */ +void close_tty(TTYdescr **descr){ + if(descr == NULL || *descr == NULL) return; + TTYdescr *d = *descr; + if(d->comfd){ + DBG("close file.."); + ioctl(d->comfd, TCSANOW, &d->oldtty); // return TTY to previous state + close(d->comfd); + } + FREE(d->portname); + FREE(d->buf); + FREE(*descr); + DBG("done!\n"); +} + +/** + * @brief read_tty - read data from TTY with 10ms timeout + * @param buff (o) - buffer for data read + * @param length - buffer len + * @return amount of bytes read + */ +size_t read_tty(TTYdescr *d){ + if(d->comfd < 0) return 0; + 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 = 50000; + 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){ + break; + } + ptr += l; L += l; + length -= l; + } + }while(l && length); + d->buflen = L; + d->buf[L] = 0; + return (size_t)L; +} + +/** + * copy given string to `buf` & add '\n' if need + * @return 0 if failed + */ +char *cpy2buf(char *string, size_t *l){ + size_t L = strlen(string); + if(L > TBUFLEN-1){ + return NULL; + } + strcpy(bufo, string); + if(bufo[L-1] != '\n'){ + bufo[L++] = '\n'; + bufo[L] = 0; + } + if(l) *l = L; + return bufo; +} + +/** + * @brief write_tty - write data to serial port + * @param buff (i) - data to write + * @param length - its length + * @return 0 if all OK + */ +int write_tty(int comfd, const char *buff, size_t length){ + ssize_t L = write(comfd, buff, length); + if((size_t)L != length){ + return 1; + } + return 0; +} + +/** + * return static buffer - value of `key` + * NOT THREAD SAFE! + */ +char *keyval(char *key, char *haystack){ + static char buff[32]; + char *got = strstr(haystack, key); + if(!got) return NULL; + got = strchr(got, '='); + if(!got) return NULL; + ++got; + char *el = strchr(got, '\n'); + if(!el) return NULL; + size_t L = (size_t)(el - got); + if(L > 31 || L == 0 || !*got) return NULL; + strncpy(buff, got, L); + buff[L] = 0; + return buff; +} + +/** + * @brief str2double - safely convert data from string to double + * @param num (o) - double number read from string + * @param str (i) - input string + * @return 1 if success, 0 if fails + */ +int str2double(double *num, const char *str){ + double res; + char *endptr; + if(!str) return 0; + res = strtod(str, &endptr); + if(endptr == str || *str == '\0' || *endptr != '\0'){ + return false; + } + if(num) *num = res; // you may run it like myatod(NULL, str) to test wether str is double number + return true; +} diff --git a/MMPP_lib/tty_procs.h b/MMPP_lib/tty_procs.h new file mode 100644 index 0000000..989ccc7 --- /dev/null +++ b/MMPP_lib/tty_procs.h @@ -0,0 +1,98 @@ +/* + * This file is part of the libmmpp project. + * Copyright 2019 Edward V. Emelianov . + * + * 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 . + */ + +#pragma once +#ifndef TTY_PROCS_H__ +#define TTY_PROCS_H__ + +#include +#include +#include +#include // tcsetattr, baudrates + +// tty Rx static buffer +#define TBUFLEN (1024) +// read timeout (in seconds) +#define TTYTIMEOUT (0.05) + +// unused arguments of functions +#define _U_ __attribute__((__unused__)) +// break absent in `case` +#define FALLTHRU __attribute__ ((fallthrough)) +// and synonym for FALLTHRU +#define NOBREAKHERE __attribute__ ((fallthrough)) +// weak functions +#define WEAK __attribute__ ((weak)) + +#define COLOR_RED "\033[1;31;40m" +#define COLOR_GREEN "\033[1;32;40m" +#define COLOR_OLD "\033[0;0;0m" +/* + * print function name, debug messages + * debug mode, -DEBUG + */ +#ifdef EBUG + #define FNAME() do{ fprintf(stderr, COLOR_OLD); \ + fprintf(stderr, "\n%s (%s, line %d)\n", __func__, __FILE__, __LINE__);} while(0) + #define DBG(...) do{ fprintf(stderr, COLOR_OLD); \ + fprintf(stderr, "%s (%s, line %d): ", __func__, __FILE__, __LINE__); \ + fprintf(stderr, __VA_ARGS__); \ + fprintf(stderr, "\n");} while(0) +#else + #define FNAME() do{}while(0) + #define DBG(...) do{}while(0) +#endif //EBUG + +/* + * Memory allocation + */ +#define ALLOC(type, var, size) type * var = ((type *)my_alloc(size, sizeof(type))) +#define MALLOC(type, size) ((type *)my_alloc(size, sizeof(type))) +#define FREE(ptr) do{if(ptr){free(ptr); ptr = NULL;}}while(0) + +#ifndef DBL_EPSILON +#define DBL_EPSILON (2.2204460492503131e-16) +#endif + +typedef struct { + char *portname; // device filename (should be freed before structure freeing) + int speed; // baudrate in human-readable format + tcflag_t baudrate; // baudrate (B...) + struct termios oldtty; // TTY flags for previous port settings + struct termios tty; // TTY flags for current settings + int comfd; // TTY file descriptor + char *buf; // buffer for data read + size_t bufsz; // size of buf + size_t buflen; // length of data read into buf + bool exclusive; // should device be exclusive opened +} TTYdescr; + +void *my_alloc(size_t N, size_t S); +int str2double(double *num, const char *str); +double dtime(); +TTYdescr *new_tty(char *comdev, int speed, size_t bufsz); +int tty_init(TTYdescr *descr); +TTYdescr *tty_open(TTYdescr *d, bool exclusive); +size_t read_tty(TTYdescr *d); +int write_tty(int comfd, const char *buff, size_t length); +void close_tty(TTYdescr **descr); +int handshake(); +char *keyval(char *key, char *haystack); +char *cpy2buf(char *string, size_t *l); + +#endif // TTY_PROCS_H__