[{"data":1,"prerenderedAt":605},["ShallowReactive",2],{"article-/blog/lumo-piloter-un-afficheur-led-awtrix":3},{"id":4,"title":5,"body":6,"date":585,"description":586,"extension":587,"image":588,"meta":589,"navigation":595,"path":596,"seo":597,"stem":598,"tags":599,"__hash__":604},"articles/blog/lumo-piloter-un-afficheur-led-awtrix.md","Lumo, une app native pour mon afficheur LED",{"type":7,"value":8,"toc":570},"minimark",[9,26,34,41,48,53,56,82,86,93,127,133,136,140,147,194,201,217,224,228,239,245,252,256,275,282,286,289,300,307,311,384,388,403,410,417,424,428,431,463,467,489,503,509,515,521,538,545,549,556,563],[10,11,12,13,17,18,21,22,25],"p",{},"Tout est parti d'un achat impulsif : un ",[14,15,16],"strong",{},"Ulanzi TC001",", une petite horloge de bureau avec une matrice de ",[14,19,20],{},"32×8 LED",". Sortie de la boîte, elle affiche l'heure, la température, et pas grand-chose de plus. Mais la communauté a développé ",[14,23,24],{},"AWTRIX",", un firmware alternatif qui la transforme en afficheur programmable, pilotable en HTTP depuis n'importe quoi sur le réseau local. On flashe l'objet une fois en USB-C via le flasher web officiel, c'est réversible, et on se retrouve avec un petit écran qui peut afficher à peu près n'importe quoi.",[10,27,28,29,33],{},"Le firmware est génial. Son interface web, beaucoup moins. Pour changer l'ordre de ce qui défile, il faut renvoyer une liste à la main ; pour afficher une donnée externe, il faut écrire soi-même une requête ",[30,31,32],"code",{},"curl"," avec le bon JSON ; et pour voir ce que ça donne, il faut lever les yeux vers l'objet. Rien d'insurmontable pour un développeur, mais on est loin du « je tape, ça s'affiche ».",[10,35,36,37,40],{},"C'est comme ça que ",[14,38,39],{},"Lumo"," est né, le 2 juin 2026 : une app native macOS, puis iPhone et iPad, pour piloter l'afficheur sans jamais ouvrir l'interface web ni un terminal.",[10,42,43],{},[44,45],"img",{"alt":46,"src":47},"Lumo sur Mac","/images/Lumo/mac.webp",[49,50,52],"h2",{"id":51},"trois-vagues-de-travail","Trois vagues de travail",[10,54,55],{},"En regardant l'historique, le projet s'est fait en trois temps bien distincts, avec de longues pauses entre les deux (les autres projets n'attendaient pas 😅).",[57,58,59,70,76],"ul",{},[60,61,62,65,66,69],"li",{},[14,63,64],{},"Début juin, la 0.1"," : une app Mac minimaliste. Découverte automatique de l'afficheur sur le réseau, aperçu de la matrice, météo via Open-Meteo, envoi de notifications. Une CI GitHub Actions et un pipeline de release en ",[30,67,68],{},".dmg"," dès le premier jour.",[60,71,72,75],{},[14,73,74],{},"Mi-juillet, de la 0.2 à la 0.4"," : le vrai produit. Refonte complète de la navigation, les connecteurs, le studio, les alertes, le menu-bar, les Raccourcis, l'interface FR/EN… puis une grosse passe de performance et le passage à Swift 6.",[60,77,78,81],{},[14,79,80],{},"Les 20 et 21 août, la 1.0"," : le portage iOS et iPadOS, une refonte visuelle autour de la vignette 32×8, l'afficheur de démonstration, la landing, et tout le dossier de publication App Store.",[49,83,85],{"id":84},"quatre-sections-par-intention","Quatre sections, par intention",[10,87,88,89,92],{},"La 0.1 organisait l'app comme le firmware : par fonction technique. La 0.2 a tout renversé pour organiser l'app ",[14,90,91],{},"par ce qu'on veut faire",", en quatre sections :",[57,94,95,105,115,121],{},[60,96,97,100,101,104],{},[14,98,99],{},"Écran"," : qu'est-ce qui défile sur l'afficheur, et dans quel ordre ? La rotation est éditée comme une playlist, sous forme d'une grille de vignettes qui montrent chacune leur ",[14,102,103],{},"rendu réel en 32×8",". Une carte dorée est à l'écran, une carte grise attend qu'on la tape. On réordonne au glisser-déposer ou par un menu « Avancer / Reculer », et un aperçu live de la matrice trône en haut.",[60,106,107,110,111,114],{},[14,108,109],{},"Studio"," : qu'est-ce que je veux créer ? Des compositions texte avec couleur et icône, du ",[14,112,113],{},"pixel art"," directement en 32×8, et des scènes enregistrées qu'on renvoie d'un geste. Une galerie d'icônes LaMetric intégrée permet de chercher parmi des milliers de pictogrammes et de les installer sur l'afficheur, animations comprises.",[60,116,117,120],{},[14,118,119],{},"Moments"," : qu'est-ce qui doit passer devant la rotation ? Une notification ponctuelle, des règles d'alerte par seuil (CPU, RAM, batterie, température, ou la valeur d'un connecteur) ou à heure fixe, un minuteur et un Pomodoro qui comptent à rebours sur la matrice, des LED témoins.",[60,122,123,126],{},[14,124,125],{},"Appareil"," : luminosité manuelle ou automatique, mode nuit programmé, lampe d'ambiance, fiche des capteurs.",[10,128,129],{},[44,130],{"alt":131,"src":132},"Le Studio et les Moments, sur iPhone","/images/Lumo/studio.webp",[10,134,135],{},"Chaque écran ne fait qu'une chose. Les réglages secondaires (durée de rotation, effet de transition…) ont été sortis dans des feuilles dédiées plutôt que d'encombrer la vue principale.",[49,137,139],{"id":138},"les-connecteurs-le-cœur-du-truc","Les connecteurs, le cœur du truc",[10,141,142,143,146],{},"Si Lumo a une raison d'exister, c'est celle-là. Un ",[14,144,145],{},"connecteur",", c'est quatre blocs :",[148,149,150,160,178,188],"ol",{},[60,151,152,155,156,159],{},[14,153,154],{},"Où"," : une URL, et éventuellement une méthode d'authentification (clé API, jeton Bearer, ou ",[14,157,158],{},"OAuth 2.0 avec PKCE"," et renouvellement automatique du jeton).",[60,161,162,165,166,169,170,173,174,177],{},[14,163,164],{},"Quelle valeur"," : un chemin dans la réponse JSON, du type ",[30,167,168],{},"bitcoin.eur"," ou ",[30,171,172],{},"items[0].value",". Et c'est là que j'ai passé le plus de temps en UX : plutôt que d'écrire ce chemin à la main, l'app affiche la ",[14,175,176],{},"réponse reçue sous forme d'explorateur",", et on touche la ligne qui nous intéresse. Le chemin s'écrit tout seul.",[60,179,180,183,184,187],{},[14,181,182],{},"Comment ça s'affiche"," : un gabarit du type ",[30,185,186],{},"BTC {value}€",", une couleur, une icône, avec un aperçu de la matrice qui se met à jour à chaque frappe.",[60,189,190,193],{},[14,191,192],{},"À quelle fréquence"," : l'intervalle de rafraîchissement.",[10,195,196,197,200],{},"Une trentaine de modèles sont fournis, classés par catégorie : les cours crypto et l'indice Fear & Greed, les taux de change, les étoiles et la CI d'un dépôt GitHub, les téléchargements npm, les visiteurs Plausible en direct, l'altitude et la vitesse de l'ISS, un capteur Home Assistant, la qualité de l'air, la couleur du jour ",[14,198,199],{},"Tempo EDF",", le titre Spotify en cours, les abonnés YouTube… et des trucs plus bêtes comme une blague ou un fait sur les chats.",[10,202,203,204,207,208,211,212,216],{},"Les deux que j'utilise vraiment sont plus personnels. Le premier est ",[14,205,206],{},"mon MRR Stripe"," : un connecteur qui interroge mes abonnements et calcule le revenu mensuel récurrent, rafraîchi toutes les demi-heures. Voir ce chiffre briller sur mon bureau, c'est bête, mais c'est une petite dose de motivation quotidienne 😄. Le second est le ",[14,209,210],{},"quota Claude Code"," : session de 5 h, semaine, et temps avant remise à zéro, avec une couleur qui passe du vert à l'orange puis au rouge à l'approche de la limite. Celui-là a une histoire : il lit le jeton de Claude Code dans le Trousseau d'une ",[213,214,215],"em",{},"autre"," application, ce qui est impossible dans le bac à sable de l'App Store. Il n'existe donc que dans la version distribuée en direct, hors boutique, et la version App Store se rabat sur une clé de session collée à la main.",[10,218,219,220,223],{},"Pour les services connus, tout se passe dans l'app : un bouton « Connecter mon compte », la page de connexion du service s'ouvre dans une fenêtre système, et c'est fini. Aucune clé à créer, aucun identifiant de chaîne à retrouver. Sous le capot, c'est de l'OAuth avec PKCE, donc ",[14,221,222],{},"aucun secret client"," dans l'app, et le refresh token va dans le Trousseau. On se connecte une fois, pas tous les matins.",[49,225,227],{"id":226},"ce-que-seul-liphone-sait","Ce que seul l'iPhone sait",[10,229,230,231,234,235,238],{},"Le portage iOS n'était pas qu'une question d'écran plus petit. Le téléphone sait des choses que le Mac ignore, et elles font d'excellentes sources pour l'afficheur : les pas, les calories, la distance et les minutes d'exercice du jour via ",[14,232,233],{},"Santé",", chacune avec une barre de progression vers l'objectif repris de Fitness ; la fréquence cardiaque ; le sommeil de la nuit ; la batterie du téléphone ; les rappels et les anniversaires du jour ; le mode de concentration actif. Et tout ça continue ",[14,236,237],{},"app fermée"," : HealthKit réveille Lumo à chaque nouvelle donnée et la valeur repart sur l'afficheur.",[10,240,241],{},[44,242],{"alt":243,"src":244},"L'afficheur de démonstration sur iPhone","/images/Lumo/iphone.webp",[10,246,247,248,251],{},"Ce portage m'a aussi appris une règle que je n'avais pas anticipée : ",[14,249,250],{},"une mesure périmée ne doit pas rester affichée",". Quand HealthKit ne renvoyait rien, l'app gardait silencieusement la dernière valeur connue, et un pouls pouvait rester figé pendant des heures en donnant l'impression d'être mesuré en direct. Désormais, passé deux heures, la carte se vide et quitte l'afficheur. Presque toujours, c'est juste la montre qui n'a pas encore synchronisé.",[49,253,255],{"id":254},"la-passerelle-les-raccourcis-le-menu-bar","La passerelle, les Raccourcis, le menu-bar",[10,257,258,259,262,263,266,267,270,271,274],{},"Sur Mac, Lumo embarque une ",[14,260,261],{},"passerelle de notifications"," : un mini serveur HTTP sur le port 8787, ",[14,264,265],{},"en loopback uniquement",", jamais exposé sur le réseau. N'importe quel script peut donc faire clignoter l'afficheur avec un simple ",[30,268,269],{},"curl -X POST localhost:8787/notify -d '{\"text\":\"Build OK\"}'",". Le schéma d'URL ",[30,272,273],{},"lumo://notify"," fait la même chose depuis un Raccourci ou un navigateur. Et des App Intents en français exposent l'essentiel à Siri et aux Raccourcis : envoyer une notification, afficher un texte, basculer sur une app, allumer ou éteindre l'écran, régler la luminosité.",[10,276,277,278,281],{},"Le ",[14,279,280],{},"menu-bar"," macOS, lui, donne la météo, le minuteur, les quotas et l'extinction sans ouvrir la fenêtre.",[49,283,285],{"id":284},"lafficheur-de-démonstration","L'afficheur de démonstration",[10,287,288],{},"C'est la fonctionnalité de la 1.0 dont je suis le plus content, et elle est née d'un vrai problème produit. Le TC001 sort d'usine avec le firmware Ulanzi, pas AWTRIX. Il se passe souvent des jours entre le moment où on installe l'app et celui où on prend le temps de flasher l'objet, et pendant ce temps, l'app n'avait rien à montrer. Sans compter le reviewer Apple, qui n'a évidemment pas d'afficheur sous la main.",[10,290,291,292,295,296,299],{},"« Visiter la démo, sans afficheur », sur l'écran d'accueil, ajoute donc un afficheur ",[14,293,294],{},"simulé en mémoire"," qui répond aux mêmes requêtes que le vrai firmware : rotation qui défile, aperçu live, réglages, notifications, extinction. Le reste de l'app ne sait même pas qu'il est fictif, l'interception tient en cinq lignes dans le client HTTP. Petit détail dont je suis fier : son hôte se termine par ",[30,297,298],{},".invalid",", un domaine réservé par la RFC 2606 qu'aucun résolveur ne résoudra jamais. Même si l'interception venait à manquer, aucune requête ne peut partir sur le réseau de quelqu'un.",[10,301,302,303,306],{},"Bonus inattendu : c'est aussi ce qui permet de ",[14,304,305],{},"générer les captures d'écran automatiquement"," avec des tests UI, avec une matrice identique sur iPhone, iPad et Mac. Elles se régénèrent, elles ne se reprennent jamais à la main.",[49,308,310],{"id":309},"quelques-choix-techniques","Quelques choix techniques",[57,312,313,323,329,339,345,355,369],{},[60,314,315,318,319,322],{},[14,316,317],{},"SwiftUI et Swift 6"," en concurrence stricte, avec ",[30,320,321],{},"@Observable"," et Swift Testing. Environ 15 000 lignes de Swift et 200 tests.",[60,324,325,328],{},[14,326,327],{},"Une seule base de code et un seul bundle"," pour Mac, iPhone et iPad. Ce qui n'existe pas sur iOS (barre des menus, stats du Mac, passerelle locale) est écarté à la compilation, pas dupliqué.",[60,330,331,334,335,338],{},[14,332,333],{},"Pas de mDNS"," : AWTRIX n'annonce aucun service Bonjour. La découverte réseau sonde donc les 254 adresses du sous-réseau en parallèle sur ",[30,336,337],{},"/api/stats",", avec une session URL dédiée pour ne pas monopoliser le pool de connexions de l'aperçu live.",[60,340,341,344],{},[14,342,343],{},"HTTP uniquement",", alors qu'AWTRIX supporte aussi MQTT. Pour une app qui parle à un objet sur le réseau local, c'est plus simple et largement suffisant.",[60,346,347,350,351,354],{},[14,348,349],{},"XcodeGen"," génère le projet Xcode depuis un ",[30,352,353],{},"project.yml"," qui reste la source de vérité. Mais le projet est quand même versionné, parce que Xcode Cloud clone le dépôt et a besoin de quelque chose à compiler ; la CI vérifie à chaque push que le projet commité découle bien du YAML.",[60,356,357,358,361,362,364,365,368],{},"Deux CI complémentaires : ",[14,359,360],{},"GitHub Actions"," pour les tests et le ",[30,363,68],{}," signé et notarisé, ",[14,366,367],{},"Xcode Cloud"," pour archiver et envoyer vers TestFlight.",[60,370,371,372,375,376,383],{},"Une landing en ",[14,373,374],{},"Nuxt 4"," sur ",[377,378,382],"a",{"href":379,"rel":380},"https://lumo.playfan.fr",[381],"nofollow","lumo.playfan.fr",", avec des composants LED maison et une police matricielle reconstruite en TypeScript pour les animations de la page.",[49,385,387],{"id":386},"le-design","Le design",[10,389,390,391,394,395,398,399,402],{},"Le système de design est venu tard, avec la 1.0, et il découle entièrement de l'objet : ",[14,392,393],{},"une lumière chaude dans le noir",". Fond ",[30,396,397],{},"#0A0A0D"," (un noir légèrement bleuté, jamais du noir pur), cartes plates, filet à 7 %, et un doré signature ",[14,400,401],{},"réservé à ce qui est diffusé",", à la section active et à l'action principale. Nulle part ailleurs.",[10,404,405,406,409],{},"J'avais commencé avec le Liquid Glass des systèmes 26, barre latérale translucide et tout. J'y ai renoncé pour des surfaces plates, avec un seul élément élevé par écran : l'aperçu de la matrice. Effet de bord inattendu : l'app n'ayant plus besoin d'API exclusives au millésime 26, j'ai pu abaisser le plancher à macOS 15 et iOS 18, et ",[14,407,408],{},"la CI a enfin pu exécuter les tests"," au lieu de seulement compiler.",[10,411,412,413,416],{},"Tout est calé sur les libellés français, plus longs que l'anglais, avec des cibles tactiles de 44 pt minimum. Et plein de petites décisions de micro-UX que je n'aurais pas vues sans passer par l'iPhone : la confirmation d'un ajout se produit ",[14,414,415],{},"sous le doigt"," (avant, la carte partait tout en haut de la page, hors champ, et on avait l'impression que rien ne s'était passé) ; les pictogrammes 8×8 sont dessinés spécialement, animés en deux ou trois images seulement parce que le décodeur de l'afficheur avale mal les GIF longs ; et les valeurs se passent de leur unité, parce que l'icône porte le sens. « 412 » à côté d'un poulet se lit mieux que « 412 kcal » qui défile.",[10,418,419,420,423],{},"Pour les visuels de l'App Store, j'ai utilisé ",[14,421,422],{},"Claude Design"," avec une charte écrite noir sur blanc (palette, typographie Archivo, trame LED en fond, bandeau de matrice en pied) et une règle stricte : les captures réelles servent de référence de contenu, on redessine plus grand et recadré, mais on n'invente aucun élément d'interface qui n'existe pas. Apple demande que les captures représentent fidèlement l'app, et c'est aussi plus honnête.",[49,425,427],{"id":426},"la-passe-de-performance","La passe de performance",[10,429,430],{},"La 0.4 a été une version de fond, sans nouvelle fonctionnalité visible, parce que j'avais fini par mesurer ce que l'app faisait subir à l'afficheur et au réseau. Ce n'était pas joli :",[57,432,433,440,443,446,453,460],{},[60,434,435,436,439],{},"L'aperçu live tournait ",[14,437,438],{},"à pleine vitesse en permanence",", dans les quatre sections, et même avec Lumo en arrière-plan. Il suit désormais l'attention : 40 ms quand il est déployé, 200 ms pour la mini-matrice, 1 s en arrière-plan, et plus rien quand aucune fenêtre n'est visible.",[60,441,442],{},"Deux vues interrogeaient l'appareil chacune de leur côté. Un sondage unifié a divisé les requêtes par deux.",[60,444,445],{},"Le rendu de la matrice faisait 256 tracés et 256 couleurs par image. Les LED éteintes sont maintenant dessinées en une passe.",[60,447,448,449,452],{},"La barre de progression du scan réseau était rafraîchie ",[14,450,451],{},"254 fois en une seconde",".",[60,454,455,456,459],{},"L'icône météo était re-téléchargée chez LaMetric puis renvoyée à l'afficheur ",[14,457,458],{},"toutes les 15 minutes",", alors qu'elle y était déjà.",[60,461,462],{},"Les connecteurs se rafraîchissent maintenant quatre par quatre : une API qui met 8 secondes à répondre ne retarde plus les autres.",[49,464,466],{"id":465},"les-galères","Les galères",[10,468,469,475,476,478,479,482,483,488],{},[14,470,471,472,452],{},"Le crash Swift 6 dans ",[30,473,474],{},"ForEach"," Découvert la veille de la campagne de captures : ouvrir la section Écran tuait l'app, de façon systématique sous automatisation. En Swift 6, les vues sont isolées sur le MainActor, mais SwiftUI appelle les fermetures d'un ",[30,477,474],{}," ",[14,480,481],{},"depuis son fil de rendu asynchrone",". Appeler une méthode de la vue depuis là déclenche une assertion d'isolation non récupérable. Les deux premières tentatives de correction ont échoué parce qu'elles préparaient encore les données à l'intérieur de la boucle. La règle que j'en ai tirée : ",[14,484,485,486],{},"aucune méthode de la vue ne doit être appelée depuis l'intérieur d'un ",[30,487,474],{},". Tout se calcule avant, la fermeture ne fait que lire. Le plus inquiétant, c'est que le plantage guettait aussi les utilisateurs de VoiceOver, dont l'automatisation d'accessibilité force exactement ces placements.",[10,490,491,494,495,498,499,502],{},[14,492,493],{},"Deux refus de validation le même jour."," L'archive a été signée et téléversée sans une plainte ; c'est la validation d'App Store Connect qui a refusé, par courriel. Le build 7 parce que l'accès à HealthKit oblige à déclarer un texte d'usage ",[213,496,497],{},"en écriture",", même si Lumo n'écrit jamais dans Santé (le texte le dit franchement plutôt que d'inventer un usage). Le build 9 parce que j'avais retiré la description HomeKit alors que le binaire y faisait encore référence : ",[14,500,501],{},"Apple regarde le binaire, pas la liste des sources",". Les deux sont désormais couverts par des tests qui inspectent le bundle construit, pour que l'aller-retour ne se reproduise plus.",[10,504,505,508],{},[14,506,507],{},"Le Trousseau qui redemande l'autorisation à chaque build."," Le trousseau attache sa liste d'accès à la signature du binaire, et une signature ad-hoc change à chaque compilation. Résultat : macOS demandait en boucle si Lumo pouvait lire ses propres secrets. La solution propre existe, mais elle échoue en Debug faute d'entitlement. J'ai fini par signer les builds de développement avec ma vraie identité, ce qui est contre-intuitif mais règle le problème.",[10,510,511,514],{},[14,512,513],{},"La locale des tests et Xcode Cloud."," Plusieurs tests comparent des libellés localisés en français. Xcode Cloud n'accepte aucun argument en ligne de commande et tourne sur un hôte anglophone. La langue est donc fixée dans les schémas Xcode, et la CI vérifie à la ligne qu'elle y reste. Je le sais parce que Tempo, mon autre app, avait perdu deux tests exactement là.",[10,516,517,520],{},[14,518,519],{},"XcodeGen 2.45 contre 2.46."," Deux versions, deux ordres de cibles dans le fichier projet, tous les deux stables mais différents. Le contrôle de dérive de la CI est tombé dès sa première exécution.",[10,522,523,526,527,530,531,534,535,452],{},[14,524,525],{},"L'arobase de vue-i18n."," Sur la landing, ",[30,528,529],{},"vue-i18n"," réserve le ",[30,532,533],{},"@"," pour les messages liés. Une adresse e-mail écrite telle quelle dans un fichier de locale fait partir toutes les pages en erreur 500 au pré-rendu, sans message clair. Il faut écrire ",[30,536,537],{},"contact{'@'}playfan.fr",[10,539,540,541,544],{},"Et une correction moins glorieuse, passée dans la 1.0 : jusqu'ici, ",[14,542,543],{},"une clé Stripe se lisait à l'œil nu dans le fichier de préférences"," de l'app. Les clés d'API, jetons Bearer et jetons OAuth sont maintenant dans le Trousseau, avec une migration silencieuse au premier lancement.",[49,546,548],{"id":547},"et-maintenant","Et maintenant ?",[10,550,551,552,555],{},"La version 1.0 est prête depuis le 20 août, et la publication sur l'App Store est en cours sous le nom ",[14,553,554],{},"Lumo for AWTRIX"," (le nom court était déjà pris). Le manifeste de confidentialité déclare zéro collecte, et c'est vrai : pas de compte, pas de serveur, pas d'analytique, pas de publicité. Lumo parle à ton afficheur sur ton réseau local, et à personne d'autre.",[10,557,558,559,562],{},"L'app est et restera ",[14,560,561],{},"gratuite",". C'est un projet communautaire, non affilié à AWTRIX ni à Ulanzi, fait pour le plaisir d'avoir une belle app pour un bel objet. Et accessoirement, pour voir mon MRR briller sur mon bureau.",[10,564,565,566],{},"La fiche projet, ",[377,567,569],{"href":568},"/projects/lumo","👉 c'est par ici !",{"title":571,"searchDepth":572,"depth":572,"links":573},"",2,[574,575,576,577,578,579,580,581,582,583,584],{"id":51,"depth":572,"text":52},{"id":84,"depth":572,"text":85},{"id":138,"depth":572,"text":139},{"id":226,"depth":572,"text":227},{"id":254,"depth":572,"text":255},{"id":284,"depth":572,"text":285},{"id":309,"depth":572,"text":310},{"id":386,"depth":572,"text":387},{"id":426,"depth":572,"text":427},{"id":465,"depth":572,"text":466},{"id":547,"depth":572,"text":548},"2026-08-27T11:00:00.000Z","J'ai acheté une petite horloge à LED, je l'ai flashée avec AWTRIX, et je me suis retrouvé à lui écrire une app complète pour Mac et iPhone. 💡 Découverte réseau, connecteurs d'API, pixel art, notifications… et deux refus d'Apple au passage.","md","/images/Lumo/cover.webp",{"readingTime":590},{"text":591,"minutes":592,"time":593,"words":594},"14 min read",13.8,828000,2760,true,"/blog/lumo-piloter-un-afficheur-led-awtrix",{"title":5,"description":586},"blog/lumo-piloter-un-afficheur-led-awtrix",[600,601,602,603],"macOS","iOS","Domotique","Projet","YFiLv-4EpMflO3BkxszKk2T5Jgd_NOZOhxuhpl11Qu0",1787859225380]