#include <Arduino.h>

#include <stdint.h>

// ==================================
	String version="2026-09-07";
// ==================================

/*---------------------------------------------------------------------------------------
	ND_main.cpp  - Navigation Display pour ESP32 - pour Afficheur TFT480 480x320
	
	par Silicium628
	Ce logiciel est libre est gratuit sous licence GNU GPL
	
	Pour les #includes issus de l'univers Arduino (que je ne fournis pas), il faut voir au cas par cas.
	(drivers d'affichage en particulier)

---------------------------------------------------------------------------------------
	De petites images à placer sur la SDcard centrées sur les aérodromes proviennent de OpenStreetMap
	OpenStreetMap® est un ensemble de données ouvertes,
	disponibles sous la licence libre Open Data Commons Open Database License (ODbL)
	accordée par la Fondation OpenStreetMap (OSMF).

	Voir ce lien pour plus de détails :
	https://www.openstreetmap.org/copyright
---------------------------------------------------------------------------------------
*/


/**
==========================================================================================================
CONCERNANT L'AFFICHAGE TFT : connexion :

( Pensez à configurer le fichier User_Setup.h de la bibliothèque ~/Arduino/libraries/TFT_eSPI/ )

les lignes qui suivent ne sont q'un commentaire pour vous indiquer la config à utiliser
placée ici, elle ne sont pas fonctionnelles
Il FAUT modifier le fichier User_Setup.h installé par le système Arduino dans ~/Arduino/libraries/TFT_eSPI/

// ESP32 pins used for the parallel interface TFT
#define TFT_CS   27  // Chip select control pin
#define TFT_DC   14  // Data Command control pin - must use a pin in the range 0-31
#define TFT_RST  26  // Reset pin

#define TFT_WR   12  // Write strobe control pin - must use a pin in the range 0-31
#define TFT_RD   13

#define TFT_D0   16  // Must use pins in the range 0-31 for the data bus
#define TFT_D1   4  // so a single register write sets/clears all bits
#define TFT_D2   2  // 23
#define TFT_D3   22
#define TFT_D4   21
#define TFT_D5   15 // 19
#define TFT_D6   25 // 18
#define TFT_D7   17
==========================================================================================================
**/

/*
Notes :

- ce Navigation Display reçoit toutes les données par WiFi, émises par l'ESP du PFD. Il n'est donc pas nécessaire de le connecter
sur un port USB, il peut être simplement alimenté par un adaptateur 5V, ou sur l'alim 5V du PFD (en amont du régulateur 3V3 )
si toutefois le port USB0 (et son câble) qui alimentent le PFD peuvent fournir suffisamment de courant.
*/


// le numéro de version doit être identique à celui du PFD (Primary Flight Display)

// affichage carte + zoom
// selection des carte par encodeur rotatif (ce qui met à jour les fréquences radio et les paramètres de la piste, et ILS) 
// affiche avion sur la carte
// affiche faisceau ILS sur la carte
// affiche cercles 10 , 20, 40, 80, 160, 320NM sur la carte
// v14.0 les fichiers principaux ont été renommés par soucis de clarté
// v15.6 ajout de cartes .bmp (à copier sur la SDcard)
// v19.0 choix aérodrome dans une liste. Affichage d'une photo de l'aérodrome en plus de la carte

// todo : mieux centrer certaines cartes (j'ai ajouté une mire dans le .zip)




#include "main.h"
#include <stdint.h>

#include <TFT_eSPI.h> // Hardware-specific library
#include "Free_Fonts2.h"

#include "FS.h"
#include "SD.h"
#include "SPI.h"

// #include "FG_data2.h"

#include "FG_data.h"
#include "Fonctions2.h"


#include <WiFi.h>
#include <HTTPClient.h> // nous nous connecterons en tant que client au serveur PFD.
// Remarque: les fonctions WiFi consomment 50% de la mémoire flash (de programme)




const char* ssid = "PFD_srv";		// doit être identique à celui du serveur du PFD
const char* password = "72r4TsJ28"; // doit être identique à celui du serveur du PFD


// addresse IP + chemins URL du PFD:
const char* srvName_hdg = "http://192.168.4.1/hdg";
const char* srvName_cap = "http://192.168.4.1/cap";
const char* srvName_ils_nm = "http://192.168.4.1/ILSnm";
const char* srvName_ils_actual = "http://192.168.4.1/ILSactual";
////const char* srvName_sw = "http://192.168.4.1/switch";
const char* srvName_latitude = "http://192.168.4.1/latitude";
const char* srvName_longitude = "http://192.168.4.1/longitude";
const char* srvName_hauteur = "http://192.168.4.1/hauteur";
const char* srvName_flags = "http://192.168.4.1/flags";



// données reçues par WiFi depuis le serveur du PFD
String recp_hdg; // heading bug - HDG = consigne de cap
String recp_cap; //	cap actuel (orientation/Nord du nez de l'avion) - sens horaire
String recp_latitude;
String recp_longitude;
String recp_hauteur;
String recp_flags;

Led Led1;
Led Led2;

TFT_eSprite SPR_VOR = TFT_eSprite(&TFT480);
TFT_eSprite SPR_avion = TFT_eSprite(&TFT480); // le petit avion sur la carte

uint16_t memo_img[20*20];

TFT_eSprite SPR_E = TFT_eSprite(&SPR_VOR);  // Déclare Sprite object "SPR_11" with pointer to "TFT480" object
TFT_eSprite SPR_N = TFT_eSprite(&SPR_VOR);
TFT_eSprite SPR_O = TFT_eSprite(&SPR_VOR);
TFT_eSprite SPR_S = TFT_eSprite(&SPR_VOR);

TFT_eSprite SPR_3 = TFT_eSprite(&SPR_VOR);  
TFT_eSprite SPR_6 = TFT_eSprite(&SPR_VOR);
TFT_eSprite SPR_12 = TFT_eSprite(&SPR_VOR);
TFT_eSprite SPR_15 = TFT_eSprite(&SPR_VOR);
TFT_eSprite SPR_21 = TFT_eSprite(&SPR_VOR);  
TFT_eSprite SPR_24 = TFT_eSprite(&SPR_VOR);
TFT_eSprite SPR_30 = TFT_eSprite(&SPR_VOR);
TFT_eSprite SPR_33 = TFT_eSprite(&SPR_VOR);

Cadre cadre_top1;
Cadre cadre_top1b;
Cadre cadre_top2;

Cadre cadre_00;
Cadre cadre_01;

Cadre cadre_10;
Cadre cadre_11;

Cadre cadre_20;
Cadre cadre_21;

Cadre cadre_cap1;
Cadre cadre_hdg1;

Cadre cadre_AZ_piste;
Cadre cadre_distance_piste;

Cadre cadre_bas1; // dans la partie droite
Cadre cadre_bas2;

Cadre cadre_position; // affiche la position de l'avion (Lat/Long WGS84)

Cadre cadre_info;  // messages d'info au bas de la partie gauche


uint8_t saisir_ecran=0;

int16_t num_airport=9; // peut être négatif
uint32_t memo_micros = 0;
uint32_t temps_ecoule;
uint16_t nb_100ms=0;
uint16_t nb_secondes=0;

float cap;
float memo_cap;

int16_t hdg1  = 150;  // consigne cap
int16_t memo1_hdg1;	// pour le changement manuel
int16_t memo2_hdg1; // pour l'autoland

uint8_t SDcardOk=0;
uint8_t gs_ok;

uint8_t refresh_ND =1;
uint8_t refresh_Encoche =1;
uint8_t refresh_carte =1;

// les deux valeurs suivantes sont chosies pour afficher LFMT au lancement du programme. On peut choisir autre chose...
uint16_t decal=6;		// pour la liste des aérodromes
uint16_t num_ligne =14; // pour la liste des aérodromes

float longueur_piste; 
float orient_piste;

float lat_centre_pst;
float lon_centre_pst;

uint16_t x_avion; // position (en px) du petit avion qui se déplace sur la carte
uint16_t y_avion;
uint16_t memo_x_avion;
uint16_t memo_y_avion;

float lon_avion;
float lat_avion;
float hauteur_AAL;

float GPS_distance_piste;  // en NM
float GPS_azimut_piste;

uint16_t memo_flags; // servira à détecter et agir rapidement lors de la modif de la valeur

int8_t zoom; // pour la carte; = numéro de la carte .bmp
int8_t memo_zoom;

float px_par_km;
float px_par_NM;

float plan_ref_x; // centre de la piste
float plan_ref_y;
float plan_echelle;


uint16_t SW0=1;
const int GPIO_SW0 = 36;
bool SW0_etat;
bool memo_SW0_etat;


uint16_t bouton0=1; //  bouton poussoir smd
const int GPIO_bouton0 = 34; 
bool bouton0_etat;
bool memo_bouton0_etat;

uint16_t bouton1=2; //  bouton poussoir smd
const int GPIO_bouton1 = 35; 
bool bouton1_etat;
bool memo_bouton1_etat;

uint16_t bouton2=4; //  bouton poussoir smd
const int GPIO_bouton2 = 32; 
bool bouton2_etat;
bool memo_bouton2_etat;

uint16_t bouton3=8; //  bouton poussoir smd
const int GPIO_bouton3 = 33; 
bool bouton3_etat;
bool memo_bouton3_etat;




// encodeurs rotatif pas à pas

/* remplacé par 2 boutons poussoirs
const int rot1a = 32;  // GPIO32 -> câbler une R au +3V3
const int rot1b = 33;  // GPIO33 -> câbler une R au +3V3

/* remplacé par 2 boutons poussoirs
const int rot2a = 35;  // GPIO35 -> câbler une R au +3V3
const int rot2b = 34;  // GPIO34 -> câbler une R au +3V3
*/

//const int led1 = 25; // GPIO15

#define TEMPO 20 // tempo anti-rebond pour l'acquisition des encodeurs rotatifs
volatile uint32_t timer1 = 0;
volatile uint32_t timer2 = 0;

uint16_t compteur1;
uint8_t premier_passage=1;
uint8_t zoom_auto=0;

uint32_t bmp_offset = 0;

String switches="null"; // boutons connectés au 3eme ESP32, reçus par WiFi


#define MODE_ND 0
#define MODE_LISTE 1
#define MODE_PLAN 2
#define MODE_CARTE_FR 3

uint8_t mode_affi=MODE_LISTE;




void RAZ_variables()
{
	hdg1=0;  // consigne cap
}


void httpGetHDG()
{
	String s1;
	String s2;
	
	HTTPClient http;
	char c1[4];

	s1= "?a1=";  // a1 (nom arbitraire..) pour désigner l'argument envoyé dans l'URL de la requête vers le serveur (le PFD)
	s1+= num_airport;

	s1+= "&swND=";  // swND pour désigner l'argument envoyé dans l'URL de la requête
	s2= switches;
	s1+= s2;
		
	http.begin(srvName_hdg+s1); // rappel : srvName_hdg = "http://192.168.4.1/switch"
	int httpResponseCode = http.GET();
	if (httpResponseCode>0)	{ recp_hdg = http.getString();}
	http.end();
}



void httpGetCap()
{
	HTTPClient http;
	http.begin(srvName_cap);
	int httpResponseCode = http.GET();
	if (httpResponseCode>0)	{ recp_cap = http.getString();	}
	http.end();
}


void httpGetLatitude() // Latitude de l'avion
{
	HTTPClient http;
	http.begin(srvName_latitude);
	int httpResponseCode = http.GET();
	if (httpResponseCode>0)	{ recp_latitude = http.getString();	}
	http.end();
}


void httpGetLongitude() // Longitude de l'avion
{
	HTTPClient http;
	http.begin(srvName_longitude);
	int httpResponseCode = http.GET();
	if (httpResponseCode>0)	{ recp_longitude = http.getString();	}
	http.end();
}


void httpGetHauteur() // Hauteur de l'avion / sol 
{
	HTTPClient http;
	http.begin(srvName_hauteur);
	int httpResponseCode = http.GET();
	if (httpResponseCode>0)	{ recp_hauteur = http.getString();	}
	http.end();
}



void httpGetParams()
{
	HTTPClient http;
	http.begin(srvName_flags);
	int httpResponseCode = http.GET();
	if (httpResponseCode>0)	{ recp_flags = http.getString();	}
	http.end();
}

////void httpGet_SW() // depuis le SW: c.a.d l'ESP32 n°3
////{
	////HTTPClient http;
	////http.begin(srvName_sw);
	////int httpResponseCode = http.GET();
	////if (httpResponseCode>0)	{ switches = http.getString();	}
	////http.end();
////}



