From 9726239fe59084c50afc4b734c26796880a867d7 Mon Sep 17 00:00:00 2001 From: rawhide kobayashi Date: Tue, 25 Feb 2025 17:22:25 -0600 Subject: [PATCH] 'k' and 'v' are minimally functional --- .gitignore | 1 + esp32/src/main.cpp | 84 ++++++++++--- platformio.ini | 1 + profiles/hong.json | 2 +- serialtest.py | 28 +++++ webui/app.py | 62 ++++++++++ webui/ipkvm/__init__.py | 6 +- webui/ipkvm/feed.py | 4 +- webui/ipkvm/routes.py | 57 ++++++--- webui/ipkvm/static/mkb_handler.js | 30 +++++ webui/ipkvm/static/style.css | 8 ++ webui/ipkvm/static/vendor/socket.io.min.js | 7 ++ webui/ipkvm/templates/index.html | 22 ++-- webui/ipkvm/util/mkb.py | 134 +++++++++++++++++++++ webui/ipkvm/util/super_serial.py | 2 + webui/launch.py | 4 +- webui/serial_test.py | 52 ++++++++ 17 files changed, 447 insertions(+), 57 deletions(-) create mode 100644 serialtest.py create mode 100644 webui/app.py create mode 100644 webui/ipkvm/static/mkb_handler.js create mode 100644 webui/ipkvm/static/style.css create mode 100644 webui/ipkvm/static/vendor/socket.io.min.js create mode 100644 webui/ipkvm/util/mkb.py create mode 100644 webui/ipkvm/util/super_serial.py create mode 100644 webui/serial_test.py diff --git a/.gitignore b/.gitignore index 8d1f6c5..f3a04c3 100644 --- a/.gitignore +++ b/.gitignore @@ -4,3 +4,4 @@ .vscode/c_cpp_properties.json .vscode/launch.json .vscode/ipch +ArduinoJson/ \ No newline at end of file diff --git a/esp32/src/main.cpp b/esp32/src/main.cpp index 969c4e9..af27733 100644 --- a/esp32/src/main.cpp +++ b/esp32/src/main.cpp @@ -7,40 +7,86 @@ void loop() {} #else #include +#include #include #include #include + USBHIDMouse Mouse; USBHIDKeyboard Keyboard; +HardwareSerial &host_serial = Serial; +HardwareSerial &mobo_serial = Serial1; + +JsonDocument mkb_input; + // put function declarations here: int myFunction(int, int); -void setup() { - // put your setup code here, to run once: - int result = myFunction(2, 3); - Serial.begin(115200); - Mouse.begin(); - Keyboard.begin(); - USB.begin(); +char buffer[100]; + +void setup() +{ + host_serial.begin(115200); + mobo_serial.begin(115200, SERIAL_8N1, 18); + Mouse.begin(); + Keyboard.begin(); + USB.begin(); } -void loop() { - //Keyboard.write(0x4C); - //Keyboard.pressRaw(HID_KEY_DELETE); - //Keyboard.releaseRaw(HID_KEY_DELETE); - // put your main code here, to run repeatedly: - /*if (Serial.available() > 0) { - char inChar = Serial.read(); +void loop() +{ + // Keyboard.write(0x4C); + // Keyboard.pressRaw(HID_KEY_DELETE); + // Keyboard.releaseRaw(HID_KEY_DELETE); + // put your main code here, to run repeatedly: + /*if (Serial.available() > 0) { + char inChar = Serial.read(); - - }*/ - sleep(1000); + + }*/ + //while (mobo_serial.available()) + //{ + // char c = mobo_serial.read(); + // host_serial.write(c); + //} + if (host_serial.available()) + { + DeserializationError error = deserializeJson(mkb_input, host_serial); + + if (error) + { + host_serial.print("deserializeJson() failed: "); + host_serial.println(error.c_str()); + return; + } + + else + { + JsonArray key_down = mkb_input["key_down"]; + JsonArray key_up = mkb_input["key_up"]; + //host_serial.println("Hej!"); + //serializeJsonPretty(key_down, host_serial); + //serializeJsonPretty(key_up, host_serial); + //host_serial.println("Hej2!"); + for (JsonVariant key : key_down) + { + Keyboard.pressRaw(key.as()); + host_serial.println(key.as()); + } + for (JsonVariant key : key_up) + { + Keyboard.releaseRaw(key.as()); + host_serial.println(key.as()); + } + } + } } // put function definitions here: -int myFunction(int x, int y) { - return x + y; +int myFunction(int x, int y) +{ + return x + y; } #endif /* ARDUINO_USB_MODE */ \ No newline at end of file diff --git a/platformio.ini b/platformio.ini index cb511dd..90ca7a3 100644 --- a/platformio.ini +++ b/platformio.ini @@ -17,6 +17,7 @@ board = rymcu-esp32-s3-devkitc-1 framework = arduino build_flags = -DARDUINO_USB_MODE=0 monitor_speed = 115200 +lib_deps = bblanchon/ArduinoJson @ ~7.3.0 [platformio] src_dir = esp32/src diff --git a/profiles/hong.json b/profiles/hong.json index aebc4bf..38c8b75 100644 --- a/profiles/hong.json +++ b/profiles/hong.json @@ -1 +1 @@ -{"video_device": {"friendly_name": "WARRKY USB 3.0", "format": "mjpeg", "resolution": "1920x1080", "fps": "60.000"}, "esp32_serial": "usb-Espressif_USB_JTAG_serial_debug_unit_CC:8D:A2:0F:C0:08-if00"} \ No newline at end of file +{"video_device": {"friendly_name": "WARRKY USB 3.0", "format": "mjpeg", "resolution": "1920x1080", "fps": "60.000"}, "esp32_serial": "usb-1a86_USB_Single_Serial_585D015807-if00"} \ No newline at end of file diff --git a/serialtest.py b/serialtest.py new file mode 100644 index 0000000..f60f512 --- /dev/null +++ b/serialtest.py @@ -0,0 +1,28 @@ +import serial +import sys +import datetime + +def read_serial(port): + try: + # Open the serial port + with serial.Serial(port, 115200, bytesize=serial.EIGHTBITS, parity=serial.PARITY_NONE, stopbits=serial.STOPBITS_ONE) as ser: + print(f"Listening on {port} at 115200 baud...") + while True: + # Read a line from the serial port + line = ser.read() + # Print the raw data + print(f'{datetime.datetime.now()} {line}') + except serial.SerialException as e: + print(f"Error: {e}") + except KeyboardInterrupt: + print("Exiting...") + sys.exit() + +if __name__ == "__main__": + #if len(sys.argv) != 2: + # print("Usage: python read_serial.py ") + # print("Example: python read_serial.py COM3 (Windows) or /dev/ttyUSB0 (Linux)") + # sys.exit(1) + + #port_name = sys.argv[1] + read_serial("/dev/serial/by-id/usb-1a86_USB_Single_Serial_585D015807-if00") diff --git a/webui/app.py b/webui/app.py new file mode 100644 index 0000000..b56f6a3 --- /dev/null +++ b/webui/app.py @@ -0,0 +1,62 @@ +import subprocess +from flask import Flask, Response + +app = Flask(__name__) + +def generate(): + # FFmpeg command to capture the MJPEG stream without re-encoding. + command = [ + 'ffmpeg', + '-f', 'v4l2', + '-input_format', 'mjpeg', '-video_size', '1920x1080', '-framerate', '60.00', + '-i', '/dev/video0', + '-c', 'copy', + '-f', 'mjpeg', + 'pipe:1' + ] + # Start the FFmpeg subprocess. + process = subprocess.Popen(command, stdout=subprocess.PIPE, bufsize=10**8) + data = b"" + while True: + # Read raw bytes from FFmpeg's stdout. + chunk = process.stdout.read(1024) + if not chunk: + break + data += chunk + + # Look for complete JPEG frames by finding start and end markers. + while True: + start = data.find(b'\xff\xd8') # JPEG start + end = data.find(b'\xff\xd9') # JPEG end + if start != -1 and end != -1 and end > start: + # Extract the JPEG frame. + jpg = data[start:end+2] + data = data[end+2:] + # Yield the frame with the required multipart MJPEG boundaries. + yield (b'--frame\r\n' + b'Content-Type: image/jpeg\r\n\r\n' + jpg + b'\r\n') + else: + break + +@app.route('/video_feed') +def video_feed(): + # Set the MIME type to multipart so browsers render it as an MJPEG stream. + return Response(generate(), mimetype='multipart/x-mixed-replace; boundary=frame') + +@app.route('/') +def index(): + return """ + + + Webcam Stream + + +

