UI to manage lan etherdream dacs via builtin OSC commands
  • Python 99.4%
  • Shell 0.6%
Find a file
leduc 755252fb7b docs: README, changelog, devbook et journal alignés sur le code
Le README garde ce qui a de la valeur hors du dépôt : les six pièges du
protocole ED1, cités dans le firmware (port de réponse 60001, .ild à la
racine de la carte SD, /ilda/<n>/play borné à 10, noms tronqués à 15
caractères, /ilda/pps sans effet sur un fichier de carte SD car le lecteur
réimpose le débit de l'en-tête, liste UDP Arriv par rafales), et le format
d'autoplay.txt.

Corrections : l'arborescence omettait graph.py et EtherTool.app, le compte
de tests annoncé (57) datait de la v0.1.0 (70 depuis la v0.2.0), la section
interface listait quatre onglets pour cinq puces, et le CHANGELOG était
dupliqué en fin de README. Les chemins absolus de la machine de
développement sont retirés au profit de la racine du dépôt, et la recette
pytest (il faut un Python avec _tkinter) est écrite.

État assumé dans le README : le protocole est validé contre le firmware
source et le faux DAC, jamais contre du matériel réel (D5 du devbook : le
réseau du DAC passe par le VPN utun4, aucune réponse en ping, TCP 7765,
UDP 7654 et UDP 60000). Un Ether Dream V2 n'est pas couvert.
2026-10-24 23:25:32 +02:00
docs docs: README, changelog, devbook et journal alignés sur le code 2026-10-24 23:25:32 +02:00
ethertool feat(cli): ligne de commande équivalente à l'interface graphique 2026-10-24 23:25:32 +02:00
EtherTool.app/Contents feat(ui): interface Tkinter à cinq onglets, graphique d'occupation du buffer 2026-10-24 23:25:02 +02:00
tests feat(ui): interface Tkinter à cinq onglets, graphique d'occupation du buffer 2026-10-24 23:25:02 +02:00
.gitignore chore: initialisation du dépôt 2026-10-24 23:23:51 +02:00
CHANGELOG.md docs: README, changelog, devbook et journal alignés sur le code 2026-10-24 23:25:32 +02:00
pyproject.toml chore: initialisation du dépôt 2026-10-24 23:23:51 +02:00
README.md docs: README, changelog, devbook et journal alignés sur le code 2026-10-24 23:25:32 +02:00

ethertool

Pilotage OSC des Ether Dream (ED1) du réseau local : détecter les DAC, voir ce qu'ils jouent, et leur envoyer les commandes de l'User Guide (lecture d'un .ild de la carte SD, cadence, géométrie, réseau).

Interface graphique Tkinter et ligne de commande, sans aucune dépendance Python tierce (l'OSC est encodé à la main).

ethertool/
├── ethertool/
│   ├── osc.py        encodage / décodage OSC (UDP, sans dépendance)
│   ├── protocol.py   décodage du broadcast ED1 (datagramme de 36 octets)
│   ├── dac.py        les commandes OSC du firmware ED1
│   ├── discovery.py  détection : broadcast UDP 7654 + sonde TCP 7765
│   ├── graph.py      graphique Canvas de l'occupation du buffer
│   ├── app.py        interface graphique Tkinter
│   └── cli.py        ligne de commande
├── tests/            70 tests, dont un faux DAC qui imite le firmware
├── EtherTool.app/    bundle macOS (double-clic = lancement)
└── docs/             devbook + journal

Installation

Aucun paquet à installer. Il faut Python 3.11 ou plus récent, et ce Python doit avoir Tkinter (l'interface graphique en dépend) :

# macOS, avec Homebrew (python@3.11 embarque tkinter)
brew install python@3.11

# ou avec uv
uv python install 3.11

État de la version

  • Le protocole est validé contre le firmware source (j4cDAC) et contre le faux DAC des tests, pas contre du matériel réel : aucune machine de ce dépôt n'avait d'Ether Dream joignable au moment du développement (le réseau du DAC passait par un VPN, sans réponse — voir D5 dans le devbook). La première connexion réelle reste à faire.
  • Les commandes exposées sont celles du firmware ED1 (V1). Un Ether Dream V2 (etherdream-2) a d'autres adresses OSC : non testé ici.

Lancement

cd ethertool            # à la racine du dépôt

# interface graphique
python3.11 -m ethertool.app

# ligne de commande
python3.11 -m ethertool.cli --help

Ligne de commande

# balayer une plage d'adresses (le broadcast peut etre filtre par le reseau)
python3.11 -m ethertool.cli scan 192.168.1. 1-254

# sonder une adresse precise sur le port TCP de controle
python3.11 -m ethertool.cli probe 192.168.1.4

# lister les .ild presents sur la carte SD
python3.11 -m ethertool.cli list 192.168.1.4

# jouer un fichier par son nom
python3.11 -m ethertool.cli play 192.168.1.4 maze.ild

# jouer le 3e fichier trouve sur la carte
python3.11 -m ethertool.cli play-index 192.168.1.4 3

# arreter, regler la cadence, la repetition
python3.11 -m ethertool.cli stop   192.168.1.4
python3.11 -m ethertool.cli pps    192.168.1.4 30000
python3.11 -m ethertool.cli fps    192.168.1.4 25
python3.11 -m ethertool.cli repeat 192.168.1.4 on

# geometrie
python3.11 -m ethertool.cli geom 192.168.1.4 size 0.5
python3.11 -m ethertool.cli geom 192.168.1.4 offset 0.4 -0.2
python3.11 -m ethertool.cli geom 192.168.1.4 corner tl -1 1

Interface graphique

Quatre onglets, une ligne par Ether Dream détecté, et un journal en bas.

  • Contenu — la liste des .ild de la carte SD (renvoyée par le firmware), lecture par index ou par nom exact, /ilda/repeat, /stop.
  • Cadence — /ilda/fps (0 à 100) est mis en avant : c'est le seul levier de vitesse réel sur un .ild. /ilda/pps est aussi présent mais annoté comme sans effet sur un fichier de carte SD (le firmware réimpose le débit déclaré dans l'en-tête du fichier). Bouton « Relire » pour récupérer les valeurs rapportées par le DAC.
  • Buffer — graphique de la taille utilisée du buffer (buffer_fullness), un échantillon par seconde. Une courbe qui descend vers zéro signale un underflow (le DAC consomme plus de points qu'il n'en reçoit).
  • Géométrie — /geom/size, /geom/offset, les quatre coins, le verrouillage des côtés, et par couleur le gain, l'offset et le décalage temporel.
  • Réseau — configuration IP statique (/net/ipaddr, /net/netmask, /net/gateway) et diagnostics.

Les quatre pièges du protocole ED1

Ces points viennent de la lecture du firmware j4cDAC (https://github.com/j4cbo/j4cDAC), pas de la documentation.

1. Les réponses arrivent sur le port 60001

Le firmware écoute les commandes sur le port UDP 60000, mais répond toujours sur le port 60001 de la machine d'origine :

/* firmware/net/osc.c */
udp_bind(&osc_pcb, IP_ADDR_ANY, 60000);
udp_sendto(&osc_pcb, p, osc_last_source, 60001);

Un client qui n'écoute pas sur 60001 ne recevra jamais les readouts (/ilda/<n>/name, /ilda/ppsreadout, …). C'est la raison pour laquelle ethertool lie explicitement son socket sur 60001.

2. Les .ild sont lus à la racine de la carte SD

Le firmware ouvre le répertoire racine et itère sur ses entrées :

/* firmware/net/ilda-osc.c, walk_fs() */
f_opendir(&dir, "");
...
if (finfo.fattrib & AM_DIR) continue;   /* les sous-dossiers sont ignorés */

Un nom comme shows/maze.ild est donc refusé par le client.

3. /ilda/<n>/play s'arrête à 10

Le firmware n'enregistre que /ilda/1/play … /ilda/10/play. Au-delà, il faut passer par /ilda/play <nom>.

De plus, les noms renvoyés dans la liste sont tronqués à 15 caractères (contrainte héritée de TouchOSC) :

if (strlen(fn) > 15) fn[15] = '\0';

L'interface signale les noms tronqués et propose un champ « nom exact ».

4. L'User Guide est trompeur sur un point

La documentation écrit : « Playing a file by name can currently only be done via autoplay.txt, not via OSC ». Le firmware expose pourtant { "/ilda/play", PARAM_TYPE_S1, ... } : c'est bien possible en OSC.

5. /ilda/pps est sans effet sur un fichier de la carte SD

Le lecteur .ild appelle lui-même dac_set_rate() avec le débit déclaré dans l'en-tête du fichier ILDA, donc le pps envoyé par OSC est écrasé à chaque nouvelle frame. /ilda/fps est le vrai levier : le firmware calcule ilda_points_per_frame = dac_current_pps / max_fps et répète les frames pour atteindre ce ratio.

6. La liste des fichiers peut arriver incomplète

L'UDP est sans connexion : le firmware envoie les noms d'un seul trait (85 paquets pour 85 fichiers) et certains peuvent se perdre. ethertool refait donc la collecte tant que les index reçus ne forment pas une suite contiguë, et fusionne les lots successifs — la liste est complète même avec 50 % de paquets perdus (testé dans tests/test_dac.py).

Le fichier autoplay.txt

Au démarrage, le firmware lit autoplay.txt à la racine de la carte SD et exécute chaque ligne comme une commande OSC. C'est le moyen de faire démarrer un show automatiquement au boot :

/net/ipaddr 192.168.1.4
/net/netmask 255.255.255.0
/net/gateway 192.168.1.1
/ilda/pps 30000
/ilda/fps 25
/ilda/repeat 1
/ilda/play maze.ild

Un répertoire de ressources (~/codesync/etherdream/) contient déjà 85 .ild et un autoplay.txt de ce type.

Tests

cd ethertool            # à la racine du dépôt
python3.11 -m unittest discover -s tests -t .

Septante tests, sans aucun matériel : tests/fake_dac.py simule un Ether Dream (réponses sur le port 60001, troncature des noms à 15 caractères, index limité à 10, perte de paquets). Il sert aussi de documentation exécutable du protocole.

$ python3.11 -m unittest discover -s tests -t .
Ran 70 tests in 16.9s

OK

Avec pytest : il faut un Python qui possède _tkinter (l'onglet Buffer importe Tkinter). Le pytest installé via pipx sur un Python sans Tkinter échoue à la collecte. Recette :

python3.11 -m venv .venv && .venv/bin/pip install -q pytest
.venv/bin/python -m pytest -q      # 70 passed

Voir aussi

  • CHANGELOG.md — l'historique des versions.
  • docs/devbook.md — la file des demandes (D…) et les décisions à trancher.
  • docs/journal.md — le journal daté du développement, avec les causes racines et les mesures.