BLE MIDI
The ble_midi component connects to a device that speaks MIDI over Bluetooth Low Energy — a wireless
keyboard, a pad controller, a foot switch — and turns the notes, knobs and buttons it sends into
automations. It can also send MIDI back to the device.
It does not create any entities of its own. What you do with a note or a knob is up to your automations.
The component needs a BLE Client to hold the connection, which in turn needs the ESP32 BLE Tracker to find the device.
WARNING
The BLE software stack on the ESP32 consumes a significant amount of RAM on the device.
Crashes are likely to occur if you include too many additional components in your device’s configuration. Memory-intensive components such as Voice Assistant and other audio components are most likely to cause issues.
esp32_ble_tracker:
ble_client: - mac_address: XX:XX:XX:XX:XX:XX id: midi_keyboard
ble_midi: id: my_midi ble_client_id: midi_keyboard on_note_on: - logger.log: format: "Note %u pressed with velocity %u" args: [note, velocity]Configuration Variables
Section titled “Configuration Variables”- ble_client_id (Required, ID): ID of the associated BLE client.
- id (Optional, ID): The ID to use for code generation, and for
reference by the
ble_midi.*actions. - pair (Optional, boolean): Pair with the device after connecting. Some devices refuse to send
anything until they are paired. Defaults to
false. - max_sysex_size (Optional, int): The size of the buffer used to reassemble System Exclusive messages,
in bytes. Longer messages are truncated. Range: 8-2048. Defaults to
256.
Automations:
- on_connect (Optional, Automation): An automation to perform when the MIDI device is
connected and ready. See
on_connect. - on_disconnect (Optional, Automation): An automation to perform when the MIDI device
disconnects. See
on_disconnect. - on_note_on (Optional, Automation): An automation to perform when a key or pad is
pressed. See
on_note_on. - on_note_off (Optional, Automation): An automation to perform when a key or pad is
released. See
on_note_off. - on_control_change (Optional, Automation): An automation to perform when a knob,
fader or switch is moved. See
on_control_change. - on_program_change (Optional, Automation): An automation to perform when the device
selects a program. See
on_program_change. - on_pitch_bend (Optional, Automation): An automation to perform when the pitch bend
wheel moves. See
on_pitch_bend. - on_sysex (Optional, Automation): An automation to perform when a System Exclusive
message arrives. See
on_sysex. - on_message (Optional, Automation): An automation to perform for every MIDI message,
as raw bytes. See
on_message.
Channel and Value Ranges
Section titled “Channel and Value Ranges”Channels are 0 to 15, as they are on the wire. What a keyboard labels “channel 1” is channel 0 here.
Notes, velocities, controller numbers, controller values and programs are 0 to 127. Pitch bend is
-8192 to 8191, with 0 at rest.
BLE MIDI Automation
Section titled “BLE MIDI Automation”on_connect
Section titled “on_connect”Triggered when the MIDI device is connected and ready to send. This is later than the on_connect of the
BLE Client itself: it fires once the MIDI service has been found and the device
has been told to start sending.
ble_midi: ble_client_id: midi_keyboard on_connect: - logger.log: "MIDI device ready"on_disconnect
Section titled “on_disconnect”Triggered when the MIDI device disconnects.
ble_midi: ble_client_id: midi_keyboard on_disconnect: - logger.log: "MIDI device gone"on_note_on
Section titled “on_note_on”Triggered when a key or a pad is pressed. The variables note, velocity and channel are available.
ble_midi: ble_client_id: midi_keyboard on_note_on: - logger.log: format: "Note %u on channel %u, velocity %u" args: [note, channel, velocity]A note is often used as a button. Pick the one you want and act on it:
ble_midi: ble_client_id: midi_keyboard on_note_on: - if: condition: lambda: "return note == 60;" then: - light.toggle: desk_lightNOTE
Many devices signal a release by sending a note on with velocity 0 instead of a note off. The component
reports those as on_note_off with velocity 0, so you do not have to handle both cases.
on_note_off
Section titled “on_note_off”Triggered when a key or a pad is released. The variables note, velocity and channel are available.
ble_midi: ble_client_id: midi_keyboard on_note_off: - logger.log: format: "Note %u released" args: [note]on_control_change
Section titled “on_control_change”Triggered when a knob, fader or switch is moved. The variables control_number, value and channel are
available.
ble_midi: ble_client_id: midi_keyboard on_control_change: - if: condition: lambda: "return control_number == 7;" then: - light.turn_on: id: desk_light brightness: !lambda "return value / 127.0f;"on_program_change
Section titled “on_program_change”Triggered when the device selects a program. The variables program and channel are available.
ble_midi: ble_client_id: midi_keyboard on_program_change: - logger.log: format: "Program %u selected" args: [program]on_pitch_bend
Section titled “on_pitch_bend”Triggered when the pitch bend wheel moves. The variables value and channel are available.
ble_midi: ble_client_id: midi_keyboard on_pitch_bend: - logger.log: format: "Pitch bend %d" args: [value]on_sysex
Section titled “on_sysex”Triggered when a System Exclusive message arrives. The variable data is available, a view of the
payload without the surrounding 0xF0 and 0xF7 bytes (data.size(), data[i]).
ble_midi: ble_client_id: midi_keyboard on_sysex: - logger.log: format: "SysEx of %u bytes" args: ["data.size()"]on_message
Section titled “on_message”Triggered for every message the device sends, including the ones the triggers above already cover and the
ones they do not, such as aftertouch and clock. The variable data is available, a view of the status
byte followed by its data bytes (data.size(), data[i]).
ble_midi: ble_client_id: midi_keyboard on_message: - logger.log: format: "MIDI status 0x%02X" args: ["data[0]"]NOTE
In on_sysex and on_message, data is only valid while the automation runs, so these two triggers
do not accept actions that wait, such as delay, wait_until or script.wait. Copy what you need
into a global variable first, then wait in a separate script.
ble_midi.send_note_on Action
Section titled “ble_midi.send_note_on Action”Sends a note on message.
- ble_midi.send_note_on: id: my_midi note: 60Configuration Variables
Section titled “Configuration Variables”- id (Required, ID): ID of the
ble_midicomponent. - note (Required, int, templatable): The note,
0to127. - velocity (Optional, int, templatable): The velocity,
0to127. Defaults to64. - channel (Optional, int, templatable): The channel,
0to15. Defaults to0.
ble_midi.send_note_off Action
Section titled “ble_midi.send_note_off Action”Sends a note off message. Takes the same options as
ble_midi.send_note_on.
- ble_midi.send_note_off: id: my_midi note: 60ble_midi.send_control_change Action
Section titled “ble_midi.send_control_change Action”Sends a control change message. This is what most devices use to set an LED, a motorized fader or a mode.
- ble_midi.send_control_change: id: my_midi control_number: 7 value: 100Configuration Variables
Section titled “Configuration Variables”- id (Required, ID): ID of the
ble_midicomponent. - control_number (Required, int, templatable): The controller number,
0to127. - value (Required, int, templatable): The value,
0to127. - channel (Optional, int, templatable): The channel,
0to15. Defaults to0.
ble_midi.send_program_change Action
Section titled “ble_midi.send_program_change Action”Sends a program change message.
- ble_midi.send_program_change: id: my_midi program: 3Configuration Variables
Section titled “Configuration Variables”- id (Required, ID): ID of the
ble_midicomponent. - program (Required, int, templatable): The program,
0to127. - channel (Optional, int, templatable): The channel,
0to15. Defaults to0.
ble_midi.send_pitch_bend Action
Section titled “ble_midi.send_pitch_bend Action”Sends a pitch bend message.
- ble_midi.send_pitch_bend: id: my_midi value: 2048Configuration Variables
Section titled “Configuration Variables”- id (Required, ID): ID of the
ble_midicomponent. - value (Required, int, templatable): The amount of bend,
-8192to8191. - channel (Optional, int, templatable): The channel,
0to15. Defaults to0.
ble_midi.send_sysex Action
Section titled “ble_midi.send_sysex Action”Sends a System Exclusive message. The 0xF0 and 0xF7 bytes are added for you if the payload does not
already carry them. The message is sent in a single BLE packet and is not split, so the payload can be at
most 125 bytes, and less if the connection negotiated a small packet size; a longer payload is refused with
a warning.
- ble_midi.send_sysex: id: my_midi payload: [0x7E, 0x7F, 0x06, 0x01]Configuration Variables
Section titled “Configuration Variables”- id (Required, ID): ID of the
ble_midicomponent. - payload (Required, list of bytes, templatable): The message body.
ble_midi.send_raw Action
Section titled “ble_midi.send_raw Action”Sends MIDI bytes as they are: one complete message, a status byte followed by exactly the data bytes it
needs. Use this for messages the other actions do not cover. A payload that is not a single complete
message, or a System Exclusive message, is refused with a warning; use
ble_midi.send_sysex for those.
- ble_midi.send_raw: id: my_midi data: [0xFA]Configuration Variables
Section titled “Configuration Variables”- id (Required, ID): ID of the
ble_midicomponent. - data (Required, list of bytes, templatable): The bytes to send.
ble_midi.connected Condition
Section titled “ble_midi.connected Condition”Checks whether the MIDI device is connected and ready.
- if: condition: ble_midi.connected: id: my_midi then: - ble_midi.send_note_on: id: my_midi note: 60NOTE
Sending while the device is disconnected does nothing; the message is dropped and a warning is logged. Use this condition when you want to take a different action instead.
Finding Out What Your Device Sends
Section titled “Finding Out What Your Device Sends”Log everything, press every key and turn every knob:
ble_midi: ble_client_id: midi_keyboard on_message: - logger.log: format: "MIDI status 0x%02X, data 0x%02X 0x%02X" args: ["data[0]", "data.size() > 1 ? data[1] : 0", "data.size() > 2 ? data[2] : 0"]The note and controller numbers you see in the log are the ones to use in your automations.