home castoo
chapitre electronique
Electronique testeur nmea

Testeur NMEA WIFI et RS422
Emission / Réception de trame

janvier 2020

Fabrication d'un outil de test NMEA autonome
pour liaison wifi ou RS422

Depuis quelques temps j'avais envie de réaliser un petit outil qui m'aide à identifier les éventuelles problèmes sur la config nmea du voilier, vérifier la portée du wifi... Je me suis donc fixé un petit cahier des charges :


Ce type de projet est inenvisageable si l'on ne dispose pas d'un moyen de saisie pour paramétrer l'outil, j'ai cherché sur internet et je n'ai pas trouvé de librairie qui permette ceci avec un écran affichant suffisamment de ligne et qui consomme très peu. J'ai donc été obligé de concevoir deux objets logiciel pour créer une interface utilisateur simple sur écran Oled à l'aide d'un encodeur rotatif. Le code développé est donc disponible sur ce site. Je le mets à dispo dans le domaine public et j'espère que cela permettra à des amateurs comme moi la réalisation de nouveaux projets.

Schéma testeur nmea

Le schéma est simple, l'utilisation de petits blocs "LEGO" facilite grandement le montage. L'interface utilisateur est composée du bouton marche/arret (pas de bouton reset, il faut jouer avec ce bouton M/A), d'une LED qui indique la mise sous tension (En cas d'utilisation sur batterie, il vaut mieux couper l'alim dès que l'on utilise plus le testeur).
Lorsque que l'on n'utilise pas le testeur il faut prendre l'habitude de placer les cellules solaire vers l'éclairage maxi. afin de recharger la batterie. Les cellules solaires sont câblées en parallèle et fournisse du 5v. L'écran OLED est directement lié au déplacement du bouton rotatif, la saisie de texte (mot de passe wifi, adresse IP, port, vitesse RS422 est possible grace à l'affichage de trois types de caractères (Majuscules, minuscules et car. spéciaux).
L'interface vers le NMEA0183 du bateau est réalisée par un circuit bien pratique, celui-ci n'est pas relié à l'interface série classique du microprocesseur mais vers une interface créée par logiciel grace à la librairie SoftwareSerial (grand merci à l'auteur), ceci libère l'interface usb de l'esp (Wemos D1 mini pro) pour la programmation de nouvelles versions du testeur. J'ai rencontré des problèmes lorsque j'utilisais l'usb pour modifier le programme alors que je recevais des trames nmea (RS422) sur cette même liaison, avec cette interface logiciele ce problème est réglé !

Détail Schéma testeur nmea wemos D1 mini Pro

Le Wemos D1 mini pro qui est le coeur du système regroupe pas mal de connexion et il faut jouer des coudes pour tout loger dans le petit boitier. J'ai précisé sur le schéma les deux notations les "Dx" gravés sur le boitier et les "Gpioxx" correspondant que l'on retrouve dans le code.

