Last active
May 3, 2025 10:12
-
-
Save m1no/90c5776df3f1c06e067076d14477ef43 to your computer and use it in GitHub Desktop.
MOTU Ultralite-mk5 API Bridge
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// MOTU Ultralite-mk5 API Bridge | |
// Author: mino | |
// Date: 2023-02-22 | |
// License: MIT | |
// | |
// Description: | |
// ------------ | |
// I was looking for a easy way to mute and umute my main speakers via a Stream Deck button on the MOTU Ultralite-mk5. | |
// Unfortunately, Motu does not provide an HTTP API for this series, this feature is only available on the AVB devices. | |
// But with a bit of tinkering and reverse engineering of the Motu CueMix 5 app, it is possible to understand the used protocols and to send arbitrary commands. | |
// A quick and dirty program was written to allow the user to toggle the mute state of the main output of the device with automation in mind (e.g. Stream Deck). | |
// | |
// Installation: | |
// ------------ | |
// Install Node.js and the depdenencies "ws" and "http" with "npm install ws http". | |
// | |
// Usage: | |
// ------ | |
// Start the server with "node motu-bridge.js" and then call the server with a GET request to the endpoint /toggleMuteMain to toggle the mute state of the main output of the device. | |
// | |
// Reverse engineering of the Motu Mk5 API: | |
// ---------------------------------------- | |
// MOTU provides at this time for the Ultralite-mk5 only a Windows application called "CueMix 5". | |
// This app is based on Electron and uses in the background a WebSocket connection to communicate with the device over its own network interface. | |
// By further looking into the installed source code of the app, as the app is not obfuscated, it is possible to run the index.html in a browser with enabled developer tools. | |
// In the developer tools, the network tab can be used to debug the WebSocket traffic between the app and the device and obtain the binary codes that are sent to the device for the different actions. | |
let motu_device_ip = '169.254.23.124'; | |
let motu_device_port = '1280' // On my Windows machine, but another user had to change this to 1281 on his Mac | |
const http = require('http'); | |
const WebSocket = require('ws'); | |
let messageIndex = 0; | |
const server = http.createServer((req, res) => { | |
if (req.url === '/toggleMuteMain' && req.method === 'GET') { | |
res.writeHead(200, { 'Content-Type': 'text/plain' }); | |
messageIndex = (messageIndex + 1) % 2; | |
let muteToggle = getMuteToggle(messageIndex); | |
sendToMotu(muteToggle); | |
res.end(); | |
} else { | |
res.writeHead(404, { 'Content-Type': 'text/plain' }); | |
res.write('Not found.\n'); | |
res.end(); | |
} | |
}); | |
server.listen(3000, () => { | |
console.log('Server running at http://localhost:3000'); | |
console.log('[GET] ToggleMute Endpoint: http://localhost:3000/toggleMuteMain'); | |
}); | |
function sendToMotu(binaryCode) { | |
const ws = new WebSocket(`ws://${motu_device_ip}:${motu_device_port}`); | |
ws.binaryType = 'arraybuffer'; | |
ws.on('open', () => { | |
console.log('WebSocket connection opened.'); | |
const buffer = hexStringToBuffer(binaryCode); | |
const arrayBuffer = bufferToArrayBuffer(buffer); | |
ws.send(arrayBuffer, { binary: true }); | |
}); | |
ws.on('close', () => { | |
console.log('WebSocket connection closed.'); | |
}); | |
return ws; | |
} | |
function hexStringToBuffer(hexString) { | |
return Buffer.from(hexString, 'hex'); | |
} | |
// Convert a Buffer object to an ArrayBuffer | |
function bufferToArrayBuffer(buffer) { | |
return buffer.buffer.slice(buffer.byteOffset, buffer.byteOffset + buffer.byteLength); | |
} | |
function getMuteToggle(index) { | |
// 03fb0012000101 mute main output | |
// 03fb0012000100 unmute main output | |
return index === 0 ? '03fb0012000100' : '03fb0012000101'; | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
@ccrome there should be no encryption.
Control commands usually consist of:
they are then transmitted as the following bytes
for me muting the headphones is
these bytes are then concatenated into a
Uint8Array
and sent over the websockethere are more indices for the mute channels:
you can also add
console.log
statements to the cuemix source code and get the bytes this way.using the dev tools in cuemix looks like this:

here you can see the console output of my print statements.$4* 256+4=1028$ , then the index $0$ for line 1/2 length $1$ and value $0$ for unmute.
when I mute/unmute line out 1/2 it sends the bytes
4, 4, 0, 0, 0, 1, 0
in decimal which is0x04040000000100
in hex.The first two bytes are the command id e.g
You can also see the binary messages in the network tab when clicking on the websocket request.