Stream Deck is a handy gadget targeted primarily to people who stream online. It allows you to automate tasks in 3rd party tools like OBS, Streamlabs, Mixer and more. It also allows you to open various executable programs on your computer, making it not only valuable to streamers, but also a great overall productivity tool. Elgato, the creator of Stream Deck, has recently released an SDK allowing developers to extend the platform with their own custom plugins.
While building the .NET Core library for Stream Deck plugins along with Jeff Fritz’ Twitch Stream, it became evident that we can’t expect all contributors to have physical hardware to be able to code for this project.
I took it upon myself to complete a simple solution to this problem. What is nice is that all communication between the Stream Deck and its plugins are all via web sockets, so standing up an emulator is just a matter of setting up a server. I decided to stand up this emulator in Node.js due to its simplicity and terseness.
Obtaining the Source Code
All code is available through GitHub and is hosted alongside the .NET Core library project. Initially (which hopefully by the time you read this has matured somewhat) – I’ve implemented an application that will emulate the Stream Deck hardware by initiating a web socket server, then firing up the plugin executable. It also listens for user interaction to send a message from the server to the plugin. I will walk you through the “original” version of this code here.
Configuration
Configuration for this Node application is available in the config.js file. This file contains settings for the folder path to the plugin code, the name of the manifest file, as well as the name of the plugin. It also contains websocket server settings including device id (which is a made up hardware id assigned to our Stream Deck emulator), and the port on which the plugin will communicate with the server.
// config.js listing
let config = {};
config.executable = {
path: "C:\\SourceCode\\Fritz\\StreamDeckToolkit\\src\\SamplePlugin\\bin\\Debug\\netcoreapp2.2\\win10-x64",
manifest: "manifest.json",
exe: "SamplePlugin.exe"
};
config.server = {
deviceId : '55F16B35884A859CCE4FFA1FC8D3DE5B',
port : 3000
}
module.exports = config;
Websocket Server Code
Next is the websocket server code, located in server.js. This file is responsible for creating the websocket server listening on the configured port. It will also listen for commands from the main program (index.js) to know when to send the keyUp event across a connected socket, emulating a button press. Originally the keyUp event is the only event implemented in this code, adding others would just be a matter of adding cases to the switch statement.
// server.js listing
const config = require('./config');
var manifest = require(config.executable.path +'\\'+ config.executable.manifest);
const WebSocket = require('ws');
const server = new WebSocket.Server({ port: config.server.port});
let _socket;
server.on('connection', (socket) => {
_socket = socket;
_socket.on('message',(msg)=> {
console.log("\n"+ msg);
});
});
// Message from index.js
process.on('message', (msg) => {
switch(msg) {
case 'button':
var json = {
"action": manifest.Actions[0].UUID,
"event": "keyUp",
"context": '',
"device": config.deviceId,
"payload": {
"settings": {},
"coordinates": {
"column": 3,
"row": 1
},
"state": 0,
"userDesiredState": 1,
"isInMultiAction": false
}
};
_socket.send(JSON.stringify(json));
break;
default:
console.log('\nUnknown Index message: '+ msg);
break;
}
});
The main application
Finally, the main application, available in index.js. This application is responsible for starting up the websocket server, as well as initiating the plugin via the command line using the appropriate command-line parameters. This code also listens for user input, in the case of the original code, we are only listening for the character b to be pressed by the user in order to initiate the keyUp event. By pressing b, the application sends a message to the server application telling it to execute the socket message associated with ‘button’.
// index.js
const config = require('./config');
const { fork } = require('child_process');
var rlSync = require('readline-sync');
var exec = require('child_process').execFile;
var manifest = require(config.executable.path +'\\'+ config.executable.manifest);
const forked = fork('server.js');
forked.send('<status>Web Socket Server Started....');
// Registration Stuff
var info = {
'application': {
'language': 'en',
'platform': 'windows',
'version': '4.0.0'
},
'devices': [
{
'id': config.server.deviceId,
'size': {
'columns': 5,
'rows': 3
},
'type': 0
}
]
};
var registrationParams = ['-port', config.server.port, '-pluginUUID', manifest.Actions[0].UUID,'-registerEvent','registerEvent','-info', JSON.stringify(info)];
exec(config.executable.exe, registrationParams, { cwd: config.executable.path }, (err, data) => {
if(err){
console.log(`ERROR: ${err}`);
} else {
console.log(`DATA: ${data}`);
}
} );
// Type b at any time to send a KeyUp event to the plugin
promptUser();
function promptUser() {
var cmd = rlSync.question("Enter 'b' to simulate a button press ");
switch(cmd) {
case 'b':
forked.send('button');
break;
default:
break;
}
promptUser();
}
Running the emulator
The first thing you need to do is to pull down the code, and ensuring your configuration is pointed to the correct path.
Then, install the dependencies using:
npm install
And run the application using:
node .
In this case, I am using a debug version of the Sample Plugin available from the StreamDeckToolkit, it has a debugger break that will fire when the plugin is initiated. Select “StreamDeckToolKit” to initiate the debugger in the plugin code.
It will break on a line that says Debugger.Launch() , just press the continue button in Visual Studio.
On the console of the Node application – you’ll see a couple things.
#1 – The prompt for the user to press ‘b’ to initiate the button press (this will seem out of order with the server messages being output, but rest assured, the user can press ‘b’ at any time to initiate the button press).
#2 – The registration message that is received from the plugin is output.
Once the user enters b <enter> – the magic starts – it initates the keyUp event in the plugin, and the plugin responds with commands for the Stream Deck (Emulator). Developers can now inspect the messages obtained from the plugin in the console of the Node application.
To finish off…
This blog post gives an overview of the beginnings of the Stream Deck Emulator project – I’m certain it will grow from here – but this gives us a starting point to ease plugin development for the Stream Deck hardware.