Summary of how the tree BeagleBone Blacks are set up (October 2026), with the low-level details needed to change or undo things in the field.
Each unit is a BeagleBone Black (Debian 10 Buster, BeagleBoard.org IoT image 2020-04-06, kernel 4.19) with a USB C-Media sound card and one LED strip.
Signal chain:
mic -> USB card (hw:1,0) -> jackd -> Pd 0.55 (effectTry4.pd) -> speaker
|
+-- [netreceive] TCP 9990 --> ww-led --> OPC over UDP 7891 --> ledscape (opc-server) --> LEDs
jackdaudio.service) runs the USB sound card. The card only supports 44.1k/48k; never give jackd a lower -r. The card takes 30–50 s to appear after boot.doTheTree.service → ~/bin/doTheTree.sh) waits for jackd, sets the mixer, then runs ~/ghostTree/effectTry4.pd. The effect runs at 12 kHz inside subpatch main12k. effectTry2.pd is the untouched 48k original; effectTry3.pd is the 12k version without recording.ww-led.service, ~/dev/ww-led) is a TCP client of Pd on 127.0.0.1:9990. It sends a frame to ledscape only when Pd sends something. Config is in python/config.py; it drives one string of 100 LEDs. See LED count and frame rate.ledscape.service, opc-server, config /etc/ledscape-config.json) plays its built-in “fade” demo after 5 s with no data. That idle fade is the intended look. opc-server always shows as a zombie in ps; that's normal.jackPlumbing.service → ~/bin/runPlumbing.sh, rules in /etc/jack-plumbing) is kept for future use. It waits for jackd before starting.[pd recordLive] in effectTry4 writes the raw 48k mic to ~/recordings/mic_YYYYMMDD_HHMMSS.wav while live.| Unit | IP | Notes |
|---|---|---|
| unit1 | 10.42.0.61 | flasher image made from this unit |
| unit2 | (to do) | spare card ready (has rec/) |
| unit3 | (to do) | spare card ready (has rec/) |
| unit4 | 10.42.0.64 | was “unit2” on .62; reference unit; has ShellHub (chameleons-bb) |
| unit5 | (to do) | spare card ready (has rec/) |
Login: debian with an SSH key. sudo asks for the debian password. Gateway 10.42.0.1, DNS 8.8.8.8 and 1.1.1.1.
Everything is done from a PC by ~/treeSetup/setup-tree.sh.
treeSetup folder (about 80 MB): the five scripts, payload/ and README.md. Copy it to another PC as a tar archive (tar -cf treeSetup.tar treeSetup); a FAT USB drive loses the execute permissions Pd and the scripts need.ssh, scp, ssh-copy-id, ssh-keygen, rsync 3.1 or newer, and an SSH key (ssh-keygen -t ed25519 if there is none).payload/ is the copy of Pd, ghostTree, the ww-led edits, ~/bin scripts and doTheTree.service taken from the reference unit. If you delete it, the script re-fetches it from unit4 (10.42.0.64, or set REF=ip).sudo bash ~/test-sdcard.sh
The result must be OK; fake cards fail here. On a PC with a card reader, use sudo bash test-sdcard.sh /dev/sdX from the treeSetup folder (find the device with lsblk). It refuses non-removable disks and asks you to type yes.
sudo bash ~/setup-sdcard.sh format
rec/ folder. setup-tree.sh creates it on the inserted card; for a spare card, reformat it or run sudo bash ~/recordings-subfolder.sh with it inserted.ip neigh, or connmanctl services on its console).cd treeSetup && ./setup-tree.sh
It asks for:
unit1 to unit5)activeww-led connected to Pdrecordings: microSD card /dev/mmcblk0p1[/rec]fstrim.timer: enabledrsyslog inactive, /tmp tmpfs, commit=60RemoveIPC=no ok and fsck.repair=yes okreboot rule ok
The script stops if ~/dev/ww-led on the unit is not at git commit 400f44de..., so it never overwrites an unknown version. The ww-led files it replaces are first saved as *.orig.
As debian:
~/installs/pd-0.55-0, ~/ghostTree (without recordings), the four ww-led files, and ~/bin (doTheTree.sh, prune-recordings.sh, restartIfJackIsJacked.sh, runPlumbing.sh).setup-sdcard.sh, test-sdcard.sh and reduce-writes.sh in ~.*/10 * * * * /home/debian/bin/prune-recordings.sh*/5 * * * * /home/debian/bin/restartIfJackIsJacked.sh
As root (~/setup-tree/root-setup.sh):
127.0.1.1 line in /etc/hosts./opt/dataplicity, the dataplicity user).doTheTree.service and enables jackdaudio, doTheTree, ledscape, ww-led and jackPlumbing./etc/ledscape-config.json (original kept as .orig).reduce-writes.sh.recordings-subfolder.sh: the two fstab lines for the card, and rec/ on the inserted card.fstrim.timer (weekly TRIM).fsck.repair=yes to /boot/uEnv.txt, with a backup at /boot/uEnv.txt.bak./etc/systemd/logind.conf.d/keep-ipc.conf.
~/chameleon-ghostTree-test-2026-10-23.img.xz (914 MB) is an eMMC flasher made with BeagleBoard's /opt/scripts/tools/eMMC/beaglebone-black-make-microSD-flasher-from-eMMC.sh. It fits any card of 8 GB or more.
sdX with the card's device (check with lsblk): xzcat chameleon-ghostTree-test-2026-10-23.img.xz | sudo dd of=/dev/sdX bs=4M status=progress conv=fsync
setup-tree.sh on a flashed unit. The flasher writes a brand-new minimal /etc/fstab onto the eMMC (_generate_fstab in /opt/scripts/tools/eMMC/functions.sh). A freshly flashed unit therefore has no card mount (recordings go to the eMMC), no commit=60 and no /tmp in RAM. The image also predates the 2026-10-24 changes: 80/60 pruning, weekly TRIM and 100 LEDs. setup-tree.sh puts all of that back, so the image itself doesn't need updating for those.sudo rm /etc/ssh/ssh_host_* && sudo dpkg-reconfigure openssh-server sudo rm /etc/machine-id && sudo systemd-machine-id-setup
setup-tree.sh to set its name and IP.How the image was made from the 116 GB card it was written to (shrunk so it fits small cards):
sudo umount /dev/sda1 sudo e2fsck -f /dev/sda1 sudo resize2fs /dev/sda1 4G echo ',4G' | sudo sfdisk -N 1 /dev/sda sudo dd if=/dev/sda bs=1M count=4100 status=progress | xz -T0 > chameleon-ghostTree-test-2026-10-23.img.xz
systemctl status jackdaudio jackPlumbing doTheTree ww-led ledscape journalctl -u doTheTree -b # Pd output for this boot sudo systemctl restart doTheTree # restart Pd only sudo systemctl restart jackdaudio jackPlumbing doTheTree # restart the audio chain JACK_NO_START_SERVER=1 jack_lsp -c # jack ports and connections ss -tn | grep 9990 # is ww-led connected to Pd?
Always set JACK_NO_START_SERVER=1 when running jack tools by hand. Without it, a jack client can start its own temporary server when jackd isn't reachable.
Start command (in ~/bin/doTheTree.sh):
/home/debian/installs/pd-0.55-0/bin/pd -jack -nogui -send "sensitivity 35" -send "wet 0.6" -send "windowSize 2048" -send "micNoisegate 60" -send "exponent 2" -path /home/debian/ghostTree -path /home/debian/ghostTree/iemlib -stderr effectTry4.pd
-send values, then sudo systemctl restart doTheTree.doTheTree.sh sets the volumes before starting Pd: amixer -c 1 -- sset Speaker 50% and amixer -c 1 -- sset Mic 100%.-path /home/debian/ghostTree. ELSE abstractions refer to else/xxx and won't load without it (“couldn't create”).doTheTree.service runs as debian with Restart=always, RestartSec=5 and After=jackdaudio.service. Its LimitRTPRIO and LimitMEMLOCK are infinity.To run Pd by hand to see its messages:
sudo systemctl stop doTheTree cd ~/ghostTree && /home/debian/installs/pd-0.55-0/bin/pd -jack -nogui ...same args... effectTry4.pd sudo systemctl start doTheTree # when done
trunk in ~/dev/ww-led/python/config.py sets how many pixels each frame has. The range counts both ends, so [0, 99] is 100 LEDs.ledsPerStrip in /etc/ledscape-config.json sets how many LEDs it actually outputs to, including the idle fade.Show the current settings:
grep '^trunk' ~/dev/ww-led/python/config.py grep -E 'ledsPerStrip|usedStripCount' /etc/ledscape-config.json
Set N LEDs (example: 100):
N=100 sed -i -E "s/^trunk = \[0, [0-9]+\]/trunk = [0, $((N-1))]/" ~/dev/ww-led/python/config.py sudo sed -i -E "s/(\"ledsPerStrip\":\s*)[0-9]+/\1$N/" /etc/ledscape-config.json sudo systemctl restart ledscape ww-led
setup-tree.sh sets 100 on new units: LEDS=100 in its root part and trunk in payload/ww-led/python/config.py. The original ledscape config is kept as /etc/ledscape-config.json.orig.
The rate the LEDs change is set by Pd, with a cap in ww-led. ledscape has no frame-rate setting; it smooths between frames itself (enableInterpolation).
amplitude message per envelope period. The period is half of windowSize, and runs at 12 kHz inside main12k: messages per second = 12000 / (windowSize / 2)
windowSize 2048 (current) gives about 11.7 per second.micNoisegate); the LEDs then go back to the fade after 5 s.TARGET_FPS times per second (~/dev/ww-led/python/config.py, currently 25) and sends one frame when there is some. So it caps the rate at TARGET_FPS; it never sends more frames than Pd sends messages.
Change Pd's rate (edit -send "windowSize 2048" in the Pd start line):
sed -i 's/-send "windowSize [0-9]*"/-send "windowSize 1024"/' ~/bin/doTheTree.sh sudo systemctl restart doTheTree
Change ww-led's cap:
sed -i -E 's/^TARGET_FPS = [0-9]+/TARGET_FPS = 30/' ~/dev/ww-led/python/config.py sudo systemctl restart ww-led
Show the current settings:
grep -o 'windowSize [0-9]*' ~/bin/doTheTree.sh grep '^TARGET_FPS' ~/dev/ww-led/python/config.py
To change these on future units too, make the same edits in treeSetup/payload/bin/doTheTree.sh and treeSetup/payload/ww-led/python/config.py.
sudo sed -i "s/\bunit2\b/unit4/g" /etc/hosts # old name -> new name in the 127.0.1.1 line sudo hostnamectl set-hostname unit4
Change /etc/hosts first. Otherwise sudo prints “unable to resolve host” (harmless).
connmanctl services # shows e.g. ethernet_647060b02dc3_cable (the MAC without colons) sudo connmanctl config ethernet_647060b02dc3_cable --ipv4 manual 10.42.0.64 255.255.255.0 10.42.0.1 --nameservers 8.8.8.8 1.1.1.1 sudo connmanctl config ethernet_647060b02dc3_cable --ipv4 dhcp # back to DHCP
The change takes effect immediately, so an SSH session drops. Close it with Enter, then ~.. The setting is stored per MAC address, so a cloned eMMC on another board starts on DHCP.
Logs are kept in RAM only and are lost at reboot:
/var/log/journal, so it is volatile./var/log/syslog or /var/log/messages.
Read the logs with journalctl (-b for this boot, -u SERVICE, -f to follow).
Persistent logging, for debugging something that happens across reboots:
sudo mkdir -p /var/log/journal sudo systemd-tmpfiles --create --prefix /var/log/journal sudo systemctl restart systemd-journald journalctl --list-boots # after a reboot: previous boots are now there journalctl -b -1 # previous boot
Undo it (back to RAM only):
sudo rm -rf /var/log/journal && sudo systemctl restart systemd-journald
To bring back rsyslog and text files in /var/log, use reduce-writes.sh undo (next section) or only:
sudo rm /etc/systemd/journald.conf.d/no-syslog.conf sudo systemctl restart systemd-journald sudo systemctl enable --now rsyslog
Files that survive reboots anyway:
~/jack-reboots.log: every reboot made by the jack watchdog, with the reason.sudo bash ~/reduce-writes.sh # apply (setup-tree does this) sudo bash ~/reduce-writes.sh undo # put everything back
What it changes:
/etc/systemd/system/testPin.service.d/quiet.conf (StandardOutput=null). The service still runs./etc/systemd/journald.conf.d/no-syslog.conf), and rsyslog is disabled.commit=60 in /etc/fstab. Data reaches the eMMC every 60 s instead of every 5 s, so a power cut can lose up to the last minute of writes./tmp is in RAM: tmpfs /tmp tmpfs defaults,noatime,nosuid,nodev,mode=1777,size=16M 0 0. If it fills, writes to /tmp fail with “No space left”; nothing is cleaned automatically. Only tiny files live there (for example /tmp/jack-unreachable-count).
After undo, /tmp returns to disk at the next reboot.
Possible later step: overlayroot (read-only root with a RAM overlay). The rcn-ee package exists for Buster, and the kernel and initramfs support it. It's not installed.
fsck.repair=yes is on the cmdline= line of /boot/uEnv.txt. After a power cut, boot repairs the filesystem itself instead of stopping at an emergency shell.grep fsck.repair /proc/cmdline/boot/uEnv.txt.bak. On unit1 and unit4 the backup is ~/uEnv.txt.bak, because that change was made by hand.sed ... > ~/uEnv.new && cat ~/uEnv.new > /boot/uEnv.txt. sed -i can't create its temp file in /boot.
/etc/systemd/logind.conf.d/keep-ipc.conf:
[Login] RemoveIPC=no
By default, when the last login session of user debian ends (for example an SSH logout), systemd-logind deletes all shared memory and semaphores owned by debian. jackd also runs as debian, so this broke jack: “JACK semaphore error”, “jack_shm_lock_registry fails”, and no sound. Never remove this file. It needs a reboot to take effect.
~/bin/restartIfJackIsJacked.sh runs from debian's crontab every 5 min. It reboots the unit if:
jack_lsp can't reach it on 2 checks in a row.
It logs the reason to ~/jack-reboots.log. To reboot without a password it relies on /etc/sudoers.d/reboot-debian:
debian ALL=(root) NOPASSWD: /sbin/reboot
To pause it while debugging (otherwise stopping jackd by hand can get the unit rebooted):
crontab -e # put # in front of the restartIfJackIsJacked.sh line; remove it when done
recordings. The recordings are in its rec/ folder, never in its top folder.rec/ boots./etc/fstab lines mount the card on /mnt/sdcard, then its rec/ folder on /home/debian/recordings (a bind mount): LABEL=recordings /mnt/sdcard ext4 noatime,commit=60,nofail,x-systemd.device-timeout=120s 0 2 /mnt/sdcard/rec /home/debian/recordings none bind,nofail,x-systemd.requires-mounts-for=/mnt/sdcard,x-systemd.before=doTheTree.service 0 0
~/recordings-subfolder.sh (run with sudo) sets this up on a unit. It writes both lines, keeping the old fstab as /etc/fstab.before-subfolder, and creates rec/ on the inserted card. If the card is mounted the old way, it also moves the card's recordings into rec/. A card formatted before this change has no rec/ and is ignored (recordings go to the eMMC) until this script has been run with it inserted.~/recordings folder on the eMMC is used. Pd always writes to the same path and doesn't know which one it is. Pd then starts about 2 minutes after boot, because of the 120 s card timeout.nofail means the unit boots normally without a card.findmnt ~/recordings # SOURCE /dev/mmcblk0p1[/rec] = card; no output = eMMC ls -A /mnt/sdcard # card top folder: should only hold lost+found and rec
sudo umount ~/recordings /mnt/sdcardsudo mount /mnt/sdcard && sudo mount ~/recordings, or reboot.~/bin/prune-recordings.sh, every 10 min) deletes the oldest mic_*.wav files:HIGH_PCT), it deletes the oldest recordings in one batch until the card is down to 60% (LOW_PCT);MAX_MB).fstrim.timer runs fstrim weekly. It tells the card and the eMMC which blocks are free, so their wear leveling can use them. Without TRIM, space freed by deleting files still looks used to the card. Check with systemctl status fstrim.timer; run it by hand with sudo fstrim -av.test-sdcard.sh (destructive) writes markers every 512 MB across the card and reads them back, which catches fake-capacity cards (one “NCard” 32 GB card failed).setup-sdcard.sh (destructive) creates one ext4 partition labelled recordings.rec/ folder on the card.rec/ and writes the two fstab lines.format, it only formats the card./dev/mmcblk0 (the SD slot), and refuses if the system is booted from SD.
Removed from all units (supervisor purged, /opt/dataplicity and the dataplicity user deleted). Nothing to undo.
/usr/local/bin/shellhub-agent, settings in /etc/shellhub/agent.env (server, tenant ID, key /etc/shellhub/agent.key, PREFERRED_HOSTNAME=chameleons-bb), run by shellhub-agent.service.GOOS=linux GOARCH=arm GOARM=7 CGO_ENABLED=0, with passwordHandler in agent/server/authentication.go changed to return false, so password login is refused and only keys work. The built-in updater does nothing; update by rebuilding and copying the binary.sudo systemctl disable --now shellhub-agent
ksshaskpass saves them in KWallet. If one was typed wrong, delete it in KWallet Manager under kdewallet → ksshaskpass. To be asked every time for one login: SSH_ASKPASS_REQUIRE=never ssh ....known_hosts has the wrong key. Clear it with ssh-keygen -R 10.42.0.xx. Only do this when you know why it changed.