Cette petite vidéo montre l'avancée du projet.
On peut voir :
L'intégration des trois cellules solaires.
L'appareil se commande à l'aide d'un bouton (commutateur rotatif sans fin) on le tourne pour faire défiler les choix et on le presse pour valider un choix.
Un petit écran permet de visualiser les menus et les résultats des commandes. Il aurait été facile d'utiliser un écran plus grand mais la consommation aurait explosée et l'autonomie du testeur aurait été compromise !
On peut voir l'interface RS422 avec son connecteur à vis (4 fils qui correspondent Out + et -, In + et -) nmea0183.
L'antenne wifi externe (optionnelle mais elle permet un signal plus puissant.) se replie pour diminuer l'encombrement.
Les menus permettent le paramétrage du wifi :
Détection des réseaux wifi alentours, sélection de votre choix et saisie du mot de passe.
Saisie de l'adresse IP et du numéro de port.
Connexion au réseau wifi sélectionné.
Tous les paramètres sont sauvegardés dans une mémoire eeprom qui conserve les données quand l'alimentation est coupée ce qui permet une connexion directe au réseau utilisé à la dernière connexion au démarrage suivant.
Choix de la vitesse de l'interface RS422 nmea0183.
Choix de la liaison utilisée (wifi ou RS422) pour les tâches suivantes (c'est évolutif...) :
- Affichage des trames NMEA ou AIS reçues.
- Comptage des 12 types de trames les plus couramment reçues.
- Affichage des principales données des trames $GPRMC reçues (heure, latitude et longitude).
Emission de trames sur la liaison RS422
A suivre...

Oled sd1306 pour le testeur NMEA

Nous trouvons donc dans les entrailles du testeur :
La batterie, qui est rechargée soit par un câble USB soit par les cellules solaires qui sont sur le dessous du testeur.
L'Esp-8266 qui est le cœur du montage, j'ai utilisé un Wemos D1 mini Pro qui possède entre autre une prise permettant de raccorder une antenne wifi externe même si une antenne céramique est installée sur le CI du composant. Ce circuit se programme directement depuis l'IDE ARDUINO qui est disponible gratuitement et sur tous les systèmes.
Je n'ai utilisé que des circuits "LEGO", inutile d'être informaticien ou électronicien, il suffit de savoir tenir un fer à souder pour raccorder des fils entres eux ! Ce montage est super simple et facile à réaliser.
On retrouve dans les circuits "LEGO" : Un circuit pour gerer l'alim TP4056 (chargeur solaire, pile, usb et alim.).
Un circuit OLED qui est un petit écran LCD, celui que j'utilise est le 1306 (attention il y en a des bicolore (1ere ligne jaune et le reste bleu) ils ne correspondent pas vraiment au besoin, perso j'ai choisi un blanc (j'ai désoudé celui de la photo pour le remplacer par un blanc comme sur la vidéo ci-dessus.).
Un bouton rotatif sans fin ( le mien doit être un 30 pas mais cela n'a pas d'importance ), dans le programme je ne compte pas les tours mais seulement les pas en avant ou en arrière).
Un circuit qui gère l'interface nmea RS422 qui est protégé contre toutes les erreurs de manipulation par photo coupleurs. Un bouton, une led et le boitier que j'ai conçu sur Freecad et qui est imprimable sur une imprimante 3D. Le cout total du montage ne dépasse pas les 25 euros.

test electronique du testeur nmea

Tout montage commence par des tests en volant. On voit ici que le câblage est très simple, la difficulté était plutôt côté programmation !


Cablage electronique du testeur nmea
Cablage et tests de l'électronique
nomenclature testeur nmea

Liste des éléments necessaires et les mots clés utilisablent pour trouver son bonheur sur GG :
Le Wemos D1 mini pro : "Wemos D1 mini pro" Prendre l'antenne wifi et son câble avec.
L'interface RS422 : "RS422 module MAX490"
L'écran LCD OLED : "OLED 128X64 I2C SSD1306 blanc" Attention ne pas prendre les modèles bi-color (voir remarques).
Le chargeur gestion alim TP4056 : "tp4056" prendre un modèle installé sur un petit circuit imprimé.
Le bouton rotatif sans fin : "Encodeur rotatif KY-040 arduino"
Les 3 cellules solaires 5v : "cellule solaire 5v 53x30"
La batterie : "batterie 18650 6800ma"
Le support de la batterie : "support batterie 18650 6800ma" Attention en trouver un en plastique rigide (plus chere) ! Le bouton M/A : "Interrupteur à 2 positions SPDT Mini Micro"
La LED : "mini led rouge avec support"

Un total de 25 euros max, mais 15 euros si vous pistez les bonnes affaires... Pour le boitier, il faut l'imprimer et pour le logiciel, je vais le mettre à dispo...

outils testeur nmea

Il faut aussi prévoir quelques bouts de fils, pas trop gros, il n'y a pas beaucoup de place dans le boitier et de la gaine thermo-retractable.


boitier brut testeur nmea

Le boitier brut à la sortie de l'impression, le couvercle viendra appuyer sur les circuits pour les maintenir au fond. Il faut agrandir un peu les deux trous des entrées usb, j'ai prévu un peu petit !


implantation éléments testeur nmea

Chaque élément a sa petite place bien à lui ! Pour le moment c'est cool on a l'impression d'avoir de la place mais ca va se compliquer...


couvercle testeur nmea

Sur le couvercle il n'y a que la led qui a son support. Perso j'ai trouvé une led avec sa petite résistance de limitation (160 ohms) directement intégrée sous une gaine thermo.


solaire testeur nmea

Les cellules solaires sont câblées en parallèle serrées sans excès de fil afin de pouvoir loger au dos du boitier.


cellules solaire testeur nmea

Les deux premières cellules solaires sont insérées sous le boitier dans les glissières.


cellule solaire testeur nmea

Les fils sont passés dans le trou avant l'insertion de la troisième cellule. La troisième cellule solaire est ensuite insérée sous le boitier dans les glissières.


TP4056 testeur nmea

Les fils des cellules sont regroupés à l'intérieur du boitier avec deux fils connectés vers In+ et In- du TP4056. Les deux fils de la batterie sont eux connectés sur B+ et B- du TP4056.


connexions testeur nmea

Perso j'ai préparé les différents éléments en ne laissant que le minimum de longueur de fil et en protégeant les connexions avec de la gaine thermo. Si le circuit est livré avec des broches, je les ai coupées de façon à garder le maximum de place dans le boitier.


modif antenne

Si vous choisissez d'utiliser l'antenne wifi externe vous devez déplacer le petit strap indiqué sur la photo (plus facile à dire qu’à faire !) . Vous trouverez des vidéos sur YouTube qui montrent comment faire cela proprement !


montage testeur nmea

Avec tous mes essais de câblage j'ai fini pas casser les petits ergots de fixation de l'écran, comme on le voit sur l'image j'ai simplement utilisé des petites vis de maintien après avoir fait des avant trous. Si vous réalisez le montage vous ne devriez pas rencontrer ce problème, pour vous en principe, ce sera bon du premier coup !


matériel testeur nmea

Lors de la commande du matériel il faut veiller à prendre des écrans blanc, les modèles bi-colors sont jaune sur le haut, puis il y a un trou dans les lignes de l'écran et ensuite on passe à la deuxième couleur, c'est pas top avec ce montage je me suis fait avoir avec une commande et on voit des exemples sur certaines photos comme sur celle ci-dessous !


circuit testeur nmea

Le câblage est ici terminé, comme on peut le voir les fils sont finalement nombreux et il faut serrer tout ce petit monde dans la partie réservée sous l'écran. Le couvercle va aussi compliquer les choses puisqu'il a pour mission de bloquer les circuits en fond de boitier, il faut bien regarder le meilleur circuit pour faire passer les fils.
Comme on le voit ici l'écran bi-colors ce n’est pas top, je l'ai changé dès que j'en ai reçu des nouveaux tout blanc !


Le code V2 (la V1 reste en téléchargement) du testeur, j'espère pouvoir encore rapidement ajouter de nouvelles fonctionnalités...
Pour fonctionner ce code doit être accompagné dans le même répertoire des deux objets "sai_oled" et "menu_oled" présentés dans la partie électronique du site.
Vous devez donc avoir dans le même répertoire :
menu_oled.h et menu_oled.cpp (une mise à jour de menu_oled.cpp est disponible depuis le 20/02/2020)
sais_oled.h et sai_oled.cpp
et le fichier ci-dessous.
L'IDE Arduino doit avoir été mis à jour avec les librairies mentionnées dans le programme ci-dessous et la liste "type de carte" doit comporter celles de l'Esp-8266.

recuperer lib ecran oled
Attention pour la librairie de l'écran Oled "SSD1306Wire.h", c'est celle-ci qu'il faut télécharger sur le site "github", elle est différente de celle qui est disponible dans l'environnement "Arduino".
On la reconnait au "w" de Wire qui doit dans notre cas être une majuscule. (Merci à Guy, pour cette précision qui peut éviter pas mal de galère !).
Merci également à tous les contributeurs(46) de ce petit package qui contient plusieurs librairies. Vous devez charger le zip complet comme montré sur l'image ci-contre.


Je ne vais pas plus décrire ici l'utilisation de l'IDE Arduino, de très nombreux sites expliquent cela très bien.
Le code ci-dessous correspond à la V1 du logiciel "Testeur nmea" une version téléchargeable est également disponible plus bas.


// Testeur NMEA V2
// Reception de trames nmea soit par wifi soit par liaison nmea0183 RS422
// Emission trame profondeur et position GPS sur liaison RS422 nmea0183
// V2 Ajout de la lecture d'un fichier kml
// Castoo
// Janvier 2020

#include "Wire.h"
#include "SSD1306Wire.h"
#include "sai_oled.h"
#include "menu_oled.h"
#include "ESP8266WiFi.h"
#include "ESP_EEPROM.h"
#include "SoftwareSerial.h"

SSD1306Wire  display(0x3c, 4, 5); // I2C sur un esp8266 donc gpio 4 > SDA & gpio 5 > SCL
menu_oled mes_menus_oled(16, 13, 0, display); // init saisie (Gpio vclk, Gpio vdt, Gpio vsw et nom de l'objet LCD)
sai_oled mon_sai_oled(16, 13, 0, display); // init saisie (Gpio vclk, Gpio vdt, Gpio vsw et nom de l'objet LCD)
uint8_t PinSW = 0;  // Gpio x sur sortie SW du selecteur rotatif
#define sl_rx 14 // Reception interface serie logiciele
#define sl_tx 12 // Emission interface serie logiciele
SoftwareSerial swSer; // Déclaration du port serie RS422 (port logiciel)

int B_menu; // Choix selectionné dans menu
char ch_result[25]; // Chaine résultat du menu

const int nb_ch_menuA = 5; // choix du menu Principal
const String ch_menuA[] = {"Modif. parametres", "Lecture flux NMEA", "Compteur trames NMEA", "Affiche trames RMC", "Emission trame RMC RS422"};
String _ch_menuA = * ch_menuA;

const int nb_ch_menuB = 5; // choix du menu Parametres
const String ch_menuB[] = {"Type de liaison active", "Parametres Wifi", "Parametres série", "Afficher Parametres", "Retour"};
String _ch_menuB = * ch_menuB;

const int nb_ch_menuC = 4; // choix du menu Parametres type de réseau
const String ch_menuC[] = {"Réseau Wifi", "Réseau Série", "Lecture kml sur usb", "Retour"};
String _ch_menuC = * ch_menuC;

const int nb_ch_menuD = 5; // choix du menu Parametres wifi
const String ch_menuD[] = {"Selection du réseau Wifi", "Adresse IP réseau Wifi", "Port réseau Wifi", "Connexion Wifi", "Retour"};
String _ch_menuD = * ch_menuD;

const int nb_ch_menuE = 5; // choix du menu Parametres Liaison série RS422
const String ch_menuE[] = {"Vitesse 4800", "Vitesse 38400", "Vitesse choisie", "Schéma de connexion", "Retour"};
String _ch_menuE = * ch_menuE;

String trame_nmea = ""; // Trame nmea

// gestion de l'eeprom
struct s_memo_rom {
	char verif_data_w[9];
	char verif_data_s[3];
	char wifi_ssid[50];
	char wifi_passe[25];
	char wifi_ip[15];
	uint16_t wifi_port;
	uint16_t rs422_vit;
	bool v_wifi;
	char position[51][26]; // Max 50 paires latitudes et longitudes
	uint8_t nb_position; // Nb de position mémorisées
} memo_rom, memo_ram;
int adr_deb_memo_rom = 0;

WiFiClient client;

// Initialise des valeurs standard pour le LCD
void prep_aff_LCD(uint8_t typ_pol) {
	display.clear();
	if(typ_pol == 16) display.setFont(ArialMT_Plain_16);
	else if(typ_pol == 24) display.setFont(ArialMT_Plain_16);
	else display.setFont(ArialMT_Plain_10);
	display.setTextAlignment(TEXT_ALIGN_LEFT);
}

// Attente donnees en lecture
void oups() {
	int delai = 1000;
	prep_aff_LCD(10);
	display.drawString(0, 10, ">> Oups Rien en ligne ! <<");
	display.drawString(0, 30, ">> Un peu de patience.. <<");
	display.drawString(0, 50, "> Si vraiment rien >reset<");
	display.display();
	while(delai > 0){delai--; if (!(digitalRead(PinSW))) return;}
}

// Mise à jour des verifications memo eeprom
void maj_verif(char v1 = 'z', char v2 = 'z', char v3 = 'z', char v4 = 'z', char v5 = 'z') {
	if( v1 != 'z' ){ memo_ram.verif_data_w[0]='1'; memo_ram.verif_data_w[1]='x';}
	if( v2 != 'z' ){ memo_ram.verif_data_w[2]='2'; memo_ram.verif_data_w[3]='x';}
	if( v3 != 'z' ){ memo_ram.verif_data_w[4]='3'; memo_ram.verif_data_w[5]='x';}
	if( v4 != 'z' ){ memo_ram.verif_data_w[6]='4'; memo_ram.verif_data_w[7]='x';}
	if( v5 != 'z' ){ memo_ram.verif_data_s[0]='1'; memo_ram.verif_data_s[1]='x';}
	memo_ram.verif_data_w[8]=0;
	memo_ram.verif_data_s[2]=0;
}

// Affichage des parametres
void aff_param(){
	prep_aff_LCD(10);
	display.drawString(0, 0, "Parametres :");
	display.drawString(0, 10, memo_ram.wifi_ssid);
	display.drawString(0, 20, memo_ram.wifi_passe);
	display.drawString(0, 30, memo_ram.wifi_ip);
	display.drawString(0, 40, String(memo_ram.wifi_port));
	display.drawString(0, 50, String(memo_ram.rs422_vit));
	display.display();
	delay(200);
	while(digitalRead(PinSW)){delay(10);}
}

// Emission de trame
void envoi_rmc_rs422(){
	int chksum = 0;
	float profondeur = 1;
	String s_profond;
	String s_chksum;
	String trame_SDDBT = "$SDDBT,7.8,f,?,M,1.3,F*";
	String trame_RMC = "$GPRMC,123030.00,A,?,0.009,,270219,,,A*";
	String p1_trame_SDDBT = trame_SDDBT.substring(0, 13);
	String p2_trame_SDDBT = trame_SDDBT.substring(14);
	String p1_trame_RMC = trame_RMC.substring(0, 19);
	String p2_trame_RMC = trame_RMC.substring(20);
	String trame_em_SDDBT;
	String trame_em_RMC;
	delay(400);
	for(int i = 0; i <= 100; i++){
		profondeur += 0.1;
		s_profond = String(profondeur); 
		s_profond = s_profond.substring(0, s_profond.length()-1);
		trame_em_SDDBT = p1_trame_SDDBT + s_profond + p2_trame_SDDBT;
		trame_em_RMC = p1_trame_RMC + memo_ram.position[i%memo_ram.nb_position] + p2_trame_RMC;
		chksum = 0;
		for(int i = 1; i < trame_em_SDDBT.length()-1; i++) chksum ^= trame_em_SDDBT[i];
		s_chksum = String(chksum, HEX);
		if(s_chksum.length() < 2) s_chksum = '0' + s_chksum;
		s_chksum.toUpperCase();
		trame_em_SDDBT += s_chksum;
		swSer.println(trame_em_SDDBT);
		prep_aff_LCD(10);
		display.drawString(10, 0, "<< Emission RS422 >>");
		display.drawString(10, 10, "Trame DBT N° : " + String(i));
		display.drawStringMaxWidth(0, 25, 120, String(trame_em_SDDBT));
		display.display();
		delay(200);
		chksum = 0;
		for(int i = 1; i < trame_em_RMC.length()-1; i++)chksum ^= trame_em_RMC[i];
		s_chksum = String(chksum, HEX);
		if(s_chksum.length() < 2) s_chksum = '0' + s_chksum;
		s_chksum.toUpperCase();
		trame_em_RMC += s_chksum;
		swSer.println(trame_em_RMC);
		prep_aff_LCD(10);
		display.drawString(10, 0, "<< Emission RS422 >>");
		display.drawString(10, 10, "Trame RMC N° : " + String(i));
		display.drawStringMaxWidth(0, 25, 120, String(trame_em_RMC));
		display.display();
		if(!digitalRead(PinSW)) break;
		delay(200);
	}
}

// Affichage trames RMC
void aff_rmc() {
	char heur_format[12];
	int cpt = 0;
	char* tbuf[12] = { 0 };
	char buf[trame_nmea.length() + 1];
	trame_nmea.toCharArray(buf, trame_nmea.length());
	char *valeur = strtok(buf, ","); 
	while (valeur != NULL) {
		if (cpt < 12) {	tbuf[cpt++] = valeur; } else { break; }
		valeur = strtok(NULL, ","); 
	}
	prep_aff_LCD(16);
	display.drawString(30, 0, "$GPRMC");
	cpt = 1;
	while (cpt < 7){
		if(cpt == 1){
			heur_format[0] = tbuf[cpt][0];heur_format[1] = tbuf[cpt][1];heur_format[2] = ':';
			heur_format[3] = tbuf[cpt][2];heur_format[4] = tbuf[cpt][3];heur_format[5] = ':';
			heur_format[6] = tbuf[cpt][4];heur_format[7] = tbuf[cpt][5];heur_format[8] = ' ';
			heur_format[9] = 'U';heur_format[10] = 'T';heur_format[11] = 'C';heur_format[12] = 0;
			display.drawString(15, 15, String(heur_format));
		}else if(cpt == 3){
			display.drawString(10, 30, String(tbuf[cpt]));
			display.drawString(110, 30, String(tbuf[cpt+1]));
		}else if(cpt == 5){
			display.drawString(10, 45, String(tbuf[cpt]));
			display.drawString(110, 45, String(tbuf[cpt+1]));
		}
		cpt++;
	}
	display.display();
}

// Affichage trames RMC WIFI
void aff_nmea_rmc() {
	String type_trame = ""; // Type de trame nmea
	unsigned long timeout;
	while(true){
		timeout = millis();
		while (client.available() == 0) {
			if (millis() - timeout > 5000) oups();
			if (!(digitalRead(PinSW))) return;
		}
		while (client.available()) {
			char ch = static_cast(client.read());
			if (ch == '$' || ch == '!'){
				type_trame = trame_nmea.substring(0,6);
				if(String(type_trame) == "$GPRMC") aff_rmc();
				trame_nmea = "";
			}
			trame_nmea += ch;
			if (!(digitalRead(PinSW))) return;
		}
	}
}

// Affichage trames RMC liaison série
void aff_nmea_rmc_serie() {
	String type_trame = ""; // Type de trame nmea
	unsigned long timeout;
	while(true){
		timeout = millis();
		while (swSer.available() == 0) {
			if (millis() - timeout > 5000) oups();
			if (!(digitalRead(PinSW))) return;
		}
		while (swSer.available()) {
			char ch = static_cast(swSer.read());
			if (ch == '$' || ch == '!'){
				type_trame = trame_nmea.substring(0,6);
				if(String(type_trame) == "$GPRMC") aff_rmc();
				trame_nmea = "";
			}
			trame_nmea += ch;
			if (!(digitalRead(PinSW))) return;
		}
	}
}

// Attente donnees en lecture
void aff_trame() {
	prep_aff_LCD(24);
	display.drawString(0, 0, trame_nmea.substring(0,6));
	display.setFont(ArialMT_Plain_10);
	display.drawStringMaxWidth(0, 25, 120, String(trame_nmea));
	display.display();
	trame_nmea = "";
}

// Lecture trames NMEA WIFI
void li_trame_nmea() {
	unsigned long timeout;
	while(true){
		timeout = millis();
		while (client.available() == 0) {
			if (millis() - timeout > 5000) oups();
			if (!(digitalRead(PinSW))) return;
		}
		while (client.available()) {
			char ch = static_cast(client.read());
			if (ch == '$' || ch == '!') aff_trame();
			trame_nmea += ch;
			if (!(digitalRead(PinSW))) return;
		}
	}
}

// Lecture trames NMEA sur liaison serie
void li_trame_nmea_serie() {
	unsigned long timeout;
	while(true){
		timeout = millis();
		while (swSer.available() == 0) {
			if (millis() - timeout > 5000) oups();
			if (!(digitalRead(PinSW))) return;
		}
		while( swSer.available() ){
			char ch = static_cast(swSer.read());
			if (ch == '$' || ch == '!') aff_trame();
			trame_nmea += ch;
			if (!(digitalRead(PinSW))) return;
		}
		if (!(digitalRead(PinSW))) return;
	}
}

// Comptage des trames NMEA par type sur liaison wifi
void cpt_trame_nmea() {
	String type_trame = ""; // Type de trame nmea
	int cpt[10]; 
	String trame[10];
	uint8_t max_type = 12;
	uint8_t nb_type = 0;
	unsigned long timeout;
	while(true){
		timeout = millis();
		while (client.available() == 0) {
			if (millis() - timeout > 5000) oups();
			if (!(digitalRead(PinSW))) return;
		}
		while (client.available()) {
			char ch = static_cast(client.read());
			if (ch == '$' || ch == '!'){
				if(trame_nmea.length() > 6){
					type_trame = trame_nmea.substring(1,6);
					for (uint8_t i = 0; i <= max_type; i++){
							if( String(type_trame) == String(trame[i])){
								cpt[i] ++;
								break;
							}else if( i >= nb_type ){
								trame[i] = String(type_trame);
								cpt[i] = 1;
								nb_type ++;
								break;
							}
					}
					prep_aff_LCD(10);
					for (uint8_t i = 0; i < nb_type; i++){
						if (i<6){
							display.drawString(0, 10*i, trame[i]);
							snprintf (ch_result, 12,  " %d", cpt[i]);
							display.drawString(38, 10*i, ch_result);
						}else{
							display.drawString(65, 10*(i-6), String(trame[i]));
							snprintf (ch_result, 12,  " %d", cpt[i]);
							display.drawString(102, 10*(i-6), ch_result);
						}
					}
					display.display();
					trame_nmea = "";
					type_trame = "";
				}
			}
			trame_nmea += ch;
			if (!(digitalRead(PinSW))) return;
		}
	}
}

// Comptage des trames NMEA par type sur liaison serie
void cpt_trame_nmea_serie(){
	String type_trame = ""; // Type de trame nmea
	int cpt[10]; 
	String trame[10];
	uint8_t max_type = 12;
	uint8_t nb_type = 0;
	unsigned long timeout;
	while(true){
		timeout = millis();
		while (swSer.available() == 0) {
			if (millis() - timeout > 5000) oups();
			if (!(digitalRead(PinSW))) return;
		}
		while (swSer.available()) {
			char ch = static_cast(swSer.read());
			if (ch == '$' || ch == '!'){
				if(trame_nmea.length() > 6){
					type_trame = trame_nmea.substring(1,6);
					for (uint8_t i = 0; i <= max_type; i++){
							if( String(type_trame) == String(trame[i])){
								cpt[i] ++;
								break;
							}else if( i >= nb_type ){
								trame[i] = String(type_trame);
								cpt[i] = 1;
								nb_type ++;
								break;
							}
					}
					prep_aff_LCD(10);
					for (uint8_t i = 0; i < nb_type; i++){
						if (i<6){
							display.drawString(0, 10*i, trame[i]);
							snprintf (ch_result, 12,  " %d", cpt[i]);
							display.drawString(38, 10*i, ch_result);
						}else{
							display.drawString(65, 10*(i-6), String(trame[i]));
							snprintf (ch_result, 12,  " %d", cpt[i]);
							display.drawString(102, 10*(i-6), ch_result);
						}
					}
					display.display();
					trame_nmea = "";
					type_trame = "";
				}
			}
			trame_nmea += ch;
			if (!(digitalRead(PinSW))) return;
		}
	}
}

// Découverte des réseaux wifi accessibles
void decouverte_wifi() {
	String ssid_wifi[5];
	WiFi.disconnect(true);
	prep_aff_LCD(10);
	display.drawString(2, 0,  "RAZ wifi en cours...");
	display.drawString(2, 10,  ">>> Patience ... <<<");
	display.drawString(8, 30, "Attention !");
	display.drawString(0, 40, "Max 5 réseaux affichés");
	display.display();
	delay(400);
	uint8_t n = WiFi.scanNetworks();
	if( n == 0 ){
		prep_aff_LCD(16);
		display.drawString(0, 20, "Aucun réseau Wifi en vue !");
		display.drawString(0, 40, "Relancez la recherche...");
		display.display();
		while(digitalRead(PinSW)){delay(10);}
	}else{
		if(n > 5) n = 5;
		uint8_t plus_23 = 0;
		uint8_t nb_val = 0;
		for (uint8_t i = 0; i < n+1; ++i) {
			if(WiFi.SSID(i).length() <= 48 && WiFi.SSID(i).length() > 1){
				ssid_wifi[i-plus_23] = WiFi.SSID(i);
				nb_val ++;
			}else{
				plus_23++;
				i++;
			}
			delay(20);
		}
		B_menu = 0;
		B_menu = mes_menus_oled.init(nb_val-1, *ssid_wifi);
		ssid_wifi[B_menu+1].toCharArray(memo_ram.wifi_ssid, ssid_wifi[B_menu+1].length()+1);
		if(memo_ram.wifi_ssid != ""){
			prep_aff_LCD(10);
			display.drawString(0, 10, "Saisir le mot de passe");
			display.drawString(0, 20, "du réseau :");
			display.drawString(0, 35, memo_ram.wifi_ssid);
			display.display();
			delay(200);
			while(digitalRead(PinSW)){delay(10);}
			String ch_result;
			mon_sai_oled.init(); // Initialisation ecran OLED pour saisie
			ch_result = mon_sai_oled.aff_menu_Alpha();
			if(ch_result.length() > 2){
				ch_result.toCharArray(memo_ram.wifi_passe, ch_result.length()+1);
				maj_verif('x','x');
				ecriture_eeprom();
			}
		}
	}
}

// Ecriture des positions fichier kml dans eeprom
void ecriture_eeprom_positions(){
	if(memo_ram.nb_position > 0){ 
		EEPROM.begin(sizeof(s_memo_rom));
		Serial.println("Ecriture EEPROM");
		Serial.println("Qte Memmoire EEPROM utilisee : " + String(sizeof(s_memo_rom)));
		delay(500);
		memo_rom.nb_position = memo_ram.nb_position;
		Serial.println("Nb de position memorisee : " + String(memo_rom.nb_position));
		for(uint8_t i = 0; i <= memo_ram.nb_position; i++){ 
			strcpy(memo_rom.position[i], memo_ram.position[i]); 
			delay(5); 
			Serial.println(memo_rom.position[i]);
		}
		EEPROM.put(adr_deb_memo_rom, memo_rom);
		delay(2000);
		bool ok = EEPROM.commit();
		delay(2000);
		EEPROM.end();
		delay(500);
	}
}

// Lecture de fichier kml et traduction en coordonées nmea trames RMC
void lire_kml() {
	memo_ram.nb_position = 0;
	char char_lu; // Caractère lu sur interface série/usb
	unsigned long timeout; // Pour gérer attente début de transmission
	String longitude = "";
	String latitude = "";
	String profondeur = ""; // Au moment du dev. l'info profondeur est toujours égale à 0
	char v_long ='E'; // Est par défaut passe à Ouest si la longitude est négative
	bool lect_lat = false; // Passe à vrai quand la longitude est ok
	bool lect_prof = false; // Passe à vrai quand la latitude est ok
	uint8_t pos_pt_long = 0; // Repérage du point décimal en longitude
	uint8_t pos_pt_lat = 0; // Repérage du point décimal en latitude
	bool fin_reception = false; // On est arrivé à la fin des mesures
	String v_tmp; // Tempo pour calcul lat et long
	prep_aff_LCD(24);
	display.setTextAlignment(TEXT_ALIGN_CENTER);
	display.drawString(64, 10, "En attente...");
	display.display();
	while(!fin_reception){
		// Attente début d'émission...
		timeout = millis();
		while (Serial.available() == 0){
			if (millis() - timeout > 5000){
				oups();
				prep_aff_LCD(24);
				display.setTextAlignment(TEXT_ALIGN_CENTER);
			}
			if (!(digitalRead(PinSW))) break;
		}
		// Attente début de la liste des coordonées
		String ligne = "";
		while(ligne.indexOf(" 0){
				char_lu = Serial.read();
				if(char_lu == '>'){
					ligne = "";
				} else {
					ligne += char_lu;
				}
				delay(2);
			}
		}
		// Supression des "rdinate>\l\t\t\t\t"
		int i = 0;
		while(i < 13){
			if(Serial.available() > 0){ 
				char_lu = Serial.read();
				i++;
				delay(2);
			}
		}
		// Lecture des coordonées
		while(true){
			longitude = "";
			latitude = "";
			profondeur = "";
			v_long ='E'; // Est
			lect_lat = false;
			lect_prof = false;
			// Lecture d'une coordonée
			while (true){
				if(Serial.available() > 0){
					char_lu = Serial.read();
					delay(2);
					if(! lect_lat){
						// lecture Longitude
						if(char_lu == '-') v_long ='W'; // Ouest
						  else{
							if(char_lu == ','){
								lect_lat = true;
							}else{
								if(char_lu != '.'){
									if(char_lu != '\t') longitude += char_lu;
								} else pos_pt_long = longitude.length();
							}
						}
					}else if(lect_lat){
						// Lecture Latitude
						if(char_lu == ','){
							lect_prof = true;
						}else{
							if(char_lu != '.'){
								latitude += char_lu;
							}else pos_pt_lat = latitude.length();
						}
					}else if(lect_prof){
						// Lecture de la profondeur
						profondeur += char_lu;
						if(char_lu == ' '){
							break;
						}
					}
					if (!(digitalRead(PinSW))) break;
					if (char_lu == ' ') break;
				}
			}
			// Si chaine de longitude vide on sort
			if(longitude.length() < 3){ fin_reception = true; memo_ram.nb_position--; break;}
			// Mise en forme des coordonées au format nmea avec gestion du point décimal
			if(pos_pt_long == 1){ 
				v_tmp = "0." + longitude.substring(1, 7);
				v_tmp = String( v_tmp.toFloat() * 60 );
				if(v_tmp.toInt() < 10)
					longitude = "00" + longitude.substring(0,1) + 0  + String( longitude.substring(1, 7).toInt() * 6 );
				else
					longitude = "00" + longitude.substring(0,1) + String( longitude.substring(1, 7).toInt() * 6 );
				longitude = longitude.substring(0,5) + "." + longitude.substring(5, longitude.length());
			}else if(pos_pt_long == 2){ 
				v_tmp = "0." + longitude.substring(2, 7);
				v_tmp = String( v_tmp.toFloat() * 60 );
				if(v_tmp.toInt() < 10)
					longitude = "0" + longitude.substring(0, 2) + 0  + String( longitude.substring(2, 7).toInt() * 6 );
				else
					longitude = "0" + longitude.substring(0, 2) + String( longitude.substring(2, 7).toInt() * 6 );
				longitude = longitude.substring(0,5) + "." + longitude.substring(5, longitude.length());
			}
			if(pos_pt_lat == 1){ 
				v_tmp = "0." + latitude.substring(1, 7);
				v_tmp = String( v_tmp.toFloat() * 60 );
				if(v_tmp.toInt() < 10)
					latitude = latitude.substring(0, 1) + 0 + String( latitude.substring(2, 7).toInt() * 6 );
				else
					latitude = latitude.substring(0, 1) + String( latitude.substring(2, 7).toInt() * 6 );
				latitude = latitude.substring(0,4) + "." + latitude.substring(4, latitude.length());
			}else if(pos_pt_lat == 2){ 
				v_tmp = "0." + latitude.substring(2, 7);
				v_tmp = String( v_tmp.toFloat() * 60 );
				if(v_tmp.toInt() < 10)
					latitude = latitude.substring(0, 2) + 0 + String( latitude.substring(2, 7).toInt() * 6 );
				else
					latitude = latitude.substring(0, 2) + String( latitude.substring(2, 7).toInt() * 6 );
				latitude = latitude.substring(0,4) + "." + latitude.substring(4, latitude.length());
			}
			latitude = latitude + ",N," + longitude + "," + v_long;
			latitude.toCharArray(memo_ram.position[memo_ram.nb_position], latitude.length()+2);
			// Affichage flèches sur LCD Oled pour montrer qu'on avance
			display.clear();
			if (memo_ram.nb_position % 2 == 1 ) display.drawString(64, 20, "<<<<---"); else display.drawString(64, 20, "---<<<<");
			display.display();
			memo_ram.nb_position ++;
			if(!(Serial.available())) break;
			if(fin_reception) break;
		}
		if(Serial.available() == 0 || memo_ram.nb_position >= 50 || fin_reception) break;
	}
	// On va à la fin du fichier
	while(Serial.available()){ Serial.read(); }
}

// Menu Parametre wifi
void param_wifi() {
	B_menu = 0;
	B_menu = mes_menus_oled.init(nb_ch_menuD, _ch_menuD);
	delay(400);
	if (B_menu == 0) decouverte_wifi();
	else if (B_menu == 1){
		String ch_result;
		mon_sai_oled.init();
		ch_result = mon_sai_oled.aff_menu_Alpha();
		if(ch_result.length() > 2){
			ch_result.toCharArray(memo_ram.wifi_ip, ch_result.length()+1);
			maj_verif('z','z','x');
			ecriture_eeprom();
		}
	}
	else if (B_menu == 2){
		String ch_result;
		mon_sai_oled.init();
		ch_result = mon_sai_oled.aff_menu_Alpha();
		if(ch_result.length() > 2){
			memo_ram.wifi_port = ch_result.toDouble();
			maj_verif('z','z','z','x');
			ecriture_eeprom();
		}
	}
	else if (B_menu == 3){
		if( String(memo_ram.verif_data_w) == "1x2x3x4x"){ 
			cnx_wifi();
		}else{
				prep_aff_LCD(10);
				display.drawString(0, 0,  "*********************");
				display.drawString(0, 10, "Parametres incomplets");
				display.drawString(0, 20, "*********************");
				display.display();
		}
	}
	delay(400);
}

// Menu Parametre serie
void param_serie() {
	B_menu = 0;
	B_menu = mes_menus_oled.init(nb_ch_menuE, _ch_menuE);
	delay(400);
	if (B_menu == 0) memo_ram.rs422_vit = 4800;
	else if (B_menu == 1) memo_ram.rs422_vit = 38400;
	else if (B_menu == 2){
		String ch_result;
		mon_sai_oled.init();
		ch_result = mon_sai_oled.aff_menu_Alpha();
		if(ch_result.length() > 2){
			memo_ram.rs422_vit = ch_result.toDouble();
		}
	}
	else if (B_menu == 3){
		prep_aff_LCD(10);
		display.drawString(0, 0,  "SCHEMA LIAISON NMEA");
		display.drawString(0, 10, "  NMEA          RS422");
		display.drawString(0, 20, "Out(+)------------A");
		display.drawString(0, 30, "Out(-)------------B");
		display.drawString(0, 40, "IN(-)----------------Z");
		display.drawString(0, 50, "IN(+)----------------Y");
		display.display();
		delay(400);
		while(digitalRead(PinSW)){delay(10);}
	}
	if (B_menu != 4){
		maj_verif('z','z','z','z','x');
		ecriture_eeprom();
		swSer.begin(memo_ram.rs422_vit, SWSERIAL_8N1, sl_rx, sl_tx, false, 256);
	}
}

// Menu Parametre type réseau
void param_type_reseau() {
	B_menu = 0;
	B_menu = mes_menus_oled.init(nb_ch_menuC, _ch_menuC);
	delay(400);
	if (B_menu == 0) memo_ram.v_wifi = true;
	else if (B_menu == 1) memo_ram.v_wifi = false;
	else if (B_menu == 2){
		lire_kml();
		// Affichage du nombre de positions trouvées
		prep_aff_LCD(24);
		display.setTextAlignment(TEXT_ALIGN_CENTER);
		display.drawString(64, 10, String(memo_ram.nb_position) + " positions");
		display.drawString(64, 30, "trouvées");
		display.display();
		while(digitalRead(PinSW)){delay(10);}
		if(memo_ram.nb_position > 0){
			prep_aff_LCD(24);
			display.setTextAlignment(TEXT_ALIGN_CENTER);
			display.drawString(64, 10, "Sauvegarde");
			display.drawString(64, 30, "en cours");
			display.display();
			ecriture_eeprom_positions();
		}
	}
	if (B_menu < 2) ecriture_eeprom();
	delay(400);
}

// Menu Parametre
void parametre() {
	B_menu = 0;
	B_menu = mes_menus_oled.init(nb_ch_menuB, _ch_menuB);
	delay(400);
	if (B_menu == 0) param_type_reseau();
	else if (B_menu == 1) param_wifi();
	else if (B_menu == 2) param_serie();
	else if (B_menu == 3) aff_param();
	delay(200);
}

// Affiche compte-rendu enregistrement eeprom
void aff_eeprom(bool lect = true, bool memo_ok = false){
	delay(200);
		prep_aff_LCD(10);
		if(lect) display.drawString(20, 0, "Lecture EEPROM"); else display.drawString(20, 0, "Ecriture EEPROM");
		display.drawString(0, 10, "ssid : " + String(memo_ram.wifi_ssid));
		display.drawString(0, 20, "@ IP : " + String(memo_ram.wifi_ip));
		display.drawString(0, 30, "Port : " + String(memo_ram.wifi_port));
		if(memo_ok) display.drawString(40, 30, "< MEMO OK >");
		display.drawString(0, 40, "Vit S: " + String(memo_ram.rs422_vit));
		if(memo_ram.v_wifi) display.drawString(0, 50, "Liaison Wifi Active"); else display.drawString(0, 50, "Liaison RS422 Active");
		display.display();
	delay(4000);
}

// Lecture eeprom
void lecture_eeprom(){
	EEPROM.begin(sizeof(s_memo_rom));
	delay(500);
	EEPROM.get(adr_deb_memo_rom, memo_rom);
	delay(500);
	strcpy(memo_ram.verif_data_w, memo_rom.verif_data_w);
	strcpy(memo_ram.verif_data_s, memo_rom.verif_data_s);
	if(memo_ram.verif_data_w[0] == '1' && memo_ram.verif_data_w[2] == '2'){
		if(memo_ram.verif_data_w[1] == 'x') strcpy(memo_ram.wifi_ssid, memo_rom.wifi_ssid); else strcpy(memo_ram.wifi_ssid, "");
		if(memo_ram.verif_data_w[3] == 'x') strcpy(memo_ram.wifi_passe, memo_rom.wifi_passe); else strcpy(memo_ram.wifi_passe, "");
		if(memo_ram.verif_data_w[5] == 'x') strcpy(memo_ram.wifi_ip, memo_rom.wifi_ip); else strcpy(memo_ram.wifi_ip, "");
		if(memo_ram.verif_data_w[7] == 'x') memo_ram.wifi_port = memo_rom.wifi_port; else memo_ram.wifi_port = 0;
	}
	if(memo_ram.verif_data_s[0] == '1'){
		if(memo_ram.verif_data_s[1] == 'x') memo_ram.rs422_vit = memo_rom.rs422_vit; else memo_ram.rs422_vit = 0;
	}
	Serial.println();
	Serial.println("Lecture des positions kml de l'EEPROM");
	memo_ram.nb_position = memo_rom.nb_position;
	Serial.println("Nb Pos : " + String(memo_ram.nb_position));
	Serial.println();
	for(uint8_t i = 0; i <= memo_ram.nb_position; i++){ 
		strcpy(memo_ram.position[i], memo_rom.position[i]);
		delay(5);
		Serial.println(memo_rom.position[i]);
	}
	memo_ram.v_wifi = memo_rom.v_wifi;
	EEPROM.end();
	delay(500);
	aff_eeprom();
}

// Ecriture eeprom
void ecriture_eeprom(){
	EEPROM.begin(sizeof(s_memo_rom));
	delay(500);
	if(memo_ram.wifi_ssid != ""){ strcpy(memo_rom.wifi_ssid, memo_ram.wifi_ssid); memo_rom.verif_data_w[0] = '1'; memo_rom.verif_data_w[1] = 'x';}
	if(memo_ram.wifi_passe != ""){ strcpy(memo_rom.wifi_passe, memo_ram.wifi_passe); memo_rom.verif_data_w[2] = '2';; memo_rom.verif_data_w[3] = 'x';}
	if(memo_ram.wifi_ip != ""){ strcpy(memo_rom.wifi_ip, memo_ram.wifi_ip); memo_rom.verif_data_w[4] = '3'; memo_rom.verif_data_w[5] = 'x';}
	if(memo_ram.wifi_port != 0){ memo_rom.wifi_port = memo_ram.wifi_port; memo_rom.verif_data_w[6] = '4'; memo_rom.verif_data_w[7] = 'x';}else{memo_rom.wifi_port = 0;}
	if(memo_ram.rs422_vit != 0){ memo_rom.rs422_vit = memo_ram.rs422_vit; memo_rom.verif_data_s[0] = '1'; memo_rom.verif_data_s[1] = 'x';}else{memo_rom.rs422_vit = 0;}
	memo_rom.v_wifi = memo_ram.v_wifi;
	EEPROM.put(adr_deb_memo_rom, memo_rom);
	delay(500);
	bool ok = EEPROM.commit();
	delay(500);
	aff_eeprom(false, ok);
	EEPROM.end();
	delay(500);
}

// connexion wifi
void cnx_wifi() {
		int progress;
		int cpt=1;
		prep_aff_LCD(10);
		display.drawString(0, 0, "Tentative de connexion sur :");
		display.drawString(10, 10, memo_ram.wifi_ssid);
		display.display();
		WiFi.disconnect(true);
		delay(400);
		WiFi.begin(memo_ram.wifi_ssid, memo_ram.wifi_passe);
		while (WiFi.status() != WL_CONNECTED){
			progress = (cpt / 5) % 100;
			display.drawProgressBar(0, 30, 120, 10, progress);
			display.display();
			cpt++;
			if(cpt>=501) break;
			delay(10); 
		}
		display.setTextAlignment(TEXT_ALIGN_LEFT);
		if (!client.connect(memo_ram.wifi_ip, memo_ram.wifi_port)) {
			prep_aff_LCD(10);
			display.drawString(0, 0, "Problème de connexion !");
			display.drawString(0, 10, "Parametres :");
			display.drawString(0, 20, memo_ram.wifi_ssid);
			display.drawString(0, 30, memo_ram.wifi_passe);
			display.drawString(0, 40, memo_ram.wifi_ip);
			display.drawString(0, 50, String(memo_ram.wifi_port));
		}else{
			display.drawString(0, 52, ">> Connexion réussie <<");
		}
		display.display();
		delay(200);
		while(digitalRead(PinSW)){delay(10);}
}

// Initialisation
void setup() {
	Serial.begin(4800); // Interface utilisée pour chargement des fichiers de coordonnées kml
	pinMode(PinSW, INPUT);
	display.init();
	display.flipScreenVertically();
	prep_aff_LCD(10);
	Wire.begin();
	// Init saisie OLED
	mon_sai_oled.init();
	mon_sai_oled.sortie_deb(false);
	lecture_eeprom();
	WiFi.mode(WIFI_STA);
	if( String(memo_ram.verif_data_w) == "1x2x3x4x") cnx_wifi();
	if( String(memo_ram.verif_data_s) == "1x") swSer.begin(memo_ram.rs422_vit, SWSERIAL_8N1, sl_rx, sl_tx, false, 256);
}

// Boucle principale
void loop() {
	delay(200);
	B_menu = 0;
	B_menu = mes_menus_oled.init(nb_ch_menuA, _ch_menuA);
	delay(300);
	if (B_menu == 0) parametre();
	else if (B_menu == 1) {if(memo_ram.v_wifi) li_trame_nmea(); else li_trame_nmea_serie(); delay(400); while(digitalRead(PinSW)){delay(10);}}
	else if (B_menu == 2) {if(memo_ram.v_wifi) cpt_trame_nmea(); else cpt_trame_nmea_serie(); delay(400);}
	else if (B_menu == 3) {if(memo_ram.v_wifi) aff_nmea_rmc(); else aff_nmea_rmc_serie(); delay(400); while(digitalRead(PinSW)){delay(10);}}
	else if (B_menu == 4) {envoi_rmc_rs422();}
	delay(400);
}	

Téléchargement du fichier testeur nmea V1 (vous pouvez le nommer comme vous voulez) : lect_nmea.ino
ou
Téléchargement du fichier testeur nmea V2 (vous pouvez le nommer comme vous voulez) : lect_nmea_v2.ino
Vous pouvez commencer avec la V2 ou la V1 ou inversement, pour changer de version, il suffit de la téléverser dans le testeur nmea avec l'IDE Arduino.

Téléchargement du fichier : sai_oled.h (à installer dans le même rep que lect_nmea)
Téléchargement du fichier : sai_oled.cpp (à installer dans le même rep que lect_nmea)

Téléchargement du fichier : menu_oled.h (à installer dans le même rep que lect_nmea)
Téléchargement du fichier : menu_oled.cpp (à installer dans le même rep que lect_nmea)


J’ai réalisé ce montage pour mes propres besoins, mais si quelqu’un est intéressé par ce projet, je vais mettre à dispo sur ce site les informations nécessaires à sa réalisation par domaine :

- Les infos pour l'impression du boitier, du couvercle et du bouton dans "Imp 3D"
- Les infos sur les fonctionnalités dans "Navigation"

La premiere évolution V2 :
- Lecture de fichier kml et émission des trames sur la liaison RS422"
Le projet étant évolutif, je vais numéroter les versions (impressions 3D, logiciel).

accueil electronique

Bricolage Robotique Informatique Peinture Voyage
Téléc. portail Le robot "mécano" Astuces informatique Henri Bertrou Auvergne
Bat. Iphone 6S Le robot "solaire" Réseau couche app. Jean-Michel Castille Floride
Robot piscine Servo et IR" Réseau Les couches New York
Xiaomi M365 Le robot "thymio" Réseaux Outils L'Ouest américain
Mac Mini Le robot "Rovio" Unités grandeur inf. L'Ile Maurice
Putty SSH Windows L'Italie / Venise
Bases Raspberry Tunisie
Termius IPhone/IPad Grece
Le vieux ZX 81
...
Navigation La Rochelle CNC / Imp3D Electronique Programmation
Rencontre dauphins Les Minimes Construction CNC Alim. TPL 5110 Doc. programme
Analyse NMEA 0183 Le Vieux port CNC du commerce Carte ESP8266 Indent programme
graph. NMEA 0183 L'Ile de Ré Martyr CNC ESP8266 1 relai Prog. objet
Analyse trames AIS A visiter Réa. imp. 3D ESP8266 Alarme Prog. procédurale
Analyse AIS TCP-IP Cura impression 3D ESP8266 MQTT
Sortie en ketch Plateau CR10 ESP8266 Temp.
Echange GPS C80 Anémomètre.
HP Sun-Odyssey CNC / 3D en vrac MCP9808 Librairie
LCD yanmar Saisie Oled
Testeur nmea esp1 i2c