/** ***********************************************************************************
		IMAGE.bmp					
***************************************************************************************/


/**
Rappel et décryptage de la fonction Color_To_565 : (elle se trouve dans le fichier LCDWIKI_KBV.cpp)

//Pass 8-bit (each) R,G,B, get back 16-bit packed color

uint16_t Color_To_565(uint8_t r, uint8_t g, uint8_t b)
{
	return ((r & 0xF8) << 8) | ((g & 0xFC) << 3) | ((b & 0xF8) >> 3);
}

0xF8 = 11111000
0xFC = 11111100

(r & 0xF8) -> 5 bit de gauche de r (on ignore donc les 3 bits de poids faible)
(g & 0xFC) -> 6 bit de gauche de g (on ignore donc les 2 bits de poids faible)
(b & 0xF8) -> 5 bit de gauche de b (on ignore donc les 3 bits de poids faible)

rrrrr---
gggggg--
bbbbb---

après les décalages on obtient les 16 bits suivants:

rrrrr---========
     gggggg--===
        ===bbbbb

soit après le ou :

rrrrrggggggbbbbb

calcul de la Fonction inverse : 
RGB565_to_888
**/

uint16_t Color_To_565(uint8_t r, uint8_t g, uint8_t b)
{
	return ((r & 0xF8) << 8) | ((g & 0xFC) << 3) | ((b & 0xF8) >> 3);
}


void RGB565_to_888(uint16_t color565, uint8_t *R, uint8_t *G, uint8_t *B)
{
	*R=(color565 & 0xFFFFF800) >> 8;
	*G=(color565 & 0x7E0) >> 3;
	*B=(color565 & 0x1F) << 3 ;
}




uint16_t bmp_width;
uint16_t bmp_heigh;


uint16_t read_16(File fp)
{
    uint8_t low;
    uint16_t high;
    low = fp.read();
    high = fp.read();
    return (high<<8)|low;
}



uint32_t read_32(File fp)
{
    uint16_t low;
    uint32_t high;
    low = read_16(fp);
    high = read_16(fp);
    return (high<<8)|low;   
}



void write_16(uint16_t v16, File fp)
{
    uint8_t low, high;
	
	low = v16 & 0xFF;
	high= v16 >>8;
    
    fp.write(low);
    fp.write(high);
}


void write_32(uint32_t v32, File fp)
{
    uint16_t low, high;
	
	low = v32 & 0xFFFF;
	high= v32 >>16;
    
    write_16(low, fp);
    write_16(high, fp);
}



bool teste_bmp_header(File fp)
{
    if(read_16(fp) != 0x4D42)  { return false;  } // (2 bytes) The header field used to identify the BMP
    read_32(fp); // (4 bytes) get bmp size (nombre total d'octets)
    read_32(fp); // (4 bytes) get creator information
    bmp_offset = read_32(fp); // (4 bytes) get offset information
    read_32(fp);//get DIB information

// ici on a lu 16 octets  
	bmp_width = read_32(fp); //(4 bytes) get width and heigh information
	bmp_heigh = read_32(fp); //(4 bytes)
	
// ici on a lu 24 octets 	
    //if(read_16(fp)!= 1) {return false;}
    read_16(fp);
    //if(read_32(fp)!= 0) {return false;}
    return true;
}



uint8_t LumCtr(uint8_t vi, float lum, float ctr)
{
	float v2;
	uint8_t result;
	v2 = ((float)vi - lum) * ctr;
	if (v2<0) {v2=0;}
	if (v2>255) {v2=255;}
	result = (uint8_t) v2;
	return result;
}



void draw_bmp(uint16_t x0, uint16_t y0, uint8_t rot, float lum, float contraste, File* fp)
{

	//sram = freeRam(); Serial.print("03-freeRam="); Serial.println(sram);
		uint16_t i,j,k,p,m=0;
		uint16_t y1;
		uint8_t bmp_data[2*3]={0};
		uint16_t bmp_color[2];
		
		fp->seek(bmp_offset);
		for(i=0; i<bmp_heigh; i++)
		{
			for(j=0; j<(bmp_width/2); j++)
			{
				m=0;
				fp->read(bmp_data,2*3);
				for(k=0; k<2; k++)
				{
bmp_color[k]= Color_To_565(LumCtr(bmp_data[m+2],lum, contraste), LumCtr(bmp_data[m+1],lum, contraste), LumCtr(bmp_data[m+0],lum, contraste));
					m+=3;
				}
				for(p=0; p<2; p++)
				{
					if (rot==0)
					{
						y1=y0;
						TFT480.drawPixel(x0+i, y0+j*2+p, bmp_color[p]);
					}
					if (rot==1)
					{
						y1=160-y0;
						TFT480.drawPixel(x0+j*2+p,320-(y1+i), bmp_color[p]);
					}
				}   
			}
		}
}




void affi_img(uint16_t x0, uint16_t y0, float lum, float contraste, const char* filename1)
{

	File bmp_file;
	TFT480.setFreeFont(FF1);
	TFT480.setTextColor(ORANGE, NOIR);
	bmp_file = SD.open(filename1);
	if(!bmp_file)
	{

		bmp_file.close(); 
		return;
	}
	if(!teste_bmp_header(bmp_file))
	{
		bmp_file.close(); 
		return;
	}
	
	draw_bmp(x0, y0, 1, lum, contraste, &bmp_file);

	bmp_file.close(); 
//	delay(1000);

}



/** -----------------------------------------------------------------------------------
		CAPTURE D'ECRAN vers SDcard				
/** ----------------------------------------------------------------------------------- */

void write_TFT480_on_SDcard() // enregistre le fichier .bmp
{
	if (SDcardOk==1)
	{
		////TFT480.drawString("capture bmp !", 100, 100);
		////delay(1000);
		
		String s1;
		uint16_t ys=200;
		TFT480.setFreeFont(FF1);
		TFT480.setTextColor(JAUNE, NOIR);
		
		uint16_t x, y;
		uint16_t color565;
		uint16_t bmp_color;
		uint8_t R, G, B;
		
		if( ! SD.exists("/bmp/capture2.bmp"))
		{
			TFT480.fillRect(0, 0, 480, 320, NOIR); // efface
			TFT480.setTextColor(ROUGE, NOIR);
			TFT480.drawString("NO /bmp/capture2.bmp !", 100, ys);
			delay(300);
			TFT480.fillRect(100, ys, 220, 20, NOIR); // efface
			return;
		}


		File File1 = SD.open("/bmp/capture2.bmp", FILE_WRITE); // ouverture du fichier binaire (vierge) en écriture
		if (File1)
		{
	/*
	Les images en couleurs réelles BMP888 utilisent 24 bits par pixel: 
	Il faut 3 octets pour coder chaque pixel, en respectant l'ordre de l'alternance bleu, vert et rouge.
	*/
			uint16_t bmp_offset = 138;
			File1.seek(bmp_offset);

			
			TFT480.setTextColor(VERT, NOIR);
			
			for (y=320; y>0; y--)
			{
				for (x=0; x<480; x++)
				{
					color565=TFT480.readPixel(x, y);

					RGB565_to_888(color565, &R, &G, &B);
					
					File1.write(B); //G
					File1.write(G); //R
					File1.write(R); //B
				}

				s1=(String) (y/10);
				TFT480.fillRect(2, 300, 20, 20, NOIR);
				TFT480.drawString(s1, 2, 300);// affi compte à rebour
			} 

			File1.close();  // referme le fichier
			
			TFT480.fillRect(2, 300, 20, 20, NOIR); // efface le compte à rebour
		}
	}
}

/** ----------------------------------------------------------------------------------- */




void Draw_arc_elliptique(uint16_t x0, uint16_t y0, int16_t dx, int16_t dy, float angle1, float angle2, uint16_t couleur)
{
/*
REMARQUES :	
-cette fonction permet également de dessiner un arc de cercle (si dx=dy), voire le cercle complet
- dx et dy sont du type int (et pas uint) et peuvent êtres négafifs, ou nuls.
-alpha1 et alpha2 sont les angles (en radians) des caps des extrémités de l'arc 
*/

// angle1 et angle2 en degrés décimaux

 
	float alpha1 =angle1/57.3;  // (57.3 ~ 180/pi)
	float alpha2 =angle2/57.3; 
	uint16_t n;
	float i;
	float x,y;

	i=alpha1;
	while(i<alpha2)
	{
		x=x0+dx*cos(i);
		y=y0+dy*cos(i+M_PI/2.0);
		TFT480.drawPixel(x,y, couleur);
		i+=0.01;  // radians
	}
}


void affi_rayon1(uint16_t x0, uint16_t y0, uint16_t rayon, float angle_i, float pourcent, uint16_t couleur_i, bool gras)
{
// trace une portion de rayon de cercle depuis 100%...à pourcent du rayon du cercle
// angle_i en degrés décimaux - sens trigo

	float angle =angle_i/57.3;  // (57.3 ~ 180/pi)
	float x1, x2;
	float y1, y2;

	x1=x0+rayon* cos(angle);
	y1=y0-rayon* sin(angle);

	x2=x0+pourcent*rayon* cos(angle);
	y2=y0-pourcent*rayon* sin(angle);

	TFT480.drawLine(x1, y1, x2, y2, couleur_i);

/*	
	if (gras)
	{
		TFT480.drawLine(x1, y1-1, x2, y2-1, couleur_i);
		TFT480.drawLine(x1, y1+1, x2, y2+1, couleur_i);
	}
*/	
}



void affi_rayon2(uint16_t x0, uint16_t y0, float r1, float r2, float angle_i, uint16_t couleur_i, uint8_t gras)
{
// trace une portion de rayon de cercle entre les distances r1 et r2 du centre
// angle_i en degrés décimaux - sens trigo (= anti-horaire)
// origine d'angle -> trigo (à droite)

	float angle =angle_i/57.3;  // (57.3 ~ 180/pi)
	int16_t x1, x2;
	int16_t y1, y2;

	
	x1=x0+int16_t(r1* cos(angle));
	y1=y0-int16_t(r1* sin(angle));

	x2=x0+int16_t(r2* cos(angle));
	y2=y0-int16_t(r2* sin(angle));

//en fait si les extrémités dépassent de la taille de l'afficheur, ça se passe bien !
	//if ((x1>0) && (x2>0) && (y1>0) && (y2>0) && (x1<480) && (x2<480) && (y1<320) && (y2<320) )
	{
		TFT480.drawLine(x1, y1, x2, y2, couleur_i);
		
		if (gras==1)
		{
			TFT480.drawLine(x1, y1-1, x2, y2-1, couleur_i);
			TFT480.drawLine(x1, y1+1, x2, y2+1, couleur_i);
		}
	}
}



void affi_char_r_alpha(uint16_t x0, uint16_t y0, float r1, float angle_i, char c1, uint16_t couleur_i)
{
// affiche un caractère à un emplacement situé à une distance r1 du centre et à la distance r1
// angle_i en degrés décimaux - sens trigo (= anti-horaire)
// origine d'angle -> trigo (à droite)
	float angle =angle_i/57.3;  // (57.3 ~ 180/pi)
	int16_t x1;
	int16_t y1;
	
	x1=x0+int16_t(r1* cos(angle));
	y1=y0-int16_t(r1* sin(angle));

	TFT480.setFreeFont(FF1);
	TFT480.setTextColor(couleur_i, BLANC);
	String s1 =String(c1);
	TFT480.drawString(s1, x1-4, y1-4);
}



void affi_SPR_rayon2(uint16_t x0, uint16_t y0, float r1, float r2, float angle_i, uint16_t couleur_i, bool gras)
{
// trace une portion de rayon de cercle entre les distances r1 et r2 du centre
// angle_i en degrés décimaux - sens trigo

	float angle =angle_i/57.3;  // (57.3 ~ 180/pi)
	int16_t x1, x2;
	int16_t y1, y2;

	x1=x0+int16_t(r1* cos(angle));
	y1=y0-int16_t(r1* sin(angle));

	x2=x0+int16_t(r2* cos(angle));
	y2=y0-int16_t(r2* sin(angle));

	if ((x1>0) && (x2>0) && (y1>0) && (y2>0) && (x1<480) && (x2<480) && (y1<320) && (y2<320) )
	{
		SPR_VOR.drawLine(x1, y1, x2, y2, couleur_i);
		
		if (gras)
		{
			SPR_VOR.drawLine(x1, y1-1, x2, y2-1, couleur_i);
			SPR_VOR.drawLine(x1, y1+1, x2, y2+1, couleur_i);
		}
	}
}



