v1.0.0-alpha
This commit is contained in:
126
WebApp/constants.js
Normal file
126
WebApp/constants.js
Normal file
@@ -0,0 +1,126 @@
|
||||
export const DEVICE_MODE_KEY_VALUE =
|
||||
{
|
||||
"Xbox OG" : 7,
|
||||
"Xbox OG: Steel Battalion" : 8,
|
||||
"Xbox OG: XRemote" : 9,
|
||||
"XInput" : 6,
|
||||
"PS3" : 2,
|
||||
"PS Classic" : 5,
|
||||
"Switch" : 4,
|
||||
"WebApp" : 99,
|
||||
};
|
||||
|
||||
export const PROFILE_ID_KEY_VALUE =
|
||||
{
|
||||
"Profile 1": 0x01,
|
||||
"Profile 2": 0x02,
|
||||
"Profile 3": 0x03,
|
||||
"Profile 4": 0x04,
|
||||
"Profile 5": 0x05,
|
||||
"Profile 6": 0x06,
|
||||
"Profile 7": 0x07,
|
||||
"Profile 8": 0x08,
|
||||
};
|
||||
|
||||
export const DPAD_KEY_VALUE =
|
||||
{
|
||||
"Up" : 0x01,
|
||||
"Down" : 0x02,
|
||||
"Left" : 0x04,
|
||||
"Right" : 0x08,
|
||||
};
|
||||
|
||||
export const BUTTON_KEY_VALUE =
|
||||
{
|
||||
"A" : 0x0001,
|
||||
"B" : 0x0002,
|
||||
"X" : 0x0004,
|
||||
"Y" : 0x0008,
|
||||
"L3" : 0x0010,
|
||||
"R3" : 0x0020,
|
||||
"Back" : 0x0040,
|
||||
"Start" : 0x0080,
|
||||
"LB" : 0x0100,
|
||||
"RB" : 0x0200,
|
||||
"Guide" : 0x0400,
|
||||
"Misc" : 0x0800
|
||||
};
|
||||
|
||||
export const ANALOG_KEY_VALUE =
|
||||
{
|
||||
"Up" : 0x00,
|
||||
"Down" : 0x01,
|
||||
"Left" : 0x02,
|
||||
"Right" : 0x03,
|
||||
"A" : 0x04,
|
||||
"B" : 0x05,
|
||||
"X" : 0x06,
|
||||
"Y" : 0x07,
|
||||
"LB" : 0x08,
|
||||
"RB" : 0x09
|
||||
};
|
||||
|
||||
export const PACKET_IDS =
|
||||
{
|
||||
INIT_READ : 0x88,
|
||||
READ_PROFILE : 0x01,
|
||||
WRITE_PROFILE : 0x02,
|
||||
// WRITE_MODE : 0x03,
|
||||
RESPONSE_OK : 0x10,
|
||||
RESPONSE_ERROR : 0x11,
|
||||
};
|
||||
|
||||
export function new_packet()
|
||||
{
|
||||
return {
|
||||
report_id: 0,
|
||||
input_mode: 0,
|
||||
max_gamepads: 0,
|
||||
player_idx: 0,
|
||||
|
||||
profile:
|
||||
{
|
||||
id: 0,
|
||||
|
||||
dz_trigger_l: 0,
|
||||
dz_trigger_r: 0,
|
||||
|
||||
dz_joystick_l: 0,
|
||||
dz_joystick_r: 0,
|
||||
|
||||
invert_ly: 0,
|
||||
invert_ry: 0,
|
||||
|
||||
dpad_up: 0x01,
|
||||
dpad_down: 0x02,
|
||||
dpad_left: 0x04,
|
||||
dpad_right: 0x08,
|
||||
|
||||
button_a: 0x0001,
|
||||
button_b: 0x0002,
|
||||
button_x: 0x0004,
|
||||
button_y: 0x0008,
|
||||
button_l3: 0x0010,
|
||||
button_r3: 0x0020,
|
||||
button_back: 0x0040,
|
||||
button_start: 0x0080,
|
||||
button_lb: 0x0100,
|
||||
button_rb: 0x0200,
|
||||
button_sys: 0x0400,
|
||||
button_misc: 0x0800,
|
||||
|
||||
analog_enabled: 1,
|
||||
|
||||
analog_off_up: 0,
|
||||
analog_off_down: 1,
|
||||
analog_off_left: 2,
|
||||
analog_off_right: 3,
|
||||
analog_off_a: 4,
|
||||
analog_off_b: 5,
|
||||
analog_off_x: 6,
|
||||
analog_off_y: 7,
|
||||
analog_off_lb: 8,
|
||||
analog_off_rb: 9
|
||||
}
|
||||
}
|
||||
};
|
||||
BIN
WebApp/favicon.ico
Normal file
BIN
WebApp/favicon.ico
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 1.8 KiB |
235
WebApp/index.html
Normal file
235
WebApp/index.html
Normal file
@@ -0,0 +1,235 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<meta http-equiv="X-UA-Compatible" content="ie=edge">
|
||||
<title>OGX-Mini Settings</title>
|
||||
<link rel="stylesheet" href="style.css">
|
||||
</head>
|
||||
<body>
|
||||
<div class="container">
|
||||
<h1>OGX-Mini Settings</h1>
|
||||
<div class="connect-ui" id="connect-ui">
|
||||
<button class="connect" id="connect">Connect</button><br><br>
|
||||
<label>To ensure your adapter is in WebApp mode, hold Start + Left Bumper + Right Bumper for 3 seconds.</label>
|
||||
<div class="error">
|
||||
⚠️ This browser does not support the WebSerial API. Please try a different browser.
|
||||
</div>
|
||||
</div>
|
||||
<div class="settings-ui" id="settings-ui">
|
||||
|
||||
<div class="settings">
|
||||
|
||||
<!-- <div>
|
||||
<label>Firmware: <span id="firmwareVersion">v1.0.0</span></label>
|
||||
</div> -->
|
||||
<label>Minimum firmware required: v1.0.0</label>
|
||||
<div>
|
||||
<h3>Device Mode</h3>
|
||||
</div>
|
||||
<div class="setting-item">
|
||||
<select id="device-mode"></select>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<h3>Current Profile</h3>
|
||||
</div>
|
||||
<div class="setting-item">
|
||||
<select id="profile-id"></select>
|
||||
</div>
|
||||
|
||||
<h3>Axis Settings</h3>
|
||||
<div class="setting-item">
|
||||
<label for="invert-joy-l">Invert Y Left</label>
|
||||
<input type="checkbox" id="invert-joy-l">
|
||||
<span id="invert-joy-l-value"></span>
|
||||
</div>
|
||||
<div class="setting-item">
|
||||
<label for="invert-joy-r">Invert Y Right</label>
|
||||
<input type="checkbox" id="invert-joy-r">
|
||||
<span id="invert-joy-r-value"></span>
|
||||
</div>
|
||||
|
||||
<div class="deadzone-settings">
|
||||
<div class="setting-item">
|
||||
<label for="deadzone-joy-l">Joystick Left Deadzone</label>
|
||||
<input type="range" id="deadzone-joy-l" min="0" max="100">
|
||||
<span id="deadzone-joy-l-value"></span>%
|
||||
</div>
|
||||
<div class="setting-item">
|
||||
<label for="deadzone-joy-r">Joystick Right Deadzone</label>
|
||||
<input type="range" id="deadzone-joy-r" min="0" max="100">
|
||||
<span id="deadzone-joy-r-value"></span>%
|
||||
</div>
|
||||
<div class="setting-item">
|
||||
<label for="deadzone-trigger-l">Trigger Left Deadzone</label>
|
||||
<input type="range" id="deadzone-trigger-l" min="0" max="100">
|
||||
<span id="deadzone-trigger-l-value"></span>%
|
||||
</div>
|
||||
<div class="setting-item">
|
||||
<label for="deadzone-trigger-r">Trigger Right Deadzone</label>
|
||||
<input type="range" id="deadzone-trigger-r" min="0" max="100">
|
||||
<span id="deadzone-trigger-r-value"></span>%
|
||||
</div>
|
||||
</div>
|
||||
<h3>Digital Mappings</h3>
|
||||
<h4>D-Pad</h4>
|
||||
<div class="button-map-settings">
|
||||
<div class="button-pair">
|
||||
<div class="button-item">
|
||||
<label for="mapping-Up">Up:</label>
|
||||
<select class="button-mapping" id="mapping-Up"></select>
|
||||
</div>
|
||||
<div class="button-item">
|
||||
<label for="mapping-Down">Down:</label>
|
||||
<select class="button-mapping" id="mapping-Down"></select>
|
||||
</div>
|
||||
</div>
|
||||
<div class="button-pair">
|
||||
<div class="button-item">
|
||||
<label for="mapping-Left">Left:</label>
|
||||
<select class="button-mapping" id="mapping-Left"></select>
|
||||
</div>
|
||||
<div class="button-item">
|
||||
<label for="mapping-Right">Right:</label>
|
||||
<select class="button-mapping" id="mapping-Right"></select>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<h4>Buttons</h4>
|
||||
<div class="button-map-settings">
|
||||
<div class="button-pair">
|
||||
<div class="button-item">
|
||||
<label for="mapping-A">A:</label>
|
||||
<select class="button-mapping" id="mapping-A"></select>
|
||||
</div>
|
||||
<div class="button-item">
|
||||
<label for="mapping-B">B:</label>
|
||||
<select class="button-mapping" id="mapping-B"></select>
|
||||
</div>
|
||||
</div>
|
||||
<div class="button-pair">
|
||||
<div class="button-item">
|
||||
<label for="mapping-X">X:</label>
|
||||
<select class="button-mapping" id="mapping-X"></select>
|
||||
</div>
|
||||
<div class="button-item">
|
||||
<label for="mapping-Y">Y:</label>
|
||||
<select class="button-mapping" id="mapping-Y"></select>
|
||||
</div>
|
||||
</div>
|
||||
<div class="button-pair">
|
||||
<div class="button-item">
|
||||
<label for="mapping-L3">L3:</label>
|
||||
<select class="button-mapping" id="mapping-L3"></select>
|
||||
</div>
|
||||
<div class="button-item">
|
||||
<label for="mapping-R3">R3:</label>
|
||||
<select class="button-mapping" id="mapping-R3"></select>
|
||||
</div>
|
||||
</div>
|
||||
<div class="button-pair">
|
||||
<div class="button-item">
|
||||
<label for="mapping-LB">LB:</label>
|
||||
<select class="button-mapping" id="mapping-LB"></select>
|
||||
</div>
|
||||
<div class="button-item">
|
||||
<label for="mapping-RB">RB:</label>
|
||||
<select class="button-mapping" id="mapping-RB"></select>
|
||||
</div>
|
||||
</div>
|
||||
<div class="button-pair">
|
||||
<div class="button-item">
|
||||
<label for="mapping-Start">Start/Plus/Options:</label>
|
||||
<select class="button-mapping" id="mapping-Start"></select>
|
||||
</div>
|
||||
<div class="button-item">
|
||||
<label for="mapping-Back">Back/Minus/Share:</label>
|
||||
<select class="button-mapping" id="mapping-Back"></select>
|
||||
</div>
|
||||
</div>
|
||||
<div class="button-pair">
|
||||
<div class="button-item">
|
||||
<label for="mapping-Guide">Guide/Home:</label>
|
||||
<select class="button-mapping" id="mapping-Guide"></select>
|
||||
</div>
|
||||
<div class="button-item">
|
||||
<label for="mapping-Misc">Misc/Capture/Mic:</label>
|
||||
<select class="button-mapping" id="mapping-Misc"></select>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<h3>Analog Mappings</h3>
|
||||
<div class="setting-item">
|
||||
<label for="analog-enabled">Enable Analog Buttons</label>
|
||||
<input type="checkbox" id="analog-enabled">
|
||||
<span id="analog-enabled-value"></span>
|
||||
</div>
|
||||
|
||||
<div class="button-map-settings">
|
||||
<div class="button-pair">
|
||||
<div class="button-item">
|
||||
<label for="mapping-analog-Up">Up:</label>
|
||||
<select class="button-mapping" id="mapping-analog-Up"></select>
|
||||
</div>
|
||||
<div class="button-item">
|
||||
<label for="mapping-analog-Down">Down:</label>
|
||||
<select class="button-mapping" id="mapping-analog-Down"></select>
|
||||
</div>
|
||||
</div>
|
||||
<div class="button-pair">
|
||||
<div class="button-item">
|
||||
<label for="mapping-analog-Left">Left:</label>
|
||||
<select class="button-mapping" id="mapping-analog-Left"></select>
|
||||
</div>
|
||||
<div class="button-item">
|
||||
<label for="mapping-analog-Right">Right:</label>
|
||||
<select class="button-mapping" id="mapping-analog-Right"></select>
|
||||
</div>
|
||||
</div>
|
||||
<div class="button-pair">
|
||||
<div class="button-item">
|
||||
<label for="mapping-analog-A">A:</label>
|
||||
<select class="button-mapping" id="mapping-analog-A"></select>
|
||||
</div>
|
||||
<div class="button-item">
|
||||
<label for="mapping-analog-B">B:</label>
|
||||
<select class="button-mapping" id="mapping-analog-B"></select>
|
||||
</div>
|
||||
</div>
|
||||
<div class="button-pair">
|
||||
<div class="button-item">
|
||||
<label for="mapping-analog-X">X:</label>
|
||||
<select class="button-mapping" id="mapping-analog-X"></select>
|
||||
</div>
|
||||
<div class="button-item">
|
||||
<label for="mapping-analog-Y">Y:</label>
|
||||
<select class="button-mapping" id="mapping-analog-Y"></select>
|
||||
</div>
|
||||
</div>
|
||||
<div class="button-pair">
|
||||
<div class="button-item">
|
||||
<label for="mapping-analog-LB">LB:</label>
|
||||
<select class="button-mapping" id="mapping-analog-LB"></select>
|
||||
</div>
|
||||
<div class="button-item">
|
||||
<label for="mapping-analog-RB">RB:</label>
|
||||
<select class="button-mapping" id="mapping-analog-RB"></select>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<label>Saving will reboot your OGX-Mini, the last saved profile will be active.</label><br><br>
|
||||
<button class="settings-button" id="save">Save Profile</button>
|
||||
<button class="settings-button" id="reload">Reload Profile</button>
|
||||
<button class="settings-button" id="load-default">Load Defaults</button>
|
||||
<button class="settings-button" id="disconnect">Disconnect</button>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<script type="module" src="index.js"></script>
|
||||
</body>
|
||||
</html>
|
||||
282
WebApp/index.js
Normal file
282
WebApp/index.js
Normal file
@@ -0,0 +1,282 @@
|
||||
import { populate_ui_elements, update_ui_elements, create_packet_from_ui } from './ui_modules.js';
|
||||
import { PACKET_IDS, new_packet } from './constants.js';
|
||||
|
||||
async function send_report(writer, in_packet)
|
||||
{
|
||||
const packet = new Uint8Array([
|
||||
in_packet.packet_id,
|
||||
in_packet.input_mode,
|
||||
in_packet.max_gamepads,
|
||||
in_packet.player_idx,
|
||||
|
||||
in_packet.profile.id,
|
||||
|
||||
in_packet.profile.dz_trigger_l,
|
||||
in_packet.profile.dz_trigger_r,
|
||||
|
||||
in_packet.profile.dz_joystick_l,
|
||||
in_packet.profile.dz_joystick_r,
|
||||
|
||||
in_packet.profile.invert_ly ? 1 : 0,
|
||||
in_packet.profile.invert_ry ? 1 : 0,
|
||||
|
||||
in_packet.profile.dpad_up,
|
||||
in_packet.profile.dpad_down,
|
||||
in_packet.profile.dpad_left,
|
||||
in_packet.profile.dpad_right,
|
||||
|
||||
in_packet.profile.button_a & 0xff,
|
||||
in_packet.profile.button_a >> 8,
|
||||
in_packet.profile.button_b & 0xff,
|
||||
in_packet.profile.button_b >> 8,
|
||||
in_packet.profile.button_x & 0xff,
|
||||
in_packet.profile.button_x >> 8,
|
||||
in_packet.profile.button_y & 0xff,
|
||||
in_packet.profile.button_y >> 8,
|
||||
in_packet.profile.button_l3 & 0xff,
|
||||
in_packet.profile.button_l3 >> 8,
|
||||
in_packet.profile.button_r3 & 0xff,
|
||||
in_packet.profile.button_r3 >> 8,
|
||||
in_packet.profile.button_back & 0xff,
|
||||
in_packet.profile.button_back >> 8,
|
||||
in_packet.profile.button_start & 0xff,
|
||||
in_packet.profile.button_start >> 8,
|
||||
in_packet.profile.button_lb & 0xff,
|
||||
in_packet.profile.button_lb >> 8,
|
||||
in_packet.profile.button_rb & 0xff,
|
||||
in_packet.profile.button_rb >> 8,
|
||||
in_packet.profile.button_sys & 0xff,
|
||||
in_packet.profile.button_sys >> 8,
|
||||
in_packet.profile.button_misc & 0xff,
|
||||
in_packet.profile.button_misc >> 8,
|
||||
|
||||
in_packet.profile.analog_enabled ? 1 : 0,
|
||||
|
||||
in_packet.profile.analog_off_up,
|
||||
in_packet.profile.analog_off_down,
|
||||
in_packet.profile.analog_off_left,
|
||||
in_packet.profile.analog_off_right,
|
||||
in_packet.profile.analog_off_a,
|
||||
in_packet.profile.analog_off_b,
|
||||
in_packet.profile.analog_off_x,
|
||||
in_packet.profile.analog_off_y,
|
||||
in_packet.profile.analog_off_lb,
|
||||
in_packet.profile.analog_off_rb,
|
||||
]);
|
||||
|
||||
await writer.write(packet);
|
||||
console.log("Packet sent, length:", packet.length);
|
||||
}
|
||||
|
||||
function in_packet_from_in_report(data)
|
||||
{
|
||||
const in_packet = new_packet();
|
||||
|
||||
in_packet.packet_id = data[0],
|
||||
in_packet.input_mode = data[1],
|
||||
in_packet.max_gamepads = data[2],
|
||||
in_packet.player_idx = data[3],
|
||||
|
||||
in_packet.profile = {
|
||||
id: data[4], // uint8_t
|
||||
|
||||
dz_trigger_l: data[5], // uint8_t
|
||||
dz_trigger_r: data[6], // uint8_t
|
||||
|
||||
dz_joystick_l: data[7], // uint8_t
|
||||
dz_joystick_r: data[8], // uint8_t
|
||||
|
||||
invert_ly: data[9], // uint8_t
|
||||
invert_ry: data[10], // uint8_t
|
||||
|
||||
dpad_up: data[11], // uint8_t
|
||||
dpad_down: data[12], // uint8_t
|
||||
dpad_left: data[13], // uint8_t
|
||||
dpad_right: data[14], // uint8_t
|
||||
|
||||
button_a: data[15] | (data[16] << 8), // uint16_t
|
||||
button_b: data[17] | (data[18] << 8), // uint16_t
|
||||
button_x: data[19] | (data[20] << 8), // uint16_t
|
||||
button_y: data[21] | (data[22] << 8), // uint16_t
|
||||
button_l3: data[23] | (data[24] << 8), // uint16_t
|
||||
button_r3: data[25] | (data[26] << 8), // uint16_t
|
||||
button_back: data[27] | (data[28] << 8), // uint16_t
|
||||
button_start: data[29] | (data[30] << 8),// uint16_t
|
||||
button_lb: data[31] | (data[32] << 8), // uint16_t
|
||||
button_rb: data[33] | (data[34] << 8), // uint16_t
|
||||
button_sys: data[35] | (data[36] << 8), // uint16_t
|
||||
button_misc: data[37] | (data[38] << 8), // uint16_t
|
||||
|
||||
analog_enabled: data[39], // uint8_t
|
||||
|
||||
analog_off_up: data[40], // uint8_t
|
||||
analog_off_down: data[41], // uint8_t
|
||||
analog_off_left: data[42], // uint8_t
|
||||
analog_off_right: data[43], // uint8_t
|
||||
analog_off_a: data[44], // uint8_t
|
||||
analog_off_b: data[45], // uint8_t
|
||||
analog_off_x: data[46], // uint8_t
|
||||
analog_off_y: data[47], // uint8_t
|
||||
analog_off_lb: data[48], // uint8_t
|
||||
analog_off_rb: data[49], // uint8_t
|
||||
}
|
||||
return in_packet;
|
||||
}
|
||||
|
||||
function process_in_packet(data)
|
||||
{
|
||||
if (data.length != 50)
|
||||
{
|
||||
console.error("Received packet length does not match.");
|
||||
return;
|
||||
}
|
||||
|
||||
if (data[0] === PACKET_IDS.RESPONSE_OK)
|
||||
{
|
||||
console.log("Packet OK.");
|
||||
update_ui_elements(in_packet_from_in_report(data));
|
||||
}
|
||||
else if (data[0] === PACKET_IDS.RESPONSE_ERROR)
|
||||
{
|
||||
console.error("Packet error.");
|
||||
}
|
||||
else
|
||||
{
|
||||
console.error("Unknown packet ID.");
|
||||
}
|
||||
}
|
||||
|
||||
async function connect_to_device()
|
||||
{
|
||||
if (!("serial" in navigator))
|
||||
{
|
||||
console.log("Web Serial API not supported.");
|
||||
return;
|
||||
}
|
||||
|
||||
populate_ui_elements();
|
||||
|
||||
try
|
||||
{
|
||||
const filters = [{ usbVendorId: 0xCafe }];
|
||||
const port = await navigator.serial.requestPort({ });
|
||||
await port.open({ baudRate: 9600 });
|
||||
|
||||
const reader = port.readable.getReader();
|
||||
const writer = port.writable.getWriter();
|
||||
|
||||
const init_packet = new_packet();
|
||||
init_packet.packet_id = PACKET_IDS.INIT_READ;
|
||||
await send_report(writer, init_packet);
|
||||
|
||||
document.getElementById('connect-ui').classList.add('hide');
|
||||
document.getElementById('settings-ui').classList.remove('hide');
|
||||
|
||||
async function read_serial_data()
|
||||
{
|
||||
let in_data = new Uint8Array();
|
||||
try
|
||||
{
|
||||
while (true)
|
||||
{
|
||||
const { value, done } = await reader.read();
|
||||
|
||||
if (done)
|
||||
{
|
||||
console.log("Stream closed.");
|
||||
break;
|
||||
}
|
||||
|
||||
if (value)
|
||||
{
|
||||
// console.log("Chunk received, length:", value.length);
|
||||
|
||||
let temp_data = new Uint8Array(in_data.length + value.length);
|
||||
temp_data.set(in_data);
|
||||
temp_data.set(value, in_data.length);
|
||||
in_data = temp_data;
|
||||
|
||||
while (in_data.length >= 50)
|
||||
{
|
||||
// console.log("Packet received, length:", in_data.length);
|
||||
|
||||
let packet = in_data.slice(0, 50);
|
||||
process_in_packet(packet);
|
||||
|
||||
in_data = in_data.slice(50);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (error)
|
||||
{
|
||||
console.error("Read error:", error);
|
||||
window.location.reload();
|
||||
}
|
||||
}
|
||||
|
||||
read_serial_data();
|
||||
|
||||
document.getElementById('profile-id').addEventListener('change', async () =>
|
||||
{
|
||||
const in_packet = create_packet_from_ui();
|
||||
in_packet.packet_id = PACKET_IDS.READ_PROFILE;
|
||||
console.log("Loading profile: ", in_packet.profile.id);
|
||||
await send_report(writer, in_packet);
|
||||
});
|
||||
|
||||
document.getElementById('reload').addEventListener('click', async () =>
|
||||
{
|
||||
const in_packet = create_packet_from_ui();
|
||||
in_packet.packet_id = PACKET_IDS.READ_PROFILE;
|
||||
console.log("Loading profile: ", in_packet.profile.id);
|
||||
await send_report(writer, in_packet);
|
||||
});
|
||||
|
||||
document.getElementById('save').addEventListener('click', async () =>
|
||||
{
|
||||
const in_packet = create_packet_from_ui();
|
||||
in_packet.packet_id = PACKET_IDS.WRITE_PROFILE;
|
||||
await send_report(writer, in_packet);
|
||||
});
|
||||
|
||||
document.getElementById('disconnect').addEventListener('click', async () =>
|
||||
{
|
||||
await handle_disconnect(reader, writer, port);
|
||||
});
|
||||
|
||||
reader.closed.then(async () =>
|
||||
{
|
||||
console.log("Device disconnected.");
|
||||
await handle_disconnect(reader, writer, port);
|
||||
});
|
||||
}
|
||||
catch (error)
|
||||
{
|
||||
console.error('Connection error:', error);
|
||||
}
|
||||
}
|
||||
|
||||
async function handle_disconnect(reader, writer, port)
|
||||
{
|
||||
if (reader) reader.cancel();
|
||||
if (writer) writer.releaseLock();
|
||||
if (port) await port.close();
|
||||
|
||||
console.log("Port closed. Reloading page...");
|
||||
window.location.reload();
|
||||
}
|
||||
|
||||
document.getElementById('load-default').addEventListener('click', async () =>
|
||||
{
|
||||
const in_packet = new_packet();
|
||||
in_packet.profile.id = parseInt(document.getElementById('profile-id').value);
|
||||
update_ui_elements(in_packet);
|
||||
});
|
||||
|
||||
document.getElementById('connect').addEventListener('click', async () =>
|
||||
{
|
||||
await connect_to_device();
|
||||
});
|
||||
|
||||
document.getElementById('settings-ui').classList.add('hide');
|
||||
101
WebApp/style.css
Normal file
101
WebApp/style.css
Normal file
@@ -0,0 +1,101 @@
|
||||
:root {
|
||||
--font: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif;
|
||||
--primary-color: #007bff;
|
||||
--background-color: #f8f9fa;
|
||||
--text-color: #212529;
|
||||
}
|
||||
|
||||
h1 {
|
||||
margin-top: 0;
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
|
||||
|
||||
body {
|
||||
font-family: var(--font);
|
||||
background-color: var(--background-color);
|
||||
color: var(--text-color);
|
||||
padding: 20px;
|
||||
}
|
||||
|
||||
.container {
|
||||
max-width: 600px;
|
||||
margin: auto;
|
||||
background-color: #fff;
|
||||
padding: 20px;
|
||||
border-radius: 5px;
|
||||
box-shadow: 0 2px 4px rgba(0,0,0,0.1);
|
||||
}
|
||||
|
||||
.connect-ui, .ui {
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
|
||||
.settings > .setting-item, .deadzone-settings > .setting-item {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
margin-bottom: 15px;
|
||||
}
|
||||
|
||||
.settings > .setting-item label, .deadzone-settings > .setting-item label {
|
||||
flex-basis: 40%;
|
||||
text-align: left;
|
||||
}
|
||||
|
||||
.settings > .setting-item input[type=range], .deadzone-settings > .setting-item input[type=range] {
|
||||
flex-grow: 1;
|
||||
}
|
||||
|
||||
.settings > .setting-item span, .deadzone-settings > .setting-item span {
|
||||
margin-left: 10px;
|
||||
min-width: 40px;
|
||||
text-align: right;
|
||||
}
|
||||
|
||||
.connect, .settings-button {
|
||||
background-color: var(--primary-color);
|
||||
color: #fff;
|
||||
border: none;
|
||||
padding: 10px 20px;
|
||||
border-radius: 5px;
|
||||
cursor: pointer;
|
||||
transition: background-color 0.2s;
|
||||
margin-right: 15px;
|
||||
}
|
||||
|
||||
.connect:hover, .settings-button:hover {
|
||||
/* background-color: darken(var(--primary-color), 50%); */
|
||||
opacity: 0.5;
|
||||
}
|
||||
|
||||
.error {
|
||||
color: #dc3545;
|
||||
margin-top: 10px;
|
||||
display: none;
|
||||
}
|
||||
|
||||
.hide {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.button-map-settings .button-pair {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
margin-bottom: 15px;
|
||||
}
|
||||
|
||||
.button-item {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
width: 48%;
|
||||
align-items: flex-start;
|
||||
}
|
||||
|
||||
.button-item label {
|
||||
margin-bottom: 5px;
|
||||
}
|
||||
|
||||
.button-mapping {
|
||||
width: 100%;
|
||||
}
|
||||
218
WebApp/ui_modules.js
Normal file
218
WebApp/ui_modules.js
Normal file
@@ -0,0 +1,218 @@
|
||||
import { DEVICE_MODE_KEY_VALUE, PROFILE_ID_KEY_VALUE, BUTTON_KEY_VALUE, DPAD_KEY_VALUE, ANALOG_KEY_VALUE, new_packet } from './constants.js';
|
||||
|
||||
function percent_to_threshold(percent, max_value)
|
||||
{
|
||||
return Math.round((percent / 100) * max_value);
|
||||
}
|
||||
|
||||
function threshold_to_percent(threshold, max_value)
|
||||
{
|
||||
return Math.round((threshold / max_value) * 100);
|
||||
}
|
||||
|
||||
function update_deadzone_slider(slider_id, display_id, value, max_value)
|
||||
{
|
||||
const percentage = threshold_to_percent(value, max_value);
|
||||
const slider = document.getElementById(slider_id);
|
||||
const display = document.getElementById(display_id);
|
||||
|
||||
if (slider && display)
|
||||
{
|
||||
slider.value = percentage;
|
||||
display.textContent = percentage;
|
||||
// console.log(`Setting ${slider_id} to ${percentage}%`);
|
||||
}
|
||||
else
|
||||
{
|
||||
console.error(`Slider or display element not found for ${slider_id}`);
|
||||
}
|
||||
|
||||
slider.addEventListener('input', () =>
|
||||
{
|
||||
const updatedValue = threshold_to_percent(slider.value, 100);
|
||||
display.textContent = updatedValue;
|
||||
});
|
||||
}
|
||||
|
||||
function populate_dropdown_menus(element_prefix, key_value)
|
||||
{
|
||||
for (const [key, value] of Object.entries(key_value))
|
||||
{
|
||||
const select_element_id = `${element_prefix}${key.replace(/\s+/g, '-')}`;
|
||||
const select_element = document.getElementById(select_element_id);
|
||||
|
||||
for (const [key_2, value_2] of Object.entries(key_value))
|
||||
{
|
||||
const option = document.createElement("option");
|
||||
option.value = value_2;
|
||||
option.text = key_2;
|
||||
select_element.add(option);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function populate_dropdown_menu(element_id, key_value)
|
||||
{
|
||||
const select_element = document.getElementById(element_id);
|
||||
if (select_element)
|
||||
{
|
||||
select_element.innerHTML = '';
|
||||
for (const [key, value] of Object.entries(key_value))
|
||||
{
|
||||
const option = document.createElement("option");
|
||||
option.value = value;
|
||||
option.text = key;
|
||||
select_element.add(option);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
console.error(`Element ${element_id} not found!`);
|
||||
}
|
||||
}
|
||||
|
||||
export function populate_ui_elements()
|
||||
{
|
||||
populate_dropdown_menu('profile-id', PROFILE_ID_KEY_VALUE);
|
||||
populate_dropdown_menu('device-mode', DEVICE_MODE_KEY_VALUE);
|
||||
|
||||
update_deadzone_slider('deadzone-joy-l', 'deadzone-joy-l-value', 0, 100);
|
||||
update_deadzone_slider('deadzone-joy-r', 'deadzone-joy-r-value', 0, 100);
|
||||
update_deadzone_slider('deadzone-trigger-l', 'deadzone-trigger-l-value', 0, 100);
|
||||
update_deadzone_slider('deadzone-trigger-r', 'deadzone-trigger-r-value', 0, 100);
|
||||
|
||||
populate_dropdown_menus('mapping-', DPAD_KEY_VALUE);
|
||||
populate_dropdown_menus('mapping-', BUTTON_KEY_VALUE);
|
||||
populate_dropdown_menus('mapping-analog-', ANALOG_KEY_VALUE);
|
||||
}
|
||||
|
||||
const update_dropdown = (element_id, value) =>
|
||||
{
|
||||
const element = document.getElementById(element_id);
|
||||
if (element)
|
||||
{
|
||||
element.value = value;
|
||||
}
|
||||
else
|
||||
{
|
||||
console.error(`Element ${element_id} not found!`);
|
||||
}
|
||||
};
|
||||
|
||||
export function update_ui_elements(in_packet)
|
||||
{
|
||||
console.log(in_packet);
|
||||
console.log("Updating UI elements...");
|
||||
|
||||
update_dropdown("device-mode", in_packet.input_mode);
|
||||
update_dropdown("profile-id", in_packet.profile.id);
|
||||
|
||||
update_deadzone_slider('deadzone-joy-l', 'deadzone-joy-l-value', in_packet.profile.dz_joystick_l, 0xff);
|
||||
update_deadzone_slider('deadzone-joy-r', 'deadzone-joy-r-value', in_packet.profile.dz_joystick_r, 0xff);
|
||||
update_deadzone_slider('deadzone-trigger-l', 'deadzone-trigger-l-value', in_packet.profile.dz_trigger_l, 0xff);
|
||||
update_deadzone_slider('deadzone-trigger-r', 'deadzone-trigger-r-value', in_packet.profile.dz_trigger_r, 0xff);
|
||||
|
||||
function update_checkbox(checkbox_id, display_id, value)
|
||||
{
|
||||
const checkbox = document.getElementById(checkbox_id);
|
||||
const display = document.getElementById(display_id);
|
||||
|
||||
if (checkbox && display)
|
||||
{
|
||||
checkbox.checked = value === 1;
|
||||
// console.log(`Setting ${checkbox_id} to ${value === 1}`);
|
||||
}
|
||||
else
|
||||
{
|
||||
console.error(`Checkbox or display element not found for ${checkbox_id}`);
|
||||
}
|
||||
}
|
||||
|
||||
update_checkbox('invert-joy-l', 'invert-joy-l-value', in_packet.profile.invert_ly);
|
||||
update_checkbox('invert-joy-r', 'invert-joy-r-value', in_packet.profile.invert_ry);
|
||||
|
||||
update_dropdown("mapping-Up", in_packet.profile.dpad_up);
|
||||
update_dropdown("mapping-Down", in_packet.profile.dpad_down);
|
||||
update_dropdown("mapping-Left", in_packet.profile.dpad_left);
|
||||
update_dropdown("mapping-Right", in_packet.profile.dpad_right);
|
||||
|
||||
update_dropdown("mapping-A", in_packet.profile.button_a);
|
||||
update_dropdown("mapping-B", in_packet.profile.button_b);
|
||||
update_dropdown("mapping-X", in_packet.profile.button_x);
|
||||
update_dropdown("mapping-Y", in_packet.profile.button_y);
|
||||
update_dropdown("mapping-L3", in_packet.profile.button_l3);
|
||||
update_dropdown("mapping-R3", in_packet.profile.button_r3);
|
||||
update_dropdown("mapping-Back", in_packet.profile.button_back);
|
||||
update_dropdown("mapping-Start", in_packet.profile.button_start);
|
||||
update_dropdown("mapping-LB", in_packet.profile.button_lb);
|
||||
update_dropdown("mapping-RB", in_packet.profile.button_rb);
|
||||
update_dropdown("mapping-Guide", in_packet.profile.button_sys);
|
||||
update_dropdown("mapping-Misc", in_packet.profile.button_misc);
|
||||
|
||||
update_checkbox("analog-enabled", "analog-enabled-value", in_packet.profile.analog_enabled);
|
||||
|
||||
update_dropdown("mapping-analog-Up", in_packet.profile.analog_off_up);
|
||||
update_dropdown("mapping-analog-Down", in_packet.profile.analog_off_down);
|
||||
update_dropdown("mapping-analog-Left", in_packet.profile.analog_off_left);
|
||||
update_dropdown("mapping-analog-Right", in_packet.profile.analog_off_right);
|
||||
update_dropdown("mapping-analog-A", in_packet.profile.analog_off_a);
|
||||
update_dropdown("mapping-analog-B", in_packet.profile.analog_off_b);
|
||||
update_dropdown("mapping-analog-X", in_packet.profile.analog_off_x);
|
||||
update_dropdown("mapping-analog-Y", in_packet.profile.analog_off_y);
|
||||
update_dropdown("mapping-analog-LB", in_packet.profile.analog_off_lb);
|
||||
update_dropdown("mapping-analog-RB", in_packet.profile.analog_off_rb);
|
||||
}
|
||||
|
||||
export function create_packet_from_ui()
|
||||
{
|
||||
const in_packet = new_packet();
|
||||
|
||||
in_packet.report_id = 0;
|
||||
in_packet.input_mode = parseInt(document.getElementById("device-mode").value);
|
||||
in_packet.max_gamepads = 0;
|
||||
in_packet.player_idx = 0;
|
||||
|
||||
in_packet.profile.id = parseInt(document.getElementById("profile-id").value);
|
||||
|
||||
in_packet.profile.dz_joystick_l = percent_to_threshold(document.getElementById("deadzone-joy-l").value, 0xFF);
|
||||
in_packet.profile.dz_joystick_r = percent_to_threshold(document.getElementById("deadzone-joy-r").value, 0xFF);
|
||||
|
||||
in_packet.profile.dz_trigger_l = percent_to_threshold(document.getElementById("deadzone-trigger-l").value, 0xFF);
|
||||
in_packet.profile.dz_trigger_r = percent_to_threshold(document.getElementById("deadzone-trigger-r").value, 0xFF);
|
||||
|
||||
in_packet.profile.invert_ly = document.getElementById("invert-joy-l").checked ? 1 : 0;
|
||||
in_packet.profile.invert_ry = document.getElementById("invert-joy-r").checked ? 1 : 0;
|
||||
|
||||
in_packet.profile.dpad_up = parseInt(document.getElementById("mapping-Up").value);
|
||||
in_packet.profile.dpad_down = parseInt(document.getElementById("mapping-Down").value);
|
||||
in_packet.profile.dpad_left = parseInt(document.getElementById("mapping-Left").value);
|
||||
in_packet.profile.dpad_right = parseInt(document.getElementById("mapping-Right").value);
|
||||
|
||||
in_packet.profile.button_a = parseInt(document.getElementById("mapping-A").value);
|
||||
in_packet.profile.button_b = parseInt(document.getElementById("mapping-B").value);
|
||||
in_packet.profile.button_x = parseInt(document.getElementById("mapping-X").value);
|
||||
in_packet.profile.button_y = parseInt(document.getElementById("mapping-Y").value);
|
||||
in_packet.profile.button_l3 = parseInt(document.getElementById("mapping-L3").value);
|
||||
in_packet.profile.button_r3 = parseInt(document.getElementById("mapping-R3").value);
|
||||
in_packet.profile.button_back = parseInt(document.getElementById("mapping-Back").value);
|
||||
in_packet.profile.button_start = parseInt(document.getElementById("mapping-Start").value);
|
||||
in_packet.profile.button_lb = parseInt(document.getElementById("mapping-LB").value);
|
||||
in_packet.profile.button_rb = parseInt(document.getElementById("mapping-RB").value);
|
||||
in_packet.profile.button_sys = parseInt(document.getElementById("mapping-Guide").value);
|
||||
in_packet.profile.button_misc = parseInt(document.getElementById("mapping-Misc").value);
|
||||
|
||||
in_packet.profile.analog_enabled = document.getElementById("analog-enabled").checked ? 1 : 0;
|
||||
|
||||
in_packet.profile.analog_off_up = parseInt(document.getElementById("mapping-analog-Up").value);
|
||||
in_packet.profile.analog_off_down = parseInt(document.getElementById("mapping-analog-Down").value);
|
||||
in_packet.profile.analog_off_left = parseInt(document.getElementById("mapping-analog-Left").value);
|
||||
in_packet.profile.analog_off_right = parseInt(document.getElementById("mapping-analog-Right").value);
|
||||
in_packet.profile.analog_off_a = parseInt(document.getElementById("mapping-analog-A").value);
|
||||
in_packet.profile.analog_off_b = parseInt(document.getElementById("mapping-analog-B").value);
|
||||
in_packet.profile.analog_off_x = parseInt(document.getElementById("mapping-analog-X").value);
|
||||
in_packet.profile.analog_off_y = parseInt(document.getElementById("mapping-analog-Y").value);
|
||||
in_packet.profile.analog_off_lb = parseInt(document.getElementById("mapping-analog-LB").value);
|
||||
in_packet.profile.analog_off_rb = parseInt(document.getElementById("mapping-analog-RB").value);
|
||||
|
||||
return in_packet;
|
||||
}
|
||||
Reference in New Issue
Block a user