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;
}