commit 160efe6906dadbc00725c05fd9fd03e034159589 Author: shironeko Date: Sun Feb 2 17:30:09 2025 -0300 new file: .gitignore new file: main.js new file: package-lock.json new file: package.json diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..1170717 --- /dev/null +++ b/.gitignore @@ -0,0 +1,136 @@ +# Logs +logs +*.log +npm-debug.log* +yarn-debug.log* +yarn-error.log* +lerna-debug.log* +.pnpm-debug.log* + +# Diagnostic reports (https://nodejs.org/api/report.html) +report.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json + +# Runtime data +pids +*.pid +*.seed +*.pid.lock + +# Directory for instrumented libs generated by jscoverage/JSCover +lib-cov + +# Coverage directory used by tools like istanbul +coverage +*.lcov + +# nyc test coverage +.nyc_output + +# Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files) +.grunt + +# Bower dependency directory (https://bower.io/) +bower_components + +# node-waf configuration +.lock-wscript + +# Compiled binary addons (https://nodejs.org/api/addons.html) +build/Release + +# Dependency directories +node_modules/ +jspm_packages/ + +# Snowpack dependency directory (https://snowpack.dev/) +web_modules/ + +# TypeScript cache +*.tsbuildinfo + +# Optional npm cache directory +.npm + +# Optional eslint cache +.eslintcache + +# Optional stylelint cache +.stylelintcache + +# Microbundle cache +.rpt2_cache/ +.rts2_cache_cjs/ +.rts2_cache_es/ +.rts2_cache_umd/ + +# Optional REPL history +.node_repl_history + +# Output of 'npm pack' +*.tgz + +# Yarn Integrity file +.yarn-integrity + +# dotenv environment variable files +.env +.env.development.local +.env.test.local +.env.production.local +.env.local + +# parcel-bundler cache (https://parceljs.org/) +.cache +.parcel-cache + +# Next.js build output +.next +out + +# Nuxt.js build / generate output +.nuxt +dist + +# Gatsby files +.cache/ +# Comment in the public line in if your project uses Gatsby and not Next.js +# https://nextjs.org/blog/next-9-1#public-directory-support +# public + +# vuepress build output +.vuepress/dist + +# vuepress v2.x temp and cache directory +.temp +.cache + +# vitepress build output +**/.vitepress/dist + +# vitepress cache directory +**/.vitepress/cache + +# Docusaurus cache and generated files +.docusaurus + +# Serverless directories +.serverless/ + +# FuseBox cache +.fusebox/ + +# DynamoDB Local files +.dynamodb/ + +# TernJS port file +.tern-port + +# Stores VSCode versions used for testing VSCode extensions +.vscode-test + +# yarn v2 +.yarn/cache +.yarn/unplugged +.yarn/build-state.yml +.yarn/install-state.gz +.pnp.* diff --git a/main.js b/main.js new file mode 100644 index 0000000..698e95b --- /dev/null +++ b/main.js @@ -0,0 +1,261 @@ +/* +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 +*/ + +var clients = {} + +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); + }); +} + +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(); \ No newline at end of file diff --git a/package-lock.json b/package-lock.json new file mode 100644 index 0000000..cc6bc86 --- /dev/null +++ b/package-lock.json @@ -0,0 +1,46 @@ +{ + "name": "macrodeckreimp", + "version": "1.0.0", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "macrodeckreimp", + "version": "1.0.0", + "license": "ISC", + "dependencies": { + "qrcode-terminal": "^0.12.0", + "ws": "^8.18.0" + } + }, + "node_modules/qrcode-terminal": { + "version": "0.12.0", + "resolved": "https://registry.npmjs.org/qrcode-terminal/-/qrcode-terminal-0.12.0.tgz", + "integrity": "sha512-EXtzRZmC+YGmGlDFbXKxQiMZNwCLEO6BANKXG4iCtSIM0yqc/pappSx3RIKr4r0uh5JsBckOXeKrB3Iz7mdQpQ==", + "bin": { + "qrcode-terminal": "bin/qrcode-terminal.js" + } + }, + "node_modules/ws": { + "version": "8.18.0", + "resolved": "https://registry.npmjs.org/ws/-/ws-8.18.0.tgz", + "integrity": "sha512-8VbfWfHLbbwu3+N6OKsOMpBdT4kXPDDB9cJk2bJ6mh9ucxdlnNvH1e+roYkKmN9Nxw2yjz7VzeO9oOz2zJ04Pw==", + "license": "MIT", + "engines": { + "node": ">=10.0.0" + }, + "peerDependencies": { + "bufferutil": "^4.0.1", + "utf-8-validate": ">=5.0.2" + }, + "peerDependenciesMeta": { + "bufferutil": { + "optional": true + }, + "utf-8-validate": { + "optional": true + } + } + } + } +} diff --git a/package.json b/package.json new file mode 100644 index 0000000..c98c6fa --- /dev/null +++ b/package.json @@ -0,0 +1,15 @@ +{ + "name": "macrodeckreimp", + "version": "1.0.0", + "main": "index.js", + "scripts": { + "test": "echo \"Error: no test specified\" && exit 1" + }, + "author": "", + "license": "ISC", + "description": "", + "dependencies": { + "qrcode-terminal": "^0.12.0", + "ws": "^8.18.0" + } +}