Webcam Stream

+ + + + """ + +if __name__ == '__main__': + app.run(host='0.0.0.0', port=5000) + diff --git a/webui/ipkvm/__init__.py b/webui/ipkvm/__init__.py index 8553967..5eb67cb 100644 --- a/webui/ipkvm/__init__.py +++ b/webui/ipkvm/__init__.py @@ -1,10 +1,12 @@ from os import name, listdir from flask import Flask +from flask_socketio import SocketIO import json import logging -ui = Flask(__name__) -logger = ui.logger +app = Flask(__name__) +ui = SocketIO(app) +logger = app.logger logger.setLevel(logging.INFO) def new_profile(): diff --git a/webui/ipkvm/feed.py b/webui/ipkvm/feed.py index 1b00daa..2ccdc94 100644 --- a/webui/ipkvm/feed.py +++ b/webui/ipkvm/feed.py @@ -21,8 +21,6 @@ class FrameBuffer(threading.Thread): def capture_feed(self): device = self.acquire_device() - print(device) - time.sleep(5) while True: # try: # for frame in device.decode(video=0): @@ -72,7 +70,7 @@ class FrameBuffer(threading.Thread): else: raise RuntimeError("We're on something other than Linux, and that's not yet supported!") - device.set(cv2.CAP_PROP_FOURCC, cv2.VideoWriter_fourcc(*"MJPG")) + device.set(cv2.CAP_PROP_FOURCC, cv2.VideoWriter_fourcc(*"YUYV")) device.set(cv2.CAP_PROP_FRAME_WIDTH, int(profile["video_device"]["resolution"].split('x')[0])) device.set(cv2.CAP_PROP_FRAME_HEIGHT, int(profile["video_device"]["resolution"].split('x')[1])) device.set(cv2.CAP_PROP_FPS, float(profile["video_device"]["fps"])) diff --git a/webui/ipkvm/routes.py b/webui/ipkvm/routes.py index e39ab79..355e199 100644 --- a/webui/ipkvm/routes.py +++ b/webui/ipkvm/routes.py @@ -1,7 +1,10 @@ -from ipkvm import ui +from ipkvm import app, ui from ipkvm import frame_buffer -from flask import Response +from flask import Response, render_template import time +from ipkvm.util.mkb import HIDKeyCode +import serial +import json def generate_frames(): while True: @@ -9,22 +12,46 @@ def generate_frames(): frame_buffer.new_frame.clear() yield (b'--frame\r\n' b'Content-Type: image/jpeg\r\n\r\n' + frame_buffer.cur_frame + b'\r\n') + +@ui.on('key_down') +def handle_keydown(data): + test_json_a = { + "mouseX": 99999, + "mouseY": 99999, + "mouse_down": ["rbutton", "lbutton"], + "mouse_up": ["otherbutton"], + "key_up": [], + "key_down": [HIDKeyCode[data]] + } -@ui.route('/video_feed') + print(HIDKeyCode[data]) + with serial.Serial('/dev/serial/by-id/usb-1a86_USB_Single_Serial_585D015807-if00', 115200, bytesize=serial.EIGHTBITS, parity=serial.PARITY_NONE, stopbits=serial.STOPBITS_ONE) as ser: + ser.write(json.dumps(test_json_a).encode()) + +@ui.on('key_up') +def handle_keyup(data): + test_json_a = { + "mouseX": 99999, + "mouseY": 99999, + "mouse_down": ["rbutton", "lbutton"], + "mouse_up": ["otherbutton"], + "key_up": [HIDKeyCode[data]], + "key_down": [] + } + + print(HIDKeyCode[data]) + with serial.Serial('/dev/serial/by-id/usb-1a86_USB_Single_Serial_585D015807-if00', 115200, bytesize=serial.EIGHTBITS, parity=serial.PARITY_NONE, stopbits=serial.STOPBITS_ONE) as ser: + ser.write(json.dumps(test_json_a).encode()) + +@app.route('/video_feed') def video_feed(): return Response(generate_frames(), mimetype='multipart/x-mixed-replace; boundary=frame') -@ui.route('/') +@app.route('/') def index(): - return """ - - - Webcam Stream - - -

