/* Ansteuerung für EINE serielle Conrad-Relaiskarte 8fa Best. Nr. 967720, rund 40 EUR. Original Source: http://www.netzmafia.de/skripten/hardware/relais/relais.html Another program for multiple 8fa cards on one port: http://www.relaiskarte.thomas-dohl.de/ * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License * as published by the Free Software Foundation; either version 2 * of the License, or (at your option) any later version. * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software Foundation, * Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. compile e. g. with gcc -O3 -pthread -lpthread -o relais relais.c This works in 2019; in 2008 the Option "-pthread" was not necessary. Usage: relais Examples: ./relais /dev/ttyS0 -off -s3 for first serial port, default setting off, switch on relais 3. ./relais /dev/ttyS0 -x0x33 for first serial port, switch on only relays 1, 2, 5 and 6. The relays do have the number 1..8, so BIT0 is for relay one and so on. Rolf Freitag 2005: Added select, tcflush, timeout with pthread, ioctl for exclusive mode, time checks, packet length checks, check of the address, printing of the firmware version, pthread_join, 200 ms pause, signal handler, option for the device file (e. g. /dev/ttyS0), hex friendly input/output ... 2019-02-22 */ //#define DEBUG #include #include #include #include #include /* File control definitions */ #include /* POSIX terminal control definitions */ #include #include #include #include #include #include #include #include #include // signals // simple version number #define SOFTWARE_VERSION_NUMBER 1.4 /* * Befehlssatz der Karte: * = 'dont't care', in der Regel 0 * I_XOR = Pruefinfo (XOR aller Werte, wird vom Programm berechnet) * * Kommando Kommandorahmen Antwort * ------------------------------------------------------------------------- * 0 NO OPERATION - keine Aktion 0 I_XOR 255 I_XOR * 1 SETUP - Initialisierung 1 I_XOR 254 I_XOR * 2 GET PORT - Schaltzustand 2 I_XOR 254 I_XOR * 3 SET PORT - Relais schalten 3 I_XOR 253 I_XOR * 4 GET OPTION - Option lesen 4 I_XOR 254 I_XOR * 5 SET OPTION - Option setzen 5 I_XOR 254 I_XOR * ------------------------------------------------------------------------- * * Das Init-Kommando initialisiert alle Karten einer Kette. Der Computer * erhaelt also bei N Karten N+1 Antworten! Dasselbe gilt fuer * das GET PORT-Kommando. * Bei den Optionen koennen nur "enable broadcast" und "block broadcast" * gesetzt werden: * Wert Broadcast Broadcast * ausfuehren blockieren * --------------------------------------------- * 0 nein nein * 1 ja nein (Voreinstellung) * 2 nein ja * 3 ja ja (nicht sinnvoll) * --------------------------------------------- * Bei blockierten Brodcast wird bei INIT und GET PORT nur ein NOP an * die nachfolgenden Platinen gesendet. * * Ausgangsport ist normalerweise ttS0 bis ttyS3. * Der Port muss fuer den user oder die Gruppe, unter der * das Programm laeuft, Schreibberechtigung haben. * Die User des Programms koennen in die Gruppe von ttySx * aufgenommen werden (meist 'tty' oder 'dialout'). * Gegebenfalls kann man auch das Programm der Gruppe von ttySx * zuordnen und das SetGroupId-Bit setzen. */ // show conrad bugs #define IGNORE_CONRAD_BUGS // repeat till success #define EXIT_ONLY_ON_SUCCESS volatile static int g_fd; // global file descriptor (for device file) volatile static long long int g_lli_time0, g_lli_time1, g_lli_time2, g_lli_time3; // global times static pthread_t thread1; // open thread static char a_device[256]; // device name // time(NULL) with microsecond resolution //inline signed long long int get_time () { struct timeval ti; struct timezone tzp; gettimeofday (&ti, &tzp); return (ti.tv_usec + 1000000 * ((long long int) ti.tv_sec)); } int open_port (void) { /* * Oeffnet seriellen Port * Gibt das Filehandle zurueck oder -1 bei Fehler * der Parameter port muss 0, 1, 2 oder 3 sein * * RS232-Parameter * - 19200 baud * - 8 bits/byte * - no parity * - no handshake * - 1 stop bit */ int fd, modelines = 0; struct termios options; fd = open (a_device, O_RDWR | O_NOCTTY | O_NDELAY); printf ("got file descriptor fd=%d\n", fd); if (fd >= 0) { if (!isatty (fd)) { fprintf (stderr, "The Device %s is no Terminal-Device !\n", a_device); return (-1); } /* get the current options */ fcntl (fd, F_SETFL, 0); if (tcgetattr (fd, &options) != 0) return (-1); cfsetispeed (&options, B19200); /* setze 19200 bps */ cfsetospeed (&options, B19200); /* setze Optionen */ options.c_cflag &= ~PARENB; /* kein Paritybit */ options.c_cflag &= ~CSTOPB; /* 1 Stopbit */ options.c_cflag &= ~CSIZE; /* 8 Datenbits */ options.c_cflag |= CS8; // 8 bit per Byte options.c_cflag |= (CLOCAL | CREAD); // CD-Signal ignorieren, enable receive, /* Kein Echo, keine Steuerzeichen, keine Interrupts */ options.c_lflag &= ~(ICANON | ECHO | ECHOE | ISIG); options.c_oflag &= ~OPOST; /* setze "raw" Input */ options.c_cc[VMIN] = 0; /* warten auf min. 0 Zeichen */ options.c_cc[VTIME] = 10; /* Timeout 10 Sekunden */ #ifdef DEBUG printf ("before tcflush\n"); #endif tcflush (fd, TCIOFLUSH); #ifdef DEBUG printf ("before tcsetattr\n"); #endif if (tcsetattr (fd, TCSAFLUSH, &options) != 0) { fprintf (stderr, "ERROR: Could not set terminal attributes for %s !\n", a_device); return (-1); } #ifdef DEBUG printf ("before ioctl\n"); #endif // Set IO-Control-Parameter and check the device. if (-1 == ioctl (fd, TIOCEXCL, &modelines)) // Put the tty into exclusive mode. { (void)fprintf (stderr, "ERROR at ioctl TIOCEXCL on port %s!\n", a_device); perror ("ioctl()"); return (-1); } } else { fprintf (stderr, "Could not open device %s.\n", a_device); } return (fd); } int sndcmd (const int fd, const unsigned char command, const unsigned char addr, const unsigned char data) { /* * Sendet Kommando an die Relaiskarte * die Berechnung der Pruefsumme erfolgt automatisch * Rueckgabe: 0 bei Erfolg, -1 bei Fehler */ unsigned char wbuf[4]; int i; g_lli_time0 = get_time (NULL); // store start time wbuf[0] = command; wbuf[1] = addr; wbuf[2] = data; /* Pruefsumme */ wbuf[3] = wbuf[0] ^ wbuf[1] ^ wbuf[2]; #ifdef DEBUG printf (" -> send: %d %d %d %d\n", wbuf[0], wbuf[1], wbuf[2], wbuf[3]); #endif usleep (100000L); // 100 ms Pause, Pause für die Karte i = write (fd, wbuf, 4); tcdrain (fd); // wait till the output has been transmitted g_lli_time1 = get_time (NULL); // store send time if (4 == i) return (0); printf ("ERROR: Could send only %d of 4 Bytes\n", i); return (-1); } int rcvstat (const int fd, unsigned char *answer, unsigned char *addr, unsigned char *data) { /* * Empfaengt Status von der Relaiskarte * die Berechnung der Pruefsumme erfolgt automatisch * Rueckgabe: 0 bei Erfolg, -1 bei Fehler */ unsigned char rbuf[4] = { 0 }; int i_xor, i_retval, i = 0; long long int lli_timediff0, lli_timediff1; struct timeval timeout; fd_set readfs; // file descriptor set loop: memset (&timeout, 0, sizeof (timeout)); timeout.tv_sec = 1; // 1 second timeout for select FD_ZERO (&readfs); FD_SET (fd, &readfs); i_retval = select (fd + 1, &readfs, NULL, NULL, &timeout); if (0 != i_retval) { if (i_retval == -1) { perror ("select()"); tcflush (fd, TCIOFLUSH); // flush buffers return (-1); } g_lli_time2 = get_time (NULL); // store first receive time i_retval = read (fd, rbuf, 4); // number of received bytes if (-1 == i_retval) // read error { fprintf(stderr, "Error: read returned -1.\n"); } else { i += i_retval; } if (i < 4) // not all bytes have been received: loop goto loop; g_lli_time3 = get_time (NULL); // store last receive time lli_timediff0 = g_lli_time3 - g_lli_time0; // first receive time - send time lli_timediff1 = g_lli_time3 - g_lli_time1; // last receive time - send time if ((lli_timediff0 < 5000) or (lli_timediff1 > 25000)) // < 5 ms or > 25 ms { printf ("frame warning\n"); // return (-1); // impossible short time or extrem long time } i_xor = rbuf[0] ^ rbuf[1] ^ rbuf[2]; #ifdef DEBUG printf (" -> recv: %d %d %d %d\n", rbuf[0], rbuf[1], rbuf[2], rbuf[3]); #endif *answer = rbuf[0]; *addr = rbuf[1]; *data = rbuf[2]; if ((i_xor == rbuf[3]) and (4 == i) and (1 == *addr)) // checksum of 4 bytes and addr ok return (0); else { i_retval = 0; if (i_xor != rbuf[3]) { printf ("ERROR: XOR checksum is %x but should be %x.\n", i_xor, rbuf[3]); i_retval--; } if (4 != i) { printf ("ERROR: Could receive only %d of 4 Bytes.\n", i); i_retval--; } if (1 != *addr) { #ifndef IGNORE_CONRAD_BUGS printf ("ERROR: Receive address is %d but should be 1, ignoring.\n", *addr); #endif // do not set i_retval: ignore the old conrad bug } return (i_retval); } } // if (0 != i_retval) fprintf (stderr, "Connection timeout (select failed).\n"); return (-1); } void help (void) { /* Hilfetext ausgeben */ fprintf (stderr, "Ansteuerprogramm fuer die serielle Relaiskarte von Conrad\n"); fprintf (stderr, "No Parameters!\n"); fprintf (stderr, "\n"); fprintf (stderr, "Usage: relais \n"); fprintf (stderr, "\aor: %s -V \n", "relais"); fprintf (stderr, "[] = Optional <> = Parameter Requirement. 1<=N parameters required.\n"); fprintf (stderr, "\n"); fprintf (stderr, "Parameter:\n"); fprintf (stderr, " -stat: Status der Relais als Dezimalzahl\n"); fprintf (stderr, " Bit=1: Relais an, Bit=0: Relais aus\n"); fprintf (stderr, " keine weiteren Parameter moeglich\n"); fprintf (stderr, " -off: alle Relais aus\n"); fprintf (stderr, " -on: alle Relais an\n"); fprintf (stderr, " -sx: Relais x einschalten (1 <= x <= 8)\n"); fprintf (stderr, " -rx: Relais x ausschalten (1 <= x <= 8)\n"); fprintf (stderr, "\n"); fprintf (stderr, "Beispiel:\n"); fprintf (stderr, " relais /dev/ttyUSB0 -off -s1 -s3: Relais 1 und 3 einschalten\n"); fprintf (stderr, " relais /dev/ttyUSB0 -s4 -r3: Relais 4 ein- und 3 ausschalten\n"); fprintf (stderr, " relais /dev/ttyUSB0 -on -r6: alle Relais ausser 6 einschalten\n"); fprintf (stderr, "\n"); fprintf (stderr, "Rueckgabewert:\n"); fprintf (stderr, "OK, Kontaktstellung: \n"); fprintf (stderr, "z. B. 'OK, Kontaktstellung: 5' --> Relais 1 und 3 on\n"); fprintf (stderr, "Bei Fehler wird FAIL: und der komplette Status\n"); fprintf (stderr, "zurueckgegeben (Antwortcode Adresse Daten/Info).\n"); fprintf (stderr, "\n"); } void sig_handler_main (const int sig) // signal handler { if ((SIGINT == sig) or (SIGILL == sig) or (SIGKILL == sig) or (SIGSEGV == sig) or (SIGTERM == sig)) { (void) printf ("\a\a\a\a\a\a\a Signal %d, program exiting... \r\n\a\a\a", sig); exit (-sig); } return; } void sig_handler1 (const int sig) // signal handler for the open thread { static int retval; retval = -sig; if (SIGUSR1 == sig) { pthread_exit ((void *) &retval); } if ((SIGINT == sig) or (SIGILL == sig) or (SIGKILL == sig) or (SIGSEGV == sig) or (SIGTERM == sig)) { (void) printf ("\a\a\a\a\a\a\a Signal %d, program exiting... \r\n\a\a\a", sig); exit (retval); } return; } // simple timeout void * func_timeout (void *threadid) { static int retval; // Without static the returned retval would always be zero. int i; retval = 0; for (i = 0; i <= 0xff; i++) signal (i, sig_handler_main); sleep (1); // timeout in s, deadline for open thread pthread_kill (thread1, SIGUSR1); if (0 >= g_fd) // device file could not be opened { printf ("ERROR: Timeout, device file could not be opened, file descriptor is %d.\n", g_fd); retval = -1; } pthread_exit ((void *) &retval); } // open thread which can hang at open_port void * func_open (void *threadid) { static int retval; // Without static the returned retval would always be zero. int i; retval = 0; for (i = 0; i <= 0xff; i++) signal (i, sig_handler1); if (-1 == (g_fd = open_port ())) { fprintf (stderr, "Cannot open %s\n", a_device); retval = -1; } #ifdef DEBUG printf ("g_fd=%d\n", g_fd); #endif pthread_exit ((void *) &retval); } int main (int argc, char *argv[]) { // static asserts that all values are initialized with 0 recursively static int i, i_ret, id0, id1; // i, File descriptor for the port, return value, thread id static unsigned char ans, adr, stat, val, rval, n, retval; static pthread_attr_t attr; static int thread_return0, thread_return1; // for pthread_return static void *statusp; // for pthread_return static jmp_buf env; // environment static pthread_t thread0; if ( (argc>1) and not strncmp (argv[1], "-V", 123) ) { fprintf (stdout, "%s version %.1f\n", argv[0], SOFTWARE_VERSION_NUMBER); exit ( (2 == argc) ? 0 : -1); } setjmp (env); // restart point after failure for (i = 0; i <= 0xff; i++) signal (i, sig_handler_main); if ((argc <= 2) || (strncmp (argv[1], "--help", 123) == 0)) { help (); exit (0); } strncpy (a_device, argv[1], sizeof (a_device)); // copy device name if (pthread_attr_init (&attr)) { printf ("pthread_attr_init FAILED\n"); goto err_end; } if (pthread_attr_setdetachstate (&attr, PTHREAD_CREATE_JOINABLE)) { printf ("\npthread_attr_setdetachstate FAILED\n"); (void) pthread_attr_destroy (&attr); goto err_end; } // create thread for timeout (to avoid hangup in open_serial, e. g. when the device can not be opened) if ((i_ret = pthread_create (&thread0, &attr, func_timeout, (void *) &id0))) { printf ("ERROR; return code from pthread_create(...) is %d\n", i_ret); perror ("pthread_create(...)\n"); goto err_end; } if ((i_ret = pthread_create (&thread1, &attr, func_open, (void *) &id1))) { printf ("ERROR; return code from pthread_create(...) is %d\n", i_ret); perror ("pthread_create(...)\n"); goto err_end; } (void) pthread_join (thread0, &statusp); thread_return0 = *(int *) statusp; (void) pthread_join (thread1, &statusp); thread_return1 = *(int *) statusp; if (thread_return0 or thread_return1) // if timeout goto err_end; // Karte Initialisieren sndcmd (g_fd, 1, 1, 0); val = rcvstat (g_fd, &ans, &adr, &stat); if (val or (ans != 254)) { fprintf (stderr, "FAIL: card init failed (%d %d %d %d).\n", ans, adr, stat, val); sleep (1); // wait till the card wakes up ... goto err_end; } printf ("Firmware-Version %hhu\n", stat); // zweite Antwort auf das init vernichten val = rcvstat (g_fd, &ans, &adr, &rval); if (val or (ans != 1)) { fprintf (stderr, "FAIL: second part of card init failed (%d %d %d %d).\n", ans, adr, rval, val); sleep (1); // wait till the card wakes up ... goto err_end; } /* Aktuellen Stand abfragen */ sndcmd (g_fd, 2, 1, 0); val = rcvstat (g_fd, &ans, &adr, &stat); if ((val) or (ans != 253)) { fprintf (stderr, "FAIL: Could not read the status (%d %d %d %d).\n", ans, adr, stat, val); sleep (1); // wait till the card wakes up ... goto err_end; } printf ("OK, Aktuelle Kontaktstellung: %d = 0x%x\n", stat, stat); /* Parameter auswerten */ rval = stat; // rval (=new stat value) default: old value for (n = 2; n < argc; n++) { if (strncmp (argv[n], "-stat", 123) == 0) { close (g_fd); exit (0); } else { if (strncmp (argv[n], "-off", 123) == 0) { /* Alles ausschalten */ rval = 0; } else { if (strncmp (argv[n], "-on", 123) == 0) { /* Alles einschalten */ rval = 255; } else { if ((argv[n][0] == '-') && (argv[n][1] == 's')) { val = strtol(&argv[n][2], (char **)NULL, 0); /* Zahl hinter dem "-s" umwandeln */ if ((1 <= val) && (val <= 8)) { val = 1 << (val - 1); rval = rval | val; } } else { if ((argv[n][0] == '-') && (argv[n][1] == 'r')) { val = strtol(&argv[n][2], (char **)NULL, 0); /* Zahl hinter dem "-r" umwandeln */ if ((1 <= val) && (val <= 8)) { val = ~(1 << (val - 1)); rval = rval & val; } } else { if ((argv[n][0] == '-') && (argv[n][1] == 'x')) { val = strtol(&argv[n][2], (char **)NULL, 0); /* Zahl hinter dem "-x" umwandeln */ // if ((0 <= val) && (val <= 0xff)) // comparison is always true due to limited range of data type { rval = val; } } else { fprintf (stderr, "ERROR: Wrong Parameter: %s, exiting.\n", argv[n]); close (g_fd); exit (-1); } } } } } } } sndcmd (g_fd, 3, 1, rval); val = rcvstat (g_fd, &ans, &adr, &stat); close (g_fd); if ((val) or (ans != 252)) { printf ("FAIL: %d %d %d %d\n", ans, adr, stat, val); goto err_end; } printf ("OK, Neue Kontaktstellung: %d = 0x%x\n", stat, stat); exit (0); err_end: if (g_fd) close (g_fd); retval = -1; // use another value before goto and delete this line if you need different error codes #ifdef EXIT_ONLY_ON_SUCCESS longjmp (env, retval); #else exit (retval); #endif }