XI. Les voix de la nature humaine sont impénétrables (21 juillet 2025 09:30)
Même si ce projet n’est pas encore terminé, il est déjà bien avancé. Il ne demande plus qu’à être déployé en conditions réelles pour piloter un réseau de trains miniatures. J’ai vraiment pris plaisir à t’en partager les différentes étapes, et je te remercie sincèrement pour l’intérêt que tu y as porté.
Aujourd’hui, j’ai reçu un message qui détonne au milieu de toute la bienveillance que j’ai reçue de la part des personnes avec qui j’ai pu discuter de ce projet — à l’oral, par écrit, ou encore via les forums spécialisés. Je ne vais pas le copier ici, car honnêtement, il n’en vaut pas la peine.
Par mon parcours, je suis plutôt quelqu’un de solide. J’accepte volontiers les critiques constructives, et j’ai cette capacité assez naturelle à me détacher de celles qui ne visent qu’à blesser ou rabaisser gratuitement. Mais toi et moi, on sait comment ça fonctionne : la jalousie, l’envie mêlée à l’incapacité de faire aussi bien — ou mieux — peut rendre certaines personnes… exécrables.
Pour te résumer l’affaire de manière extrêmement courte, ce message disait en gros qu’une personne comme moi ne pouvait pas avoir créé ce que j’ai fait. Une jolie histoire, selon lui, juste bonne à attirer l’attention… Je te passe les détails les plus croustillants.
Je n’ai même pas pris le temps de lui répondre. À quoi bon ? J’ai préféré ajouter un petit chapitre à mon histoire, et enrichir encore un peu plus cette « belle histoire ». Je n’ai aucun besoin de me justifier. Il suffit de visiter mon site pour y trouver tout un tas de vidéos en contexte dans lesquelles ma cécité ne fait plus aucun doute.
Mais parce que je sais que d’autres — sincèrement curieux — se posent la question de ma capacité, en tant que personne aveugle, à programmer un tel système, je partage ici un bout de code. C’est une fonction qui permet de lire les états de la Z21. Et les petits “///”, ce sont des commentaires : des parties de texte non exécutées par le système, qui permettent de documenter le code. Je t’en ai mis quelques-uns pour rendre la lecture plus digeste 😀
/// Permet de récupérer les informations techniques de la Z21
/// Mise à jour le 12 juillet 2025
FUNCTION Z21ObtenirInfos()
{
// Déclaration des variables globales pour l'IP et le port UDP de la Z21
GLOBAL $Z21Ip, $Z21Port;
// Tableau de correspondance entre codes matériels et modèles de Z21
$TBUModeleZ21 = [
1 => 'Z21 noire',
2 => 'z21 blanche',
3 => 'z21 start',
4 => 'z21 blanche ou start déverrouillée'
];
// Création de la socket UDP
$Socket = SOCKET_CREATE(AF_INET, SOCK_DGRAM, SOL_UDP);
IF (!$Socket) RETURN JSON_ENCODE(['Succes' => FALSE, 'Erreur' => 'ÉCHEC DE CRÉATION DE LA SOCKET']);
// Définition du timeout de réception à 2 secondes
SOCKET_SET_OPTION($Socket, SOL_SOCKET, SO_RCVTIMEO, ['sec' => 2, 'usec' => 0]);
// Trame pour demander le numéro de série de la centrale
$TrameNumeroSerie = PACK('vv', 0x0004, 0x0010);
Z21EnvoyerCommande($Socket, $TrameNumeroSerie, $Z21Ip, $Z21Port);
$ReponseSerie = Z21RecevoirReponse($Socket);
IF (!$ReponseSerie) {
SOCKET_CLOSE($Socket);
RETURN JSON_ENCODE(['Succes' => FALSE, 'Erreur' => 'AUCUNE RÉPONSE POUR LE NUMÉRO DE SÉRIE']);
}
$NumeroSerie = UNPACK('VVal', SUBSTR($ReponseSerie, 4, 4))['Val'];
// Trame pour obtenir la version du bus X-BUS
$TrameVersion = PACK('vvC3', 0x0007, 0x0040, 0x21, 0x21, 0x00);
Z21EnvoyerCommande($Socket, $TrameVersion, $Z21Ip, $Z21Port);
$ReponseVersion = Z21RecevoirReponse($Socket);
$VersionXBusNum = ($ReponseVersion !== FALSE && isset($ReponseVersion[7])) ? ORD($ReponseVersion[7]) : 0;
$VersionXBusTexte = ($VersionXBusNum > 0) ? ($VersionXBusNum >> 4) . '.' . ($VersionXBusNum & 0x0F) : 'INCONNUE';
// Trame pour obtenir le type matériel et les versions HW/FW
$TrameHardware = PACK('vv', 0x0004, 0x001A);
Z21EnvoyerCommande($Socket, $TrameHardware, $Z21Ip, $Z21Port);
$ReponseHardware = Z21RecevoirReponse($Socket);
$TypeHardware = 0;
$TypeHardwareHex = 'INCONNU';
$VersionFirmware = 'INCONNUE';
$VersionHardware = 'INCONNUE';
$Modele = 0;
IF ($ReponseHardware !== FALSE && STRLEN($ReponseHardware) >= 10) {
$TypeHardware = UNPACK('V', SUBSTR($ReponseHardware, 4, 4))[1];
$FwRaw = UNPACK('v', SUBSTR($ReponseHardware, 8, 2))[1];
$TypeHardwareHex = '0x' . STR_PAD(STRTOUPPER(DECHEX($TypeHardware)), 8, '0', STR_PAD_LEFT);
$VersionFirmware = sprintf('%d.%02d', ($FwRaw >> 8) & 0xFF, $FwRaw & 0xFF);
$VersionHardware = $TypeHardwareHex;
// Détermination du modèle selon les codes matériels connus
SWITCH ($TypeHardware):
CASE 0x00000200:
CASE 0x00000201: $Modele = 1; BREAK;
CASE 0x00000203: $Modele = 2; BREAK;
CASE 0x00000204: $Modele = 3; BREAK;
ENDSWITCH;
}
// Trame pour demander le niveau d’activation de la centrale
$TrameCode = PACK('vv', 0x0004, 0x0018);
Z21EnvoyerCommande($Socket, $TrameCode, $Z21Ip, $Z21Port);
$ReponseCode = Z21RecevoirReponse($Socket);
$CodeActivation = ($ReponseCode !== FALSE && isset($ReponseCode[4])) ? ORD($ReponseCode[4]) : -1;
IF ($CodeActivation === 0) {
$CodeActivationTexte = 'Z21 noire, Tous les services autorisés';
} ELSEIF ($CodeActivation === 1) {
$CodeActivationTexte = 'z21 start verrouillée';
} ELSEIF ($CodeActivation === 2) {
$CodeActivationTexte = 'z21 start déverrouillée';
} ELSE {
$CodeActivationTexte = 'Inconnu (' . $CodeActivation . ')';
}
// Si c’est une z21 blanche ou start déverrouillée, on ajuste le modèle
IF (IN_ARRAY($Modele, [2, 3]) && $CodeActivation === 2) $Modele = 4;
// --- État système ---
// Trame pour obtenir l’état global de la centrale
$TrameEtatSysteme = PACK('vv', 0x0004, 0x0085);
Z21EnvoyerCommande($Socket, $TrameEtatSysteme, $Z21Ip, $Z21Port);
$ReponseEtat = Z21RecevoirReponse($Socket);
IF (!$ReponseEtat || STRLEN($ReponseEtat) < 20) {
SOCKET_CLOSE($Socket);
RETURN JSON_ENCODE(['Succes' => FALSE, 'Erreur' => 'AUCUNE RÉPONSE ÉTAT SYSTÈME']);
}
// Conversion de la trame brute en hexadécimal pour debug
$TrameBruteHex = strtoupper(chunk_split(BIN2HEX($ReponseEtat), 2, ' '));
// Extraction des valeurs physiques
$CourantPrincipal = UNPACK('sVal', SUBSTR($ReponseEtat, 4, 2))['Val'];
$CourantProgrammation = UNPACK('sVal', SUBSTR($ReponseEtat, 6, 2))['Val'];
$CourantFiltre = UNPACK('sVal', SUBSTR($ReponseEtat, 8, 2))['Val'];
$Temperature = UNPACK('sVal', SUBSTR($ReponseEtat, 10, 2))['Val'];
$TensionAlim = UNPACK('vVal', SUBSTR($ReponseEtat, 12, 2))['Val'];
$TensionVCC = UNPACK('vVal', SUBSTR($ReponseEtat, 14, 2))['Val'];
$EtatCentrale = ORD($ReponseEtat[16]);
// Interprétation des bits d’état
$Etats = [];
IF ($EtatCentrale & 0x01) $Etats[] = 'Arrêt d’urgence';
IF ($EtatCentrale & 0x02) $Etats[] = 'Tension voie coupée';
IF ($EtatCentrale & 0x04) $Etats[] = 'Court-circuit';
IF ($EtatCentrale & 0x20) $Etats[] = 'Mode programmation actif';
IF ($EtatCentrale & 0x08) $Etats[] = 'Autre (bit 0x08)';
IF ($EtatCentrale & 0x10) $Etats[] = 'Autre (bit 0x10)';
IF ($EtatCentrale & 0x40) $Etats[] = 'Autre (bit 0x40)';
IF ($EtatCentrale & 0x80) $Etats[] = 'Autre (bit 0x80)';
$EtatCentraleTexte = COUNT($Etats) ? implode(', ', $Etats) : 'normal';
// --- Compilation des informations finales ---
$Infos = [
'Succes' => TRUE,
'TrameBruteEtatHex' => $TrameBruteHex,
'NumeroSerie' => $NumeroSerie,
'Modele' => $TBUModeleZ21[$Modele] ?? 'INCONNU',
'VersionHardware' => $VersionHardware,
'VersionFirmware' => $VersionFirmware,
'TypeHardware' => $TypeHardware . ' (' . $TypeHardwareHex . ')',
'VersionXBus' => $VersionXBusTexte,
'CodeActivation' => $CodeActivationTexte,
'CourantPrincipal_mA' => $CourantPrincipal,
'CourantProgrammation_mA' => $CourantProgrammation,
'CourantFiltre_mA' => $CourantFiltre,
'Temperature_C' => $Temperature . '°c',
'TensionAlimentation_V' => ($TensionAlim > 0) ? round($TensionAlim / 1000, 1) . 'V' : 'INCONNUE',
'TensionVCC_V' => ($TensionVCC > 0) ? round($TensionVCC / 1000, 2) . 'V' : 'INCONNUE',
'EtatCentrale' => $EtatCentrale,
'EtatCentraleTexte' => $EtatCentraleTexte
];
// Fermeture propre de la socket
SOCKET_CLOSE($Socket);
// Retour des informations au format JSON
RETURN JSON_ENCODE($Infos);
}
Ces prochains jours, je vais mettre en forme ce récit pour le publier sur mon site web, l’agrémenter de photos (grâce à ma fille), et continuer la construction de mon réseau.
Je vais aussi réfléchir à un processus de configuration et de diffusion : flasher la carte SD du Raspberry Pi, réinstaller tout le système minimal pour le déploiement de l’application, tester les fonctions de l’appli sur la Z21 noire, puis sur la Z21 blanche, avec mon Mac, puis un Windows en machine virtuelle, puis via mon smartphone et la tablette de ma fille…
Et si tout roule, je chercherai un designer pour rendre l’interface visuelle plus agréable, tout en restant sobre.
Ouh là là… et puis il y a la documentation 🙂 Programmer, déboguer, échouer, réfléchir, reprogrammer, redéboguer… puis sourire parce que ça marche, c’est trop cool. Rédiger une doc, franchement… j’vais rester poli 😀 Mais c’est indispensable, pour ceux qui voudraient peut-être tester ou utiliser mon application.