Doc de Laumio : une lampe connectée programmable

Un Laumio est une lampe Ikea bidouillée par le HAUM et incluant 13 LEDs RGB. Celui-ci peut être programmé directement en Wifi via socat, l’interprêteur Python or en… regardant l’API.

Tout le code est disponible sur le dépôt Github dédié. Vous êtes libres de l’utilier, de le cloner, de le modifier, et de le publier.

Une version anglaise de la documentation est aussi disponible ici.

Conception du Laumio

Le Laumio est constitué d’une lampe Ikea FADO et d’une structure « maison » contrôlant 13 LEDs RGB.

Cette structure physique est elle-même constituée d’une partie mécanique supportant les lampes et du système électronique associé.

Partie mécanique

Astuce

Tous les fichiers de conception peuvent être récupérés sur le dépôt Git.

La structure mécanique est composée d’un tronc central conçu pour se tenir sur la douille de l’ampoule. Celui-ci a été usiné grâce à une minifraiseuse et assemblé sans colle.

_images/structure.png

Deux propositions ont ensuite été portées pour accueillir les LEDs en elles-mêmes.

La première proposition est basée sur des bras pliables, les LEDs étant collées à leurs extrémités. Les problèmes qui en découlent sont principalement la fragilité de la conception (notamment au niveau des soudures) et l’importante manipulation nécessaire pour insérer cet arbre dans le verre.

La seconde proposition est toujours à l’étude. Celle-ci utiliserait des bandes de plastique souple reliées en haut et en bas de la structure.

Système électronique

Le système électronique est construit autour d’un ESP Wemos d1 Mini de de 13 LED WS2812. Afin de réutiliser le Wemos dans d’autres projets, il a été décidé de créer un shield adapter pour y connecter le microcontrôleur. Les fichiers de conception KiCAD design de ce shield peuvent être téléchargés ici.

Le code utilisé dans l’ESP est aussi présent sur le dépôt, merci de lire le fichier README pour être en mesure de compiler et de flasher le programme.

Une fois flashé et démarré, le Laumio va chercher à se connecter au réseau WiFi spécifié (voir wifi-config.h) et attendre des instructions UDP ou HTTP.

Démarrage

Au lancement, le Laumio clignote en violet puis démarre une animation de couleur rouge : il est en train de chercher le réseau WiFi spécifié et va tenter de s’y connecter.

Si la connexion est réussie, le Laumio lancera une animation Arc-en-ciel. Sinon, il s’illuminera en orage, signalant par-là que le mode Access Point est activé. Ce mode permettra à l’utilisateur de donner les informations nécessaires pour connecter l’appareil à un réseau WiFi valide.

Anatomie du Laumio’s et and commandes en valeurs hexadécimales

Le laumio peut être commandé via une API UDP minimale utilisant des commandes simples en hexadécimal envoyées sur le port 6969, via une interface REST ou à travers MQTT.

Quatre types de sous-ensembles sont utilisables pour commander les LEDs du Laumio : le pixel (une seule led), l’anneau, la colonne et le Laumio dans son intégralité. Il est aussi possible de lancer quelques animations plus spécifiques.

Les LEDs sont disposées en 4 branches de trois LEDs, en plus d’une à son sommet. Celles-ci sont toutes numérotées de 0 à 12 depuis le bas d’une des branches vers sa cîme, évitant l’unique led du sommet, descendant ensuite de l’autre côté de la structure, et enfin remontant du bas d’une des deux branches restantes pour atteindre le bas de la dernière (la LED de sommet étant cette fois-ci reliée entre les deux branches).

Les anneaux correspondant quant à eux à une ligne horizontale de LEDs. Il y en a trois numérotées de 0 à 2, de bas en haut.

Enfin, dans le cas des colonnes, celles-ci se rapportent aux quatre branches de LEDs, bien entendu sans la LED du sommet.

API UDP minimale

Commande pixel par pixel

