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