Teleinfoserial mysql.c

De MicElectroLinGenMet.

Programme C Linux pour lire les données d'un démodulateur Téléinfo connecté sur le port série d'un PC ou du Wrt54gl et les enregistre dans base MySql.

Version pour abonnement triphasé heures creuses, voir source pour modifier en fonction abonnement.


Voir la table MySql Téléinfo: Structure de la table Teleinfo



/*       teleinfoserial_mysql.c										*/
/* Version pour PC et wrt54gl										*/
/* Lecture données Téléinfo et enregistre données sur base mysql vesta si ok sinon dans fichier csv.	*/
/* Connexion par le port série du Wrt54gl (Console désactivée dans inittab.)				*/
/* Vérification checksum données téléinfo et boucle de 3 essais si erreurs.				*/
/* Par domos78 at free point fr										*/
 
/*
Paramètres à adapter: 
- Port série à modifier en conséquence avec SERIALPORT.
- Nombre de valeurs à relever: NB_VALEURS + tableaux "etiquettes" et "poschecksum" à modifier selon abonnement (ici triphasé heures creuses).
- Paramètres Mysql (Serveur, Base, table et login/password)
- Autorisé le serveur MySql à accepter les connexions distantes pour le Wrt54gl.
 
Compilation PC:  
- gcc -Wall teleinfoserial_mysql.c -o teleinfoserial_mysql -lmysqlclient
 
Compilation wrt54gl: 
- avec le SDK (OpenWrt-SDK-Linux).
 
Résultat pour les données importantes dans la base MySql du serveur distant:
dan@vesta:~$ bin/listdatateleinfo.sh
timestamp       date            heure           hchp    hchc    ptec    inst1   inst2   inst3   papp
1222265525      24/09/2008      16:12:05        8209506 8026019 HP      1       0       1       460
1222265464      24/09/2008      16:11:04        8209499 8026019 HP      1       0       1       460
1222265405      24/09/2008      16:10:05        8209493 8026019 HP      1       0       1       390
1222265344      24/09/2008      16:09:04        8209487 8026019 HP      1       0       1       390
1222265284      24/09/2008      16:08:04        8209481 8026019 HP      1       0       1       390
1222265225      24/09/2008      16:07:05        8209476 8026019 HP      1       0       1       390
1222265164      24/09/2008      16:06:04        8209470 8026019 HP      1       0       1       390
1222265105      24/09/2008      16:05:05        8209464 8026019 HP      1       0       1       390
 
Résultat en mode DEBUG:
root@wrt54gl:~# ./teleinfoserial_mysql
----- 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 <syslog.h>
#include <termios.h>
#include <sys/fcntl.h>
#include <sys/types.h>
#include <mysql/mysql.h>
 
// Define port serie
#define BAUDRATE B1200
#define SERIALPORT "/dev/tts/0"
 
// Define mysql
#define MYSQL_HOST "SQLSERVEUR"
#define MYSQL_DB "BASE"
#define MYSQL_TABLE "TABLE"
#define MYSQL_LOGIN "SQLLOGIN"
#define MYSQL_PWD "SQLPASSWD"
 
// Fichier local au Wrt4gl/PC + fichier trame pour debug.
#define DATACSV "/tmp/teleinfosql.csv"
#define TRAMELOG "/tmp/teleinfotrame."
 
// Active mode debug.
#define DEBUG
 
//-----------------------------------------------------------------------------
 
// Déclaration pour le port série.
int             fdserial ;
struct termios  termiosteleinfo ;
 
// Déclaration pour les données.
char ch[2] ;
char car_prec ;
char message[512] ;
char* match;
int id ;
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];
 
/*------------------------------------------------------------------------------*/
/* Init port rs232								*/
/*------------------------------------------------------------------------------*/
int initserie(void)
// Mode Non-Canonical Input Processing, Attend 1 caractère ou time-out(avec VMIN et VTIME).
{
	int device ;
 
        // Ouverture de la liaison serie (Nouvelle version de config.)
        if ( (device=open(SERIALPORT, O_RDWR | O_NOCTTY)) == -1 ) 
	{
                syslog(LOG_ERR, "Erreur ouverture du port serie %s !", SERIALPORT);
                exit(1) ;
        }
 
        tcgetattr(device,&termiosteleinfo) ;				// Lecture des parametres courants.
 
	cfsetispeed(&termiosteleinfo, BAUDRATE) ;			// Configure le débit en entrée/sortie.
	cfsetospeed(&termiosteleinfo, BAUDRATE) ;
 
	termiosteleinfo.c_cflag |= (CLOCAL | CREAD) ;			// Active réception et mode local.
 
	// Format série "7E1"
	termiosteleinfo.c_cflag |= PARENB  ;				// Active 7 bits de donnees avec parite pair.
	termiosteleinfo.c_cflag &= ~PARODD ;
	termiosteleinfo.c_cflag &= ~CSTOPB ;
	termiosteleinfo.c_cflag &= ~CSIZE ;
	termiosteleinfo.c_cflag |= CS7 ;
 
	termiosteleinfo.c_iflag |= (INPCK | ISTRIP) ;			// Mode de control de parité.
 
	termiosteleinfo.c_cflag &= ~CRTSCTS ;				// Désactive control de flux matériel.
 
	termiosteleinfo.c_lflag &= ~(ICANON | ECHO | ECHOE | ISIG) ;	// Mode non-canonique (mode raw) sans echo.
 
	termiosteleinfo.c_iflag &= ~(IXON | IXOFF | IXANY | ICRNL) ;	// Désactive control de flux logiciel, conversion 0xOD en 0x0A.
 
	termiosteleinfo.c_oflag &= ~OPOST ;				// Pas de mode de sortie particulier (mode raw).
 
	termiosteleinfo.c_cc[VTIME] = 80 ;  				// time-out à ~8s.
	termiosteleinfo.c_cc[VMIN]  = 0 ;   				// 1 car. attendu.
 
	tcflush(device, TCIFLUSH) ;					// Efface les données reçues mais non lues.
        tcsetattr(device,TCSANOW,&termiosteleinfo) ;			// Sauvegarde des nouveaux parametres
	return device ;
}
 