void affi_tiret_H(uint16_t x0, uint16_t y0, uint16_t r, float angle_i, uint16_t couleur_i)
{
// trace un tiret perpendiculaire à un rayon de cercle de rayon r
// angle_i en degrés décimaux - sens trigo

	float angle =angle_i/57.3;  // (57.3 ~ 180/pi)
	float x1, x2;
	float y1, y2;

	x1=x0+(r)* cos(angle-1);
	y1=y0-(r)* sin(angle-1);		

	x2=x0+(r)* cos(angle+1);
	y2=y0-(r)* sin(angle+1);

	TFT480.drawLine(x1, y1,  x2, y2, couleur_i);	
}



void affi_pointe(uint16_t x0, uint16_t y0, uint16_t r, float angle_i, uint16_t couleur_i)
{
// trace une pointe de flèche sur un cercle de rayon r
// angle_i en degrés décimaux - sens trigo

	float angle = angle_i /57.3;  // (57.3 ~ 180/pi)
	int16_t x1, x2, x3;
	int16_t y1, y2, y3;

	x1=x0+r* cos(angle); // pointe
	y1=y0-r* sin(angle); // pointe

	x2=x0+(r-7)* cos(angle-0.1); // base A
	y2=y0-(r-7)* sin(angle-0.1); // base A		

	x3=x0+(r-7)* cos(angle+0.1); // base B
	y3=y0-(r-7)* sin(angle+0.1); // base B

	TFT480.fillTriangle(x1, y1,  x2, y2,  x3, y3,	couleur_i);	
}



void affi_SPR_encoche(uint16_t x0, uint16_t y0, uint16_t r, float angle_i, uint16_t couleur_i)
{
// trace une encoche (deux 'demi'-triangles) sur un cercle de rayon r
// angle_i en degrés décimaux - sens trigo

	float angle = angle_i /57.3;  // (57.3 ~ 180/pi)
	int16_t x1, x2, x3;
	int16_t y1, y2, y3;


// partie gauche
	x1=x0+r* cos(angle+0.08); // pointe
	y1=y0-r* sin(angle+0.08); // pointe

	x2=x0+(r-10)* cos(angle+0.08); // base A
	y2=y0-(r-10)* sin(angle+0.08); // base A		

	x3=x0+(r-10)* cos(angle); // base B
	y3=y0-(r-10)* sin(angle); // base B

	SPR_VOR.fillTriangle(x1, y1,  x2, y2,  x3, y3,	couleur_i);

// partie droite
	x1=x0+r* cos(angle-0.08); // pointe
	y1=y0-r* sin(angle-0.08); // pointe

	x2=x0+(r-10)* cos(angle-0.08); // base A
	y2=y0-(r-10)* sin(angle-0.08); // base A		

	x3=x0+(r-10)* cos(angle); // base B
	y3=y0-(r-10)* sin(angle); // base B

	SPR_VOR.fillTriangle(x1, y1,  x2, y2,  x3, y3,	couleur_i);
}



void affi_rectangle_incline(uint16_t x0, uint16_t y0, uint16_t r, float angle_i, uint16_t couleur_i)
{
//rectangle inscrit dans le cerce de rayon r
// angle_i en degrés décimaux - sens trigo

	float angle =(angle_i-3.5)/57.3;
	int16_t x1, x2, x3, x4;
	int16_t y1, y2, y3, y4;
	float d_alpha=0.05; // détermine la largeur du rectangle

// point 1
	x1=x0+r*cos(angle-d_alpha); 
	y1=y0+r*sin(angle-d_alpha);	
// point 2
	x2=x0+r*cos(angle+d_alpha); 
	y2=y0+r*sin(angle+d_alpha);	
// point 3
	x3=x0+r*cos(M_PI + angle-d_alpha); 
	y3=y0+r*sin(M_PI + angle-d_alpha);
// point 4
	x4=x0+r*cos(M_PI + angle+d_alpha); 
	y4=y0+r*sin(M_PI + angle+d_alpha);

	TFT480.drawLine(x1, y1,  x2, y2, couleur_i);
	TFT480.drawLine(x2, y2,  x3, y3, couleur_i);
	TFT480.drawLine(x3, y3,  x4, y4, couleur_i);
	TFT480.drawLine(x4, y4,  x1, y1, couleur_i);
		
}





float degTOrad(float angle)
{
	return (angle * M_PI / 180.0);
}




void affi_dst_piste() // distance GPS
{
	if (mode_affi == MODE_PLAN) {return;}
	//TFT480.setTextFont(2);
	cadre_distance_piste.efface();
	int nb_decimales =2;
	if (GPS_distance_piste>=10){nb_decimales=1;}
	if (GPS_distance_piste>=100){nb_decimales=0;}
	cadre_distance_piste.affiche_float(GPS_distance_piste, nb_decimales);
	//TFT480.setTextFont(1);
	//TFT480.setTextColor(BLANC);
	//TFT480.drawString("GPS", cadre_distance_piste.x0, cadre_distance_piste.y0-15);
	TFT480.setTextFont(1);
	TFT480.setTextColor(VERT);
	TFT480.drawString("NM", cadre_distance_piste.x0+60, cadre_distance_piste.y0+12);

// ZOOM AUTO
	if (read_bit(flags, bit_autoland) == 1) // c.a.d si le mode autoland de l'ESP32 PFD est engagé
// ce qui évite le déclenchement de cet automatisme au décollage !
	{
		memo_zoom=zoom;
		if((GPS_distance_piste <16.0)&& (zoom>5))  {zoom=5;}
		if((GPS_distance_piste <8.0) && (zoom>4))  {zoom=4;}
		if((GPS_distance_piste <4.0) && (zoom>3))  {zoom=3;}
		if((GPS_distance_piste <2.0) && (zoom>2))  {zoom=2;}
		if((GPS_distance_piste <1.0) && (zoom>1))  {zoom=1;}
		if((GPS_distance_piste <0.5) && (zoom>0))  {zoom=0;}
		

		if (zoom!=memo_zoom)
		{
			affi_carte_locale(235,0,1);
			affi_ND(cap);
		}
	
		////if((GPS_distance_piste <0.5) && (hauteur_AAL<10.0))
		////{
			////mode_affi=MODE_PLAN;
			////init_sprite_avion(ROUGE);
			////affi_plan_aerodrome();
		////}

	}
}



void affi_direction_piste() // direction de la piste vue de l'avion (affiche au dessus du grand cercle)
{
	if (mode_affi == MODE_PLAN) {return;}
	String s;
	uint16_t x0=133;
	uint16_t y0=270; 

	int16_t alpha1 = GPS_azimut_piste;

	cadre_AZ_piste.efface();
	cadre_AZ_piste.affiche_int(alpha1,' ');
	TFT480.drawCircle(215, 5, 2, BLANC); // caractère 'degré'
}


void affi_dst_GPS() //distance de la piste vue de l'avion
{

	if (mode_affi == MODE_PLAN) {return;}
	String s1;
	uint16_t x0=70;
	uint16_t y0=250; 

	TFT480.setFreeFont(FM9);
	TFT480.setTextColor(JAUNE, NOIR);
	TFT480.drawString("RWY", x0-45, y0);
		
	TFT480.setTextColor(BLANC, NOIR);
	int nb_decimales;
	if(GPS_distance_piste>99) {nb_decimales =0;} else {nb_decimales =1;}
	s1 = String(GPS_distance_piste, nb_decimales);
	if (GPS_distance_piste > 2000) {s1=" --";}

	TFT480.fillRect(x0, y0, 52, 18, NOIR); // efface
	TFT480.setFreeFont(FM9);
	TFT480.drawString(s1, x0, y0);
	TFT480.drawRoundRect(x0, y0-2, 50, 18, 5, GRIS_FONCE); // encadrement de la valeur affichée

	TFT480.setTextColor(JAUNE, NOIR);
	TFT480.drawString("NM", x0+55, y0);

	
}




void init_SDcard()
{
	
	uint16_t y=0;
	
	TFT480.setTextColor(VERT, NOIR);
	TFT480.drawString("Init SDcard", 0, y);
	y+=20;
	
 	if(!SD.begin())
	{
		TFT480.drawString("Card Mount Failed", 0, y);
		delay (2000);
		TFT480.fillRect(0, 0, 480, 320, NOIR); // efface
		return;
	}
  

    uint8_t cardType = SD.cardType();

    if(cardType == CARD_NONE)
    {
        TFT480.drawString("No SDcard", 0, y);
        delay (2000);
		TFT480.fillRect(0, 0, 480, 320, NOIR); // efface
        return;
    }

    SDcardOk=1;

    TFT480.drawString("SDcard Type: ", 0, y);
	if(cardType == CARD_SD)    {TFT480.drawString("SDSC", 150, y);}
    else if(cardType == CARD_SDHC) {TFT480.drawString("SDHC", 150, y);}

	y+=20;
	
	uint32_t cardSize = SD.cardSize() / (1024 * 1024);
	String s1=(String)cardSize + " GB";
	TFT480.drawString("SDcard size: ", 0, y);
	TFT480.drawString(s1, 150, y);

//Serial.printf("Total space: %lluMB\n", SD.totalBytes() / (1024 * 1024));
//Serial.printf("Used space: %lluMB\n", SD.usedBytes() / (1024 * 1024));

	delay (500);
	TFT480.fillRect(0, 0, 480, 320, NOIR); // efface
}



void dessine_avion(uint16_t x, uint16_t y) // celui, fixe,  au centre du grand cercle
{

	TFT480.drawRoundRect(x+2, y-6, 6, 25, 2, BLANC); // fuselage
	TFT480.drawRoundRect(x-10, y-1, 32, 8, 2, BLANC);   // aile
	TFT480.drawRoundRect(x-2, y+17, 15, 6, 2, BLANC);  // stab
		
	TFT480.fillRoundRect(x+3, y-5, 8, 23, 2, BLEU); // fuselage
	TFT480.fillRoundRect(x-9, y, 30, 6, 2, BLEU);   // aile
	TFT480.fillRoundRect(x-1, y+18, 13, 4, 2, BLEU);  // stab
}



void dessine_point_sur_carte(float lat_i, float long_i, int16_t couleur)
{
// calcul de la position du point/ piste

	float distance_piste = distance_AB(lat_i, long_i, lat_centre_pst, lon_centre_pst)  / 1.852; //du centre de la piste,  en NM
	float azimut_piste = azimut_AB(lat_i, long_i, lat_centre_pst, lon_centre_pst); // latitudes et longitudes en degrés décimaux

	float dx = distance_piste * cos(raddeg * (azimut_piste - 90.0)); 
	float dy = distance_piste * sin(raddeg * (azimut_piste - 90.0));

	//if(GPS_distance_piste < 1.0){mode_affi = MODE_PLAN;}

	int32_t dx_px = (dx * px_par_NM);  
	int32_t dy_px = (dy * px_par_NM);

	int16_t x_pt = 357 - dx_px;
	int16_t y_pt =  75 - dy_px;

	if ((x_pt > 245) && (y_pt > 10) && (y_pt < 170))
	{
		TFT480.fillCircle(x_pt, y_pt+2, 2, couleur);
		//TFT480.drawPixel(x_pt, y_pt, couleur);
	}
}




void affi_avion_sur_carte(float alpha) // dessine le sprite du petit sur la carte, mobile, avec l'orientation alpha
{
	//TFT480.drawRect(x-5, y-3, 15, 12, NOIR); // pour test visu délimitation de la zone

	if ((x_avion !=0) && (memo_x_avion !=0) && (y_avion !=0) && (memo_y_avion !=0))
	{
		TFT480.pushRect(memo_x_avion-10, memo_y_avion-10, 20, 20, memo_img); // print (petit) fond de carte
	}
	
	if ((x_avion > 245) && (y_avion > 10) && (y_avion < 170))
	{
		TFT480.readRect(x_avion-10, y_avion-10, 20, 20, memo_img); // enregistre en mémoire RAM
		
	//affiche le sprite de l'avion avec la bonne orientation

		TFT480.setPivot(x_avion, y_avion);

		SPR_avion.pushRotated(alpha, TFT_BLACK); // noir définit comme transparent

		memo_x_avion = x_avion;
		memo_y_avion = y_avion;
	}
}