La commande est 0x00. Les données à envoyer sont d’abord l’ID du pixel considéré et les trois octets de la couleur (RGB) choisie:

0x00 PixelID R G B

Commande anneau par anneau

La commande est 0x01. Il faut d’abord spécifier l’ID de l’anneau (0x00, 0x01 or 0x02) puis préciser les trois octets de couleur:

0x01 RingID R G B

Commande colonne par colonne

La commande est 0x02. Ici, il faut indiquer l’ID de la colonne (0x00, 0x01, 0x02 or 0x03) puis les trois octets de couleur:

0x02 ColumnID R G B

Remplissage intégral

La commande est 0xff, il suffit ensuite de specifier la couleur RGB:

0xff R G B

Animations

Les animations prédéfinies sont encore en développement mais deux d’entre elles ont déjà implémentées dans le Laumio.

Remplissage progressif

L’animation de Remplissage progressif (« color wipe ») change passe le Laumio intégralement dans une seule couleur, mais LED après LED. Elle prend deux paramètres, que sont les trois octets de la nouvelle couleur et l’intervalle de temps entre de deux LEDs en millisecondes:

0x0b R G B Delay
Arc-en-ciel

L’animation Arc-en-ciel (« rainbow ») ne prend pas de paramètres. En l’occurence, c’est celle utilisée par le Laumio quand celui-ci réussit à se connecter à un réseau WiFi:

0x0a

API REST

L’API REST peut être appelée en envoyant des données sur le chemin /api/.

Statut

Le statut du Laumio peut être obtenu en utilisant un endpoint de l”/api/:

GET http://<laumio_ip>/api/

La réponse reçue et de la forme suivante :

{"hostname":"laumio","version":"devel"}

Commandes

Le Laumio peut être controllé à travers un simple requête POST avec une donnée JSON jointe. Voir l’API JSON pour les détails du contenu de ces JSON.

En cas de succès :

{"hostname":"laumio","status":"Success"}

En cas d’erreur :

{"hostname":"laumio","status":"Invalid Request","massage":"Unable to parse JSON"}

API MQTT

Si le Laumio a pu se connecter au broker, il peut être contrôlé à travers MQTT.

Status de connexion

A la connexion le laumio publie sur le topic laumio/<name>/status son status de connexion, Si le laumio se trouve hors ligne ce topic publiera un status offline

Annonce

À la connexion, il publie son nom sur le topic laumio/status/advertise.

Ce même message est envoyé quand il reçoit une commande discover.

Commandes

La commande est choisie en fonction du topic : laumio/all/<cmd> ou laumio/<name>/<cmd> selon que vous voulez l’envoyer à tous les clients connectés ou à un en particulier.

set_pixel

Change la couleur d’une led.

Les 4 octets du message sont le numéro de la led suivi des composantes rouge, vert, bleu de la couleur (0 à 255)

set_ring

Change la couleur d’un anneau.

Les 4 octets du message sont le numéro de l’anneau suivi des composantes rouge, vert, bleu de la couleur (0 à 255)

set_column

Change la couleur d’une colonne.

Les 4 octets du message sont le numéro de la colonne suivi des composantes rouge, vert, bleu de la couleur (0 à 255)

color_wipe

Démarre l’animation de remplissage progressif avec une couleur et une durée.

Les 4 octets du message sont les composantes rouge, vert, bleu de la couleur (0 à 255) suivies de la durée.

animate_rainbow

Démarre l’animation arc-en-ciel.

Le contenu du message est ignoré.

fill

Change la couleur de toutes les leds.

Les 3 octets du message sont les composantes rouge, vert, bleu de la couleur (0 à 255)

json

Envoie des commandes JSON via l’API JSON.

discover

Renvoie un message sur le topic laumio/status/advertise contenant son nom.

API JSON

Cette API ne peut pas être utilisée seule. Elle est accédée soit par la commande json de l’interface MQTT ou par l’API REST.

Commandes

set_pixel

Change la couleur d’une led.

