/* QR code server protocol: https://macro-deck.app/quick-setup/ + {"instanceName":"DESKTOP-J09EM3G","networkInterfaces":["192.168.1.134"],"port":8191,"ssl":false,"token":"vPl4Vq7v"} in base64 = https://macro-deck.app/quick-setup/eyJpbnN0YW5jZU5hbWUiOiJERVNLVE9QLUowOUVNM0ciLCJuZXR3b3JrSW50ZXJmYWNlcyI6WyIxOTIuMTY4LjEuMTM0Il0sInBvcnQiOjgxOTEsInNzbCI6ZmFsc2UsInRva2VuIjoidlBsNFZxN3YifQ== Device types: Web - Android Newer android app (3.3.0) identifies as web (no device settings control) Github version (2.4.1-beta) identifies as Android (brightness, auto-connection,keep awake contrll) Start WebSocket server Server broadcasts connection data via UDP: {"computer-name": "MDServer","ip-address": "192.168.1.133","port": 8191} Client gets Client connects to websocket server Client sends: {"Method": "CONNECTED","Client-Id": "CLIENTNAME","API": "20","Device-Type": "Android"} Server responds with: {"Method": "GET_CONFIG","Rows": 2,"Columns": 2,"ButtonSpacing": 10,"ButtonRadius": 10,"ButtonBackground": true,"Brightness": 0.3,"AutoConnect": false,"WakeLock": "Connected","SupportButtonReleaseLongPress": true} Client sends: {Method: 'GET_BUTTONS'} Server responds with: {"Method": "GET_BUTTONS","Buttons": [{"IconBase64": "","Position_X": 0,"Position_Y": 0,"LabelBase64": "","BackgroundColorHex": "#232323"}]} Client sends on button press: { "Method": "BUTTON_PRESS", "Message": "0_0" } (X_Y) Server can respond with single button updates: {"Method": "UPDATE_BUTTON","Buttons": [{"IconBase64": "","Position_X": 0,"Position_Y": 0,"LabelBase64": "","BackgroundColorHex": "#000000"}]} - Server can resend "GET_BUTTONS" at any moment to update setup - Server can resend "GET_CONFIG" at any moment to control client - Server can "UPDATE_BUTTON" in any moment */ function generateQR(ServerName, ServerIP, token, ServerPort = 8191) { const qrcode = require('qrcode-terminal'); // it generates the exact same link as the official one, but it does not work (?) var obj = { "instanceName": ServerName, "networkInterfaces": ServerIP, "port": ServerPort, "ssl": false, "token": token } var url = "https://macro-deck.app/quick-setup/" + Buffer.from(JSON.stringify(obj)).toString('base64'); console.log(url) qrcode.generate(url, { small: true }, function (qrcode) { console.log(qrcode); }); } var clients = {} function broadcastServer(ServerName, ServerIP, BroadcastPort = 8191, BroadcastAddress = '255.255.255.255') { const dgram = require('dgram'); const udpClient = dgram.createSocket('udp4'); udpClient.bind(() => { udpClient.setBroadcast(true); }); setInterval(() => { try { const broadcastObject = { "computer-name": ServerName, "ip-address": ServerIP, "port": BroadcastPort }; const message = Buffer.from(JSON.stringify(broadcastObject)); udpClient.send(message, 0, message.length, BroadcastPort, BroadcastAddress, (err) => { if (err) console.error(err); }); } catch (error) { console.error(error); } }, 5000); } function startWsServer(ServerPort = 8191) { const { WebSocketServer } = require("ws"); const wss = new WebSocketServer({ port: ServerPort }) wss.on('connection', function connection(ws, req) { const ip = req.socket.remoteAddress; ws.on('error', console.error) ws.on('message', function message(data) { data = JSON.parse(data) console.log(ip, data); switch (data.Method) { case "CONNECTED": clients[data["Client-Id"]] = ip.replace("::ffff:", ""); //on "CONNECTED" request, send the grid setup, and client settings var obj = JSON.stringify({ "Method": "GET_CONFIG", "Rows": 6, "Columns": 8, "ButtonSpacing": 2, "ButtonRadius": 10, "ButtonBackground": true, "Brightness": 0.5, // 0.1 -> 1.0 "AutoConnect": true, // true - false "WakeLock": "Connected", //Never - Connected - Always "SupportButtonReleaseLongPress": true }) ws.send(obj); break; case "GET_BUTTONS": //Client will request the buttons content and their position in the setup var obj = JSON.stringify({ "Method": "GET_BUTTONS", "Buttons": [ { "IconBase64": "", "Position_X": 1, "Position_Y": 0, "LabelBase64": "", "BackgroundColorHex": "#232323" }, { "IconBase64": "", "Position_X": 0, "Position_Y": 0, "LabelBase64": "", "BackgroundColorHex": "#232323" }, { "IconBase64": "", "Position_X": 1, "Position_Y": 1, "LabelBase64": "", "BackgroundColorHex": "#232323" }, { "IconBase64": "", "Position_X": 0, "Position_Y": 1, "LabelBase64": "", "BackgroundColorHex": "#232323" } ] }) ws.send(obj); break; case "BUTTON_PRESS": var obj = JSON.stringify({ "Method": "UPDATE_BUTTON", "Buttons": [ { "IconBase64": "", "Position_X": 0, "Position_Y": 0, "LabelBase64": "", "BackgroundColorHex": "#000000" } ] }) // broadcast update to all clients wss.clients.forEach(function each(client) { if (client.readyState === WebSocket.OPEN) { client.send(obj); } }); var obj = JSON.stringify({ "Method": "UPDATE_BUTTON", "Buttons": [ { "IconBase64": "", "Position_X": 7, "Position_Y": 5, "LabelBase64": "", "BackgroundColorHex": "#000000" } ] }) wss.clients.forEach(function each(client) { if (client.readyState === WebSocket.OPEN) { client.send(obj); } }); //ws.send(obj) console.log(data.Method, data.Message.split('_')) break; case "BUTTON_RELEASE": var obj = JSON.stringify({ "Method": "UPDATE_BUTTON", "Buttons": [ { "IconBase64": "", "Position_X": 0, "Position_Y": 0, "LabelBase64": "", "BackgroundColorHex": "#FFFFFF" } ] }) wss.clients.forEach(function each(client) { if (client.readyState === WebSocket.OPEN) { client.send(obj); } }); var obj = JSON.stringify({ "Method": "UPDATE_BUTTON", "Buttons": [ { "IconBase64": "", "Position_X": 7, "Position_Y": 5, "LabelBase64": "", "BackgroundColorHex": "#FFFFFF" } ] }) wss.clients.forEach(function each(client) { if (client.readyState === WebSocket.OPEN) { client.send(obj); } }); //ws.send(obj); console.log(data.Method, data.Message.split('_')) break; case "BUTTON_LONG_PRESS": console.log(data.Method, data.Message.split('_')) break; case "BUTTON_LONG_PRESS_RELEASE": console.log(data.Method, data.Message.split('_')) break; default: console.log(data); break; } }); }); } function startWsClient(ip, port = 8191) { const WebSocket = require("ws"); const fs = require("fs"); const ws = new WebSocket("ws://" + ip + ":" + port); ws.on('error', console.error); ws.on('open', function open() { var obj = JSON.stringify({ "Method": "CONNECTED", "Client-Id": "LinuxClient", "API": "20", "Device-Type": "Android" //Web - Android }); ws.send(obj); }); var logs = { data: [] } ws.on('message', function message(data) { console.log('received: %s', data); //logs.data.push(JSON.parse(data)); //fs.writeFileSync("test.json", JSON.stringify(logs), 'utf8', null); }); } //generateQR("DESKTOP-J09EM3G", ["192.168.1.134"], "vPl4Vq7v"); //startWsClient("192.168.1.134"); broadcastServer("DEBIAN-PC", "192.168.1.133"); startWsServer();