Reading the repo
Opening the box…
Reading the repo
Opening the box…
How to install on Windows
Your piano got up first
Saved setup from 11 h ago
Will it work on Windows?
winget install Git.Git) or WSL2 (wsl --install).The maintainers included install.sh. Run it from the cloned folder after reading it.
bash install.sh
docs/installation.md| Item | Details | |---|---| | System | Debian 11 or newer, Ubuntu 22.04 or newer, or Raspberry Pi OS (Bullseye or newer, 32 or 64 bit), with or without a desktop. systemd must be running | | CPU | Any. 64-bit PCs (amd64) and 64-bit ARM (arm64) are the main targets, and 32-bit ARM (armhf) works too | | Memory | 512 MB or more. The services use about 100 MB together | | Storage | 300 MB free during the install. About 100 MB stay in use, more when a Python package had to be compiled (see below), plus the songs | | Keyboard | Any class-compliant USB MIDI keyboard with 25 to 88 keys. Tested on a Yamaha PSR-E363. See hardware.md | | MQTT broker | Required, also without Home Assistant, because the two services talk through it. The installer can install one on the machine, or use an existing one such as the Mosquitto broker app in Home Assistant. See Choosing a setup | | Network | Internet access during the install, for the system and Python packages |
Home Assistant and the Telegram bot are optional. Without Home Assistant lists what works without them.
| Machine | Status | |---|---| | Virtual machine (Proxmox, VirtualBox, VMware) with USB passthrough, 2 GB RAM | Tested: Debian 13 and Ubuntu Server 26.04 guests on Proxmox VE 9.2.21 | | Mini PC or any x86 computer | Supported | | Raspberry Pi 4 | Tested on Raspberry Pi OS 64-bit, desktop and Lite | | Raspberry Pi 5 | Supported | | Raspberry Pi 3 and Zero 2 W | Supported. The install takes longer | | Raspberry Pi 1, 2 and Zero W (armv6 and armv7, 512 MB) | Expected to work with 32-bit Raspberry Pi OS, not tested | | The Android app | In testing. The bridge, the lessons and the alarm as an app, no Linux needed. See android.md | | Termux on Android | Not supported. Android gives apps no access to ALSA MIDI devices and has no systemd | | Rooted Android phone, Debian container with systemd (Droidspaces) | Tested on one phone, with limits of memory and delays. See hardware.md | | Windows, macOS | Not supported directly. A Linux virtual machine with the keyboard passed through works |
The code is bash and Python, the same on every CPU, and apt picks the right system packages by itself. The one part with compiled code is python-rtmidi, the MIDI library of the lesson engine. The installer checks the CPU and the Python version:
| CPU | Python | What happens | |---|---|---| | amd64, arm64 | 3.8 to 3.12 | A ready-made package comes from PyPI | | armv7l, armv6l (32-bit Raspberry Pi OS) | any | A ready-made package comes from piwheels.org | | any other, or Python 3.13 and newer (Debian 13, Ubuntu 26.04) | | It is compiled on the machine: about 10 seconds on a PC, a few minutes on a Raspberry Pi 3. The compiler and headers are installed for this, about 250 MB |
On Raspberry Pi OS Lite, with no desktop, everything runs over SSH. The setup screen opens in a browser on any other device in the network, or the setup runs in the terminal with --cli.
The installer asks one question about the broker. The answer depends on what is already there:
| Situation | Answer in the installer | Then | |---|---|---| | Home Assistant OS or Supervised, with an access token | The "Set Home Assistant up now?" question comes first. Answer yes | The installer installs the Mosquitto app, makes the piano's user, and sets everything up in Home Assistant, with nothing to type. Details in home-assistant.md | | Home Assistant with the Mosquitto broker app, no token | 1, an existing broker | The broker address is the Home Assistant machine, and the user is a Home Assistant user made for the piano. Details in home-assistant.md | | Another broker on the network | 1, an existing broker | Its address, port, user and password | | No broker and no Home Assistant | 2, install one here | Nothing to fill in. Mosquitto is installed on the machine with a random password, and the setup goes on to the keyboard |
A broker installed by option 2 answers port 1883 on the machine itself only, and port 9001 (websockets, for the status screen) on the network. Both require a login. To let Home Assistant on another machine use this broker, add a listener 1883 line without an address to /etc/mosquitto/conf.d/armonico.conf and restart Mosquitto, or use Home Assistant's own broker (option 1) instead. Its user is piano, and the password is in the settings file:
sudo grep MQTT_PASS /etc/armonico/config.env
A second user for Home Assistant keeps the two passwords apart:
sudo mosquitto_passwd /etc/mosquitto/armonico.passwd ha
sudo systemctl restart mosquitto
mosquitto_passwd then warns that the owner of the file is not root. The warning is expected, and the file stays owned by mosquitto, because the broker cannot open a password file owned by root.
When a broker already listens on port 1883 of the machine, option 2 installs nothing, and the setup uses that broker at 127.0.0.1. The exception is the broker of a first install that stopped before its setup was saved: its settings in /etc/mosquitto/conf.d/armonico.conf are this program's, so a new run, and --reconfigure, take that broker again with new logins instead of asking.
With a Home Assistant on another machine and the broker of option 2, the installer does not set the sensors, automations and Telegram up in Home Assistant: the broker answers this machine only, and Home Assistant cannot reach it. It says so in one line. The listener 1883 line above, or option 1, makes it possible.
| Works | How |
|---|---|
| Lessons, songs, recordings | The lesson screen at http://<machine>:8099: lessons, MIDI uploads, the song search, replays. A device enters the screen code once and is remembered for 14 days |
| Playing a song, the other terminal commands | armonico play ode_to_joy on the machine, or mosquitto_pub ... -t piano/play -m ode_to_joy from any computer on the network |
| Alarm clock | A line in root's crontab (sudo crontab -e), shown below |
| Shortcuts | Recognized and published to piano/shortcut. Any MQTT tool, such as Node-RED, can act on them |
| Needs Home Assistant | Why | |---|---| | Telegram commands | The bot runs as Home Assistant automations | | Shortcuts that run automations | The actions live in Home Assistant | | The alarm blueprint with retries | It is a Home Assistant blueprint |
An alarm at 07:00 on weekdays, with the login read from the settings file rather than written into the crontab:
0 7 * * 1-5 armonico play ode_to_joy
armonico reads the settings itself, so no password is typed on a command line where
ps would show it to every other user on the machine. Where mosquitto_pub is used
directly, its own configuration file carries the login instead of the arguments:
# ~/.config/mosquitto_pub, mode 0600
-u piano
-P the-password
The time follows the machine's clock and time zone (timedatectl).
git clone https://github.com/vestacorelabs/armonico.git
cd armonico
sudo ./install.sh
alsa-utils and python3 are installed at first, and then the installer looks for a keyboard. When it finds one, it offers to play classical music on it while the rest installs (on the first install only: an update asks nothing). Any key stops the music, which shows that both directions of the USB link work. The pieces are listed in setup/music/SOURCES.md.mosquitto-clients, python3-venv and a few small tools. The installer notes which packages were not there before, so --purge can later remove exactly those.armonico, with no login and no password, in the audio group. The services never run as root, and neither does the setup screen: the installer starts it as this user. The user who ran sudo joins the armonico, adm and systemd-journal groups, which allows the armonico command and its logs without sudo from the next login. In the current terminal, newgrp armonico makes the group active at once. Commands that change the system (restart, update, reconfigure, uninstall, purge, integrations) ask sudo for the password by themselves, so sudo need not be typed in front of them. The groups added here are noted in /etc/armonico/system-changes, and --uninstall and --purge take the user out of them again; a group the user was already in stays./opt/armonico, and a Python virtual environment is built there with mido, python-rtmidi and paho-mqtt 2. The virtual environment exists because not every distribution packages paho-mqtt 2. zeroconf is added after them, in a call of its own: it is used only to find Home Assistant on the network, and a failure there is a warning, not a stopped install. The armonico command is installed as /usr/local/bin/armonico.armonico-bridge and armonico-lessons start and are enabled at boot. The MQTT recorder, armonico-recorder, is installed too, but stays off until sudo armonico record on.Lessons, the practice streak and the alarm clock count days on the machine's clock. With a wrong time zone every day starts at the wrong hour: the streak rolls over at the wrong moment, and an alarm from cron rings at another time. Raspberry Pi OS is often left on UTC. The installer shows the current zone and time. Enter keeps it, and a name such as Europe/London changes it through timedatectl, which is the system's own setting and stays after a restart. It can be changed later with sudo timedatectl set-timezone Europe/London.
Home Assistant publishes itself on the network under the mDNS service name _home-assistant._tcp.local., and its answer carries the version, the location name and the internal address. The installer listens for a few seconds, and a Home Assistant in a container on the same machine is found by port 8123 of localhost instead. What comes back becomes the suggested broker address in the terminal and in the setup screen, and port 1883 on that address is opened once to see whether the Mosquitto broker app answers there.
The app itself publishes nothing over mDNS, and its logins are Home Assistant user accounts that cannot be read from outside, so a user name and a password are asked for in every case. A suggested address can be replaced, and an install where nothing was found asks exactly the questions it always asked. mDNS stays inside one network, so a Home Assistant on another VLAN or behind an access point that blocks multicast between clients is not seen.
When nothing is found (mDNS does not cross to another VLAN or subnet, and some routers block it), the question "Set Home Assistant up now?" still takes the address by hand, and the setup goes on exactly as if it had been found. If Home Assistant is on another VLAN or subnet, the router has to route between the two networks, and its firewall has to let this machine reach Home Assistant on port 8123 (the web page and its API) and port 1883 (the broker), with replies allowed back. A switch or access point with client isolation also blocks it, and has to allow the two devices to talk.
The setup screen uses 8098 and the lesson screen 8099, both over HTTP, which is TCP. A machine that already runs other things may well have one of them. Before anything opens a port, the installer asks the question the server itself will ask: it tries to bind the port on every address and sees whether the system allows it. That catches the case a simpler check misses, where another service holds the port on one address only and answers nothing on localhost while still blocking a listener.
When a port is taken, the installer says so, names what holds it where the system can tell,
and offers the first free port from ten above. Another number can be typed instead. The
lesson screen's port is kept in LESSON_UI_PORT in config.env, so it survives a
reconfigure and an update, and the address printed at the end is the one actually listening.
Neither port belongs on the internet. A port forward to 8099 on a router hands the keyboard, the practice history and the backup to whoever finds it.
The terminal shows an address and a 6-digit code:
Open in a browser on any device in the same network:
http://192.168.1.20:8098
One-time code: 482913
After the code, the screen has three steps:
LESSON_HOSTS), for a reverse proxy such as Caddy or a name in the local DNS. The lesson screen always asks for its code; turning that off is sudo armonico code off. The setup screen starts in English, and the language button changes it.When the broker was installed on this machine, no Home Assistant steps follow. When Home Assistant is used, the card at the top of step 1 shows whether its broker answers, checked again each time the page asks. Home Assistant and Telegram are not part of this screen. They connect to the same broker. Home Assistant is set up by the installer, or by the steps this screen shows; Telegram by armonico telegram. After saving, the screen shows what comes next, with links to the guides.
The button at the top switches the setup page itself between English and Hebrew. It does not choose the piano's language: the lesson screen and the Telegram answers start in English, and the flag menu on the lesson screen changes them from then on.
Once saved, the installer goes on in the terminal and the page can be closed. A setup screen left unused for 15 minutes stops; each use (the page opened, a step done) starts the 15 minutes again. Five wrong codes in a row from one device pause that device for a minute; the others can still enter the code.
On a machine with no browser nearby, the same steps run as questions in the terminal:
sudo ./install.sh --cli
The device's own keys, sound and screen come with --device, which also works with --cli. Details in armonico-pi.md.
sudo ./install.sh --device
| Path | Contents | Owner and permissions |
|---|---|---|
| /opt/armonico/ | The code, the Python virtual environment, and a copy of install.sh for armonico reconfigure, uninstall and purge | root, read-only for the services |
| /opt/armonico/VERSION, /opt/armonico/REPO | The installed version, and the GitHub repository updates come from | root |
| /usr/local/bin/armonico | The armonico command | root |
| /etc/armonico/config.env | Settings, including the MQTT password | root, group armonico, 640 |
| /etc/armonico/secrets.env | The Home Assistant and Telegram tokens, only when you answered yes to keeping them for updates | root, 600 |
| /etc/armonico/source_dir | The folder the project was unpacked in, so uninstall and purge can remove it | root, 600 |
| /etc/armonico/installed-packages | The system packages the installer added, for --purge. The only file --uninstall leaves behind, so a later --purge can still remove them, Mosquitto included | root, 600 |
| /etc/armonico/broker.env | Left by --uninstall only when the broker did not answer and its retained messages could not be cleared: the broker's address and login, so a later --purge clears them, and then deletes it | root, 600 |
| /etc/armonico/system-changes | What the installer changed in the system outside its own folders, with the state from before: the groups it added the user to, the pip cache when it made it, and on the Armonico Pi the user units it enabled for everyone, greetd's state and the saved ALSA levels. --uninstall and --purge put back what is listed, and nothing else | root, 600 |
| /var/lib/armonico/shortcuts.conf | Shortcuts | service user |
| /var/lib/armonico/lessons.json, history.json | Lesson progress and history of the first profile | service user |
| /var/lib/armonico/settings.json, volume, last_chat | Lesson screen settings: the language, and the first profile's pace, colors and labels | service user |
| /var/lib/armonico/profiles.json | The profiles, the active one, and each PIN as a salted hash | service user, 600 |
| /var/lib/armonico/profiles/<id>/ | Every profile except the first: its own progress, history, settings and recordings | service user |
| /var/lib/armonico/songs/ | MIDI files, and <song>.credit.json next to a downloaded one, with its source and license | service user |
| /var/lib/armonico/recordings/ | Lesson recordings, the last 30 | service user |
| /var/lib/armonico/mqtt/ | The MQTT recorder's files, one per day, when it is on | service user, readable by the group |
| /run/armonico/ | Flags shared by the two services, cleared at boot | service user |
/etc/armonico/config.env can be edited by hand. The setup writes every value in single quotes; a line written by hand may also use double quotes or none (MQTT_RECORD_DAYS=30). The installer reads the file as text and never runs it. After a change, the services are restarted with sudo armonico restart. Every setting, including the ones kept in Home Assistant, the lesson screen and the browser, is listed in configuration.md.
Running the setup again keeps the current values as defaults, and keeps keys added by hand as they are written, as well as DATA_DIR and SONGS_DIR:
sudo armonico reconfigure
Songs are plain MIDI files in /var/lib/armonico/songs/. A song's name is its file name without .mid, and case does not matter. The installer adds six classical pieces to start with. More can be uploaded on the lesson screen, downloaded with /getmidi or armonico getmidi, or copied into the folder:
sudo install -m 640 -o armonico -g armonico my_song.mid /var/lib/armonico/songs/
From the lesson screen. The footer shows the version and "Check for updates". When GitHub has a newer release, "Install" starts the update. The screen writes a request, and a root service (armonico-update.path and armonico-update.service) downloads that release from GitHub over HTTPS and runs its installer. The screen itself has no root rights and can name only a version number, and the download address is built from the repository in /opt/armonico/REPO. Progress and errors show on the screen (/var/lib/armonico/update-status.json), and the installer's full output is written to /var/lib/armonico/update.log when it finishes.
**From the t
Then run powershell -ExecutionPolicy Bypass -File .\setup.ps1. It stops at the first error and is safe to run again. Servers and tests aren't started; it lists them at the end. Read it first.
Needs Git: winget install Git.Git
git clone https://github.com/vestacorelabs/armonico.git cd armonico
Python 3.13
winget install Python.Python.3.13
python -m venv .venv .venv\Scripts\Activate.ps1 pip install -r requirements.txt
The steps run shell scripts (.sh). Use Git Bash (winget install Git.Git) or WSL2 (wsl --install).
On Debian, Ubuntu or Raspberry Pi OS, with the keyboard plugged in:
git clone https://github.com/vestacorelabs/armonico.git
cd armonico
sudo ./install.sh
With Home Assistant OS or Supervised, the installer can set Home Assistant up as well, from one access token (Home Assistant profile, Security, Long-lived access tokens): the MQTT broker, the sensors, the automations and the blueprints. It asks for the token during the install, and says how to make it. Details in docs/home-assistant.md.
While it installs, the keyboard can play a little classical music, which also shows that the USB connection works both ways. The installer then asks about the MQTT broker: an existing one, such as the Mosquitto broker app in Home Assistant, or a new one on this machine. Home Assistant is optional (what works without it). Last, a one-time setup screen opens in the browser for the broker and the keyboard.
A first test, once the install is done (without sudo after the next login, see the armonico command):
sudo armonico play ode_to_joy
No reports for Windows yet.
GitManual AI Agent reads the 5 key files and writes a step-by-step guide for Windows. Once written, it's saved for everyone.