Re: Port serie et Qt

Top Page

Reply to this message
Author: Raphael Jacquot
Date:  
To: guilde
Subject: Re: Port serie et Qt
rah, encore de #@&!§§ de reply-to

Pierre Carecchi wrote:

> ouais sauf que dans les libs de Qtfree, il n'y a rien pour les ports serie!
> dans mon programme console, i n'y avait rien de bloquant, j'avais deux
> threads ( un de lecture, un d'ecriture) et un mutex, pour empecher que
> ca se morde la queue..
> mais pas moyen d transposer ca sous Qt, il y a bien des classes de
> thread et de mutex, mais ca me parait pas super efficace; Quand aux
> threads et mutex "standard", dans un programme Qt ,ca n'a pas l'air
> mieux...
> faut peut etre que j'essaie gtk, j'ai un bouquin, mais j'ai pas encore
> joué avec...
>


oue, QT n'inclue aucune abstration des appels systeme poll et select
(c'est de ca dont il s'agit.)

g_io_channel est parfaitement adapte pour ca...

voir l'exemple joint qui gere un port serie et des sockets

/*
* ngpsd - a gpsd clone with dbus support
*
* Copyright (C) 2004, Amaury Jacquot
*
* Amaury Jacquot <sxpert@???>
*
* This program is free software, distributed under the terms of
* the GNU General Public License
*/

/* TODO:
* - system configuration file
* - parse the gps data like gpsd does
* - log all available gps data
* - dbus interface
*
* various other things (see TODO down below)
*/

#include <stdio.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <sys/poll.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <netdb.h>
#include <string.h>
#include <stdlib.h>
#include <unistd.h>
#include <glib.h>
#include <fcntl.h>
#include <termios.h>
#include <signal.h>
#include "smart-buffer.h"
#include "nmea.h"
#include "logger.h"

#define DEFAULTPORT 2947


/****************************************************************************************
* unix utility functions
*/

/*
 * connects and sets the serial port up under linux.
 */
GIOChannel* nmea_linux_init_serial (char *serial_port) {
    int        fd;
    struct termios    tio;
    GIOChannel*    channel;
    GIOStatus    status;
    GError*        error = NULL;


    fd = open(serial_port, O_RDONLY | O_NOCTTY );
    if (fd < 0) {
        perror("");
        return NULL;
    }
    /* serial port voodoo */
    bzero(&tio,sizeof(tio));
    tio.c_cflag = B4800 | CS8 | CREAD | CLOCAL;
    tio.c_iflag = IGNBRK | IGNPAR | IGNCR;
    tio.c_oflag = 0;
    tio.c_lflag = 0;
    tio.c_cc[VMIN] = 1; 
    tio.c_cc[VTIME] = 5;

    
    tio.c_cc[VEOF] = 0;
    tio.c_cc[VEOL] = 0;
    tio.c_cc[VEOL2] = 0;
    tio.c_cc[VERASE] = 0;
    tio.c_cc[VKILL] = 0;
    tio.c_cc[VLNEXT] = 0;
    tio.c_cc[VREPRINT] = 0;
    //tio.c_cc[VSTATUS] = 0;
    tio.c_cc[VWERASE] = 0;


    tcflush (fd, TCIFLUSH);
    tcsetattr (fd, TCSANOW, &tio);


    channel = g_io_channel_unix_new (fd);
    status = g_io_channel_set_encoding (channel, NULL, &error);
    if (error) {
        g_print ("error: unable to set channel encoding to NULL\n%s\n",error->message);
        return NULL;
    }
    return channel;
}


void tcp_enable_reuseaddr (gint sock) {
    gint tmp = 1;
    if (sock <= 0)
        return;
    if (setsockopt (sock, SOL_SOCKET, SO_REUSEADDR, (gchar*)&tmp, sizeof (tmp)) == -1)
        perror ("Bah! Bad setsockopt ()\n");
}


void tcp_enable_nbio (gint fd) {
    if (fcntl (fd, F_SETOWN, getpid()) == -1)
        perror("fcntl (F_SETOWN) error\n");
    if (fcntl (fd, F_SETFL, FNDELAY) == -1) 
        perror ("fcntl (F_SETFL, FNDELAY) error\n");
}


/* TODO: get port from the config */
GIOChannel* nmea_init_server_socket (int port) {
    int             s;
    struct sockaddr_in     addr;
    GIOChannel*         channel;
    GIOStatus        status;
    GError*            error=NULL;


    if (port <= 0) {
        fprintf(stderr, "illegal port %d\n", port);
        return NULL;
    }


    s = socket (AF_INET, SOCK_STREAM, IPPROTO_TCP);
    if (s == -1) {
        perror("creating socket");
        return NULL;
    }


    tcp_enable_reuseaddr (s);
    memset (&addr, 0, sizeof(addr));
    addr.sin_family        = AF_INET;
    addr.sin_port        = htons ((u_short)port);
    addr.sin_addr.s_addr    = INADDR_ANY;
    if (bind (s, (struct sockaddr *)&addr, sizeof (addr)) == -1) {
        perror ("during binding");
        close (s);
        return NULL;
    }
    if (listen (s,5) == -1) {
        perror ("during listen");
        close (s);
        return NULL;
    }
    channel = g_io_channel_unix_new (s);
    status = g_io_channel_set_encoding (channel, NULL, &error);
    if (error) {
        g_print ("error: unable to set channel encoding to NULL\n%s\n",error->message);
        exit (1);
    }
    return channel;

    
}
/****************************************************************************************
* GIOChannel utilities
*/

/* io channel for the serial port */
GIOChannel* serial;
/* io channel for the server socket */
GIOChannel* server;

/* struct to store client information */
typedef struct {
    GIOChannel* channel;
    SmartBuffer* buffer;
    gboolean send_nmea;
} client_struct;


/* list of all clients */
GSList* clients = NULL;

void g_io_condition_display (char* function,GIOCondition cond) {
    printf ("%s - ",function);
    switch (cond) {
    case G_IO_IN:   printf ("G_IO_IN\n"); break;
    case G_IO_OUT:  printf ("G_IO_OUT\n"); break;
    case G_IO_PRI:  printf ("G_IO_PRI\n"); break;
    case G_IO_ERR:  printf ("G_IO_ERR\n"); break;
    case G_IO_HUP:  printf ("G_IO_HUP\n"); break;
    case G_IO_NVAL: printf ("G_IO_NVAL\n"); break;
    default: printf ("%d", cond);
    }
}


/****************************************************************************************
* the main part of the application
*/

/* the glib main loop variable */
GMainLoop* main_loop;
gint debug = 0;
NmeaStatus ns;

const char line_end[] = "\r\n";
const char gpsd_sig[] = "GPSD";

void client_send_line (client_struct* cl, gchar *line) {
    gint len;
    gint written;
    GError* error = NULL;

    

    /* send everything except the end of line char */
    len = strlen (line) -1; 
    g_io_channel_write_chars (cl->channel, line, len, &written, &error);


    if (len != written) {
        g_warning ("not all bytes were written");
    }


    /* then send a "\r\n" end of line sequence */
    g_io_channel_write_chars (cl->channel, line_end, strlen(line_end), &written, &error); 


    /* flushes the write buffer so that the line appears immediatly */
    g_io_channel_flush (cl->channel, &error);
}



/*
* distributes the nmea line to the clients that requested it
*/
void nmea_send_line (client_struct* cl, gchar* line) {

    if (cl->send_nmea) {
        client_send_line(cl, line);
    }
}


/*
 * function called when something has to be read from the serial port
 */
gboolean nmea_serial_read (GIOChannel* serial, GIOCondition cond, SmartBuffer* buffer) {
    int nbc;
    gchar temp[9];
    gchar* line;
    GError* error = NULL;
    GIOStatus status;
    NmeaSentenceType stype;


    if (cond != G_IO_IN) return TRUE;
    bzero (&temp,9);
    /* we have been called because something was to be read from the serial port */
    status = g_io_channel_read_chars (serial, temp, 8, &nbc, &error);
    /* check if anything bad happened */
    switch (status) {
    case G_IO_STATUS_ERROR :
    case G_IO_STATUS_EOF :
    case G_IO_STATUS_AGAIN :
        fprintf (stderr, "unable to read from serial port: %s\n", error->message);
        g_error_free (error);
        /* we crapped out */
        g_main_loop_quit (main_loop);
        return FALSE;
    /* everything is normal */
    case G_IO_STATUS_NORMAL:
        if (nbc > 0) {
            smart_buffer_append_buffer (buffer, nbc, temp);
            line = smart_buffer_extract_line (buffer);
            if (line) {
                /* checks for line correctness */
                if (nmea_check_line (line)) {
                    /* parse the line */
                    stype = nmea_parse_line (line, &ns);
                    /* TODO that's an horrible hack */
                    if (stype == NMEA_SENTENCE_GPGSV) log_write_info(&ns);
                    /* print line to console if required */
                    if (debug > 2) printf ("%s",line);
                    /* send line to all clients that requested it */
                    g_slist_foreach (clients, (GFunc) nmea_send_line, line);
                }
                g_free (line);
            }
        }
    }
    return TRUE;
}


/*
 * function called when the client sends a command
 */
void nmea_client_disconnect (GIOChannel* channel,client_struct* cl) {
    GError* error = NULL;


    printf ("client has disconnected\n");
    /* closes the channel*/
    g_io_channel_shutdown (channel, FALSE, &error);
    /* destroys the buffer */
    smart_buffer_destroy (cl->buffer);
    /* removes client from list */
    clients = g_slist_remove (clients, cl);
    /* destroys the client data struct */
    g_free (cl);
}



gboolean nmea_client_command (client_struct* cl, gchar *command) {
GString *rline;
gboolean lempty = FALSE;
int i;

    rline = g_string_new(gpsd_sig);
    g_string_append_printf(rline,",");
    for (i=0; i<strlen(command); i++) {
        switch(command[i]) {
            case 'p': /* position */
            case 'P':
                if (lempty)
                    g_string_append_printf(rline,",");
                else
                    lempty=TRUE;
                g_string_append_printf(rline, "P=%4.6f %4.6f", ns.latitude, ns.longitude);
                break;
            case 'd': /* date */
            case 'D': {
                char dstr[64];


                    strftime(dstr, 64, "%m/%d/%Y %H:%M:%S", &ns.datetime);
                    if (lempty)
                        g_string_append_printf(rline,",");
                    else
                        lempty=TRUE;
                    g_string_append_printf(rline, "D=%s", dstr);
                }
                break;
            case 'a': /* altitude */
            case 'A':
                if (lempty)
                    g_string_append_printf(rline,",");
                else
                    lempty=TRUE;
                g_string_append_printf(rline, "A=%.6f", ns.altitude);
                break;
            case 'v': /* velocity */
            case 'V':


                if (lempty)
                    g_string_append_printf(rline,",");
                else
                    lempty=TRUE;
                g_string_append_printf(rline, "V=%.6f", ns.speed);
                break;
            case 's': /* GPS status */
            case 'S':
                if (lempty)
                    g_string_append_printf(rline,",");
                else
                    lempty=TRUE;
                g_string_append_printf(rline, "S=%d", ns.fixtype);
                break;
            case 'm': /* GPS mode */
            case 'M':
                if (lempty)
                    g_string_append_printf(rline,",");
                else
                    lempty=TRUE;
                break;
            case 'r': /* toggle raw mode */
            case 'R':
                if (cl->send_nmea)
                    cl->send_nmea = FALSE;
                else
                    cl->send_nmea = TRUE;
                if (lempty)
                    g_string_append_printf(rline,",");
                else
                    lempty=TRUE;
                g_string_append_printf(rline, "R=%c", cl->send_nmea ? '1' : '0');
                break;
            case 'g': /* Maidenhead grid square */
            case 'G':
                if (lempty)
                    g_string_append_printf(rline,",");
                else
                    lempty=TRUE;
                break;
            default:
                break;
        }
    }
    g_string_append_printf(rline, "\n");
    client_send_line(cl, rline->str);


    g_string_free(rline, FALSE);


return TRUE;
}


gboolean nmea_client_handle (GIOChannel* channel, GIOCondition cond, client_struct* cl) {
    char buffer[128];
    int nbc,i;
    GError* error = NULL;
    GIOStatus status;
    char* line;


    /* note... trick is, multiple conditions are possible at the same time */
    if (cond & G_IO_HUP) {
        nmea_client_disconnect (channel, cl);
        return FALSE;
    }
    if (cond & G_IO_IN) {
        /* read whatever the client send */
        /* TODO: stop dumping in the bit bucket */
        /* TODO: emulate gpsd functionnality here */
        status = g_io_channel_read_chars (channel, buffer, 128, &nbc, &error);
        switch (status) {
        case G_IO_STATUS_EOF:
            nmea_client_disconnect (channel, cl);
            return FALSE;
        case G_IO_STATUS_NORMAL:
            printf ("client said >>>> %d ",nbc);
            for (i=0;i<=nbc;i++) printf("%c 0x%02x - ",buffer[i],buffer[i]);
            printf ("\n");
            smart_buffer_append_buffer (cl->buffer, nbc, buffer);
            line = smart_buffer_extract_line (cl->buffer);
            if (line) {
                printf (">>>> client command %s",line);
                nmea_client_command(cl, line);
                g_free(line);
            }
            break;
        default:
            if (error) printf ("%s\n", error->message);
            else printf ("No error message\n");
        }
    }
    return TRUE;
}


/*
 * function called when something happens on the server socket 
 * namely when a new client connects
 */
gboolean nmea_new_client (GIOChannel* source, GIOCondition cond, gpointer data) {
    gint             sock;    
    gint             new;
    gint             client;
    GIOStatus        status;
    GIOChannel*        new_channel;
    GError*            error = NULL;
    struct sockaddr_in     client_addr;
    client_struct*         cl;

    
    if (cond & G_IO_HUP) {
        g_print ("server socket has hung up\n");
        g_print ("this shouldn't happen\n");
    }
    if (cond & G_IO_IN) {
        sock = g_io_channel_unix_get_fd (source);
        if (( new = accept (sock, (struct sockaddr *)&client_addr, &client)) < 0) {
            /* server socket is fubar
             * create a new one... 
             */
            printf("sock: %d\n client: %d\n",sock,client);
            perror ("Unable to accept new connection");
            return TRUE;
        }
        /* TODO: detail where the client comes from for the log */
        tcp_enable_nbio (new);
        new_channel = g_io_channel_unix_new (new);
        status = g_io_channel_set_encoding (new_channel, NULL, &error);
        if (error) {
            g_print ("error: unable to set channel encoding to NULL\n%s\n",error->message);
            exit (1);
        }
        printf("client is connected\n");
        /* creates the client struct */
        cl = g_new0 (client_struct, 1);
        cl->channel = new_channel;
        cl->buffer = smart_buffer_new ();
        /* for the moment, spew the nmea from start */
        cl->send_nmea = FALSE;
        /* add the client to the list of clients */
        clients = g_slist_prepend (clients, cl);

    
        /* watches the client for things to do... */
        g_io_add_watch (new_channel, G_IO_IN | G_IO_HUP, (GIOFunc)nmea_client_handle, cl);

            
    } else g_io_condition_display ("nmea_new_client",cond);
    return TRUE;
}


/*
 * main loop function
 */
void nmea_main_loop(void) {
    SmartBuffer* buffer;

    
    /* creates the smart buffer */
    buffer = smart_buffer_new ();
    nmea_cycle_initialize (&ns);
    /* creates the GMainLoop */
    main_loop = g_main_loop_new (NULL, TRUE);


    g_io_add_watch (serial, G_IO_IN, (GIOFunc)nmea_serial_read, buffer);
    g_io_add_watch (server, G_IO_IN | G_IO_HUP, (GIOFunc)nmea_new_client, NULL);


    g_main_loop_run (main_loop);
}



void print_usage(char *arg0) {
    fprintf(stderr, "Usage:\n\t%s [-h][-d][-p port][-s port][-l filename]\n", arg0);
}



/*
* initialization
*/
int main (int argc, char **argv) {
int optc;
char serial_port[PATH_MAX];
int port;

    /* init to defaults */
    strcpy(serial_port, "/dev/ttyS0");
    port = DEFAULTPORT;
    debug = 0;


    while ((optc = getopt(argc, argv, "s:p:d:l:h")) != -1) {
        switch ((char)optc) {
            case 'h':
                print_usage(argv[0]);
                exit(0);
                break;
            case 'd':
                if (optarg != NULL)
                    debug = atoi(optarg);
                else
                    debug = 1;
                break;
            case 'p':
                port = atoi(optarg);
                break;
            case 's':
                strncpy(serial_port, optarg, PATH_MAX);
                break;
            case 'l':
                if (optarg==NULL) {
                    fprintf (stderr,"logging requires a filename\n");
                    print_usage (argv[0]);
                    exit(0);
                }
                log_initialize (optarg, 1);
                break;
        }
    }


    if (debug) {
        fprintf(stderr, "NGPSD server starting:\n");
        fprintf(stderr, "\tserial port: '%s'\n", serial_port);
        fprintf(stderr, "\tIP listen port: %d\n", port);
        fprintf(stderr, "\tdebug level: %d\n", debug);
        fprintf(stderr, "\tnot going into background\n");
    } else {
        pid_t cpid;


        /* so the child does not become a zombie */
        signal (SIGCHLD, SIG_IGN);
        cpid = fork();
        if (cpid > 0) {
            printf("PID = %d\n", cpid);
            return(0);
        } else if (cpid < 0) {
            fprintf(stderr, "fork() failed, staying in foreground\n");
        }
    }
    /* first thing, prenvent SIGPIPE from killing us... */
    signal (SIGPIPE, SIG_IGN);


    /* TODO: XML configuration file */
    serial = nmea_linux_init_serial (serial_port);
    if (serial == NULL) {
        exit (-1);
    }


    /* attempts to create the server socket until it works */
    server = nmea_init_server_socket (port);
    if (server == NULL) {
        exit (-1);
    }

    
    nmea_main_loop ();
    return 0;
}