{
  'command': 'set_pixel',
  'led': PixelID,
  'rgb': [R, G, B]
}
set_ring

Change la couleur d’un anneau.

{
  'command': 'set_ring',
  'ring': RingID,
  'rgb': [R, G, B]
}
set_column

Change la couleur d’une colonne.

{
  'command': 'set_column',
  'column': ColumnID,
  'rgb': [R, G, B]
}
color_wipe

Démarre l’animation de remplissage progressif avec une couleur et une durée.

{
  'command': 'color_wipe',
  'duration': Duration,
  'rgb': [R, G, B]
}
animate_rainbow

Démarre l’animation arc-en-ciel.

{
  'command': 'animate_rainbow',
}
fill

Change la couleur de toutes les leds.

{
  'command': 'fill',
  'rgb': [R, G, B]
}

Commandes multiples

Quelques commandes peuvent être chaînées dans un même appel lorsqu’elles sont regroupées dans un tableau nommé commands, mais notez que la taille du JSON est quelque peu limitée.

{
  'commands': [
    {
      'command': 'set_column',
      'column': 0,
      'rgb': [255, 0, 0]
    },
    {
      'command': 'set_column',
      'column': 2,
      'rgb': [0, 0, 255]
    }
  ]
}

Commande pixel par pixel (ancienne API)

{
  'led': PixelID,
  'rgb': [R, G, B]
}

Remplissage intégral (ancienne API)

{
  'led': 255,
  'rgb': [R, G, B]
}

Interface bash minimale

Le Laumio peut être commandé très simplement en utilisant socat ou n’importe quel autre outil réseau bien conçu.

Nous vous proposons le code suivant en guise d’exemple et de base de travail :

#!/bin/bash

# Consts
IP=             # Laumio's IP
ANIM_TIME=0.05
PAUSE_TIME=0.3

# Utils
fill() {
  echo -en "\xff\x$1\x$2\x$3"
}

led() {
  echo -en "\x00\x$1\x$2\x$3\x$4"
}

ring() {
  echo -en "\x01\x$1\x$2\x$3\x$4"
}

column() {
  echo -en "\x02\x$1\x$2\x$3\x$4"
}

# Program
(
fill 00 00 00
sleep $ANIM_TIME

while true; do

  # write your animation here...

done
) | socat - udp-sendto:$IP:6969

En outre, il est également possible d’envoyer plusieurs commandes en une seule requête, comme dans l’exemple suivant:

Ici, le Laumio est d’abord intégralement passé en rouge, puis la LED du sommet est passée en blanc.

Librairie Python

La classe Laumio est construite comme une encapsulation des commandes minimales présentées dans la page de l’API.

class Laumio
__init__(ip)

Constructeur pour contrôler un Laumio répondant à l’adresse IP ip.

wipeOut()

Eteint toutes les LEDs.

En réalité juste un alias pour :py:method:`fillColor`.

fillColor(r, g, b)

Remplit toutes les LEDs du Laumio avec la couleur (r, g, b).

fillRing(ringid, r, g, b)

Remplit un anneau d’une seule couleur. ringid est dans l’intervalle 0~3 et r, g, b précisent la couleur. La LED du sommet n’est pas commandable ici.

fillColumn(colmunid, r, g, b)

Remplit une colonne d’une seule couleur. columnid est dans l’intervalle 0~4 et r, g, b précisentla couleur. La LED du sommet n’est pas incluse dans les colonnes.

setPixelColor(pixel, r, g, b)

Passe un pixel pixel (0~12) à la couleur (r, g, b) demandée.

colorWipe(r, g, b, delay)

Lance une animation de remplissage progressif avec la color (r, g, b) et un intervalle de temps delay entre deux LEDs.

rainbow()

Lance une animation arc-en-ciel.

status()

Return the JSON string for the Laumio’s status

_send(payload)

Conventionnaly private, this method is used to transmit a raw bytearray payload to a Laumio. It can be used to trigger custom animations.