void affi_avion_sur_plan(float alpha) // dessine le sprite du petit sur le plan des pistes, mobile, avec l'orientation alpha
{
	//TFT480.drawRect(x-5, y-3, 15, 12, NOIR); // pour test visu délimitation de la zone

	if (memo_x_avion !=0)
	{
		TFT480.pushRect(memo_x_avion-10, memo_y_avion-10, 20, 20, memo_img); // print (petit) fond de carte
	}
	
	if ((x_avion > 10) && (y_avion > 10) && (y_avion < 350))
	{
		TFT480.readRect(x_avion-10, y_avion-10, 20, 20, memo_img); // enregistre en mémoire RAM
		
	//affiche le sprite de l'avion avec la bonne orientation

		TFT480.setPivot(x_avion, y_avion);

		SPR_avion.pushRotated(alpha, TFT_BLACK); // noir définit comme transparent

		memo_x_avion = x_avion;
		memo_y_avion = y_avion;
	}
}



void calculs_GPS()
{
	float lat_A = liste_airports[num_airport].lat_A;
	float lon_A = liste_airports[num_airport].lon_A;
	
	float lat_B = liste_airports[num_airport].lat_B;
	float lon_B = liste_airports[num_airport].lon_B;

	longueur_piste = 1000.0* distance_AB(lat_A, lon_A, lat_B, lon_B); // en m
	orient_piste = azimut_AB(lat_A, lon_A, lat_B, lon_B); // en degrés

	lat_centre_pst=(lat_A +lat_B)/2.0;
	lon_centre_pst=(lon_A +lon_B)/2.0;

	// DISTANCE (variable globale)
// voir la fonction "distance_AB()" dans le fichier "Fonctions1.h"
	GPS_distance_piste = distance_AB(lat_avion, lon_avion, lat_centre_pst, lon_centre_pst)  / 1.852; // du centre de la piste,  en NM

// DIRECTION (variable globale)	
	GPS_azimut_piste = azimut_AB(lat_avion, lon_avion, lat_centre_pst, lon_centre_pst); // latitudes et longitudes en degrés décimaux
}




void calcul_point_i(float azimut_i, float distance_i, float *latitude, float *longitude)
{
/*
calcul des coordonnées GPS d'un point quelquonque PROCHE de la piste (en vue de faire des "hyppodromes" biens maitrisés) 

données:
-azimut et distance du point concerné par rapport au, et vu du, centre de la piste (paramètres: azimut_i et distance_i)
-coordonnées GPS des points extrémités de la piste (lus dans le fichier FG_data.h)
-coordonnées GPS du centre, et l'orientation de la piste (voir fonction "void calculs_GPS()" )
on va alors ajouter la valeur de l'azimut_i à l'orientation de la piste pour faire le calcul
*/

// calcul de l'orientation (relevé) du point (azimut par rapport au nord)
	float orientation_point = -1.0 * orient_piste + azimut_i; // azimut_i étant l'angle entre la piste et la direction du point

// 1 minute d'angle sur un méridien => 1 NM de latitude
// 60mn d'angle (1deg) => 60 NM
// 1 NM => 1/60 de degré -> 0.0166 degrés
	
	*latitude = lat_centre_pst + (distance_i /60.0 * sin(raddeg * (orientation_point - 90.0)));
	
// pour la longitude, il faut tenir compte que la longueur d'un parallèle dépend de la latitude 
// (max à l'équateur, nulle au pôle) suivant une loi en cos.
	*longitude= lon_centre_pst + (distance_i /(60.0 * cos(raddeg * lat_centre_pst)) * cos(raddeg * (orientation_point - 90.0)));

}



void calcul_pos_avion_sur_carte() // en px
{

// calcul de la position de l'avion / piste

	float dx = GPS_distance_piste * cos(raddeg * (GPS_azimut_piste - 90.0)); 
	float dy = GPS_distance_piste * sin(raddeg * (GPS_azimut_piste - 90.0));

	//if(GPS_distance_piste < 1.0){mode_affi = MODE_PLAN;}

	int32_t dx_px = (dx * px_par_NM);  
	int32_t dy_px = (dy * px_par_NM);

	x_avion = 357 - dx_px;
	y_avion =  75 - dy_px;	
}




void calcul_pos_avion_sur_plan() // (sur le plan de l'aérodrome)
{
	String s1;

// calcul de la position de l'avion / piste

	float dx = GPS_distance_piste * cos(raddeg * (GPS_azimut_piste - 90.0)); 
	float dy = GPS_distance_piste * sin(raddeg * (GPS_azimut_piste - 90.0));


	float dx_px = (dx * plan_echelle);
	float dy_px = (dy * plan_echelle);


	x_avion = uint16_t(plan_ref_x - dx_px);
	y_avion = uint16_t(plan_ref_y - dy_px);	
}


void init_sprite_avion(uint16_t couleur)
{
//--------------------------------------------------------------
// dessin du petit avion dans son sprite pour affichage sur la carte (orientaion nulle ici)

	//SPR_avion.fillRect(0,0,19,19, VERT);  // pour test visuel du cadrage du dessin dans le sprite
	SPR_avion.fillRoundRect(7+1, 8-3, 3, 12, 2, couleur); // fuselage
	SPR_avion.fillRoundRect(7-5, 8, 15, 3, 2, couleur);   // aile
	SPR_avion.fillRoundRect(7-1, 8+8, 7, 2, 2, couleur);  // stab
	
}



void init_sprites()
{
	// sprites représentant les lettres 'N' 'S' 'E' 'O' qui seront affichées  sur un cercle, inclinées donc.


	SPR_E.setFreeFont(FF5); 	SPR_E.setTextColor(JAUNE);
	SPR_E.createSprite(SPR_W, SPR_H);
	SPR_E.setPivot(SPR_W/2, SPR_H/2);  // Set pivot relative to top left corner of Sprite
	SPR_E.fillSprite(NOIR);
	SPR_E.drawString("E", 2, 2 );

	SPR_N.setFreeFont(FF5); 	SPR_N.setTextColor(JAUNE);
	SPR_N.createSprite(SPR_W, SPR_H);
	SPR_N.setPivot(SPR_W/2, SPR_H/2);
	SPR_N.fillSprite(NOIR);
	SPR_N.drawString("N", 2, 2 );

	SPR_O.setFreeFont(FF5); 	SPR_O.setTextColor(JAUNE);
	SPR_O.createSprite(SPR_W, SPR_H);
	SPR_O.setPivot(SPR_W/2, SPR_H/2);
	SPR_O.fillSprite(NOIR);
	SPR_O.drawString("W", 2, 2 );

	SPR_S.setFreeFont(FF5); 	SPR_S.setTextColor(JAUNE);
	SPR_S.createSprite(SPR_W, SPR_H);
	SPR_S.setPivot(SPR_W/2, SPR_H/2);
	SPR_S.fillSprite(NOIR);
	SPR_S.drawString("S", 2, 2 );

// sprites représentant les nombres '3' '6' '12' '15' '21' '24' '30' '33' 

	SPR_3.setFreeFont(FF5); 	SPR_3.setTextColor(BLANC);
	SPR_3.createSprite(SPR_W, SPR_H);
	SPR_3.setPivot(SPR_W/2, SPR_H/2);  // Set pivot relative to top left corner of Sprite
	SPR_3.fillSprite(NOIR);
	SPR_3.drawString("3", 2, 2 );

	SPR_6.setFreeFont(FF5); 	SPR_6.setTextColor(BLANC);
	SPR_6.createSprite(SPR_W, SPR_H);
	SPR_6.setPivot(SPR_W/2, SPR_H/2);
	SPR_6.fillSprite(NOIR);
	SPR_6.drawString("6", 2, 2 );

	SPR_12.setFreeFont(FF5); 	SPR_12.setTextColor(BLANC);
	SPR_12.createSprite(SPR_W, SPR_H);
	SPR_12.setPivot(SPR_W/2, SPR_H/2);
	SPR_12.fillSprite(NOIR);
	SPR_12.drawString("12", 2, 2 );

	SPR_15.setFreeFont(FF5); 	SPR_15.setTextColor(BLANC);
	SPR_15.createSprite(SPR_W, SPR_H);
	SPR_15.setPivot(SPR_W/2, SPR_H/2);
	SPR_15.fillSprite(NOIR);
	SPR_15.drawString("15", 2, 2 );

	SPR_21.setFreeFont(FF5); 	SPR_21.setTextColor(BLANC);
	SPR_21.createSprite(SPR_W, SPR_H);
	SPR_21.setPivot(SPR_W/2, SPR_H/2);  // Set pivot relative to top left corner of Sprite
	SPR_21.fillSprite(NOIR);
	SPR_21.drawString("21", 2, 2 );

	SPR_24.setFreeFont(FF5); 	SPR_24.setTextColor(BLANC);
	SPR_24.createSprite(SPR_W, SPR_H);
	SPR_24.setPivot(SPR_W/2, SPR_H/2);
	SPR_24.fillSprite(NOIR);
	SPR_24.drawString("24", 2, 2 );

	SPR_30.setFreeFont(FF5); 	SPR_30.setTextColor(BLANC);
	SPR_30.createSprite(SPR_W, SPR_H);
	SPR_30.setPivot(SPR_W/2, SPR_H/2);
	SPR_30.fillSprite(NOIR);
	SPR_30.drawString("30", 2, 2 );

	SPR_33.setFreeFont(FF5); 	SPR_33.setTextColor(BLANC);
	SPR_33.createSprite(SPR_W, SPR_H);
	SPR_33.setPivot(SPR_W/2, SPR_H/2);
	SPR_33.fillSprite(NOIR);
	SPR_33.drawString("33", 2, 2 );

	SPR_VOR.setFreeFont(FF5); 	SPR_33.setTextColor(BLANC);
	SPR_VOR.createSprite(180, 180);
	SPR_VOR.setPivot(90, 90);
	SPR_VOR.fillSprite(NOIR);  // pour test en rotation --> BLEU

	//SPR_avion.setFreeFont(FF5); 	SPR_33.setTextColor(BLANC);
	SPR_avion.createSprite(20, 20);
	SPR_avion.setPivot(10, 10);
	//SPR_avion.fillSprite(NOIR);  // pour test en rotation --> BLEU

//--------------------------------------------------------------	
//		DESSIN DU COMPAS gradué
//--------------------------------------------------------------
// dessine les tirets de graduation du compas sur le sprite SPR_VOR

	uint16_t angle1 = 0;
	uint8_t a;
	uint16_t R =90;
	for(uint8_t n=0; n<72; n+=2 )
	{
		angle1 = n*5;  // 1 tiret tous les 15 degrés
		a= 3;
		//if ((n%2) == 0){a=10;}
		if ((n%6) == 0){a=12;}
		affi_SPR_rayon2(90, 90, R-a, R, angle1, BLANC, false);
	}
//--------------------------------------------------------------
// écrit les nombres (sprites définis ci-dessus) sur le sprite SPR_VOR

	uint16_t x_spr;
	uint16_t y_spr;


	x_spr = 90 +70*cos(degTOrad(-60));
	y_spr = 90 -70*sin(degTOrad(-60));
	SPR_VOR.setPivot(x_spr, y_spr);
	SPR_15.pushRotated(&SPR_VOR, 150); // sur adresse relative du SPR_VOR


	x_spr = 90 +70*cos(degTOrad(-30));
	y_spr = 90 -70*sin(degTOrad(-30));
	SPR_VOR.setPivot(x_spr, y_spr);
	SPR_12.pushRotated(&SPR_VOR, 120);
	
	
	x_spr = 90 +70*cos(degTOrad(0-5));
	y_spr = 90 -70*sin(degTOrad(0-5));
	SPR_VOR.setPivot(x_spr, y_spr);
	SPR_E.pushRotated(&SPR_VOR, 90); // Plot rotated Sprite 'SPR_E' dans le sprite 'SPR_VOR'


	x_spr = 90 +70*cos(degTOrad(30-5));
	y_spr = 90 -70*sin(degTOrad(30-5));
	SPR_VOR.setPivot(x_spr, y_spr);
	SPR_6.pushRotated(&SPR_VOR, 60);
	

	x_spr = 90 +70*cos(degTOrad(60-5));
	y_spr = 90 -70*sin(degTOrad(60-5));
	SPR_VOR.setPivot(x_spr, y_spr);
	SPR_3.pushRotated(&SPR_VOR, 30);

	x_spr = 90 +70*cos(degTOrad(90-5));
	y_spr = 90 -70*sin(degTOrad(90-5));
	SPR_VOR.setPivot(x_spr, y_spr);
	SPR_N.pushRotated(&SPR_VOR, 0);

	x_spr = 90 +70*cos(degTOrad(120));
	y_spr = 90 -70*sin(degTOrad(120));
	SPR_VOR.setPivot(x_spr, y_spr);
	SPR_33.pushRotated(&SPR_VOR, -30);	

	x_spr = 90 +70*cos(degTOrad(150));
	y_spr = 90 -70*sin(degTOrad(150));
	SPR_VOR.setPivot(x_spr, y_spr);
	SPR_30.pushRotated(&SPR_VOR, -60);	
	
	x_spr = 90 +70*cos(degTOrad(180-5));
	y_spr = 90 -70*sin(degTOrad(180-5));
	SPR_VOR.setPivot(x_spr, y_spr);
	SPR_O.pushRotated(&SPR_VOR, -90);

	x_spr = 90 +70*cos(degTOrad(210));
	y_spr = 90 -70*sin(degTOrad(210));
	SPR_VOR.setPivot(x_spr, y_spr);
	SPR_24.pushRotated(&SPR_VOR, 240);
	
	x_spr = 90 +70*cos(degTOrad(240));
	y_spr = 90 -70*sin(degTOrad(240));
	SPR_VOR.setPivot(x_spr, y_spr);
	SPR_21.pushRotated(&SPR_VOR, 210);
	

	x_spr = 90 +70*cos(degTOrad(-90-5));
	y_spr = 90 -70*sin(degTOrad(-90-5));
	SPR_VOR.setPivot(x_spr, y_spr);
	SPR_S.pushRotated(&SPR_VOR, 180);
	


}


