diff --git a/core.js b/core.js index 5c9ccb2..ed512b3 100644 --- a/core.js +++ b/core.js @@ -16,20 +16,6 @@ var lang_cur_direction = "ltr"; var gj = 0; var gu = 0; -// DS5 finetuning -let finetune_mode = 'center'; // 'center' or 'circularity' -let finetune_original_data = [] -let last_written_finetune_data = [] -let finetune_visible = false -let on_finetune_updating = false - -// Active stick tracking for finetune modal -let active_stick = null; // 'left', 'right', or null - -// Continuous D-pad adjustment tracking -let dpad_adjustment_interval = null -let dpad_adjustment_timeout = null - // Global object to keep track of button states const ds_button_states = { // e.g. 'square': false, 'cross': false, ... @@ -1242,6 +1228,40 @@ async function on_finetune_change() { await write_finetune_data(out); } +// DS5 finetuning +const finetune = { + _mode: 'center', // 'center' or 'circularity' + original_data: [], + last_written_data: [], + visible: false, + active_stick: null, // 'left', 'right', or null + + get mode() { + return this._mode; + }, + + set mode(mode) { + if (mode !== 'center' && mode !== 'circularity') { + throw new Error(`Invalid finetune mode: ${mode}. Must be 'center' or 'circularity'`); + } + this._mode = mode; + this._updateUI(); + }, + + _updateUI() { + clear_circularity(); + + const modal = $('#finetuneModal'); + if (this._mode === 'center') { + $("#finetuneModeCenter").prop('checked', true); + modal.removeClass('circularity-mode'); + } else if (this._mode === 'circularity') { + $("#finetuneModeCircularity").prop('checked', true); + modal.addClass('circularity-mode'); + } + } +}; + async function ds5_finetune() { // Lock NVS before nvs = await ds5_nvstatus(); @@ -1275,8 +1295,8 @@ async function ds5_finetune() { // Initialize the raw numbers display state show_raw_numbers_changed(); - finetune_original_data = data - finetune_visible = true + finetune.original_data = data + finetune.visible = true refresh_finetune_sticks(); } @@ -1346,7 +1366,7 @@ async function read_finetune_data() { return null; } - last_written_finetune_data = data; + finetune.last_written_data = data; return data; } @@ -1356,54 +1376,68 @@ async function write_finetune_data(data) { } const deepEqual = (a, b) => JSON.stringify(a) === JSON.stringify(b); - if (deepEqual(data, last_written_finetune_data)) { + if (deepEqual(data, finetune.last_written_data)) { return; } - last_written_finetune_data = data + finetune.last_written_data = data const pkg = data.reduce((acc, val) => acc.concat([val & 0xff, val >> 8]), [12, 1]); await device.sendFeatureReport(0x80, alloc_req(0x80, pkg)) } -function refresh_finetune_sticks() { - if (on_finetune_updating) - return; +const refresh_finetune_sticks = (() => { + let timeout = null; - on_finetune_updating = true - setTimeout(ds5_finetune_update_all, 10); -} + return function() { + if (timeout) return; -function update_finetune_warning_messages() { - if(!active_stick) return; + timeout = setTimeout(() => { + const { left, right } = ds_button_states.sticks; + ds5_finetune_update("finetuneStickCanvasL", left.x, left.y); + ds5_finetune_update("finetuneStickCanvasR", right.x, right.y); - const currentStick = ds_button_states.sticks[active_stick]; - if (finetune_mode === 'center') { - const isNearCenter = Math.abs(currentStick.x) <= 0.5 && Math.abs(currentStick.y) <= 0.5; - $(`#finetuneCenter${isNearCenter? 'Warning' : 'Success'}`).hide(); - $(`#finetuneCenter${isNearCenter? 'Success' : 'Warning'}`).show(); - } + update_finetune_warning_messages(); + highlight_active_finetune_axis(); - if (finetune_mode === 'circularity') { - // Check if stick is in extreme position (close to edges) - const isInExtremePosition = (Math.abs(currentStick.x) >= 0.7 || Math.abs(currentStick.y) >= 0.7); - $(`#finetuneCircularity${isInExtremePosition? 'Warning' : 'Success'}`).hide(); - $(`#finetuneCircularity${isInExtremePosition? 'Success' : 'Warning'}`).show(); - } -} + timeout = null; + }, 10); + }; +})(); + +const update_finetune_warning_messages = (() => { + let timeout = null; // to stop unnecessary flicker in center mode + + return function() { + if(!finetune.active_stick) return; + + const currentStick = ds_button_states.sticks[finetune.active_stick]; + if (finetune.mode === 'center') { + const isNearCenter = Math.abs(currentStick.x) <= 0.5 && Math.abs(currentStick.y) <= 0.5; + if(!isNearCenter && timeout) return; + + clearTimeout(timeout); + timeout = setTimeout(() => { + $(`#finetuneCenter${isNearCenter? 'Warning' : 'Success'}`).hide(); + $(`#finetuneCenter${isNearCenter? 'Success' : 'Warning'}`).show(); + timeout = null; + }, isNearCenter ? 0 : 200); + } + + if (finetune.mode === 'circularity') { + // Check if stick is in extreme position (close to edges) + const isInExtremePosition = (Math.abs(currentStick.x) >= 0.7 || Math.abs(currentStick.y) >= 0.7); + $(`#finetuneCircularity${isInExtremePosition? 'Warning' : 'Success'}`).hide(); + $(`#finetuneCircularity${isInExtremePosition? 'Success' : 'Warning'}`).show(); + } + }; +})(); -function ds5_finetune_update_all() { - const { left, right } = ds_button_states.sticks; - ds5_finetune_update("finetuneStickCanvasL", left.x, left.y); - ds5_finetune_update("finetuneStickCanvasR", right.x, right.y); - update_finetune_warning_messages(); - highlight_active_finetune_axis(); -} function clear_finetune_axis_highlights(to_clear = {center: true, circularity: true}) { const { center, circularity } = to_clear; - if(finetune_mode === 'center' && center || finetune_mode === 'circularity' && circularity) { + if(finetune.mode === 'center' && center || finetune.mode === 'circularity' && circularity) { // Clear label highlights const labelIds = ["Lx-lbl", "Ly-lbl", "Rx-lbl", "Ry-lbl"]; labelIds.forEach(suffix => { @@ -1413,31 +1447,31 @@ function clear_finetune_axis_highlights(to_clear = {center: true, circularity: t } function highlight_active_finetune_axis(opts = {}) { - if(!active_stick) return; + if(!finetune.active_stick) return; - if (finetune_mode === 'center') { + if (finetune.mode === 'center') { const { axis } = opts; if(!axis) return; clear_finetune_axis_highlights({center: true}); - const labelSuffix = `${active_stick === 'left' ? "L" : "R"}${axis.toLowerCase()}`; + const labelSuffix = `${finetune.active_stick === 'left' ? "L" : "R"}${axis.toLowerCase()}`; $(`#finetuneStickCanvas${labelSuffix}-lbl`).addClass("text-primary"); } else { clear_finetune_axis_highlights({circularity: true}); const sticks = ds_button_states.sticks; - const currentStick = sticks[active_stick]; + const currentStick = sticks[finetune.active_stick]; // Only highlight if stick is moved significantly from center const deadzone = 0.5; if (Math.abs(currentStick.x) >= deadzone || Math.abs(currentStick.y) >= deadzone) { const quadrant = get_stick_quadrant(currentStick.x, currentStick.y); - const inputSuffix = get_finetune_input_suffix_for_quadrant(active_stick, quadrant); + const inputSuffix = get_finetune_input_suffix_for_quadrant(finetune.active_stick, quadrant); if (inputSuffix) { // Highlight the corresponding LX/LY label to observe const labelId = `finetuneStickCanvas${ - active_stick === 'left' ? 'L' : 'R'}${ + finetune.active_stick === 'left' ? 'L' : 'R'}${ quadrant === 'left' || quadrant === 'right' ? 'x' : 'y'}-lbl`; $(`#${labelId}`).addClass("text-primary"); } @@ -1446,7 +1480,6 @@ function highlight_active_finetune_axis(opts = {}) { } function ds5_finetune_update(name, plx, ply) { - on_finetune_updating = false const showRawNumbers = $("#showRawNumbersCheckbox").is(":checked"); const c = document.getElementById(`${name}${showRawNumbers ? '' : '_large'}`); const ctx = c.getContext("2d"); @@ -1459,8 +1492,8 @@ function ds5_finetune_update(name, plx, ply) { ctx.clearRect(0, 0, c.width, c.height); const isLeftStick = name === "finetuneStickCanvasL"; - const highlight = active_stick == (isLeftStick ? 'left' : 'right') && !!dpad_adjustment_timeout; - if (finetune_mode === 'circularity') { + const highlight = finetune.active_stick == (isLeftStick ? 'left' : 'right') && is_dpad_adjustment_active(); + if (finetune.mode === 'circularity') { // Draw stick position with circle draw_stick_position(ctx, hb, yb, sz, plx, ply, { circularity_data: isLeftStick ? ll_data : rr_data, @@ -1498,15 +1531,15 @@ function restore_show_raw_numbers_checkbox() { function finetune_close() { $("#finetuneModal").modal("hide"); - finetune_visible = false; + finetune.visible = false; clear_active_stick(); stop_continuous_dpad_adjustment(); - finetune_original_data = []; + finetune.original_data = []; } function set_stick_to_finetune(stick) { - if(active_stick === stick) { + if(finetune.active_stick === stick) { return; } @@ -1514,10 +1547,10 @@ function set_stick_to_finetune(stick) { stop_continuous_dpad_adjustment(); clear_finetune_axis_highlights(); - active_stick = stick; + finetune.active_stick = stick; const other_stick = stick === 'left' ? 'right' : 'left'; - $(`#${active_stick}-stick-card`).addClass("stick-card-active"); + $(`#${finetune.active_stick}-stick-card`).addClass("stick-card-active"); $(`#${other_stick}-stick-card`).removeClass("stick-card-active"); } @@ -1568,7 +1601,7 @@ function clear_active_stick() { $("#left-stick-card").removeClass("stick-card-active"); $("#right-stick-card").removeClass("stick-card-active"); - active_stick = null; // Clear active stick + finetune.active_stick = null; // Clear active stick clear_finetune_axis_highlights(); } @@ -1585,7 +1618,7 @@ function get_stick_quadrant(x, y) { function get_finetune_input_suffix_for_quadrant(stick, quadrant) { // This function should only be used in circularity mode // In center mode, we don't care about quadrants - use direct axis mapping instead - if (finetune_mode === 'center') { + if (finetune.mode === 'center') { // This function shouldn't be called in center mode console.warn('get_finetune_input_suffix_for_quadrant called in center mode - this should not happen'); return null; @@ -1611,9 +1644,9 @@ function get_finetune_input_suffix_for_quadrant(stick, quadrant) { } function handle_finetune_dpad_adjustment(changes) { - if(!active_stick) return; + if(!finetune.active_stick) return; - if (finetune_mode === 'center') { + if (finetune.mode === 'center') { handle_center_mode_adjustment(changes); } else { handle_circularity_mode_adjustment(changes); @@ -1642,7 +1675,7 @@ function handle_center_mode_adjustment(changes) { for (const mapping of buttonMappings) { // Check if active stick is away from center (> 0.5) const sticks = ds_button_states.sticks; - const currentStick = sticks[active_stick]; + const currentStick = sticks[finetune.active_stick]; const stickAwayFromCenter = Math.abs(currentStick.x) > 0.5 || Math.abs(currentStick.y) > 0.5; if (stickAwayFromCenter && is_navigation_key_pressed()) { flash_finetune_warning(); @@ -1651,7 +1684,7 @@ function handle_center_mode_adjustment(changes) { if (mapping.buttons.some(button => changes[button])) { highlight_active_finetune_axis({axis: mapping.axis}); - start_continuous_dpad_adjustment_center_mode(active_stick, mapping.axis, mapping.adjustment); + start_continuous_dpad_adjustment_center_mode(finetune.active_stick, mapping.axis, mapping.adjustment); return; } } @@ -1662,24 +1695,28 @@ function is_navigation_key_pressed() { return nav_buttons.some(button => ds_button_states[button] === true); } -let flash_finetune_warning_timeout = null; -function flash_finetune_warning() { - function toggle() { - $("#finetuneCenterWarning").toggleClass(['alert-warning', 'alert-danger']); - $("#finetuneCircularityWarning").toggleClass(['alert-warning', 'alert-danger']); - } +const flash_finetune_warning = (() => { + let timeout = null; + + return function() { + function toggle() { + $("#finetuneCenterWarning").toggleClass(['alert-warning', 'alert-danger']); + $("#finetuneCircularityWarning").toggleClass(['alert-warning', 'alert-danger']); + } + + if(timeout) return; - if(!flash_finetune_warning_timeout) { toggle(); // on - flash_finetune_warning_timeout = setTimeout(() => { + timeout = setTimeout(() => { toggle(); // off - flash_finetune_warning_timeout = null; + timeout = null; }, 300); - } -} + }; +})(); + function handle_circularity_mode_adjustment({sticks: _, ...changes}) { const sticks = ds_button_states.sticks; - const currentStick = sticks[active_stick]; + const currentStick = sticks[finetune.active_stick]; // Only adjust if stick is moved significantly from center const deadzone = 0.5; @@ -1729,49 +1766,60 @@ function handle_circularity_mode_adjustment({sticks: _, ...changes}) { // Start continuous adjustment on button press if (adjustment !== 0) { - start_continuous_dpad_adjustment(active_stick, quadrant, adjustment); + start_continuous_dpad_adjustment(finetune.active_stick, quadrant, adjustment); } } -function start_continuous_dpad_adjustment(active_stick, quadrant, adjustment) { - const inputSuffix = get_finetune_input_suffix_for_quadrant(active_stick, quadrant); +function start_continuous_dpad_adjustment(stick, quadrant, adjustment) { + const inputSuffix = get_finetune_input_suffix_for_quadrant(stick, quadrant); start_continuous_adjustment_with_suffix(inputSuffix, adjustment); } -function start_continuous_dpad_adjustment_center_mode(active_stick, targetAxis, adjustment) { +function start_continuous_dpad_adjustment_center_mode(stick, targetAxis, adjustment) { // In center mode, directly map to X/Y axes - const inputSuffix = active_stick === 'left' ? + const inputSuffix = stick === 'left' ? (targetAxis === 'X' ? 'LX' : 'LY') : (targetAxis === 'X' ? 'RX' : 'RY'); start_continuous_adjustment_with_suffix(inputSuffix, adjustment); } -function start_continuous_adjustment_with_suffix(inputSuffix, adjustment) { - stop_continuous_dpad_adjustment(); +const { start_continuous_adjustment_with_suffix, stop_continuous_dpad_adjustment, is_dpad_adjustment_active } = (() => { + let repeat_delay = null; + let initial_delay = null; - const element = $(`#finetune${inputSuffix}`); - if (!element.length) return; + function start_continuous_adjustment_with_suffix(inputSuffix, adjustment) { + stop_continuous_dpad_adjustment(); - // Perform initial adjustment immediately... - perform_dpad_adjustment(element, adjustment); - clear_circularity(); + const element = $(`#finetune${inputSuffix}`); + if (!element.length) return; - // ...then prime continuous adjustment - dpad_adjustment_timeout = setTimeout(() => { - dpad_adjustment_interval = setInterval(() => { - perform_dpad_adjustment(element, adjustment); - clear_circularity(); - }, 150); - }, 400); // Initial delay before continuous adjustment starts (400ms) -} + // Perform initial adjustment immediately... + perform_dpad_adjustment(element, adjustment); + clear_circularity(); -function stop_continuous_dpad_adjustment() { - clearInterval(dpad_adjustment_interval); - dpad_adjustment_interval = null; + // ...then prime continuous adjustment + initial_delay = setTimeout(() => { + repeat_delay = setInterval(() => { + perform_dpad_adjustment(element, adjustment); + clear_circularity(); + }, 150); + }, 400); // Initial delay before continuous adjustment starts (400ms) + } - clearTimeout(dpad_adjustment_timeout); - dpad_adjustment_timeout = null; -} + function stop_continuous_dpad_adjustment() { + clearInterval(repeat_delay); + repeat_delay = null; + + clearTimeout(initial_delay); + initial_delay = null; + } + + function is_dpad_adjustment_active() { + return !!initial_delay; + } + + return { start_continuous_adjustment_with_suffix, stop_continuous_dpad_adjustment, is_dpad_adjustment_active }; +})(); async function perform_dpad_adjustment(element, adjustment) { const currentValue = parseInt(element.val()) || 0; @@ -1790,24 +1838,14 @@ function finetune_save() { } async function finetune_cancel() { - if(finetune_original_data.length == 12) - await write_finetune_data(finetune_original_data) + if(finetune.original_data.length == 12) + await write_finetune_data(finetune.original_data) finetune_close(); } function set_finetune_mode(mode) { - finetune_mode = mode; - clear_circularity(); - - const modal = $('#finetuneModal'); - if (mode === 'center') { - $("#finetuneModeCenter").prop('checked', true); - modal.removeClass('circularity-mode'); - } else if (mode === 'circularity') { - $("#finetuneModeCircularity").prop('checked', true); - modal.addClass('circularity-mode'); - } + finetune.mode = mode; } @@ -2101,52 +2139,20 @@ function float_to_str(f, precision = 2) { return (f<0?"":"+") + f.toFixed(precision); } -var on_delay = false; +const refresh_sticks = (() => { + let delay = null; + return function() { + if(delay) return; -function timeout_ok() { - on_delay = false; - if(ll_updated) refresh_stick_pos(); -} + delay = setTimeout(() => { + delay = null; + if(ll_updated) + refresh_stick_pos(); + }, 20); + }; +})(); -function refresh_sticks() { - if(on_delay) - return; - - refresh_stick_pos(); - on_delay = true; - setTimeout(timeout_ok, 20); -} - -var last_bat_txt = ""; -var last_bat_disable = null; - -function bat_percent_to_text(bat_charge, is_charging, is_error) { - var icon_txt = ""; - - if(bat_charge < 20) { - icon_txt = 'fa-battery-empty'; - } else if(bat_charge < 40) { - icon_txt = 'fa-battery-quarter'; - } else if(bat_charge < 60) { - icon_txt = 'fa-battery-half'; - } else if(bat_charge < 80) { - icon_txt = 'fa-battery-three-quarters'; - } else { - icon_txt = 'fa-battery-full'; - } - - var icon_full = ''; - var bolt_txt = ''; - if(is_charging) - bolt_txt = ''; - bat_txt = bat_charge + "%" + ' ' + bolt_txt + ' ' + icon_full; - - if(is_error) { - bat_txt = '' + l("error") + ''; - } - return bat_txt; -} function update_nvs_changes_status(new_value) { if (new_value == has_changes_to_write) @@ -2163,13 +2169,10 @@ function update_nvs_changes_status(new_value) { has_changes_to_write = new_value; } -function update_battery_status({bat_capacity, cable_connected, is_charging, is_error}) { - const bat_txt = bat_percent_to_text(bat_capacity, is_charging); - const can_use_tool = (bat_capacity >= 30 && cable_connected && !is_error); // is this even being used? - - if(bat_txt != last_bat_txt) { +function update_battery_status({/* bat_capacity, cable_connected, is_charging, is_error, */ bat_txt, changed}) { + // const can_use_tool = (bat_capacity >= 30 && cable_connected && !is_error); // is this even being used? + if(changed) { $("#d-bat").html(bat_txt); - last_bat_txt = bat_txt; } } @@ -2445,7 +2448,7 @@ function process_ds_input({data}) { const changes = record_ds_button_states(data, DS5_BUTTON_MAP, 7, 4, 5); if(current_active_tab === 'controller-tab') { collectCircularityData(changes.sticks, ll_data, rr_data); - if(finetune_visible) { + if(finetune.visible) { refresh_finetune_sticks(); handle_finetune_mode_switching(changes); handle_finetune_stick_switching(changes); @@ -2649,7 +2652,7 @@ async function connect() { reset_circularity(); la("begin"); - last_bat_txt = ""; + parse_battery_status.reset_cache(); try { $("#btnconnect").prop("disabled", true); $("#connectspinner").show(); @@ -2810,7 +2813,7 @@ function close_calibrate_window() { } $("#calibCenterModal").modal("hide"); - cur_calib = 0; + reset_calib(); return; } @@ -2891,7 +2894,7 @@ function board_model_info() { function close_new_calib() { $("#calibCenterModal").modal("hide"); - cur_calib = 0; + reset_calib(); } async function calib_step(i) { @@ -2959,25 +2962,34 @@ async function calib_step(i) { } -var cur_calib = 0; -async function calib_open() { - la("calib_open"); - cur_calib = 0; - await calib_next(); - new bootstrap.Modal(document.getElementById('calibCenterModal'), {}).show() -} +const { calib_open, calib_next, reset_calib } = (() => { + let cur_calib = 0; -async function calib_next() { - la("calib_next"); - if(cur_calib == 6) { - close_new_calib() - return; + async function calib_open() { + la("calib_open"); + cur_calib = 0; + await calib_next(); + new bootstrap.Modal(document.getElementById('calibCenterModal'), {}).show() } - if(cur_calib < 6) { - cur_calib += 1; - await calib_step(cur_calib); + + async function calib_next() { + la("calib_next"); + if(cur_calib == 6) { + close_new_calib() + return; + } + if(cur_calib < 6) { + cur_calib += 1; + await calib_step(cur_calib); + } } -} + + function reset_calib() { + cur_calib = 0; + } + + return { calib_open, calib_next, reset_calib }; +})(); function la(k,v={}) { $.ajax({type: 'POST', url:"https://the.al/ds4_a/l", @@ -3146,35 +3158,38 @@ function lerp_color(a, b, t) { return rgb2hex(c[0], c[1], c[2]); } -let haptic_timeout = undefined; -let haptic_last_trigger = 0; -async function trigger_haptic_motors(strong_motor /*left*/, weak_motor /*right*/) { - // The DS4 contoller has a strong (left) and a weak (right) motor. - // The DS5 emulates the same behavior, but the left and right motors are the same. +const trigger_haptic_motors = (() => { + let haptic_timeout = undefined; + let haptic_last_trigger = 0; - const now = Date.now(); - if (now - haptic_last_trigger < 200) { - return; // Rate limited - ignore calls within 200ms - } + return async function(strong_motor /*left*/, weak_motor /*right*/) { + // The DS4 contoller has a strong (left) and a weak (right) motor. + // The DS5 emulates the same behavior, but the left and right motors are the same. - haptic_last_trigger = now; - - try { - if (mode == 1) { // DS4 - const data = new Uint8Array([0x05, 0x00, 0, weak_motor, strong_motor]); - await device.sendReport(0x05, data); - } else if (mode == 2 || mode == 3) { // DS5 or DS5 Edge - const data = new Uint8Array([0x02, 0x00, weak_motor, strong_motor]); - await device.sendReport(0x02, data); + const now = Date.now(); + if (now - haptic_last_trigger < 200) { + return; // Rate limited - ignore calls within 200ms } - // Stop rumble after duration - clearTimeout(haptic_timeout); - haptic_timeout = setTimeout(stop_haptic_motors, 250); - } catch(e) { - show_popup(l("Error triggering rumble: ") + e); - } -} + haptic_last_trigger = now; + + try { + if (mode == 1) { // DS4 + const data = new Uint8Array([0x05, 0x00, 0, weak_motor, strong_motor]); + await device.sendReport(0x05, data); + } else if (mode == 2 || mode == 3) { // DS5 or DS5 Edge + const data = new Uint8Array([0x02, 0x00, weak_motor, strong_motor]); + await device.sendReport(0x02, data); + } + + // Stop rumble after duration + clearTimeout(haptic_timeout); + haptic_timeout = setTimeout(stop_haptic_motors, 250); + } catch(e) { + show_popup(l("Error triggering rumble: ") + e); + } + }; +})(); async function stop_haptic_motors() { if (mode == 1) { // DS4 @@ -3186,75 +3201,95 @@ async function stop_haptic_motors() { } } -function lerp_color(a, b, t) { - // a, b: hex color strings, t: 0.0-1.0 - function hex2rgb(hex) { - hex = hex.replace('#', ''); - if (hex.length === 3) hex = hex.split('').map(x => x + x).join(''); - const num = parseInt(hex, 16); - return [(num >> 16) & 255, (num >> 8) & 255, num & 255]; +const parse_battery_status = (() => { + let last_bat_txt = ""; + + function reset_battery_cache() { + last_bat_txt = ""; } - function rgb2hex(r, g, b) { - return '#' + [r, g, b].map(x => x.toString(16).padStart(2, '0')).join(''); - } - const c1 = hex2rgb(a); - const c2 = hex2rgb(b); - const c = [ - Math.round(c1[0] + (c2[0] - c1[0]) * t), - Math.round(c1[1] + (c2[1] - c1[1]) * t), - Math.round(c1[2] + (c2[2] - c1[2]) * t) - ]; - return rgb2hex(c[0], c[1], c[2]); -} + function parse(data, {byte, is_ds4 = false}) { + const bat = data.getUint8(byte); + let bat_capacity = 0, cable_connected = false, is_charging = false, is_error = false; -function parse_battery_status(data, {byte, is_ds4 = false}) { - const bat = data.getUint8(byte); - let bat_capacity = 0, cable_connected = false, is_charging = false, is_error = false; - - if (is_ds4) { - // DS4: bat_data = low 4 bits, bat_status = bit 4 - const bat_data = bat & 0x0f; - const bat_status = (bat >> 4) & 1; - if (bat_status == 1) { - cable_connected = true; - if (bat_data < 10) { - bat_capacity = Math.min(bat_data * 10 + 5, 100); - is_charging = true; - } else if (bat_data == 10) { - bat_capacity = 100; - is_charging = true; - } else if (bat_data == 11) { - bat_capacity = 100; - // charged + if (is_ds4) { + // DS4: bat_data = low 4 bits, bat_status = bit 4 + const bat_data = bat & 0x0f; + const bat_status = (bat >> 4) & 1; + if (bat_status == 1) { + cable_connected = true; + if (bat_data < 10) { + bat_capacity = Math.min(bat_data * 10 + 5, 100); + is_charging = true; + } else if (bat_data == 10) { + bat_capacity = 100; + is_charging = true; + } else if (bat_data == 11) { + bat_capacity = 100; + // charged + } else { + bat_capacity = 0; + is_error = true; + } + } else { + cable_connected = false; + if (bat_data < 10) { + bat_capacity = bat_data * 10 + 5; + } else { + bat_capacity = 100; + } + } + } else { + // DS5: bat_charge = low 4 bits, bat_status = high 4 bits + const bat_charge = bat & 0x0f; + const bat_status = bat >> 4; + if (bat_status == 0) { + bat_capacity = Math.min(bat_charge * 10 + 5, 100); + } else if (bat_status == 1) { + bat_capacity = Math.min(bat_charge * 10 + 5, 100); + is_charging = true; + cable_connected = true; + } else if (bat_status == 2) { + bat_capacity = 100; + cable_connected = true; } else { - bat_capacity = 0; is_error = true; } - } else { - cable_connected = false; - if (bat_data < 10) { - bat_capacity = bat_data * 10 + 5; - } else { - bat_capacity = 100; + } + + function bat_percent_to_text(bat_charge, is_charging, is_error) { + if(is_error) { + return '' + l("error") + ''; } + + const batteryIcons = [ + { threshold: 20, icon: 'fa-battery-empty' }, + { threshold: 40, icon: 'fa-battery-quarter' }, + { threshold: 60, icon: 'fa-battery-half' }, + { threshold: 80, icon: 'fa-battery-three-quarters' }, + ]; + + const icon_txt = batteryIcons.find(item => bat_charge < item.threshold)?.icon || 'fa-battery-full'; + const icon_full = ''; + const bolt_txt = is_charging ? '' : ''; + return bat_charge + "%" + ' ' + bolt_txt + ' ' + icon_full; } - } else { - // DS5: bat_charge = low 4 bits, bat_status = high 4 bits - const bat_charge = bat & 0x0f; - const bat_status = bat >> 4; - if (bat_status == 0) { - bat_capacity = Math.min(bat_charge * 10 + 5, 100); - } else if (bat_status == 1) { - bat_capacity = Math.min(bat_charge * 10 + 5, 100); - is_charging = true; - cable_connected = true; - } else if (bat_status == 2) { - bat_capacity = 100; - cable_connected = true; - } else { - is_error = true; - } + + // Check if battery text has changed + const bat_txt = bat_percent_to_text(bat_capacity, is_charging, is_error); + const changed = bat_txt !== last_bat_txt; + last_bat_txt = bat_txt; + + return { + bat_txt, + changed, + bat_capacity, + cable_connected, + is_charging, + is_error, + }; } - return { bat_capacity, cable_connected, is_charging, is_error }; -} + + parse.reset_cache = reset_battery_cache; + return parse; +})();