Contrôler les LEDs de l’APU (PC Engines)

Depuis 2 ans déjà, j’utilise pour mon accès à Internet personnel non pas une de ces box merdiques, moches, bancales (hello Freebox v6), obscures (black box) et pleines de vide (coucou Freebox v6) mais un APU de PC Engines. C’est une carte mère disposant d’un processeur intégré AMD à architecture amd64, 3 ports Ethernet et 3 leds accessibles via GPIO, donc grosso-modo un mini-pc à consommation très réduite. Ce truc est génial.

Par défaut la première LED indique simplement si le routeur est alimenté ou non et les deux autres leds sont éteintes. Je me suis enfin décidé à me pencher dessus afin de m’en servir pour afficher des indicateurs plus utiles tels que la charge réseau ou la charge cpu.

Le noyau Linux, actuellement en version 4.4.3 au moment où j’écris ces lignes, ne dispose pas de pilotes LED ou GPIO permettant d’accéder à ces LEDS depuis le user-space. Cependant Daduke a développé un petit module linux pour combler ce petit manque.

Après une correction mineure sur les sources, probablement du à une évolution des interfaces du noyau depuis, le module compile bien, se charge bien et permet bien d’accéder aux leds. Ceux-ci sont rendus accessibles par le kernel au userspace dans le dossier /sys/class/leds/.

Une fois que les leds sont accessibles, il suffit de les contrôler manuellement en envoyant 0 ou 255 à /sys/class/leds/{your_led}/brightness  comme suit :

# switch off the first led
echo 0 > /sys/class/leds/apu\:1/brightness

# swich on the first led
echo 255 > /sys/class/leds/apu\:1/brightness

Il est également possible d’utiliser un déclencheur géré directement par le kernel, ce qui est bien plus efficace que d’utiliser un processus en user-space si le trigger existe déjà dans le kernel :

# list available triggers on the first led
# the current trigger is in brackets
cat /sys/class/leds/apu\:1/trigger

# set the trigger heartbeat on the first led
echo heartbeat > /sys/class/leds/apu\:1/trigger

Génération du package Gentoo

L’écriture du package se découpe en 3 parties :

  • le fichier Makefile (ou Makefile.am)
  • le fichier ebuild
  • enfin le mode de génération du Makefile (statique ou via configure)

Maintenant que l’on a bien vérifié que ça marche, il est temps de packager afin de faciliter les mises à jour du noyau et éviter de crasser le système avec un vieux make install en dehors de tout contrôle du système de packages de la distribution.

Tout d’abord le Makefile : doit-on l’écrire, doit-on passer par automake, aclocal, autoscan, autoconf, tous ces outils ensembles ? Je n’ai pu trouver qu’un seul how-to sur Internet indiquant comment créer un configure en charge de générer le Makefile pour un module kernel utilisant ces outils, qui pour rappels, permettent entre autre de procéder à un certain nombre de vérification sur le système, la présence de librairies, et générer un Makefile compatible avec le système.
Le tutoriel ne fonctionne pas, indiquant à autoconf une instruction non reconnue.
Vu que tout ces outils semblent un peu trop usine à gaz, je décide de laisser tomber pour le moment et de me contenter du Makefile d’origine.

Ensuite c’est l’écriture de l’ebuild. Celui-ci n’a pas été trop difficile, la documentation des eclass étant plutôt complète et il existe des ebuilds sur lequel on peut s’appuyer comme net-firewall/ipset ou net-firewall/xtables-addons. Cela donne l’ebuild qui suit (cet ebuild compile 2 modules et non un seul, qu’on va voir dans la section suivante) :

# Copyright 1999-2016 Gentoo Foundation
# Distributed under the terms of the GNU General Public License v2
# $Id$

EAPI=5
inherit autotools linux-info linux-mod

DESCRIPTION="Kernel modules to access to leds on the apu motherboard"
HOMEPAGE="https://blog.kveer.fr"
SRC_URI=""