/*
void init_affi_vor()
{

	uint16_t x0 = 120;
	uint16_t y0 = 120;
	uint16_t w = 240;
	uint16_t h = 240;
	
	uint16_t R =100;
	uint16_t R2 =70;
	
	//TFT480.fillRect(x0-w/2, y0-h/2, w, h, NOIR);
	TFT480.drawRoundRect(0, 0, 230, 240, 5, GRIS_FONCE);

	uint16_t y1 = y0+12;

	////TFT480.fillTriangle(
	////x0, y1+18-h/2,
	////x0-5, y1+12-h/2,
	////x0+5, y1+12-h/2,
	////BLANC);	
	
	//TFT480.fillCircle(x0, y0, R, NOIR);
	//TFT480.fillCircle(x0, y1, 5, GRIS_FONCE);
	//TFT480.drawCircle(x0, y1, R, GRIS_FONCE);

	dessine_avion(104, 125);
	
}
*/

void affi_index_cap() // petite pointe de flèche fixe au sommet du grand cercle
{
	uint16_t x0 = 110;
	uint16_t y0 = 120+5;
	uint16_t h = 180;
	TFT480.fillTriangle(
	x0, y0+6-h/2,
	x0-5, y0-h/2,
	x0+5, y0-h/2,
	BLANC);
	TFT480.setFreeFont(FM9);
	TFT480.setTextColor(BLANC, NOIR);
	//TFT480.drawString("HDG", x0-60, y0-30-h/2);
	
}



void affi_encoche_hdg(uint16_t alpha_i, uint16_t couleur)  // encoche qui se déplace sur le grand sprite
{

	init_sprites();
	init_sprite_avion(BLEU);
	affi_SPR_encoche(90, 90, 90, -alpha_i +90, couleur);
	
	//affi_rayon2(x0, y0, 85, 20, -alpha_i, couleur, false);
}


void affi_liste() // des aérodromes
{
	uint16_t x0=0;
	uint16_t x1=10;
	uint16_t y0=0;
	uint16_t w = 480;
	uint16_t h = 320;

	//uint16_t num_ligne =0;

	String s1;
	
	TFT480.fillScreen(NOIR);
	TFT480.drawRoundRect(x0, y0, 235, h, 3, BLANC);
	
	TFT480.setTextFont(2);

	uint16_t y=10;
	
	int n;
	uint16_t n_min;
	uint16_t n_max;

	if(decal>0){affi_indexV(225, 12, -1, JAUNE);}

	n_min = decal;
	n_max = n_min + 17;
	if (n_max>nb_elements) {n_max = nb_elements;}

	for (n=n_min; n<n_max; n++)
	{
		num_airport=n;
		
		s1=(String)(n+1);
		if(n<9){s1=" "+s1;}
		TFT480.setTextColor(BLEU, NOIR);
		TFT480.drawString(s1, x1, y);
		
		s1= liste_airports[num_airport].ID_OACI;
		TFT480.setTextColor(VERT, NOIR);
		TFT480.drawString(s1, x1+22, y);
		s1= liste_airports[num_airport].nom;
		TFT480.setTextColor(BLANC, NOIR);
		TFT480.drawString(s1, x1+65, y);
		y+=18;
	}
	
	if(n_max < nb_elements){affi_indexV(225, 300, 1, JAUNE);}
}	


void incremente_ligne()
{
	uint16_t x0=0;
	uint16_t x1=10;
	uint16_t y0=0;
	uint16_t w = 480;
	uint16_t h = 320;
	
	TFT480.drawRect(x0+2, y0+9+num_ligne*18, 215, 18, NOIR); // efface le cadre
	num_ligne++;
	if ((num_ligne+decal)>=nb_elements)
	{
		num_ligne = nb_elements - decal -1; // on ne descend pas d'avantage
	}
	else if (num_ligne>=16)
	{
		num_ligne=15;
		decal++;
		affi_liste();
	}
//affi_int_test(num_ligne, 300, 2, CYAN, NOIR);
//affi_int_test(decal, 300, 3, VERT, NOIR);
	
	num_airport = num_ligne + decal;
	TFT480.drawRect(x0+2, y0+9+num_ligne*18, 215, 18, JAUNE); // encadre la ligne choisie

}


void decremente_ligne()
{
	uint16_t x0=0;
	uint16_t x1=10;
	uint16_t y0=0;
	uint16_t w = 480;
	uint16_t h = 320;
	
	TFT480.drawRect(x0+2, y0+9+num_ligne*18, 215, 18, NOIR);
	if (num_ligne>0)
	{
		num_ligne--;
	}
	else
	{
		if(decal>0)
		{
			decal--;
			num_ligne=0;
			affi_liste();
		}
	}
//affi_int_test(num_ligne, 300, 2, CYAN, NOIR);
//affi_int_test(decal, 300, 3, VERT, NOIR);
	
	num_airport = num_ligne + decal;				
	TFT480.drawRect(x0+2, y0+9+num_ligne*18, 215, 18, JAUNE);
}


void init_mode_ND()
{
	TFT480.fillScreen(NOIR);
	mode_affi=MODE_ND;
	init_sprite_avion(BLEU);
	memo_cap=cap;
	dessine_pannel_frq();
	refresh_ND=1;
	affi_carte_locale(235,0,1);
	affi_ND(cap);
}


void choix_aerodrome() // choix d'un aérodrome dans la liste
{
	
	uint16_t x0=0;
	uint16_t x1=10;
	uint16_t y0=0;
	uint16_t w = 480;
	uint16_t h = 320;

	uint16_t compte=0;

	TFT480.fillScreen(NOIR);
	TFT480.drawRoundRect(x0, y0, 235, h, 3, BLANC);

	TFT480.setTextFont(2);

	//decal=0;

	affi_liste();

	//num_ligne=9;
	num_airport = num_ligne + decal;
	TFT480.drawRect(x0+2, y0+9+num_ligne*18, 215, 18, JAUNE);

	uint8_t sortir=0;
	uint8_t maj_carte=1;
	uint16_t compteur2=0;
	

	while (sortir==0)
	{
		bouton0_etat = digitalRead(GPIO_bouton0);
		if (bouton0_etat != memo_bouton0_etat)
		{
			memo_bouton0_etat = bouton0_etat;
			if (bouton0_etat==0)  // ============  DESCEND DANS LA LISTE ====================
			{
				Led2.allume();
				incremente_ligne(); // si appui bref
				while(bouton0_etat == 0) // puis on détecte un éventuel appui long
				{
					bouton0_etat = digitalRead(GPIO_bouton0);
					compte++;
					if (compte>=50) // appui long détecté
					{
						compte=0;
						bouton0_etat = digitalRead(GPIO_bouton0);
						while(bouton0_etat == 0) // tant que le bouton reste appuyé, on incrémente rapidement les pas
						{
							bouton0_etat = digitalRead(GPIO_bouton0);
							compteur2=0;
							incremente_ligne(); // si appuis répétitifs
							//incremente_ligne(); // incrémente les lignes 2 à 2
							delay(100);
						}
					}
					delay(20);					
				}
				maj_carte=1;
			}
			else {Led2.eteint();}
		}

	
		bouton1_etat = digitalRead(GPIO_bouton1);
		if (bouton1_etat != memo_bouton1_etat)
		{
			memo_bouton1_etat = bouton1_etat;
			if (bouton1_etat==0)  // ============  MONTE DANS LA LISTE ====================
			{
				Led1.allume();
				decremente_ligne();
				while(bouton1_etat == 0) // puis on détecte un éventuel appui long (500ms)
				{
					bouton1_etat = digitalRead(GPIO_bouton1);
					compte++;
					if (compte>=50) // appui long détecté
					{
						compte=0;
						bouton1_etat = digitalRead(bouton1);
						while(bouton1_etat == 0) // tant que le bouton reste appuyé, on incrémente rapidement les pas
						{
							bouton1_etat = digitalRead(GPIO_bouton1);
							compteur2=0;
							decremente_ligne();
							//decremente_ligne(); // 2 lignes
							delay(100);
						}
					}
					delay(20);
				}
				maj_carte=1;
			}
			else {Led1.eteint();}
		}
		compteur2++;
		
		if (compteur2 >= 20)
		{
			compteur2=0;
			{
				if(maj_carte==1)
				{
					zoom=2;
					affi_carte_locale(238,0,0);
					maj_carte=0;
					affi_photo(238,160);
				}
			}
		}

		
		delay(10);


		bouton3_etat = digitalRead(GPIO_bouton3);
		if (bouton3_etat != memo_bouton3_etat)
		{
			memo_bouton3_etat = bouton3_etat;
			if (bouton3_etat==0)
			{
				if (saisir_ecran==1) {write_TFT480_on_SDcard(); }  /* ====== saisie écran ====== */
				else {sortir=1;}
			}
		}	
	}

	init_variables();
	calculs_GPS();

	read_data_plan_sur_SDcard();

	init_mode_ND();
}



void affi_ND(float alpha_i)  // Navigation Display (le grand cercle à gauche avec les différents affichages dessus)
{
	if (mode_affi != MODE_ND) {return;}
	
	uint16_t w = 180;
	uint16_t h = 180;
	
	uint16_t R =100;
	uint16_t R2 =70;
	
	float angle1, angle2;

	uint16_t x_spr;
	uint16_t y_spr;

	angle2 = -alpha_i;

	TFT480.setPivot(110, 130);
	SPR_VOR.setPivot(90, 90);
	
	SPR_VOR.pushRotated(angle2); // affiche le grand cercle sous forme d'un (grand) sprite
	dessine_avion(104, 125);

	TFT480.drawRoundRect(0, 0, 230, 240, 5, GRIS_FONCE);

	affi_index_cap(); // petite pointe de flèche (fixe, orientée vers le bas) au sommet du grand cercle

	cadre_cap1.efface();
	cadre_cap1.affiche_int(cap,' ');
	TFT480.drawCircle(136, 5, 2, BLANC); // caractère 'degré'
	
// rectangle bleu visualisant l'orientation de la piste
	//float angle3 = 90.0+ils_orientation; // en degrés décimaux
	float angle3 = 90.0 + orient_piste;
	affi_rectangle_incline(110, 130, 60, angle3 + angle2 +3, BLEU_CLAIR);

	
// flêche -> direction(/Nord) de la piste, vue de l'avion

// DIRECTION	

	GPS_azimut_piste = azimut_AB(lat_avion, lon_avion, lat_centre_pst, lon_centre_pst); // latitudes et longitudes en degrés décimaux

	int16_t alpha2 = GPS_azimut_piste;
	if (alpha2>360)  {alpha2 -= 360;}

	affi_rayon2(110, 130, 70, 20, -angle2 -alpha2 +90, GRIS_CLAIR, 1); // vers Nav1 radial = VOR
	//affi_rayon2(110, 130, 70, 20, -angle2 -vor_radial +90, GRIS_FONCE, 0);  // azimut de l'avion vu du VOR
	
	affi_pointe(110, 130, 70, -angle2 -alpha2 +90, GRIS_CLAIR); // pointe dans la direction de l'aérodrome (piste)

// HDG	
	affi_rayon2(110, 130, 80, 20, -angle2 -hdg1 +90, OLIVE, 1); // HDG
}



