Skip to content

Instantly share code, notes, and snippets.

@m1no
Last active May 9, 2024 17:15
Show Gist options
  • Save m1no/90c5776df3f1c06e067076d14477ef43 to your computer and use it in GitHub Desktop.
Save m1no/90c5776df3f1c06e067076d14477ef43 to your computer and use it in GitHub Desktop.
MOTU Ultralite-mk5 API Bridge
// 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';
}
@m1no
Copy link
Author

m1no commented Aug 14, 2023

"Error connection refused" usually means that the Motu Device doesn't listen on that IP with this specific port or some firewall in-between blocks. From which machine do you try to run this? You could manually start the CUE mix app in a browser to use the browser developer tools -> Networking tab to discover the used IP and ports. See my 2nd last comment line above.

@amrmjd
Copy link

amrmjd commented Aug 14, 2023

I'm on a Mac so not sure if I can do that? I have the macOS firewall disabled.

@amrmjd
Copy link

amrmjd commented Aug 15, 2023

UPDATE.. so if I change to port 1281 it works. Thanks for putting this code together and posting it!

@amrmjd
Copy link

amrmjd commented May 9, 2024

Hi again.. i just got a new computer and migrated to it.. as I was doing that, I noticed there was new firmware as well as new drivers for the UltraLite mk5.. and now I am getting a 404 error when I try to run this.. it's possible that I messed up the set up on the new computer.. but curious if anyone has upgraded to the new firmware + software and if it is still working as is.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment