Teleinfo serial mono.c

De MicElectroLinGenMet.

Origine page Interface téléinfo.

Voir programme plus évolué teleinfoserial_csv.c pour abonnement triphasé heures creuses, il suffira d'adapter les paramètres de l'abonnement.


// teleinfoserial.c
// Lecture données Téléinfo par le port série du PC ou Wrt54gl et enregistre les données dans fichier csv.
// Vérification checksum et boucle de 3 essais si erreurs.
// Version pour compteur monophasé (Modifiée/Testée par Ludovic Gomez).
// Par domos78 at free point fr (Dan)
 
/*
Paramètres à adapter: 
- Port série à modifier en conséquence avec constante:
SERIALPORT (par exemple /dev/ttyS0 pour le PC, /dev/tts/0 pour le Wrt54gl)
- Nombre de valeurs et tableaux à modifier en fonction abonnement EDF: 
NB_VALEURS, "etiquettes", "valeurs" et "poschecksum"
 
Compilation PC:  
- gcc -Wall teleinfoserial.c -o teleinfoserial
 
Compilation wrt54gl: 
 Voir exemple compilation avec SDK (OpenWrt-SDK-Linux).
 
/*
Trame téléinfo. compteur monophasé:
 
ADCO 02082841xxxx D
OPTARIF HC.. <
ISOUSC 45 ?
HCHC 000007441 V
HCHP 000007487 -
PTEC HP..
IINST 004 [
IMAX 016 F
PAPP 00810 *
HHPHC A ,
MOTDETAT 000000 B
 
00017440  0a 41 44 43 4f 20 30 32  30 38 32 38 xx xx xx xx  |.ADCO 020828xxxx|
00017450  30 39 20 44 0d 0a 4f 50  54 41 52 49 46 20 48 43  |09 D..OPTARIF HC|
00017460  2e 2e 20 3c 0d 0a 49 53  4f 55 53 43 20 34 35 20  |.. <..ISOUSC 45 |
00017470  3f 0d 0a 48 43 48 43 20  30 30 30 30 30 37 34 34  |?..HCHC 00000744|
00017480  31 20 56 0d 0a 48 43 48  50 20 30 30 30 30 30 37  |1 V..HCHP 000007|
00017490  34 38 37 20 2d 0d 0a 50  54 45 43 20 48 50 2e 2e  |487 -..PTEC HP..|
000174a0  20 20 0d 0a 49 49 4e 53  54 20 30 30 34 20 5b 0d  |  ..IINST 004 [.|
000174b0  0a 49 4d 41 58 20 30 31  36 20 46 0d 0a 50 41 50  |.IMAX 016 F..PAP|
000174c0  50 20 30 30 38 31 30 20  2a 0d 0a 48 48 50 48 43  |P 00810 *..HHPHC|
000174d0  20 41 20 2c 0d 0a 4d 4f  54 44 45 54 41 54 20 30  | A ,..MOTDETAT 0|
000174e0  30 30 30 30 30 20 42 0d  03 02 0a 41 44 43 4f 20  |00000 B....ADCO |
 
*/
//-----------------------------------------------------------------------------
#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>
 
// Define port serie
#define VERSION "20090419"
#define BAUDRATE B1200
#define SERIALPORT "/dev/ttyUSB0"
 
// Fichier local au Wrt4gl + fichier trame pour debug.
#define DATACSV "/tmp/teleinfo.csv"
#define TRAMELOG "/tmp/teleinfotrame."
 
// Nombre de valeurs à relever, voir tableau "etiquettes" et "poschecksum".
#define NB_VALEURS 12
 
// Active mode debug, désactivé par defaut.
//#define DEBUG
 
//-----------------------------------------------------------------------------
 
// Déclaration pour le port série.
int             fdserial ;
struct termios  termiosteleinfo ;
char	CHARACTERSIZE[] = "7E1" ;	// Format caractère série.
 
// Déclaration pour les données.
char ch[2] ;
char car_prec ;
char message[512] ;
char* match;
int id ;
char datateleinfo[512] ;
 