void dessine_pannel_frq() // pour l'affichage des données, voir la fonction "affi_data_piste()"
{
	if (mode_affi == MODE_PLAN) {return;}
		
	uint16_t x0 = 235;
	uint16_t y0 = 172;
	uint16_t xi, yi;

	xi = x0+5;
	yi = y0+5;

	TFT480.drawRoundRect(x0, y0, 245, 146, 5, GRIS);

	yi = y0+5;

// -------------------------------------
// entête

	cadre_top1.init(xi,yi,60,22);
	cadre_top1.couleur_contour = GRIS_FONCE;
	cadre_top1.couleur_texte = BLEU_CLAIR;
	cadre_top1.traceContour();

	TFT480.setTextFont(1);;	TFT480.setTextColor(BLANC, NOIR);
	TFT480.drawString("ALTITUDE:", xi+75, yi+7);
	
	cadre_top1b.init(xi+135,yi,85,22);
	cadre_top1b.couleur_contour = GRIS_FONCE;
	cadre_top1b.couleur_texte = VIOLET2;
	cadre_top1b.traceContour();
	
	yi+=25;

	cadre_top2.init(xi,yi,220,22);
	cadre_top2.couleur_contour = GRIS_FONCE;
	cadre_top2.couleur_texte = BLEU_CLAIR;
	cadre_top2.traceContour();

	yi+=25;

	// affichage LATI - LONG

	xi=x0+5;
	cadre_position.init(xi,yi,220,15);
	cadre_position.couleur_contour = GRIS_FONCE;
	cadre_position.couleur_texte = VIOLET2;
	cadre_position.traceContour();
	yi+=18;



// -------------------------------------
// colonne 0

	yi=y0+75;
	
	TFT480.setFreeFont(FM9);	TFT480.setTextColor(GRIS_CLAIR, NOIR);
	TFT480.drawString("RWY", xi+5, yi+5);

	TFT480.drawCircle(xi+110, yi+5, 2, BLEU_CLAIR); // caractère 'degré'
	TFT480.drawCircle(xi+190, yi+5, 2, BLEU_CLAIR); // caractère 'degré'

	cadre_00.init(xi+45,yi,80,22);
	cadre_00.couleur_contour = GRIS_FONCE;
	cadre_00.couleur_texte = BLEU_CLAIR;
	cadre_00.traceContour();
	yi+=25;
	
	TFT480.setFreeFont(FM9);
	TFT480.setTextColor(GRIS_CLAIR, NOIR);
	TFT480.drawString("longueur", xi+5, yi+5);

	cadre_01.init(xi+100,yi,80,22);
	cadre_01.couleur_contour = GRIS_FONCE;
	cadre_01.couleur_texte = VERT;
	cadre_01.traceContour();
	

// -------------------------------------
// colonne 1

	xi+=85;
	yi=y0+75;

	cadre_10.init(xi+45,yi,80,22);
	cadre_10.couleur_contour = GRIS_FONCE;
	cadre_10.couleur_texte = BLEU_CLAIR;
	cadre_10.traceContour();
	yi+=25;

	////cadre_11.init(xi+45,yi,80,22);
	////cadre_11.couleur_contour = GRIS_FONCE;
	////cadre_11.couleur_texte = VERT;
	////cadre_11.traceContour();
	////yi+=25;
	

	// -------------------------------------
// colonne 2

	xi+=55;
	yi=y0+75;

	////cadre_20.init(xi+45,yi,40,22);
	////cadre_20.couleur_contour = GRIS_FONCE;
	////cadre_20.couleur_texte = GRIS_CLAIR;
	////cadre_20.traceContour();
	yi+=25;


	////cadre_21.init(xi+45,yi,40,22);
	////cadre_21.couleur_contour = GRIS_FONCE;
	////cadre_21.couleur_texte = BLEU_CLAIR;
	////cadre_21.traceContour();
	yi+=25;



// -------------------------------------
// pied

	xi=x0+5;
	yi=y0+125;

	cadre_bas1.init(xi,yi,105,15);
	cadre_bas1.couleur_contour = GRIS_FONCE;
	cadre_bas1.couleur_texte = JAUNE;
	cadre_bas1.traceContour();

	xi+=110;
	cadre_bas2.init(xi,yi,110,15);
	cadre_bas2.couleur_contour = GRIS_FONCE;
	cadre_bas2.couleur_texte = VERT;
	cadre_bas2.traceContour();

// -------------------------------------
//juste sous la carte

	////cadre_distance_piste.init(235,150,60,18);
	////cadre_distance_piste.couleur_contour = GRIS;
	////cadre_distance_piste.couleur_texte = VERT;
	////cadre_distance_piste.traceContour();		
	
// -------------------------------------
// 3 cadres au dessus du grand cercle :

	cadre_hdg1.init(10,3,40,22);
	cadre_hdg1.couleur_contour = GRIS;
	cadre_hdg1.couleur_texte = OLIVE;
	cadre_hdg1.traceContour();

	TFT480.setTextFont(1);
	TFT480.setTextColor(BLANC, NOIR);
	TFT480.drawString("HDG", 14, 26);	

	cadre_cap1.init(90,3,40,22);
	cadre_cap1.couleur_contour = GRIS;
	cadre_cap1.couleur_texte = BLANC;
	cadre_cap1.traceContour();
	TFT480.drawString("CAP", 94, 26);
	

	cadre_AZ_piste.init(170,3,40,22);
	cadre_AZ_piste.couleur_contour = GRIS;
	cadre_AZ_piste.couleur_texte = JAUNE;
	cadre_AZ_piste.traceContour();
	TFT480.drawString("Dir PST", 174, 26);


// -------------------------------------
// cadre INFO en bas à gauche de l'afficheur
	cadre_info.init(5,273,225,44);
	cadre_info.couleur_contour = GRIS;
	cadre_info.couleur_texte = BLEU_CLAIR;
	cadre_info.traceContour();
}



void efface_carte(uint16_t x0, uint16_t y0)
{

	TFT480.fillRoundRect(x0, y0, 245, 170, 5, GRIS_TRES_FONCE);
	affi_X(x0+120, y0+80);
}


/**
 ECHELLES des cartes OpenStreetMap

pour les petites cartes 240x160 px:
N°
0.bmp	500m->70px	= 70/0.5 soit 140 px/km
1.bmp	1km->70px	= 70/1	soit  70 px/km 
2.bmp	2km->70px	= 70/2	soit  35 px/km
3.bmp	5km->87px	= 87/5	soit  17.5 px/km 
4.bmp	10km->87px	= 87/10	soit  8.75 px/km 
5.bmp	20km->87px	= 87/20 soit  4.37 px/km 
6.bmp	30km->66px	= 65/30 soit  2.19 px/km 

On voit que la progression est /2 depuis 140

**/


void affi_X(uint16_t x, uint16_t y)
{
	TFT480.drawLine(x-20, y-20, x+20, y+20, JAUNE);
	TFT480.drawLine(x-20, y+20, x+20, y-20, JAUNE);
	
}


void affi_photo(uint16_t x0, uint16_t y0)
{
	String s1;
	char var_array[30];
	uint8_t numero=1;

	s1="/bmp/";
	s1 +=  liste_airports[num_airport].ID_OACI;
	s1 +=  "/240x160/img";
	s1 += String(numero);
	s1+= ".bmp";
	s1+='\0';
	//Serial.println(s1);
	
	for(int i=0; i<30; i++)	{var_array[i] = s1[i];}
	var_array[29]=0;  // zéro terminal  -> string

	float contraste=1.4; // 1.4 à ajuster en fonction de l'afficheur et de la qualité de son rétroéclairage
	float lum = 100; // 100 ; 180 pour photos - une augmentation de cette valeur assombrit l'affichage

	TFT480.fillRect(x0, y0, 240, 160, GRIS_TRES_FONCE);
	affi_X(x0+120, y0+80);

	affi_img(x0,y0, 0, 1.0, var_array); // affichage sans correction de lum ni contraste ; pour test différence...

	TFT480.fillRect(x0, y0-2, 240, 3, NOIR); // trait horizontal
}


void read_data_plan_sur_SDcard()
{
// récupération des coordonnées du centre de la piste et de l'échelle du plan des installations
// ce qui servira à afficher l'avion correctement sur le plan	
	File file1;
	String filename1;
	String s1, s2;
	uint8_t c1;
	uint16_t p1;

	float x0, y0, ech;

	filename1="/bmp/";
	filename1 +=  liste_airports[num_airport].ID_OACI;
	filename1 +=  "/480x320/plan1.dat";
	filename1+='\0';

	s1="";
	file1 = SD.open(filename1, FILE_READ); //ouverture du fichier en lecture
	if (file1)
	{
		while (file1.available() > 0)
		{
			c1 = file1.read();
			s1 += char (c1);
		}

 // centre de la piste	
		p1 = s1.indexOf("x0=");
		s2=s1.substring(p1+3, p1+8);
		plan_ref_x=s2.toFloat(); // variable globale

		p1 = s1.indexOf("y0=");
		s2=s1.substring(p1+3, p1+8);
		plan_ref_y=s2.toFloat(); // variable globale

		p1 = s1.indexOf("ech=");
		s2=s1.substring(p1+4, p1+9);
		plan_echelle=s2.toFloat(); // variable globale

	}

	file1.close(); 

}




void affi_plan_aerodrome()
{
	String s1,s2;
	char var_array[30];
	uint8_t numero=1;

	s1="/bmp/";
	s1 +=  liste_airports[num_airport].ID_OACI;
	s1 +=  "/480x320/plan1.bmp";
	s1+='\0';
	//Serial.println(s1);
	
	for(int i=0; i<30; i++)	{var_array[i] = s1[i];}
	var_array[29]=0;  // zéro terminal  -> string

	float contraste=1.4; // 1.4 à ajuster en fonction de l'afficheur et de la qualité de son rétroéclairage
	float lum = 100; // 100 ; 180 pour photos - une augmentation de cette valeur assombrit l'affichage

	TFT480.fillRect(0, 0, 479, 319, GRIS_TRES_FONCE);
	TFT480.setFreeFont(FF1);
	TFT480.setTextColor(VERT, GRIS_TRES_FONCE);
	TFT480.drawString("Plan", 200, 150);
	
	s2 =  liste_airports[num_airport].ID_OACI;
	TFT480.drawString(s2, 200, 170);

	//efface_carte(x0,y0);
	affi_img(0,160, lum, contraste, var_array);
}



void affi_flags() 
{
// la variable 'flags' est reçue du module PFD (où elle est configurée)
	TFT480.setFreeFont(FF1);
	
	cadre_info.efface();
	
	if (read_bit(flags, bit_autoland) == 1) { cadre_info.affiche_string("Approche ILS","Auto-land"); }
	if (read_bit(flags, bit_rudder_decol) == 1) { cadre_info.affiche_string("Rudder auto","(for takeoff)"); }
	if (read_bit(flags, bit_rudder_attero) == 1){ cadre_info.affiche_string("Rudder auto","(for Landing)"); }
	if (read_bit(flags, bit_nav_to_piste) == 1){ cadre_info.affiche_string("Nav to RWY","Rappelez verticale"); }
	if (read_bit(flags, bit_nav_to_pti) == 1){ cadre_info.affiche_string("Nav to pti",""); }
	if (read_bit(flags, bit_nav_to_ptAA) == 1){ cadre_info.affiche_string("Nav to A","Approche directe"); }
	if (read_bit(flags, bit_nav_to_ptBB) == 1){ cadre_info.affiche_string("Nav to B","Approche directe"); }
	
}





void affi_carte_France()
{

	//uint16_t y0 = 0;
	String s1;
	char var_array[25];

	s1="/bmp/FRANCE/img1.bmp";

	for(int i=0; i<24; i++)	{var_array[i] = s1[i];}
	var_array[24]=0;  // zéro terminal  -> string
	float contraste=0.7; // 1.4 à ajuster en fonction de l'afficheur et de la qualité de son rétroéclairage
	float lum = 50; // 100 ; 180 pour photos - une augmentation de cette valeur assombrit l'affichage

	affi_img(0,160, lum, contraste, var_array);
	
}