LICENSE="GPL-2"
SLOT="0"
KEYWORDS="amd64"
IUSE=""

DEPEND=""
RDEPEND="${DEPEND}"

MODULE_NAMES='leds-apu(kernel/drivers/leds) apu-btn-gpio(kernel/drivers/gpio)'

pkg_setup() {
    kernel_is -lt 4 3 3 && die "${PN} requires kernel greater than 4.3.3."

    CONFIG_CHECK='LEDS_CLASS MODULES'
    ERROR_LEDS_CLASS='leds-apu requires LEDS_CLASS support in your kernel.'
    ERROR_MODULES='Nonmodular kernel detected. Build modular kernel.'

    linux-mod_pkg_setup
}

src_unpack() {
    mkdir "$S"
    cp "${FILESDIR}"/* "${S}/"
}

src_compile() {
    set_arch_to_kernel
    #KERNEL_DIR=$KERNEL_DIR emake
    BUILD_PARAMS="KERNEL_DIR=$KERNEL_DIR CONFIG_DEBUG_SECTION_MISMATCH=y" linux-mod_src_compile
}

src_install() {
    BUILD_PARAMS="KERNEL_DIR=$KERNEL_DIR" linux-mod_src_install
}

On peut maintenant revenir au Makefile. J’ai trouvé une bonne référence indiquant comment fonctionne les scripts de compilation du kernel et comment de fait écrire un Makefile. Le problème rencontré avec le Makefile de Daduke est qu’il n’est capable de compiler qu’un seul module, or j’en avais deux. Cela m’a amené à créer deux fichiers:

  • un Makefile qui est extrêmement simple, se contentant de passer la main au makefile du noyau Linux
  • un Kbuild qui contient la liste des modules et les fichiers nécessaires à la compilation de chacun des modules
PWD = $(shell pwd)
KDIR = $(KERNEL_DIR)

modules module:
        $(MAKE) -C $(KDIR) SUBDIRS=$(PWD) modules

install: $(MODULE)
        $(MAKE) -C $(KDIR) SUBDIRS=$(PWD) modules_install

clean:
        $(MAKE) -C $(KDIR) SUBDIRS=$(PWD) clean
obj-m := leds-apu.o apu-btn-gpio.o
leds-apu.y := leds-apu.c
apu-btn-gpio.y := apu-btn-gpio.c

Configurer les LEDs de l’apu

J’ai configuré mes leds comme suit:

  • la première led indique la charge CPU du routeur. J’utilise pour cela le module heartbeat, qui fait clignoter la led. La fréquence de clignotement est proportionnelle à la charge CPU (plus le routeur travaille, plus ça clognote vite)
  • la seconde led clignote pour tout paquet arrivant de l’interface wan
  • la dernière led clignote pour tout paquet arrivant de l’interface lan et sortant sur le net

Je paramètre les leds au démarrage du routeur avec ce petit script :

#!/bin/bash

echo heartbeat > /sys/devices/platform/apu-led/leds/apu\:1/trigger
echo netfilter-lednet-in > /sys/devices/platform/apu-led/leds/apu\:2/trigger
echo netfilter-lednet-out > /sys/devices/platform/apu-led/leds/apu\:3/trigger

J’utilise iptables pour déclencher le bip sur les paquets entrants ou sortants (ces deux règles sont un peu bidon et ne servent d’exemple qu’à l’utilisation de la cible LED, il faudra procéder de manière similaire pour capturer des trames IPv6 ou autre avec ebtables) :

iptables -A PREROUTING -i enp1s0 -j LED --led-trigger-id "lednet-in" --led-delay 10 --led-always-blink
iptables -A POSTROUTING -o enp1s0 -j LED --led-trigger-id "lednet-out" --led-delay 10 --led-always-blink

Enfin, on n’oubliera pas de charger les modules au démarrage en modifiant le fichier /etc/conf.d/modules :

modules='led_class ledtrig-heartbeat ledtrig-oneshot ledtrig-timer leds-apu'

N’hésitez pas à me laisser vos script en commentaires.

Amélioration du code du module

Avant de considérer le package comme terminé, j’ai activé tous les warning à gcc en ajoutant les flags -Wall CONFIG_DEBUG_SECTION_MISMATCH=y  et compilé le module, dans le but de corriger tous les warning relevés.  Et c’est là, c’est la merde…

Le principal warning est Section mismatch in reference from the function ma_function() to the variable ma_variable() . De ce que j’ai pu comprendre, cet avertissement indique qu’une fonction fait appel à une variable qui se trouve en dehors de sa section, donc potentiellement qui n’existe plus ou pas encore (donc un pointer stale).

C’est la merde parce que une fois l’erreur comprise, on lit le code source, on cherche à savoir comment ce type d’erreur se résout et on se rend compte que la documentation concernant les fonctions du kernel Linux et la manière de bien écrire un module est très morcelée. Je soulèverai notamment que :

  • pour beaucoup de fonctions constituantes du noyau Linux, il faut se contenter des deux pauvres lignes au-dessus de la signature de la fonction comme seule documentation, sans aucune information de contexte ou d’usage
  • la documentation est complètement éparpillée, entre le code source, les quelques rares fichiers du dossier Documentation dans les sources du noyau et le bouquin d’O’Reilly périmé, et les quelques mail d’une ML officielle ou d’une réponse sur stackoverflow d’un gars connaissant le noyau suffisamment

Franchement non seulement ça ne facilite en rien la maîtrise de l’infrastructure du noyau (et pour ma part cela ne concernait que la partie concernant les modules linux et les IO bruts, c’est à dire une infime partie du noyau), mais ça en devient même frustrant et retire pour ma part tout volonté à y apporter ma contribution (déjà que débugger du C sous Linux c’est une tanné, alors si en plus c’est en mode kernel avec une doc de merde…).

Fin

A mon sens et contrairement à d’autres projets utilisant des langages plus structurés (oui le C c’est quand même bien dégueulasse), il est absolument nécessaire d’avoir une documentation claire indiquant précisant l’impact de certaines macros telles que module_init, module_exit, la différence avec la macro module_platform_driver, l’impact du nom du driver, les différents moyens de déclarer de la mémoire, le mécanisme permettant de la mémoire privée pour un pilote, etc… J’ai dû découvrir tous ces éléments par essais successifs, ce qui n’est pas une méthode d’apprentissage très efficace. En l’état actuel, le développement dans le noyau Linux est pour ma part un no go.

Au final, je n’ai pu résoudre les SECTIONS MISMATCH. Cependant j’ai séparé le module initial de Daduke en 2, l’un concernant uniquement le bouton reset (qui se trouve sur la carte mère), et l’autre concernant le contrôle des LEDs. Le code source pourrait être factorisé mais après avoir passé une nuit à comprendre le fonctionnement d’un module Linux, je n’ai plus le courage de faire ce dev et perdre tous mes cheveux lors du débogage.

Je livre néanmoins le package permettant de compiler le module avec Gentoo, qui se trouve sur mon repo perso. Au moins, mes leds clignotent dans tous les sens, et ça c’est sympa #sapin-de-noel.

Domotisation de ma maison – part 1

Depuis peu, je me suis mis en tête d’ajouter une composante domotique à ma future maison.
Je ne suis pas un expert en électronique, loin de là et mes connaissances actuelles se limitent à l’étude de circuit basique composés de résistances, condensateurs et bobines.
On ne va pas aller loin avec ça, mais qu’à cela ne tienne, ce billet va concentrer le résultats de mes recherches sur le sujet.

Objectif

La domotique, comme plein d’autres secteur, offre un univers dont les perspectives ne sont limités que par notre imagination. Dis autrement, quand on sait faire, ce n’est en général pas la technique qui bloque, mais ce qu’on en fait avec. Par chance, c’est chez moi tout le contraire : l’idée est là, c’est la technique qui pêche.
Dans un premier temps, histoire de se familiariser avec ce nouveau domaine, mes objectifs vont se limiter à commander à distance des interrupteurs.

Ci-dessous la liste des exigences :

  • il faut que l’interrupteur manuel continue à fonctionner normalement, aucun programme ne peut s’adapter à toutes les situations du quotidien, et par conséquent je veux être capable d’imposer mes choix quand j’en ai envie, quitte à modifier ultérieurement le programme pour prendre en compte une nouvelle situation
  • les circuits « esclaves », ceux qui agissent sur les interrupteurs, devront être le plus petit possible, puisque je compte les mettre derrière l’interrupteur, dans le mur
  • les circuits « esclaves » devront également être auto-alimenté, ça serait vraiment con d’utiliser des piles alors qu’ils vont servir d’interrupteur sur le secteur (230V alternatif), de plus, il peut ne pas être évident de détecter un interrupteur mort et/ou d’en changer la pile
  • les circuits « esclaves » devront être conçus pour consommer un minimum d’énergie, étant donnés qu’ils vont être en fonctionnement permanent. Je vise, à priori, le 1W, avec une tension de 12V maxi et un courant consommé de 10mA. Dans ces conditions (très faible consommation), la consommation elle-même est une donnée beaucoup plus importante que l’efficacité du circuit, c’est à dire que je préfère avoir un circuit « pourri » qui consomme mon 1W, plutôt qu’un circuit à meilleur rendement qui consommerait 2W ; si on a les deux, tant mieux, mais l’important reste la consommation dans l’absolu.
  • l’ensemble des circuits esclaves devront pouvoir communiquer dans les 2 sens avec le circuit maître, afin de pouvoir recevoir un ordre du maître, mais également de pouvoir communiquer son état au maître
  • le circuit maître sera directement connecté avec un pc-routeur tout le temps allumé.

Les points à respecter étant posés, voyons chaque problèmes qui se sont présenté à moi et les solutions que j’ai retenu.

L’alimentation

L’alimentation par transformateur

Tout serait tellement plus simple si EDF envoyait du courant continu… mais ce n’est évidement pas le cas et il faudra faire avec.
J’ai étudié en cours de manière basique une alimentation « classique », c’est à dire avec transformateur, pont de diode, et un condensateur bien placé pour redresser le courant à la sortie du pont de diode (en savoir plus).

On remarquera au passage que ce type d’alimentation isole physiquement le circuit de son alimentation. C’est ce qu’on appelle une isolation galvanique et c’est une bonne chose, surtout vis à vis d’EDF.
Ce n’est pas une alimentation acceptable pour mon projet : non seulement le transformateur prend beaucoup de place, mais en plus il ferait du bruit. De plus, son rendement a l’air vraiment pourri (50%).

Les autres alimentations

J’ai pu trouver 3 autres manières de générer un courant basse tension continu à partir du secteur :

  • l’alimentation par résistance,
  • l’alimentation par condensateur ou « capa chuteur »
  • trois vieux composants, le MAX610 de MAXIM, le HIP5600 de Harris et le HV2405E de Intersil, tous obsolètes

On va sans plus s’étendre laisser tomber les vieux composants, bien qu’ils eussent pu résoudre rapidement le problème de l’alimentation.

L’alimentation par résistance semble peu prometteuse car là où le condensateur conserve de l’énergie pour la rendre plus tard, les résistances vont la dissiper. La perte énergétique risque d’être plus importante, et l’échauffement généré peut être problématique pour un circuit qui sera confiné et non aéré.

Il reste donc la solution « capa chuteur », que je retiens. Je vais probablement prendre la variante avec pont de diode puisque cela va permettre de diminuer de moitié l’ondulation résiduelle et augmenter de façon significative le rendement de l’alim.
Je ne sais pas exactement ce que va consommer mon circuit, mais l’ajout de 4 diodes, pour un prix dérisoire sont au pire superflus, au mieux augmenteront la durée de vie du micro-contrôleur par une tension plus stable.

L’interrupteur

Il n’a pas été facile de trouver l’interrupteur idéal. Qu’est ce qu’il doit faire cet interrupteur :

  • pouvoir être fermé ou ouvert par l’envoi d’un signal de très faible amplitude et si possible avec une très faible tension également, genre +3~5V
  • fonctionner sur du courant alternatif

J’ai pu trouver deux composants qui pouvaient répondre à ce besoin :

  • le TRIAC
  • le relais

Le relai électromécanique n’est pas très tentant à cause de sa taille imposante et son bruit, je le laisse donc de côté, sauf s’il n’y a pas mieux.
Le relai état solide (solid state) ne me semble pas indispensable.

Le TRIAC au contraire est plutôt intéressant par sa taille, mais en étudiant son fonctionnement, il y a juste un oOps des plus dérangeant. Pour que le TRIAC laisse passer un courant alternatif, donc qui passe sur la barre des 0V très souvent, la tension appliquée à la gateway doit être  »permanente », mais le seuil de déclenchement du TRIAC dépend de la polarité du courant passant.
http://www.bristolwatch.com/ele/triacs.htm
Soit donc il faut synchroniser les impulsions envoyés à la gateway avec le 50Hz de EDF, soit on oublie directement cette solution, qui :

  • provoque des micro-coupures
  • déforme le signal
  • serait responsables de parasites HF, et je n’ai aucune envie d’envoyer ça à mes appareils sensibles

On peut à la place utiliser un opto-triac, qui est la même chose, mais où la gateway est remplacée par une led à mettre sous tension. Tant que la LED est allumée, l’opto-triac est passant, pile ce qu’il me faut.
Actuellement, j’ai trouvé 2 candidats opto-triacs :

  • le MOC3021, peut servir d’interrupteur 230V, mais ne possède pas de détecteur de passage au 0V
  • le MOC3041, peut servir d’interrupteur 210V, mais possède un détecteur de passage au 0V, cela permet de réduire les parasites émis.

Ces modèles semblent également obsolètes, il faut que j’en cherche d’autres en choisissant judicieusement leur caractéristique, en particulier un courant d’entrée le plus petit possible.

Taille

En surfant, il se trouve que les composants traditionnels, ou traversants sont en perte de vitesse devant les composants montés en surface (CMS). J’en avais déjà vu plein en réalité, mais je n’avais tout simplement pas pu les identifier. On a ainsi sous ce format des résistantes et des diodes.
Ce format est mieux en tout :

  • beaucoup moins encombrant
  • plus efficace
  • il n’est plus nécessaire de faire des trous sur le circuit imprimé

Le seul inconvénient à mes yeux serait qu’il est plus délicat à souder que les composants traditionnels, mais c’est loin d’être impossible et cela semble se faire très bien avec un fer à souder correct (27W semble idéal, au-delà il y a risque de cramer le composant), il faudra juste plus de précision et une pince.

Communication

Je pense partir sur une communication sans-fil. L’autre moyen étant d’utiliser le CPL, cela ne conviendra pas au circuit master qui sera alimenté par le PC et non directement par le secteur.
J’ai trouvé un transceiver (émetteur et récepteur) Aurel RTX MID 3V qui pourrait bien faire l’affaire, mais à 20€ pièce, j’ai pas intérêt à le faire griller durant mes tests.
D’après les spécifications, ce composant bouffe 20.008mA au maximum, c’est à dire en émission.
Aurel propose pour ce module un [manuel utilisateur|http://www.aurelwireless.com/wp-content/uploads/user-manual/650201033G_um.pdf|en] très précieux, puisqu’il indique la procédure à suivre pour basculer en mode émission ou réception, à savoir les pins à basculer et le délai d’attente incompressible entre 2 bascules.

Récapitulatif

Sources

Il y en a beaucoup trop pour que je les cites tous, surtout que ce billet a été rédigé en plein milieu de mes recherches, voici néanmoins les principaux :