Webcam Stream

- - - - """ \ No newline at end of file + return render_template('index.html') + +"""@socketio.on("connect") +def kvm_client(): + ui.start_background_task(mkb_handler)""" diff --git a/webui/ipkvm/static/mkb_handler.js b/webui/ipkvm/static/mkb_handler.js new file mode 100644 index 0000000..d65f2e8 --- /dev/null +++ b/webui/ipkvm/static/mkb_handler.js @@ -0,0 +1,30 @@ +const streamview = document.getElementById('streamview'); + +var socket = io(); + +function keydown_handler(event) +{ + console.log(`Key pressed: ${event.code}`); + socket.emit('key_down', event.code); +} + +function keyup_handler(event) +{ + console.log(`Key released: ${event.code}`); + socket.emit('key_up', event.code); +} + +function enable_listener() +{ + document.addEventListener("keydown", keydown_handler); + document.addEventListener("keyup", keyup_handler); +} + +function disable_listener() +{ + document.removeEventListener("keydown", keydown_handler); + document.removeEventListener("keyup", keyup_handler); +} + +streamview.addEventListener("mouseenter", enable_listener); +streamview.addEventListener("mouseleave", disable_listener); \ No newline at end of file diff --git a/webui/ipkvm/static/style.css b/webui/ipkvm/static/style.css new file mode 100644 index 0000000..73aad32 --- /dev/null +++ b/webui/ipkvm/static/style.css @@ -0,0 +1,8 @@ +.stream-container { + margin: 0 auto; + padding: 20px; +} +.stream-view { + width: 100%; + height: auto; +} \ No newline at end of file diff --git a/webui/ipkvm/static/vendor/socket.io.min.js b/webui/ipkvm/static/vendor/socket.io.min.js new file mode 100644 index 0000000..c72110d --- /dev/null +++ b/webui/ipkvm/static/vendor/socket.io.min.js @@ -0,0 +1,7 @@ +/*! + * Socket.IO v4.8.1 + * (c) 2014-2024 Guillermo Rauch + * Released under the MIT License. + */ +!function(t,n){"object"==typeof exports&&"undefined"!=typeof module?module.exports=n():"function"==typeof define&&define.amd?define(n):(t="undefined"!=typeof globalThis?globalThis:t||self).io=n()}(this,(function(){"use strict";function t(t,n){(null==n||n>t.length)&&(n=t.length);for(var i=0,r=Array(n);i=n.length?{done:!0}:{done:!1,value:n[e++]}},e:function(t){throw t},f:o}}throw new TypeError("Invalid attempt to iterate non-iterable instance.\nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method.")}var s,u=!0,h=!1;return{s:function(){r=r.call(n)},n:function(){var t=r.next();return u=t.done,t},e:function(t){h=!0,s=t},f:function(){try{u||null==r.return||r.return()}finally{if(h)throw s}}}}function e(){return e=Object.assign?Object.assign.bind():function(t){for(var n=1;n1?{type:l[i],data:t.substring(1)}:{type:l[i]}:d},N=function(t,n){if(B){var i=function(t){var n,i,r,e,o,s=.75*t.length,u=t.length,h=0;"="===t[t.length-1]&&(s--,"="===t[t.length-2]&&s--);var f=new ArrayBuffer(s),c=new Uint8Array(f);for(n=0;n>4,c[h++]=(15&r)<<4|e>>2,c[h++]=(3&e)<<6|63&o;return f}(t);return C(i,n)}return{base64:!0,data:t}},C=function(t,n){return"blob"===n?t instanceof Blob?t:new Blob([t]):t instanceof ArrayBuffer?t:t.buffer},T=String.fromCharCode(30);function U(){return new TransformStream({transform:function(t,n){!function(t,n){y&&t.data instanceof Blob?t.data.arrayBuffer().then(k).then(n):b&&(t.data instanceof ArrayBuffer||w(t.data))?n(k(t.data)):g(t,!1,(function(t){p||(p=new TextEncoder),n(p.encode(t))}))}(t,(function(i){var r,e=i.length;if(e<126)r=new Uint8Array(1),new DataView(r.buffer).setUint8(0,e);else if(e<65536){r=new Uint8Array(3);var o=new DataView(r.buffer);o.setUint8(0,126),o.setUint16(1,e)}else{r=new Uint8Array(9);var s=new DataView(r.buffer);s.setUint8(0,127),s.setBigUint64(1,BigInt(e))}t.data&&"string"!=typeof t.data&&(r[0]|=128),n.enqueue(r),n.enqueue(i)}))}})}function M(t){return t.reduce((function(t,n){return t+n.length}),0)}function x(t,n){if(t[0].length===n)return t.shift();for(var i=new Uint8Array(n),r=0,e=0;e1?n-1:0),r=1;r1&&void 0!==arguments[1]?arguments[1]:{};return t+"://"+this.i()+this.o()+this.opts.path+this.u(n)},i.i=function(){var t=this.opts.hostname;return-1===t.indexOf(":")?t:"["+t+"]"},i.o=function(){return this.opts.port&&(this.opts.secure&&Number(443!==this.opts.port)||!this.opts.secure&&80!==Number(this.opts.port))?":"+this.opts.port:""},i.u=function(t){var n=function(t){var n="";for(var i in t)t.hasOwnProperty(i)&&(n.length&&(n+="&"),n+=encodeURIComponent(i)+"="+encodeURIComponent(t[i]));return n}(t);return n.length?"?"+n:""},n}(I),X=function(t){function n(){var n;return(n=t.apply(this,arguments)||this).h=!1,n}s(n,t);var r=n.prototype;return r.doOpen=function(){this.v()},r.pause=function(t){var n=this;this.readyState="pausing";var i=function(){n.readyState="paused",t()};if(this.h||!this.writable){var r=0;this.h&&(r++,this.once("pollComplete",(function(){--r||i()}))),this.writable||(r++,this.once("drain",(function(){--r||i()})))}else i()},r.v=function(){this.h=!0,this.doPoll(),this.emitReserved("poll")},r.onData=function(t){var n=this;(function(t,n){for(var i=t.split(T),r=[],e=0;e0&&void 0!==arguments[0]?arguments[0]:{};return e(t,{xd:this.xd},this.opts),new Y(tt,this.uri(),t)},n}(K);function tt(t){var n=t.xdomain;try{if("undefined"!=typeof XMLHttpRequest&&(!n||z))return new XMLHttpRequest}catch(t){}if(!n)try{return new(L[["Active"].concat("Object").join("X")])("Microsoft.XMLHTTP")}catch(t){}}var nt="undefined"!=typeof navigator&&"string"==typeof navigator.product&&"reactnative"===navigator.product.toLowerCase(),it=function(t){function n(){return t.apply(this,arguments)||this}s(n,t);var r=n.prototype;return r.doOpen=function(){var t=this.uri(),n=this.opts.protocols,i=nt?{}:_(this.opts,"agent","perMessageDeflate","pfx","key","passphrase","cert","ca","ciphers","rejectUnauthorized","localAddress","protocolVersion","origin","maxPayload","family","checkServerIdentity");this.opts.extraHeaders&&(i.headers=this.opts.extraHeaders);try{this.ws=this.createSocket(t,n,i)}catch(t){return this.emitReserved("error",t)}this.ws.binaryType=this.socket.binaryType,this.addEventListeners()},r.addEventListeners=function(){var t=this;this.ws.onopen=function(){t.opts.autoUnref&&t.ws.C.unref(),t.onOpen()},this.ws.onclose=function(n){return t.onClose({description:"websocket connection closed",context:n})},this.ws.onmessage=function(n){return t.onData(n.data)},this.ws.onerror=function(n){return t.onError("websocket error",n)}},r.write=function(t){var n=this;this.writable=!1;for(var i=function(){var i=t[r],e=r===t.length-1;g(i,n.supportsBinary,(function(t){try{n.doWrite(i,t)}catch(t){}e&&R((function(){n.writable=!0,n.emitReserved("drain")}),n.setTimeoutFn)}))},r=0;rMath.pow(2,21)-1){u.enqueue(d);break}e=v*Math.pow(2,32)+a.getUint32(4),r=3}else{if(M(i)t){u.enqueue(d);break}}}})}(Number.MAX_SAFE_INTEGER,t.socket.binaryType),r=n.readable.pipeThrough(i).getReader(),e=U();e.readable.pipeTo(n.writable),t.U=e.writable.getWriter();!function n(){r.read().then((function(i){var r=i.done,e=i.value;r||(t.onPacket(e),n())})).catch((function(t){}))}();var o={type:"open"};t.query.sid&&(o.data='{"sid":"'.concat(t.query.sid,'"}')),t.U.write(o).then((function(){return t.onOpen()}))}))}))},r.write=function(t){var n=this;this.writable=!1;for(var i=function(){var i=t[r],e=r===t.length-1;n.U.write(i).then((function(){e&&R((function(){n.writable=!0,n.emitReserved("drain")}),n.setTimeoutFn)}))},r=0;r8e3)throw"URI too long";var n=t,i=t.indexOf("["),r=t.indexOf("]");-1!=i&&-1!=r&&(t=t.substring(0,i)+t.substring(i,r).replace(/:/g,";")+t.substring(r,t.length));for(var e,o,s=ut.exec(t||""),u={},h=14;h--;)u[ht[h]]=s[h]||"";return-1!=i&&-1!=r&&(u.source=n,u.host=u.host.substring(1,u.host.length-1).replace(/;/g,":"),u.authority=u.authority.replace("[","").replace("]","").replace(/;/g,":"),u.ipv6uri=!0),u.pathNames=function(t,n){var i=/\/{2,9}/g,r=n.replace(i,"/").split("/");"/"!=n.slice(0,1)&&0!==n.length||r.splice(0,1);"/"==n.slice(-1)&&r.splice(r.length-1,1);return r}(0,u.path),u.queryKey=(e=u.query,o={},e.replace(/(?:^|&)([^&=]*)=?([^&]*)/g,(function(t,n,i){n&&(o[n]=i)})),o),u}var ct="function"==typeof addEventListener&&"function"==typeof removeEventListener,at=[];ct&&addEventListener("offline",(function(){at.forEach((function(t){return t()}))}),!1);var vt=function(t){function n(n,i){var r;if((r=t.call(this)||this).binaryType="arraybuffer",r.writeBuffer=[],r.M=0,r.I=-1,r.R=-1,r.L=-1,r._=1/0,n&&"object"===c(n)&&(i=n,n=null),n){var o=ft(n);i.hostname=o.host,i.secure="https"===o.protocol||"wss"===o.protocol,i.port=o.port,o.query&&(i.query=o.query)}else i.host&&(i.hostname=ft(i.host).host);return $(r,i),r.secure=null!=i.secure?i.secure:"undefined"!=typeof location&&"https:"===location.protocol,i.hostname&&!i.port&&(i.port=r.secure?"443":"80"),r.hostname=i.hostname||("undefined"!=typeof location?location.hostname:"localhost"),r.port=i.port||("undefined"!=typeof location&&location.port?location.port:r.secure?"443":"80"),r.transports=[],r.D={},i.transports.forEach((function(t){var n=t.prototype.name;r.transports.push(n),r.D[n]=t})),r.opts=e({path:"/engine.io",agent:!1,withCredentials:!1,upgrade:!0,timestampParam:"t",rememberUpgrade:!1,addTrailingSlash:!0,rejectUnauthorized:!0,perMessageDeflate:{threshold:1024},transportOptions:{},closeOnBeforeunload:!1},i),r.opts.path=r.opts.path.replace(/\/$/,"")+(r.opts.addTrailingSlash?"/":""),"string"==typeof r.opts.query&&(r.opts.query=function(t){for(var n={},i=t.split("&"),r=0,e=i.length;r1))return this.writeBuffer;for(var t,n=1,i=0;i=57344?i+=3:(r++,i+=4);return i}(t):Math.ceil(1.33*(t.byteLength||t.size))),i>0&&n>this.L)return this.writeBuffer.slice(0,i);n+=2}return this.writeBuffer},i.W=function(){var t=this;if(!this._)return!0;var n=Date.now()>this._;return n&&(this._=0,R((function(){t.F("ping timeout")}),this.setTimeoutFn)),n},i.write=function(t,n,i){return this.J("message",t,n,i),this},i.send=function(t,n,i){return this.J("message",t,n,i),this},i.J=function(t,n,i,r){if("function"==typeof n&&(r=n,n=void 0),"function"==typeof i&&(r=i,i=null),"closing"!==this.readyState&&"closed"!==this.readyState){(i=i||{}).compress=!1!==i.compress;var e={type:t,data:n,options:i};this.emitReserved("packetCreate",e),this.writeBuffer.push(e),r&&this.once("flush",r),this.flush()}},i.close=function(){var t=this,n=function(){t.F("forced close"),t.transport.close()},i=function i(){t.off("upgrade",i),t.off("upgradeError",i),n()},r=function(){t.once("upgrade",i),t.once("upgradeError",i)};return"opening"!==this.readyState&&"open"!==this.readyState||(this.readyState="closing",this.writeBuffer.length?this.once("drain",(function(){t.upgrading?r():n()})):this.upgrading?r():n()),this},i.B=function(t){if(n.priorWebsocketSuccess=!1,this.opts.tryAllTransports&&this.transports.length>1&&"opening"===this.readyState)return this.transports.shift(),this.q();this.emitReserved("error",t),this.F("transport error",t)},i.F=function(t,n){if("opening"===this.readyState||"open"===this.readyState||"closing"===this.readyState){if(this.clearTimeoutFn(this.Y),this.transport.removeAllListeners("close"),this.transport.close(),this.transport.removeAllListeners(),ct&&(this.P&&removeEventListener("beforeunload",this.P,!1),this.$)){var i=at.indexOf(this.$);-1!==i&&at.splice(i,1)}this.readyState="closed",this.id=null,this.emitReserved("close",t,n),this.writeBuffer=[],this.M=0}},n}(I);vt.protocol=4;var lt=function(t){function n(){var n;return(n=t.apply(this,arguments)||this).Z=[],n}s(n,t);var i=n.prototype;return i.onOpen=function(){if(t.prototype.onOpen.call(this),"open"===this.readyState&&this.opts.upgrade)for(var n=0;n1&&void 0!==arguments[1]?arguments[1]:{},r="object"===c(n)?n:i;return(!r.transports||r.transports&&"string"==typeof r.transports[0])&&(r.transports=(r.transports||["polling","websocket","webtransport"]).map((function(t){return st[t]})).filter((function(t){return!!t}))),t.call(this,n,r)||this}return s(n,t),n}(lt);pt.protocol;var dt="function"==typeof ArrayBuffer,yt=function(t){return"function"==typeof ArrayBuffer.isView?ArrayBuffer.isView(t):t.buffer instanceof ArrayBuffer},bt=Object.prototype.toString,wt="function"==typeof Blob||"undefined"!=typeof Blob&&"[object BlobConstructor]"===bt.call(Blob),gt="function"==typeof File||"undefined"!=typeof File&&"[object FileConstructor]"===bt.call(File);function mt(t){return dt&&(t instanceof ArrayBuffer||yt(t))||wt&&t instanceof Blob||gt&&t instanceof File}function kt(t,n){if(!t||"object"!==c(t))return!1;if(Array.isArray(t)){for(var i=0,r=t.length;i=0&&t.num1?e-1:0),s=1;s1?i-1:0),e=1;ei.l.retries&&(i.it.shift(),n&&n(t));else if(i.it.shift(),n){for(var e=arguments.length,o=new Array(e>1?e-1:0),s=1;s0&&void 0!==arguments[0]&&arguments[0];if(this.connected&&0!==this.it.length){var n=this.it[0];n.pending&&!t||(n.pending=!0,n.tryCount++,this.flags=n.flags,this.emit.apply(this,n.args))}},o.packet=function(t){t.nsp=this.nsp,this.io.ct(t)},o.onopen=function(){var t=this;"function"==typeof this.auth?this.auth((function(n){t.vt(n)})):this.vt(this.auth)},o.vt=function(t){this.packet({type:Bt.CONNECT,data:this.lt?e({pid:this.lt,offset:this.dt},t):t})},o.onerror=function(t){this.connected||this.emitReserved("connect_error",t)},o.onclose=function(t,n){this.connected=!1,delete this.id,this.emitReserved("disconnect",t,n),this.yt()},o.yt=function(){var t=this;Object.keys(this.acks).forEach((function(n){if(!t.sendBuffer.some((function(t){return String(t.id)===n}))){var i=t.acks[n];delete t.acks[n],i.withError&&i.call(t,new Error("socket has been disconnected"))}}))},o.onpacket=function(t){if(t.nsp===this.nsp)switch(t.type){case Bt.CONNECT:t.data&&t.data.sid?this.onconnect(t.data.sid,t.data.pid):this.emitReserved("connect_error",new Error("It seems you are trying to reach a Socket.IO server in v2.x with a v3.x client, but they are not compatible (more information here: https://socket.io/docs/v3/migrating-from-2-x-to-3-0/)"));break;case Bt.EVENT:case Bt.BINARY_EVENT:this.onevent(t);break;case Bt.ACK:case Bt.BINARY_ACK:this.onack(t);break;case Bt.DISCONNECT:this.ondisconnect();break;case Bt.CONNECT_ERROR:this.destroy();var n=new Error(t.data.message);n.data=t.data.data,this.emitReserved("connect_error",n)}},o.onevent=function(t){var n=t.data||[];null!=t.id&&n.push(this.ack(t.id)),this.connected?this.emitEvent(n):this.receiveBuffer.push(Object.freeze(n))},o.emitEvent=function(n){if(this.bt&&this.bt.length){var i,e=r(this.bt.slice());try{for(e.s();!(i=e.n()).done;){i.value.apply(this,n)}}catch(t){e.e(t)}finally{e.f()}}t.prototype.emit.apply(this,n),this.lt&&n.length&&"string"==typeof n[n.length-1]&&(this.dt=n[n.length-1])},o.ack=function(t){var n=this,i=!1;return function(){if(!i){i=!0;for(var r=arguments.length,e=new Array(r),o=0;o0&&t.jitter<=1?t.jitter:0,this.attempts=0}_t.prototype.duration=function(){var t=this.ms*Math.pow(this.factor,this.attempts++);if(this.jitter){var n=Math.random(),i=Math.floor(n*this.jitter*t);t=1&Math.floor(10*n)?t+i:t-i}return 0|Math.min(t,this.max)},_t.prototype.reset=function(){this.attempts=0},_t.prototype.setMin=function(t){this.ms=t},_t.prototype.setMax=function(t){this.max=t},_t.prototype.setJitter=function(t){this.jitter=t};var Dt=function(t){function n(n,i){var r,e;(r=t.call(this)||this).nsps={},r.subs=[],n&&"object"===c(n)&&(i=n,n=void 0),(i=i||{}).path=i.path||"/socket.io",r.opts=i,$(r,i),r.reconnection(!1!==i.reconnection),r.reconnectionAttempts(i.reconnectionAttempts||1/0),r.reconnectionDelay(i.reconnectionDelay||1e3),r.reconnectionDelayMax(i.reconnectionDelayMax||5e3),r.randomizationFactor(null!==(e=i.randomizationFactor)&&void 0!==e?e:.5),r.backoff=new _t({min:r.reconnectionDelay(),max:r.reconnectionDelayMax(),jitter:r.randomizationFactor()}),r.timeout(null==i.timeout?2e4:i.timeout),r.st="closed",r.uri=n;var o=i.parser||xt;return r.encoder=new o.Encoder,r.decoder=new o.Decoder,r.et=!1!==i.autoConnect,r.et&&r.open(),r}s(n,t);var i=n.prototype;return i.reconnection=function(t){return arguments.length?(this.kt=!!t,t||(this.skipReconnect=!0),this):this.kt},i.reconnectionAttempts=function(t){return void 0===t?this.At:(this.At=t,this)},i.reconnectionDelay=function(t){var n;return void 0===t?this.jt:(this.jt=t,null===(n=this.backoff)||void 0===n||n.setMin(t),this)},i.randomizationFactor=function(t){var n;return void 0===t?this.Et:(this.Et=t,null===(n=this.backoff)||void 0===n||n.setJitter(t),this)},i.reconnectionDelayMax=function(t){var n;return void 0===t?this.Ot:(this.Ot=t,null===(n=this.backoff)||void 0===n||n.setMax(t),this)},i.timeout=function(t){return arguments.length?(this.Bt=t,this):this.Bt},i.maybeReconnectOnOpen=function(){!this.ot&&this.kt&&0===this.backoff.attempts&&this.reconnect()},i.open=function(t){var n=this;if(~this.st.indexOf("open"))return this;this.engine=new pt(this.uri,this.opts);var i=this.engine,r=this;this.st="opening",this.skipReconnect=!1;var e=It(i,"open",(function(){r.onopen(),t&&t()})),o=function(i){n.cleanup(),n.st="closed",n.emitReserved("error",i),t?t(i):n.maybeReconnectOnOpen()},s=It(i,"error",o);if(!1!==this.Bt){var u=this.Bt,h=this.setTimeoutFn((function(){e(),o(new Error("timeout")),i.close()}),u);this.opts.autoUnref&&h.unref(),this.subs.push((function(){n.clearTimeoutFn(h)}))}return this.subs.push(e),this.subs.push(s),this},i.connect=function(t){return this.open(t)},i.onopen=function(){this.cleanup(),this.st="open",this.emitReserved("open");var t=this.engine;this.subs.push(It(t,"ping",this.onping.bind(this)),It(t,"data",this.ondata.bind(this)),It(t,"error",this.onerror.bind(this)),It(t,"close",this.onclose.bind(this)),It(this.decoder,"decoded",this.ondecoded.bind(this)))},i.onping=function(){this.emitReserved("ping")},i.ondata=function(t){try{this.decoder.add(t)}catch(t){this.onclose("parse error",t)}},i.ondecoded=function(t){var n=this;R((function(){n.emitReserved("packet",t)}),this.setTimeoutFn)},i.onerror=function(t){this.emitReserved("error",t)},i.socket=function(t,n){var i=this.nsps[t];return i?this.et&&!i.active&&i.connect():(i=new Lt(this,t,n),this.nsps[t]=i),i},i.wt=function(t){for(var n=0,i=Object.keys(this.nsps);n=this.At)this.backoff.reset(),this.emitReserved("reconnect_failed"),this.ot=!1;else{var i=this.backoff.duration();this.ot=!0;var r=this.setTimeoutFn((function(){n.skipReconnect||(t.emitReserved("reconnect_attempt",n.backoff.attempts),n.skipReconnect||n.open((function(i){i?(n.ot=!1,n.reconnect(),t.emitReserved("reconnect_error",i)):n.onreconnect()})))}),i);this.opts.autoUnref&&r.unref(),this.subs.push((function(){t.clearTimeoutFn(r)}))}},i.onreconnect=function(){var t=this.backoff.attempts;this.ot=!1,this.backoff.reset(),this.emitReserved("reconnect",t)},n}(I),Pt={};function $t(t,n){"object"===c(t)&&(n=t,t=void 0);var i,r=function(t){var n=arguments.length>1&&void 0!==arguments[1]?arguments[1]:"",i=arguments.length>2?arguments[2]:void 0,r=t;i=i||"undefined"!=typeof location&&location,null==t&&(t=i.protocol+"//"+i.host),"string"==typeof t&&("/"===t.charAt(0)&&(t="/"===t.charAt(1)?i.protocol+t:i.host+t),/^(https?|wss?):\/\//.test(t)||(t=void 0!==i?i.protocol+"//"+t:"https://"+t),r=ft(t)),r.port||(/^(http|ws)$/.test(r.protocol)?r.port="80":/^(http|ws)s$/.test(r.protocol)&&(r.port="443")),r.path=r.path||"/";var e=-1!==r.host.indexOf(":")?"["+r.host+"]":r.host;return r.id=r.protocol+"://"+e+":"+r.port+n,r.href=r.protocol+"://"+e+(i&&i.port===r.port?"":":"+r.port),r}(t,(n=n||{}).path||"/socket.io"),e=r.source,o=r.id,s=r.path,u=Pt[o]&&s in Pt[o].nsps;return n.forceNew||n["force new connection"]||!1===n.multiplex||u?i=new Dt(e,n):(Pt[o]||(Pt[o]=new Dt(e,n)),i=Pt[o]),r.query&&!n.query&&(n.query=r.queryKey),i.socket(r.path,n)}return e($t,{Manager:Dt,Socket:Lt,io:$t,connect:$t}),$t})); +//# sourceMappingURL=socket.io.min.js.map diff --git a/webui/ipkvm/templates/index.html b/webui/ipkvm/templates/index.html index 03ed53c..7f03130 100644 --- a/webui/ipkvm/templates/index.html +++ b/webui/ipkvm/templates/index.html @@ -1,22 +1,14 @@ + + - MJPEG Stream Viewer - + IP KVM & OC Tuner -
- +
+
- \ No newline at end of file + + \ No newline at end of file diff --git a/webui/ipkvm/util/mkb.py b/webui/ipkvm/util/mkb.py new file mode 100644 index 0000000..05f6e50 --- /dev/null +++ b/webui/ipkvm/util/mkb.py @@ -0,0 +1,134 @@ +from enum import IntEnum + +# God Bless CHADGPT +class HIDKeyCode(IntEnum): + """ + Enum that translates modern JS key.code values to HID scancodes. + """ + # Letter keys (A-Z) + KeyA = 4 + KeyB = 5 + KeyC = 6 + KeyD = 7 + KeyE = 8 + KeyF = 9 + KeyG = 10 + KeyH = 11 + KeyI = 12 + KeyJ = 13 + KeyK = 14 + KeyL = 15 + KeyM = 16 + KeyN = 17 + KeyO = 18 + KeyP = 19 + KeyQ = 20 + KeyR = 21 + KeyS = 22 + KeyT = 23 + KeyU = 24 + KeyV = 25 + KeyW = 26 + KeyX = 27 + KeyY = 28 + KeyZ = 29 + + # Number keys (top row) + Digit1 = 30 + Digit2 = 31 + Digit3 = 32 + Digit4 = 33 + Digit5 = 34 + Digit6 = 35 + Digit7 = 36 + Digit8 = 37 + Digit9 = 38 + Digit0 = 39 + + # Control keys + Enter = 40 + Escape = 41 + Backspace = 42 + Tab = 43 + Space = 44 + + Minus = 45 + Equal = 46 + BracketLeft = 47 + BracketRight = 48 + Backslash = 49 + + # Punctuation keys + Semicolon = 51 + Quote = 52 + Backquote = 53 + Comma = 54 + Period = 55 + Slash = 56 + + CapsLock = 57 + + # Function keys (F1-F12) + F1 = 58 + F2 = 59 + F3 = 60 + F4 = 61 + F5 = 62 + F6 = 63 + F7 = 64 + F8 = 65 + F9 = 66 + F10 = 67 + F11 = 68 + F12 = 69 + + PrintScreen = 70 + ScrollLock = 71 + Pause = 72 + + Insert = 73 + Home = 74 + PageUp = 75 + + Delete = 76 + End = 77 + PageDown = 78 + + ArrowRight = 79 + ArrowLeft = 80 + ArrowDown = 81 + ArrowUp = 82 + + # Numpad keys + NumLock = 83 + NumpadDivide = 84 + NumpadMultiply = 85 + NumpadSubtract = 86 + NumpadAdd = 87 + NumpadEnter = 88 + Numpad1 = 89 + Numpad2 = 90 + Numpad3 = 91 + Numpad4 = 92 + Numpad5 = 93 + Numpad6 = 94 + Numpad7 = 95 + Numpad8 = 96 + Numpad9 = 97 + Numpad0 = 98 + NumpadDecimal = 99 + + # Additional keys + IntlBackslash = 100 + ContextMenu = 101 + Power = 102 + + # Modifier keys + ControlLeft = 224 + ShiftLeft = 225 + AltLeft = 226 + MetaLeft = 227 # Windows / Command key (left) + ControlRight = 228 + ShiftRight = 229 + AltRight = 230 + MetaRight = 231 # Windows / Command key (right) diff --git a/webui/ipkvm/util/super_serial.py b/webui/ipkvm/util/super_serial.py new file mode 100644 index 0000000..28480f2 --- /dev/null +++ b/webui/ipkvm/util/super_serial.py @@ -0,0 +1,2 @@ +import serial + diff --git a/webui/launch.py b/webui/launch.py index 4204a27..edc8f1e 100644 --- a/webui/launch.py +++ b/webui/launch.py @@ -1,4 +1,4 @@ -from ipkvm import ui +from ipkvm import app, ui if __name__ == '__main__': - ui.run(host='0.0.0.0', port=5000) + ui.run(app, host='0.0.0.0', port=5000) diff --git a/webui/serial_test.py b/webui/serial_test.py new file mode 100644 index 0000000..1a012e2 --- /dev/null +++ b/webui/serial_test.py @@ -0,0 +1,52 @@ +import serial +import sys +import datetime +import json +import time + +test_json_a = { + "mouseX": 99999, + "mouseY": 99999, + "mouse_down": ["rbutton", "lbutton"], + "mouse_up": ["otherbutton"], + "key_up": [], + "key_down": [11, 12] +} + +test_json_b = { + "mouseX": 99999, + "mouseY": 99999, + "mouse_down": ["rbutton", "lbutton"], + "mouse_up": ["otherbutton"], + "key_up": [11, 12], + "key_down": [] +} + +def read_serial(port): + try: + # Open the serial port + with serial.Serial(port, 115200, bytesize=serial.EIGHTBITS, parity=serial.PARITY_NONE, stopbits=serial.STOPBITS_ONE) as ser: + print(f"Listening on {port} at 115200 baud...") + while True: + # Read a line from the serial port + while ser.in_waiting > 0: + line = ser.readline() + print(f'{datetime.datetime.now()} {line}') + # Print the raw data + ser.write(json.dumps(test_json_a).encode()) + ser.write(json.dumps(test_json_b).encode()) + time.sleep(1) + except serial.SerialException as e: + print(f"Error: {e}") + except KeyboardInterrupt: + print("Exiting...") + sys.exit() + +if __name__ == "__main__": + #if len(sys.argv) != 2: + # print("Usage: python read_serial.py ") + # print("Example: python read_serial.py COM3 (Windows) or /dev/ttyUSB0 (Linux)") + # sys.exit(1) + + #port_name = sys.argv[1] + read_serial('/dev/serial/by-id/usb-1a86_USB_Single_Serial_585D015807-if00')