void affi_carte_locale(uint16_t x0, uint16_t y0, uint8_t ajouts) // ajouts = cadre, avion, ILS, cercles...
{
	uint16_t x_centre = 357;
	uint16_t y_centre = 80;
	//uint16_t y0 = 0;
	String s1;
	char var_array[25];

	s1="/bmp/";
	s1 +=  liste_airports[num_airport].ID_OACI;
	s1 +=  "/240x160/";
	s1 += String(zoom);
	s1+= ".bmp";
	s1+='\0';
	//Serial.println(s1);

	for(int i=0; i<24; i++)	{var_array[i] = s1[i];}
	var_array[24]=0;  // zéro terminal  -> string

	float contraste=1.4; // 1.4 à ajuster en fonction de l'afficheur et de la qualité de son rétroéclairage
	float lum = 100; // 100 ; 180 pour photos - une augmentation de cette valeur assombrit l'affichage
	
	//affi_img(237,156, 0, 1.0, var_array); // affichage sans correction de lum ni contraste ; pour test différence...
	efface_carte(x0,y0);
	affi_img(x0,y0, lum, contraste, var_array); // affichage corrigé

	//calcul de l'échelle compte tenu des valeurs mesurées, voir commentaire au-dessus de cette fonction

	px_par_km = 140.0 / (1 << zoom); // -> variable globale
	px_par_NM = px_par_km * 1.852;    // -> variable globale


	if(ajouts==1)
	{
		//TFT480.drawRoundRect(x0, y0, 245, 170, 5, GRIS);
	
		TFT480.setFreeFont(FM9);
		TFT480.setTextColor(BLANC);
		s1=(String) (zoom);
		//s1+= ".bmp";
		TFT480.fillRect(x0+220, y0, 20, 14, GRIS_AF); //efface
		TFT480.drawString(s1, x0+220, y0); // affiche le numéro de la carte
		
	
	// trace 'faisceau localizer' (même si pas d'ILS, quand même utile sur la carte pour une approche VFR)
		float alpha1 = 90.0-orient_piste; // angle direct
		if (alpha1<0)  {alpha1 += 360.0;}
		
		float alpha2 = alpha1 + 180.0;
		if (alpha2>360)  {alpha2 -= 360.0;} // angle opposé
		
		affi_rayon2(x_centre, y_centre, 2, 150, alpha1-3.0, NOIR, 0);
		affi_rayon2(x_centre, y_centre, 2, 150, alpha1+3.0, NOIR, 0);
	
		affi_rayon2(x_centre, y_centre, 2, 150, alpha2-3.0, NOIR, 0);
		affi_rayon2(x_centre, y_centre, 2, 150, alpha2+3.0, NOIR, 0);
	
		refresh_carte=0;

		affi_char_r_alpha(x_centre, y_centre, 60, alpha1, 'B', NOIR);
		affi_char_r_alpha(x_centre, y_centre, 60, alpha2, 'A', NOIR);

		
		
	
	// cercle 10NM
		TFT480.setTextFont(1);
		TFT480.setTextColor(NOIR);
		if (zoom>4)
		{
			uint16_t r = 10*(uint16_t)px_par_NM;
			TFT480.drawCircle(x_centre, y_centre, r, NOIR);
			TFT480.drawString("10",x_centre+3+0.7*r,75+0.7*r);
		}
	
	// cercle 20NM	
		if (zoom>5)
		{
			uint16_t r = 20.0*px_par_NM;
			TFT480.drawCircle(x_centre, y_centre, r, NOIR);
			TFT480.drawString("20",x_centre+3+0.7*r,75+0.7*r);
		}
		
	// cercle 30NM	
		if (zoom>6)
		{
			uint16_t r = 40.0*px_par_NM;
			TFT480.drawCircle(x_centre, y_centre, r, NOIR);
			TFT480.drawString("40",x_centre+3+0.7*r,75+0.7*r);
		}
	
	// cercle 80NM	
		if (zoom>7)
		{
			uint16_t r = 80.0*px_par_NM;
			TFT480.drawCircle(x_centre, y_centre, r, NOIR);
			TFT480.drawString("80",x_centre+3+0.7*r,75+0.7*r);
		}
	
	// cercle 160NM	
		if (zoom>8)
		{
			uint16_t r = 160.0*px_par_NM;
			TFT480.drawCircle(x_centre, y_centre, r, NOIR);
			TFT480.drawString("160",x_centre+3+0.7*r,75+0.7*r);
		}
	
	// cercle 320NM	
		if (zoom>9)
		{
			uint16_t r = 320.0*px_par_NM;
			TFT480.drawCircle(x_centre, y_centre, r, NOIR);
			TFT480.drawString("320",x_centre+3+0.7*r,75+0.7*r);
		}
		
	// cercle 640NM	
		if (zoom>10)
		{
			uint16_t r = 640.0*px_par_NM;
			TFT480.drawCircle(x_centre, y_centre, r, NOIR);
			TFT480.drawString("640",x_centre+3+0.7*r,75+0.7*r);
		}

			
		affi_avion_sur_carte(cap);


 // POUR TEST
		dessine_point_sur_carte(lat_centre_pst, lon_centre_pst, BLEU); // ok


		float lat_A=liste_airports[num_airport].lat_A;
		float lon_A=liste_airports[num_airport].lon_A;
		dessine_point_sur_carte(lat_A, lon_A, BLEU); // ok

		float lat_B=liste_airports[num_airport].lat_B;
		float lon_B=liste_airports[num_airport].lon_B;
		dessine_point_sur_carte(lat_B, lon_B, BLEU); // ok


		float lat_1, lon_1;

/*
		for (int i=0; i<=6; i++)
		{
			calcul_point_i(30.0*i, 10.0, &lat_1, &lon_1); // en 1/2 cercle, à 10NM du centre de la piste
			dessine_point_sur_carte(lat_1, lon_1, ROUGE);
		}
*/

		float e = 1.5;
		calcul_point_i(0, 1.0*e, &lat_1, &lon_1);	dessine_point_sur_carte(lat_1, lon_1, ROUGE);
		calcul_point_i(0, 4.0*e, &lat_1, &lon_1);	dessine_point_sur_carte(lat_1, lon_1, ROUGE);
		calcul_point_i(16, 5.1*e, &lat_1, &lon_1);	dessine_point_sur_carte(lat_1, lon_1, ROUGE);
		calcul_point_i(46, 4.0*e, &lat_1, &lon_1);	dessine_point_sur_carte(lat_1, lon_1, ROUGE);
		calcul_point_i(72.3, 3.1*e, &lat_1, &lon_1);dessine_point_sur_carte(lat_1, lon_1, ROUGE);
		calcul_point_i(109, 3.1*e, &lat_1, &lon_1);	dessine_point_sur_carte(lat_1, lon_1, ROUGE);
		calcul_point_i(135, 4.0*e, &lat_1, &lon_1);	dessine_point_sur_carte(lat_1, lon_1, ROUGE);
		calcul_point_i(163, 5.1*e, &lat_1, &lon_1);	dessine_point_sur_carte(lat_1, lon_1, ROUGE);
		calcul_point_i(180, 4.0*e, &lat_1, &lon_1);	dessine_point_sur_carte(lat_1, lon_1, ROUGE);
		calcul_point_i(180, 1.0*e, &lat_1, &lon_1);	dessine_point_sur_carte(lat_1, lon_1, ROUGE);
								
		
		if (read_bit(flags, bit_autoland) == 1)
		{
			TFT480.setFreeFont(FF1);
			TFT480.setTextColor(ORANGE, NOIR);
			cadre_bas1.affiche_string("zoom auto","");
		}
	}
}



void affi_position(float lat_i, float long_i, uint16_t x_i, uint16_t y_i)
{
	String s1= String(abs(lat_i),4);
	if(lat_i>0){s1+=" N";} else {s1+=" S";}
	s1+=" / ";
	s1+= String(abs(long_i),4);
	if(long_i>0){s1+=" E";} else {s1+=" O";}
	TFT480.setTextFont(1);
	TFT480.drawString(s1, x_i, y_i);
}



void affi_data_piste() // dans la partie droite ; pour l'init des cadres, voir la fonction "dessine_pannel_frq()"
{

	if (mode_affi == MODE_PLAN) {return;}
	String s1,s2;
	float v1;
// -------------------------------------
// entête
	
	s1= liste_airports[num_airport].ID_OACI;
	TFT480.setFreeFont(FM9);
	cadre_top1.affiche_string(s1,"");

	s1= (String)liste_airports[num_airport].altitude;
	s1 +=" ft";
	TFT480.setFreeFont(FM9);
	cadre_top1b.affiche_string(s1,"");

	s1=(String)(num_airport +1);
	s1+=" ";
	s1+= liste_airports[num_airport].nom;
	if ((s1.length())>19) {TFT480.setTextFont(2);} else {TFT480.setFreeFont(FM9);}
	cadre_top2.affiche_string(s1,"");


	float alpha1 = orient_piste; // angle direct
	if (alpha1<0)  {alpha1 += 360.0;}
	
	float alpha2 = alpha1 + 180.0;
	if (alpha2>360)  {alpha2 -= 360.0;} // angle opposé

	cadre_00.affiche_float(alpha1, 1);
	cadre_10.affiche_float(alpha2, 1);

	
	uint16_t longueur1= (uint16_t)longueur_piste; // en m
	cadre_01.affiche_int(longueur1, 'm');

	//cadre_info.efface();
	//cadre_info.couleur_texte = GRIS_CLAIR;

// affichage de la position géographique de l'aérodrome
	TFT480.setTextColor(VIOLET2, NOIR);
	affi_position(lat_centre_pst , lon_centre_pst, cadre_position.x0 +5, cadre_position.y0 +5);

	refresh_ND=0;
}



/*
void affi_switches()
{
	TFT480.setTextFont(1);
	TFT480.fillRect(420, 302, 30, 10, NOIR); // efface le nombre précédemment affiché
	TFT480.setTextColor(GRIS);
	TFT480.drawString(switches, 420, 302);
}
*/


void init_WiFi()
{
	WiFi.persistent(false);
	WiFi.begin(ssid, password);
	delay(2000);
}


void init_boutons()
{
	pinMode(GPIO_SW0, INPUT);

	//pinMode(led1, OUTPUT);

	//pinMode(rot1a, INPUT_PULLUP);
	//pinMode(rot1b, INPUT_PULLUP);

	pinMode(GPIO_bouton0, INPUT_PULLUP);
	pinMode(GPIO_bouton1, INPUT_PULLUP);
	pinMode(GPIO_bouton2, INPUT_PULLUP);
	pinMode(GPIO_bouton3, INPUT_PULLUP);
	
}


void init_afficheur()
{
	TFT480.init();
	TFT480.setRotation(3); // 0..3 à voir, suivant disposition de l'afficheur

	TFT480.fillScreen(NOIR);
	TFT480.setTextColor(NOIR, BLANC);
}


void init_variables()
{
	cap=0;

	zoom=1;
	SW0_etat = digitalRead(SW0);
	memo_SW0_etat = SW0_etat;
	x_avion=0;
	y_avion=0;
	memo_x_avion=0;
	memo_y_avion=0;
}


void init_Leds() // pour l'affichage des données, voir la fonction "affi_data_piste()"
{
	uint16_t x0 = 470;
	uint16_t y0 = 116;
	
	uint16_t yi = y0;

	Led1.init(x0,yi, 20, 20);
	Led1.set_couleur(BLEU);
	Led1.eteint();

	yi+=80;

	Led2.init(x0,yi, 20, 20);
	Led2.set_couleur(ROUGE);
	Led2.eteint();
}


void trace_point_FR(float lat_i, float lon_i, uint8_t diam, uint16_t couleur) 
{
	float x;
	float y;
// sur l'image .bmp 360x480px de la France entière

// calibration :
	TFT480.fillCircle(73, 44, 2, BLANC);// 50°N -4°  ->REF
	TFT480.fillCircle(73, 258, 2, BLANC);// 44°N -4°
	TFT480.fillCircle(369, 44, 2, BLANC);// 50°N +8°
	TFT480.fillCircle(369, 258, 2, BLANC);// 44°N +8°


/**
lat=4° -> x=73px
lat=8° -> x=369px
ech_x = dx/dlon = (369-73)/(8+4)= 296/12 = 24.66

lon=44 -> y=258px
lon=50 -> y=44px
ech_y= dy/dlat = (258-44)/(50-44) = 214/6 = 35.66

il vient:
x=73+(lon-4)*74
y=44+(lat-50)*35.66

**/

	x=73.0+(lon_i+4)*24.66; // ok
	y=44.0+(50.0-lat_i)*35.66; //ok

	if (diam>1) {TFT480.fillCircle((uint16_t)x, (uint16_t)y, diam, couleur);}
	else {TFT480.drawPixel((uint16_t)x, (uint16_t)y, couleur);}
}



void affi_1_airport(float lat_i, float lon_i) // sur carte France
{
	trace_point_FR(lat_i, lon_i, 4, JAUNE);
}



void affi_avion_sur_carte_FR() // sur carte France
{
	trace_point_FR(lat_avion, lon_avion, 1, BLANC);
}