char etiquettes[NB_VALEURS][16] = {"ADCO", "OPTARIF", "ISOUSC", "HCHP", "HCHC", "PTEC", "IINST", "IMAX", "PAPP", "HHPHC", "MOTDETAT", "ADPS"} ;
char poschecksum[NB_VALEURS] = {18, 13 , 10 , 15, 15, 10, 10, 9, 11, 8, 16, 9} ;
char valeurs[NB_VALEURS][18] ;
char checksum[2] ;
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
        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.
 
	if ( strstr(CHARACTERSIZE, "8N1") != NULL )
	{
		termiosteleinfo.c_cflag &= ~PARENB ;			// Active 8 bits de donnees sans parite.
		termiosteleinfo.c_cflag &= ~CSTOPB ;
		termiosteleinfo.c_cflag &= ~CSIZE;
		termiosteleinfo.c_cflag |= CS8 ;
	}
	else
	if ( strstr(CHARACTERSIZE, "7E1") != NULL )
	{
		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 ;
}
 
/*------------------------------------------------------------------------------*/
/* 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 ;
}
 
/*------------------------------------------------------------------------------*/
/* 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", LOG_PID, LOG_USER) ;
 #ifdef DEBUG
 syslog(LOG_INFO, "Version %s", VERSION) ;
 #endif
 fdserial = initserie() ;
 do
 {
	tcflush(fdserial, TCIFLUSH) ;		// Efface les données non lus en entrée.
	message[0]='\0' ;
	memset(valeurs, 0x00, sizeof(valeurs)) ; 
	erreur_checksum = 0 ;
 
// --------------------------------------------------------------------------------------
	// Lecture données téléinfo (0d 03 02 0a => Code fin et début trame)
	do
	{
		car_prec = ch[0] ;
		res = read(fdserial, ch, 1) ;
		if (! res)
		{	
			syslog(LOG_ERR, "Erreur pas de réception début données Téléinfo !\n") ;
			close(fdserial);
			exit(1) ;
		}
	 }
	while ( ! (ch[0] == 0x02 && car_prec == 0x03) ) ;	// Attend code fin suivi de début trame téléinfo .
 
	do
	{
		res = read(fdserial, ch, 1) ;
		if (! res)
		{	
			syslog(LOG_ERR, "Erreur pas de réception fin données Téléinfo !\n") ;
			close(fdserial);
			exit(1) ;
		}
		ch[1] ='\0' ;
		strcat(message, ch) ;
	}
	while (ch[0] != 0x03) ;				// Attend code fin trame téléinfo.
 
// --------------------------------------------------------------------------------------
	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);
 
	// Recherche valeurs des étiquettes de la liste.
	for (id=0; id<NB_VALEURS; id++)
	{
		if ( (match = strstr(message, etiquettes[id])) != NULL)
		{
			sscanf(match, "%s %s", etiquettes[id], valeurs[id]) ;
			strncpy(checksum, match + poschecksum[id], 1) ;		//Lit code checksum (sscanf ne peux le lire quand checksum=0x20).
			if ( ! checksum_ok(etiquettes[id], valeurs[id], checksum[0]) ) 
			{
				erreur_checksum=1 ;
				syslog(LOG_ERR, "Donnees teleinfo [%s] corrompues (essai %d) !\n", etiquettes[id], no_essais) ;
				#ifdef DEBUG
				writetrameteleinfo(message, timestamp) ;
				#endif
			}
		}
	}
 
	// Remplace chaine "HP.." ou "HC.." par "HP ou "HC".
	valeurs[5][2] = '\0' ;
 
	if (! erreur_checksum) 
	{
		// Test si valeurs HCHP, HCHC, PAPP sont vides (possible aprés trame ADPS).
		if ( (strlen(valeurs[3])) && (strlen(valeurs[4])) && (strlen(valeurs[8])) )
		{
			#ifdef DEBUG
			printf("----- %s %s -----\n",sdate, sheure) ; for (id=0; id<NB_VALEURS; id++) printf("%s='%s'\n", etiquettes[id], valeurs[id]) ;
			#endif
			sprintf(datateleinfo,"'%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]) ;
			writecsvteleinfo(datateleinfo) ;		// Ecriture dans fichier csv.
 
			// Test si etiquette dépassement intensité sur 1 phase (ADPS).
			if ( (strlen(valeurs[11])) )
			{
				syslog(LOG_INFO, "Dépassement d'intensité: ADPS='%s' !", valeurs[11]) ;
			}
		}
		else
		{
			syslog(LOG_ERR, "Donnees teleinfo vide: HCHP=%s, HCHC=%s, PAPP=%s !\n", valeurs[3], valeurs[4], valeurs[8]) ;
			#ifdef DEBUG
			writetrameteleinfo(message, timestamp) ;
			#endif
		}
	}
	no_essais++ ;
 }
 while ( (erreur_checksum) && (no_essais <= nb_essais) ) ;
 close(fdserial) ;
 closelog() ;
 exit(0) ;
}



Outils personnels
Administration wiki