/*------------------------------------------------------------------------------*/
/* Lecture données téléinfo sur port série					*/
/*------------------------------------------------------------------------------*/
void LiTrameSerie(int device)
{
// (0d 03 02 0a => Code fin et début trame)
	tcflush(device, TCIFLUSH) ;			// Efface les données non lus en entrée.
	message[0]='\0' ;
	memset(valeurs, 0x00, sizeof(valeurs)) ; 
 
	do
	{
		car_prec = ch[0] ;
		res = read(device, ch, 1) ;
		if (! res)
		{	
			syslog(LOG_ERR, "Erreur pas de réception début données Téléinfo !\n") ;
			close(device);
			exit(1) ;
		}
	 }
	while ( ! (ch[0] == 0x02 && car_prec == 0x03) ) ;	// Attend code fin suivi de début trame téléinfo .
 
	do
	{
		res = read(device, ch, 1) ;
		if (! res)
		{	
			syslog(LOG_ERR, "Erreur pas de réception fin données Téléinfo !\n") ;
			close(device);
			exit(1) ;
		}
		ch[1] ='\0' ;
		strcat(message, ch) ;
	}
	while (ch[0] != 0x03) ;				// Attend 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 = sum + etiquette[i] ;
	for (i=0; i < strlen(valeur); i++) sum = sum + valeur[i] ;
	sum = (sum & 63) + 32 ;
	if ( sum == checksum) return 1 ;	// Return 1 si checkum ok.
	#ifdef DEBUG
		syslog(LOG_INFO, "Checksum lu:%02x   calculé:%02x", 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]) ) 
			{
				syslog(LOG_ERR, "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])) )
	{
		syslog(LOG_INFO, "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))
	{
		syslog(LOG_ERR, "Erreur: Initialisation MySQL impossible !") ; 
		return 0 ;
	}
	if(!mysql_real_connect(&mysql, MYSQL_HOST, MYSQL_LOGIN,	MYSQL_PWD, MYSQL_DB, 0, NULL, 0))
	{
		syslog(LOG_ERR, "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))
	{
		syslog(LOG_ERR, "Erreur INSERT %d: \%s \n", mysql_errno(&mysql), mysql_error(&mysql));
		mysql_close(&mysql);
		return 0 ;
	}
	#ifdef DEBUG
	else syslog(LOG_INFO, "Requete MySql ok.") ;
	#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)
        {
		syslog(LOG_ERR, "Erreur ouverture fichier teleinfo %s !", DATACSV) ; 
                exit(1);
        }
        fprintf(datateleinfo, "%s\n", data) ;
        fclose(datateleinfo) ;
}
 
#ifdef DEBUG
/*------------------------------------------------------------------------------*/
/* 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)
        {
		syslog(LOG_ERR, "Erreur ouverture fichier teleinfotrame %s !", nomfichier) ;
                exit(1);
        }
        fprintf(teleinfotrame, "%s", trame) ;
        fclose(teleinfotrame) ;
}
#endif
 
/*------------------------------------------------------------------------------*/
/* Main										*/
/*------------------------------------------------------------------------------*/
int main(int argc, char *argv[])
{
 openlog("teleinfoserial_mysql", LOG_PID, LOG_USER) ;
 fdserial = initserie() ;
 
 do
 {
	// Lit trame téléinfo.
	LiTrameSerie(fdserial) ;
 
	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.
	{
		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) ;		// Si écriture dans base MySql KO, écriture dans fichier csv.
 
		DepasseCapacite() ; 			// Test si etiquette dépassement intensité (log l'information seulement).
	}
	#ifdef DEBUG
	else writetrameteleinfo(message, timestamp) ;	// Si erreur checksum enregistre trame.
	#endif
	no_essais++ ;
 }
 while ( (erreur_checksum) && (no_essais <= nb_essais) ) ;
 
 close(fdserial) ;
 closelog() ;
 exit(0) ;
}