Teleinfosocket mysql.c
De MicElectroLinGenMet.
Programme C Linux pour lire les données d'un démodulateur Téléinfo connecté à une interface rs232/ethernet EZL-60 / EZL-300L ou au daemon ser2net sur le Wrt54gl par exemple et les enregistrer dans une base MySql.
Version pour abonnement triphasé heures creuses, gérant les timeout, checksums et fichier lock plus connexion type ser2net.
Voir source pour modifier en fonction abonnement.
Voir la table MySql Téléinfo: Structure de la table Teleinfo
/* teleinfosocket_mysql.c */ /* Version pour PC relié à une interface rs232/ethernet type ezl-60 ou à un daemon ser2net */ /* Lecture compteur triphasé (voir paramètres à changer suivant abonnement) */ /* Connexion au wrt54gl avec daemon ser2net (Rajouter le #define SER2NET) */ /* telnet wrt54gl 2001 */ /* Rajout du "#define SER2NET" pour gérer tampon réception du daemon ser2net */ /* Connexion sur ezl-60 */ /* telnet ezl-60 1470 */ /* Modifier les "define" EZLHOST, EZLPORT, SER2NET en fonction de la connexion */ /* Par domos78 at free point fr */ /* Enregistre données téléinfo sur base mysql si ok sinon dans fichier csv. */ /* Vérification checksum et boucle de 3 essais si erreurs. */ /* Timeout de 5 secondes si pas de réception */ /* Fichier LOCK pour éviter 2 instances du programme (à voir si encore necessaire avec gestion TimeOut).*/ // Compilation sur PC: gcc -Wall teleinfosocket_mysql.c -o teleinfosocket_mysql -lmysqlclient // Les messages d'erreurs sont affichés sur la sortie standart /* ----- 2008-10-12 15:59:52 ----- ADCO='70060936xxxx' OPTARIF='HC..' ISOUSC='20' HCHP='008444126' HCHC='008228815' PTEC='HP' IINST1='002' IINST2='000' IINST3='001' IMAX1='011' IMAX2='020' IMAX3='019' PMAX='07470' PAPP='00610' HHPHC='E' MOTDETAT='000000' PPOT='00' ADIR1='' ADIR2='' ADIR3='' */ #include <stdlib.h> #include <stdio.h> #include <unistd.h> #include <string.h> #include <time.h> #include <errno.h> #include <mysql/mysql.h> #include <netdb.h> #include <sys/types.h> #include <sys/stat.h> #include <sys/socket.h> #include <netinet/in.h> #include <arpa/inet.h> // Define mysql #define MYSQL_HOST "localhost" #define MYSQL_DB "BASE" #define MYSQL_TABLE "TABLE" #define MYSQL_LOGIN "SQLLOGIN" #define MYSQL_PWD "SQLPASSWD" // Fichier lock. #define LOCKFILENAME "/tmp/mysqlteleinfo_wrt54gl.lck" // Fichier csv local si problème MySql. #define DATACSV "/home/dan/bin/teleinfo/teleinfosql.csv" // Fichier trame binaire pour debug. #define TRAMELOG "/home/dan/bin/teleinfo/teleinfotrame." // Constantes à changées suivant connexion. // Paramètres socket pour connexion. #define EZLHOST "wrt54gl" #define EZLPORT 2001 // Indique une connexion au daemon ser2net pour gérer son tampon de 64ko. #define SER2NET // Fin Constantes à changées suivant connexion. // Active mode debug. //#define DEBUG // Déclaration pour les données. char ch[2] ; char message[512] ; char* match; int id ; int res ; char datateleinfo[512] ; // Constantes/Variables à changées suivant abonnement (Nombre de valeurs, voir tableau "etiquettes", 20 pour abonnement tri heures creuse). #define NB_VALEURS 20 char etiquettes[NB_VALEURS][16] = {"ADCO", "OPTARIF", "ISOUSC", "HCHP", "HCHC", "PTEC", "IINST1", "IINST2", "IINST3", "IMAX1", "IMAX2", "IMAX3", "PMAX", "PAPP", "HHPHC", "MOTDETAT", "PPOT", "ADIR1", "ADIR2" ,"ADIR3"} ; // Fin Constantes/variables à changées suivant abonnement. char valeurs[NB_VALEURS][18] ; char checksum[255] ; int res ; int no_essais = 1 ; int nb_essais = 3 ; int erreur_checksum = 0 ; // Déclaration pour la date. time_t td; struct tm *dc; char sdate[12]; char sheure[10]; char timestamp[11]; // Déclaration pour la connexion socket. int sockteleinfo; // Variables pour select. fd_set readfds; struct timeval timeout ; /*------------------------------------------------------------------------------*/ /* Crée fichier lock */ /*------------------------------------------------------------------------------*/ int touch_lock_teleinfo(void) { FILE *lockfile ; if ( ! (lockfile = fopen(LOCKFILENAME, "w")) ) { fprintf(stderr, "%s: erreur creation fichier lock %s !\n", strerror(errno), LOCKFILENAME) ; exit(1) ; } fclose(lockfile) ; return 0 ; } /*------------------------------------------------------------------------------*/ /* Test presence fichier lock */ /*------------------------------------------------------------------------------*/ int lock_teleinfo(void) { struct stat stat_buffer ; int stat_status ; stat_status = stat(LOCKFILENAME, &stat_buffer) ; // Retourne -1 si fichier inexistant et 0 sinon. return (stat_status + 1) ; } /*------------------------------------------------------------------------------*/ /* Suppression fichier lock */ /*------------------------------------------------------------------------------*/ static void rm_lock_teleinfo(void) { #ifdef DEBUG printf("Suppression fichier lock %s\n\n", LOCKFILENAME) ; #endif if ( lock_teleinfo() ) unlink(LOCKFILENAME) ; } /*------------------------------------------------------------------------------*/ /* Init. fichier lock */ /*------------------------------------------------------------------------------*/ void initfichierlock(void) { // Si fichier lock present, quitte. if ( lock_teleinfo() ) { fprintf(stderr, "Erreur fichier LOCK présent !\n") ; exit (1) ; // La gestion des timeout connexion socket et de l'interface doit empecher tout blocage. } // Crée fichier lock. touch_lock_teleinfo() ; // Enregistre fonction suppression lock (appelée aprés fonction 'exit()'). atexit(rm_lock_teleinfo) ; } #ifdef SER2NET /*------------------------------------------------------------------------------*/ /* Lit socket sans timeout pour vider tampon ser2net */ /*------------------------------------------------------------------------------*/ void readtamponsocket(int socketfd) { // Init. timeout pour select. timeout.tv_sec = 5 ; // TimeOut à 5 secondes. timeout.tv_usec = 0 ; int result; int res ; char buff[65536] ; do { // Attend pendant TimeOut une réception sur le socket. result = select(socketfd+1, &readfds, NULL, NULL, &timeout) ; switch(result) { case -1: fprintf(stderr, "%s (Erreur SELECT 2) !", strerror(errno)); exit(2) ; break; case 0: fprintf(stderr, "Erreur temps dépassé réception socket (2) Téléinfo. !\n") ; exit(2) ; break; default: res = recv(socketfd, buff, 65536, 0); //printf("Lecture tampon de %d octets !\n", res); break; } } while (res > 1) ; // Si lecture d'un seul octet c'est que le tampon est vide ! } #endif /*------------------------------------------------------------------------------*/ /* Init connexion socket */ /*------------------------------------------------------------------------------*/ int initsocket(void) { // Déclaration pour la connexion socket. int socketfd; int len; struct sockaddr_in address; // Déclaration pour la recherche DNS. struct hostent *hostinfo ; char IpHost[16] ; // Recherche ip du nom de l'Host. if ( (hostinfo=gethostbyname(EZLHOST) ) == NULL ) { fprintf(stderr, "%s: erreur de résolution de nom réseau %s !\n", hstrerror(h_errno), EZLHOST); exit(1); } sprintf(IpHost, "%s", inet_ntoa(*((struct in_addr *)hostinfo->h_addr))); #ifdef DEBUG printf("Host: %s, IpHost: %s\n", EZLHOST, IpHost) ; #endif // Init connection socket. socketfd = socket(AF_INET, SOCK_STREAM, 0) ; address.sin_family = AF_INET ; address.sin_addr.s_addr = inet_addr(IpHost) ; address.sin_port = htons(EZLPORT) ; len = sizeof(address) ; if (connect(socketfd, (struct sockaddr *)&address, len) != -1) { // Init. liste FD pour select (1 seul descripteur en lecture à écouter) FD_ZERO(&readfds); FD_SET(socketfd, &readfds); #ifdef SER2NET readtamponsocket(socketfd) ; // Si connexion ser2net, lecture du socket pour vider tampon (64ko max) ser2net. #endif return socketfd ; } else { fprintf(stderr, "%s: erreur de connexion à %s !\n", strerror(errno), EZLHOST) ; exit(3); } } /*------------------------------------------------------------------------------*/ /* Lit un caractère sur socket avec timeout */ /*------------------------------------------------------------------------------*/ char readsocket(int socketfd) { // Init. timeout pour select. timeout.tv_sec = 5 ; // TimeOut à 5 secondes. timeout.tv_usec = 0 ; int result; int res ; char rxcar ; // Attend pendant TimeOut une réception sur le socket. result = select(socketfd+1, &readfds, NULL, NULL, &timeout) ; switch(result) { case -1: fprintf(stderr, "%s (Erreur SELECT) !", strerror(errno)); exit(2) ; break; case 0: fprintf(stderr, "Erreur temps dépassé réception socket Téléinfo. !\n") ; exit(2) ; break; default: // Lecture du socket (1 caratère). res = recv(socketfd, &rxcar, 1, 0); if(res < 0) { fprintf(stderr, "Erreur RECEPTION socket !\n"); exit(2) ; } return rxcar ; break; } } /*------------------------------------------------------------------------------*/ /* Lecture données téléinfo sur port socket */ /*------------------------------------------------------------------------------*/ void LiTrameSocket(int device) { // (0d 03 02 0a => Code fin et début trame) message[0]='\0' ; memset(valeurs, 0x00, sizeof(valeurs)) ; do { ch[0] = readsocket(device) ; } while (ch[0] != 0x02) ; // Code début trame téléinfo. #ifdef DEBUG printf("Début trame ...\n") ; #endif do { ch[0] = readsocket(device) ; ch[1] ='\0' ; strcat(message, ch) ; } while (ch[0] != 0x03) ; // Code fin trame téléinfo. } /*------------------------------------------------------------------------------*/ /* Test checksum d'un message (Return 1 si checkum ok) */ /*------------------------------------------------------------------------------*/ int checksum_ok(char *etiquette, char *valeur, char checksum) { unsigned char sum = 32 ; // Somme des codes ASCII du message + un espace int i ; for (i=0; i < strlen(etiquette); i++) sum += etiquette[i] ; for (i=0; i < strlen(valeur); i++) sum += valeur[i] ; sum = (sum & 63) + 32 ; if ( sum == checksum) return 1 ; // Return 1 si checkum ok. #ifdef DEBUG fprintf(stderr, "Erreur: checksum lu:%02x calculé:%02x\n", checksum, sum) ; #endif return 0 ; } /*------------------------------------------------------------------------------*/ /* Recherche valeurs des étiquettes de la liste. */ /*------------------------------------------------------------------------------*/ int LitValEtiquettes() { int id ; erreur_checksum = 0 ; for (id=0; id<NB_VALEURS; id++) { if ( (match = strstr(message, etiquettes[id])) != NULL) { sscanf(match, "%s %s %s", etiquettes[id], valeurs[id], checksum) ; if ( strlen(checksum) > 1 ) checksum[0]=' ' ; // sscanf ne peux lire le checksum à 0x20 (espace), si longueur checksum > 1 donc c'est un espace. if ( ! checksum_ok(etiquettes[id], valeurs[id], checksum[0]) ) { fprintf(stderr, "Donnees teleinfo [%s] corrompues (essai %d) !\n", etiquettes[id], no_essais) ; erreur_checksum = 1 ; return 0 ; } } } // Remplace chaine "HP.." ou "HC.." par "HP ou "HC". valeurs[5][2] = '\0' ; #ifdef DEBUG printf("----------------------\n") ; for (id=0; id<NB_VALEURS; id++) printf("%s='%s'\n", etiquettes[id], valeurs[id]) ; #endif return 1 ; } /*------------------------------------------------------------------------------*/ /* Test si dépassement intensité */ /*------------------------------------------------------------------------------*/ int DepasseCapacite() { // Test sur les 3 phases (étiquette ADIR1, ADIR2, ADIR3) à remplacer par ADPS pour monophasé. if ( (strlen(valeurs[17])) || (strlen(valeurs[18])) || (strlen(valeurs[19])) ) { fprintf(stderr, "Dépassement d'intensité: ADRI1='%s', ADRI2='%s', ADRI3='%s' !", valeurs[17], valeurs[18], valeurs[19]) ; return 1 ; } return 0 ; } /*------------------------------------------------------------------------------*/ /* Ecrit les données teleinfo dans base mysql */ /*------------------------------------------------------------------------------*/ int writemysqlteleinfo(char data[]) { MYSQL mysql ; char query[255] ; /* INIT MYSQL AND CONNECT ----------------------------------------------------*/ if(!mysql_init(&mysql)) { fprintf(stderr, "Erreur: Initialisation MySQL impossible !\n") ; return 0 ; } if(!mysql_real_connect(&mysql, MYSQL_HOST, MYSQL_LOGIN, MYSQL_PWD, MYSQL_DB, 0, NULL, 0)) { fprintf(stderr, "Erreur connection %d: %s \n", mysql_errno(&mysql), mysql_error(&mysql)); return 0 ; } sprintf(query, "INSERT INTO %s VALUES (%s)", MYSQL_TABLE, data); if(mysql_query(&mysql, query)) { fprintf(stderr, "Erreur INSERT %d: \%s \n", mysql_errno(&mysql), mysql_error(&mysql)); mysql_close(&mysql); return 0 ; } #ifdef DEBUG else printf("Requete MySql ok.\n") ; #endif mysql_close(&mysql); return 1 ; } /*------------------------------------------------------------------------------*/ /* Ecrit les données teleinfo dans fichier DATACSV */ /*------------------------------------------------------------------------------*/ void writecsvteleinfo(char data[]) { /* Ouverture fichier csv */ FILE *datateleinfo ; if ((datateleinfo = fopen(DATACSV, "a")) == NULL) { fprintf(stderr, "Erreur ouverture fichier teleinfo %s !\n", DATACSV) ; exit(1); } fprintf(datateleinfo, "%s\n", data) ; fclose(datateleinfo) ; } /*------------------------------------------------------------------------------*/ /* Ecrit la trame teleinfo dans fichier si erreur (pour debugger) */ /*------------------------------------------------------------------------------*/ void writetrameteleinfo(char trame[], char ts[]) { char nomfichier[255] = TRAMELOG ; strcat(nomfichier, ts) ; FILE *teleinfotrame ; if ((teleinfotrame = fopen(nomfichier, "w")) == NULL) { fprintf(stderr, "Erreur ouverture fichier teleinfotrame %s !\n", nomfichier) ; exit(1); } fprintf(teleinfotrame, "%s", trame) ; fclose(teleinfotrame) ; } /*------------------------------------------------------------------------------*/ /* Main */ /*------------------------------------------------------------------------------*/ int main(int argc, char *argv[]) { initfichierlock() ; sockteleinfo = initsocket() ; do { // Lit trame téléinfo. LiTrameSocket(sockteleinfo) ; time(&td) ; //Lit date/heure système. dc = localtime(&td) ; strftime(sdate,sizeof sdate,"%Y-%m-%d",dc); strftime(sheure,sizeof sdate,"%H:%M:%S",dc); strftime(timestamp,sizeof timestamp,"%s",dc); #ifdef DEBUG writetrameteleinfo(message, timestamp) ; // Enregistre trame en mode debug. #endif if ( LitValEtiquettes() ) // Lit valeurs des étiquettes de la liste. { // Ecrit données dans base MySql, si KO: écriture dans fichier csv. sprintf(datateleinfo, "'%s','%s','%s','%s','%s','%s','%s','%s','%s','%s','%s','%s','%s','%s','%s','%s','%s','%s','%s','%s','%s','%s','%s'", timestamp, sdate, sheure, valeurs[0], valeurs[1], valeurs[2], valeurs[3], valeurs[4], valeurs[5], valeurs[6], valeurs[7], valeurs[8], valeurs[9], valeurs[10], valeurs[11], valeurs[12], valeurs[13], valeurs[14], valeurs[15], valeurs[16], valeurs[17], valeurs[18], valeurs[19]) ; if (! writemysqlteleinfo(datateleinfo) ) writecsvteleinfo(datateleinfo) ; DepasseCapacite() ; // Test si etiquette dépassement intensité (log l'information seulement). } else writetrameteleinfo(message, timestamp) ; // Si erreur checksum enregistre trame. no_essais++ ; } while ( (erreur_checksum) && (no_essais <= nb_essais) ) ; close(sockteleinfo); exit(0) ; }
2 Septembre 2007