void affi_all_airports() // sur carte France
{
	float lat_i;
	float lon_i;
	
	for(uint16_t i=0 ; i<nb_elements ; i++)
	{
		lat_i = liste_airports[i].lat_A;
		lon_i = liste_airports[i].lon_A;
		trace_point_FR(lat_i, lon_i, 2, ROUGE);
	}
	
	affi_1_airport(liste_airports[num_airport].lat_A, liste_airports[num_airport].lon_A); // celui sélectionné
	affi_avion_sur_carte_FR(); // position actuelle de l'avion
}




void affi_navi()
{
	zoom=1;
	affi_ND(cap);
	memo_cap=cap;

	affi_carte_locale(235,0,1);
	dessine_pannel_frq();
	refresh_carte=0;
}



void setup()
{
    Serial.begin(19200);

    init_variables();

	init_WiFi();
	init_boutons();
	init_afficheur();
	init_SDcard();

	init_FG_AIRPORTS();
	init_sprites();
	init_sprite_avion(BLEU);
	//init_affi_vor();
/*
	affi_carte_France();
	//trace_point_FR(46.0, 2.0); // sur carte France
	affi_all_airports();
	delay(2000);
	//while(1);
*/	

	TFT480.setTextColor(BLANC, NOIR);
	TFT480.setFreeFont(FF19);
	String s1 = "ND v";
	s1+= version;

	init_Leds();
	
	affi_navi();
}




void affichages()
{
	affi_dst_GPS();
	affi_direction_piste();
	affi_dst_piste();

	if(cap != memo_cap)
	{
		affi_ND(cap);
		
		refresh_ND=1; // pour raffraichir l'affichage du panneau de fréquences à droite
		memo_cap = cap;
	}
	//affi_float_test(145.62 , 0, BLEU);
	//affi_float_test(25.89 , 1 , GRIS_FONCE);
}



void teste_boutons() // boutons reliés à l'ESP n°2 (ND), pas ceux de l'ESP du SW
{
// REMARQUE : les boutons sont aussi testés localement dans la fonction "choix_aerodrome()"


	bouton2_etat = digitalRead(GPIO_bouton2); // ZOOM -
	//Serial.print(bouton0_etat);
	
	if (bouton2_etat != memo_bouton2_etat)
	{
		memo_bouton2_etat = bouton2_etat;
		if (bouton2_etat==0)
		{
			if (zoom == 0)
			{
				mode_affi=MODE_ND;
				init_mode_ND();
			}
			zoom++;
			if (zoom<0){zoom=0;}
			if (zoom > 11){zoom=11;}
			//refresh_carte=1;
			affi_carte_locale(235,0,1);
			
		}
		affi_ND(cap);
	}

	bouton3_etat = digitalRead(GPIO_bouton3); // ZOOM +
	//Serial.println(bouton1_etat);
	
	if (bouton3_etat != memo_bouton3_etat)
	{
		memo_bouton3_etat = bouton3_etat;
		if (bouton3_etat==0)
		{
			zoom--;
			if (zoom< 0){zoom = 0;}
			if (zoom > 11){zoom=11;}
			
			//Serial.print("zoom=");	Serial.println(zoom);
			
			if (zoom == 0) // rappel: il n'y a pas de carte 0; le 1er numéro est 1.bmp, voir dans le dossier bmp...
			{
				mode_affi=MODE_PLAN;
				init_sprite_avion(ROUGE);
				affi_plan_aerodrome();
			}
			else
			{
				mode_affi=MODE_ND;
				affi_carte_locale(235,0,1);
			}
			affi_ND(cap);
		}
	}


	bouton0_etat = digitalRead(GPIO_bouton0);
	//Serial.print(bouton0_etat);
	
	if (bouton0_etat != memo_bouton0_etat) // le bouton qui sert aussi à descendre dans la liste, en mode liste
	{
		memo_bouton0_etat = bouton0_etat;
		if (bouton0_etat==0)
		{
			mode_affi=MODE_LISTE;
			choix_aerodrome();
		}
	}


	bouton1_etat = digitalRead(GPIO_bouton1); 
	
	if (bouton1_etat != memo_bouton1_etat) // le bouton qui sert aussi à monter dans la liste, en mode liste
	{
		memo_bouton1_etat = bouton1_etat;
		if (bouton1_etat==0)
		{
			mode_affi=MODE_LISTE;
			choix_aerodrome();
		}
	}
}




void toutes_les_10s()
{
	dessine_pannel_frq(); // donc le cadre s'abime lors de la rotation du sprite à gauche
}





void toutes_les_1s()
{

	nb_secondes++;

	if (refresh_carte==1)
	{
		if(mode_affi != MODE_PLAN)
		{
			affi_ND(cap);
			affi_carte_locale(235,0,1);			
		}
	}

	if((nb_secondes>9) == 0)
	{
		nb_secondes=0;
		toutes_les_10s();
	}

	
}



void interroge_WiFi()
{
// voir les "server.on()" dans la fonction "setup()" du code du PFD pour la source des données
	recp_hdg = "";
	if(WiFi.status()== WL_CONNECTED )
	{
		httpGetHDG(); // envoie requete au serveur
		
		TFT480.setTextColor(VERT, NOIR);
		hdg1=recp_hdg.toInt();


		if(hdg1 != memo1_hdg1)
		{
			if (mode_affi == MODE_ND)
			{				
				refresh_Encoche=1;
				cadre_hdg1.efface();
				cadre_hdg1.affiche_int(hdg1,' ');
				TFT480.drawCircle(52, 5, 2, BLANC); // caractère 'degré'
			}
		}
		
		httpGetCap();
		cap=recp_cap.toInt();
		
		if(cap != memo_cap)
		{
			if (mode_affi == MODE_ND)
			{					
				affi_ND(cap);
				refresh_Encoche=1;
			}
		}

		httpGetLatitude();
		lat_avion=recp_latitude.toFloat() / 10E3;

		httpGetLongitude();
		lon_avion=recp_longitude.toFloat() / 10E3;

		httpGetHauteur();
		hauteur_AAL = recp_hauteur.toFloat()/10;

		httpGetParams();
		flags = recp_flags.toInt(); // les significations des bits sont définies dans le fichier "Fonctions1.h"

//affi_int_test(flags, 300, 1, VERT, NOIR);
		
	}
	else if (premier_passage == 1)
	{
		RAZ_variables();
		affi_ND(cap);
		premier_passage=0;
	}

}




void toutes_les_100ms()
{
	calculs_GPS();
	
	nb_100ms++;
	if(nb_100ms>10)
	{
		nb_100ms=0;
		toutes_les_1s();
	}

	if (mode_affi != MODE_PLAN)
	{

		if (refresh_ND==1)
		{
			affi_data_piste();
			affi_ND(cap);

			refresh_ND=0;
			//refresh_Encoche=1;
		}


		if (refresh_Encoche==1)
		{
			affi_encoche_hdg(memo1_hdg1, NOIR); // efface
			affi_encoche_hdg(hdg1, OLIVE);
			affi_ND(cap);
			
			memo1_hdg1=hdg1;
			refresh_Encoche=0;
		}

		calcul_pos_avion_sur_carte();
		affi_avion_sur_carte(cap);
		
		TFT480.setTextColor(VIOLET2, NOIR);
		affi_position(lat_avion, lon_avion, 55, 230);
	}

	if (mode_affi == MODE_PLAN)
	{
		calcul_pos_avion_sur_plan();
		affi_avion_sur_plan(cap);
	}
 }




/** ================================================================== 
 variables à gérer obligatoirement */
//le nombre de ports GPIO libres étant atteint, on va utiiser un switch unique pour deux fonctions :
uint8_t fonction_bt0 = 1; // 0=saisie écran ; 1=feux d'atterrissage

/** ==================================================================  */

uint16_t t=0; // temps -> rebouclera si dépassement    
void loop()
{

	SW0_etat = digitalRead(GPIO_SW0);
	if (SW0_etat==0)
	{
		mode_affi= MODE_CARTE_FR;
		affi_carte_France();
		affi_all_airports();
		while(digitalRead(GPIO_SW0)==0)
		{
			interroge_WiFi();
			affi_avion_sur_carte_FR(); // et boucle
		}
		mode_affi = MODE_ND;
		TFT480.fillScreen(NOIR);
		TFT480.setTextColor(NOIR, BLANC);
		affi_navi();
		init_mode_ND();
	}

	teste_boutons();
	
	temps_ecoule = micros() - memo_micros;

	if (temps_ecoule  >= 1E5)  // (>=) et pas strictement égal (==) parce qu'alors on rate l'instant en question
	{
		memo_micros = micros();
		toutes_les_100ms();
	}


	//Serial.print("mode_affi="); Serial.println(mode_affi);
	
	if (mode_affi == MODE_ND)
	{
		compteur1+=1;
		if (compteur1 >= 100)  // tous les 1000 passages dans la boucle 
		{
			compteur1=0;
			
		}		

		interroge_WiFi(); // et envoie les arguments

		affichages();
		
		delay(30);
	}

	if (mode_affi == MODE_LISTE)
	{
		choix_aerodrome();
	}

	if (mode_affi == MODE_PLAN)
	{
		interroge_WiFi();
	}

	if (flags != memo_flags)
	{
		memo_flags = flags;
		affi_flags();
	}

		
/** ----------------------------------------------
 pour tester si data_ok à bien la valeur attendue, c.a.d si toutes les acquisitions sont effectuées
 ce qui est le cas lorsque le dialogue avec le serveur de protocole FG est correctement programmé
 sachant que ce ne sont pas les pièges qui manquent !
*/ 
////TFT480.setFreeFont(FM9);
////TFT480.setTextColor(VERT, NOIR);
////TFT480.drawString("d_sum:", 0, 0);
////s2= (String) data_ok;
////TFT480.fillRect(70, 0, 50, 30, NOIR);
////TFT480.drawString(s2, 70, 0);

/** ---------------------------------------------- **/
	
//---------------------------------------------------------------------------------------

}


/** ***************************************************************************************
	CLASS Cadre  // affiche un nombre ou un petit texte dans un rectangle
********************************************************************************************/

// Constructeur
Cadre::Cadre()
{

}


void Cadre::init(uint16_t xi, uint16_t yi, uint16_t dxi, uint16_t dyi)
{
	x0 = xi;
	y0 = yi;
	dx = dxi;
	dy = dyi;

	TFT480.setTextColor(BLANC, NOIR);
}



void Cadre::traceContour()
{
	TFT480.drawRoundRect(x0, y0, dx, dy, 3, couleur_contour);
}



void Cadre::efface()
{
	TFT480.fillRect(x0, y0, dx, dy, NOIR);
	traceContour();
}




void Cadre::affiche_int(uint32_t valeur, char lettre) 
{
	String s1;
	efface();
	TFT480.setFreeFont(FM9);
	TFT480.setTextColor(couleur_texte, NOIR);
	s1 = String(valeur); 
	//TFT480.fillRect(x0, y0, 52, 18, NOIR); // efface
	TFT480.drawString(s1, x0+5, y0+5);
	s1 =String(lettre);
	TFT480.drawString(s1, x0+50, y0+5);
}



void Cadre::affiche_float(float valeur, int nb_dec) 
{
	efface();
	TFT480.setFreeFont(FM9);
	TFT480.setTextColor(couleur_texte, NOIR);
	String s1 = String( valeur, nb_dec); 
	//TFT480.fillRect(x0, y0, 52, 18, NOIR); // efface
	TFT480.drawString(s1, x0+5, y0+5);
}



void Cadre::affiche_string(String s1, String s2) 
{
// Pour cette méthode, la police de caract doit être choisie au préalable, extérieurement
	efface();
	TFT480.setTextColor(couleur_texte, NOIR);
	TFT480.drawString(s1, x0+5, y0+5);
	if(s2 !="") {TFT480.drawString(s2, x0+5, y0+20);}
}



/** ***************************************************************************************
	CLASS Led  // affiche un petit rectangle coloré
********************************************************************************************/

// Constructeur
Led::Led()
{

}


void Led::init(uint16_t xi, uint16_t yi, uint16_t dxi, uint16_t dyi)
{
	x0 = xi;
	y0 = yi;
	dx = dxi;
	dy = dyi;
}



void Led::allume()
{
	TFT480.fillRect(x0, y0, dx, dy, couleur);
}


void Led::eteint()
{
	TFT480.fillRect(x0, y0, dx, dy, GRIS_TRES_FONCE);
	TFT480.drawRect(x0, y0, dx, dy, couleur); // bordure
}


void Led::set_couleur(uint16_t coul_i)
{
	couleur = coul_i;
}
