diff --git a/index.html b/index.html index 908500d..4dce317 100644 --- a/index.html +++ b/index.html @@ -209,6 +209,7 @@
Support for PS VR2 controllers is minimal and highly experimental.
I currently don't own these controllers, so I cannot verify the calibration process myself.
If you'd like to help improve full support, you can contribute with a donation or even send the controllers for testing.
Feel free to contact me on Discord (the_al) or by email at ds4@the.al .
Thank you for your support!
"), true) } + + // Save finetune parameters for DS5 and Edge controllers + if (model === "DS5" || model === "DS5_Edge") { + const finetuneData = await controllerInstance.getInMemoryModuleData(); + // Extract serial number from info items + const serialNumberItem = info.infoItems?.find(item => item.key === l("Serial Number")); + const serialNumber = serialNumberItem?.value; + if (serialNumber) { + FinetuneHistory.save(finetuneData, serialNumber); + } + } } catch(err) { await disconnect(); throw err; @@ -1118,6 +1133,36 @@ window.ds5_finetune = () => ds5_finetune( { ll_data, rr_data, clear_circularity }, (success) => success && switchToRangeMode() ); + +window.apply_finetune_revert = async (finetuneData) => { + if (!controller || !controller.isConnected()) { + throw new Error('Controller not connected'); + } + if (!Array.isArray(finetuneData) || finetuneData.length !== 12) { + throw new Error('Invalid finetune data'); + } + await controller.writeFinetuneData(finetuneData); +}; + +window.openCalibrationHistoryModal = async () => { + let currentFinetuneData = null; + let controllerSerialNumber = null; + try { + if (controller && typeof controller.getInMemoryModuleData === 'function') { + currentFinetuneData = await controller.getInMemoryModuleData('finetune'); + } + // Get serial number from device info + if (controller && typeof controller.getDeviceInfo === 'function') { + const info = await controller.getDeviceInfo(); + const serialNumberItem = info?.infoItems?.find(item => item.key === l("Serial Number")); + controllerSerialNumber = serialNumberItem?.value; + } + } catch (error) { + console.warn('Could not retrieve current finetune data or serial number:', error); + } + window.show_calibration_history_modal(currentFinetuneData, controllerSerialNumber); +}; + window.flash_all_changes = flash_all_changes; window.reboot_controller = reboot_controller; window.refresh_nvstatus = refresh_nvstatus; diff --git a/js/finetune-history.js b/js/finetune-history.js new file mode 100644 index 0000000..40e1d60 --- /dev/null +++ b/js/finetune-history.js @@ -0,0 +1,183 @@ +'use strict'; + +const STORAGE_KEY = 'finetuneHistory'; +const MAX_HISTORY_ENTRIES_PER_CONTROLLER = 10; + +/** + * Manages finetune parameter history for DS5 and Edge controllers + * Stores entries per controller identified by serial number + */ +export class FinetuneHistory { + /** + * Save current finetune settings for a controller + * @param {Array} finetuneData - Array of 12 finetune values + * @param {string} controllerSerialNumber - Serial number of the controller + * @returns {string} The ID of the saved entry + */ + static save(finetuneData, controllerSerialNumber) { + if (!Array.isArray(finetuneData) || finetuneData.length !== 12) { + throw new Error(`Finetune data must be an array of 12 values, got "${finetuneData}"`); + } + + if (!controllerSerialNumber || typeof controllerSerialNumber !== 'string') { + throw new Error('Controller serial number is required'); + } + + const allHistory = this._getAllHistory(); + const controllerHistory = allHistory[controllerSerialNumber] || []; + + // Check if the most recent entry has the same data + if (controllerHistory.length > 0 && this._dataEquals(controllerHistory[0].data, finetuneData)) { + // Update the timestamp of the existing entry + controllerHistory[0].timestamp = Date.now(); + allHistory[controllerSerialNumber] = controllerHistory; + this._saveAllHistory(allHistory); + return controllerHistory[0].id; + } + + const entry = { + id: this._generateId(), + timestamp: Date.now(), + data: finetuneData + }; + + controllerHistory.unshift(entry); + + // Keep only the latest MAX_HISTORY_ENTRIES_PER_CONTROLLER for this controller + if (controllerHistory.length > MAX_HISTORY_ENTRIES_PER_CONTROLLER) { + controllerHistory.pop(); + } + + allHistory[controllerSerialNumber] = controllerHistory; + this._saveAllHistory(allHistory); + return entry.id; + } + + /** + * Get all saved finetune settings for a specific controller + * @param {string} controllerSerialNumber - Serial number of the controller + * @returns {Array} Array of saved settings entries for the controller + */ + static getAll(controllerSerialNumber) { + if (!controllerSerialNumber || typeof controllerSerialNumber !== 'string') { + return []; + } + + const allHistory = this._getAllHistory(); + return allHistory[controllerSerialNumber] || []; + } + + /** + * Get finetune settings by ID + * @param {string} id - Entry ID + * @param {string} controllerSerialNumber - Serial number of the controller + * @returns {Object|null} Entry object or null if not found + */ + static getById(id, controllerSerialNumber) { + if (!controllerSerialNumber || typeof controllerSerialNumber !== 'string') { + return null; + } + + const history = this.getAll(controllerSerialNumber); + return history.find(entry => entry.id === id) || null; + } + + /** + * Delete a saved entry + * @param {string} id - Entry ID + * @param {string} controllerSerialNumber - Serial number of the controller + * @returns {boolean} True if deleted, false if not found + */ + static delete(id, controllerSerialNumber) { + if (!controllerSerialNumber || typeof controllerSerialNumber !== 'string') { + return false; + } + + const allHistory = this._getAllHistory(); + const controllerHistory = allHistory[controllerSerialNumber] || []; + const index = controllerHistory.findIndex(entry => entry.id === id); + + if (index >= 0) { + controllerHistory.splice(index, 1); + allHistory[controllerSerialNumber] = controllerHistory; + this._saveAllHistory(allHistory); + return true; + } + return false; + } + + /** + * Clear all saved finetune settings for a specific controller + * @param {string} controllerSerialNumber - Serial number of the controller + */ + static clearAll(controllerSerialNumber) { + if (!controllerSerialNumber || typeof controllerSerialNumber !== 'string') { + return; + } + + const allHistory = this._getAllHistory(); + delete allHistory[controllerSerialNumber]; + this._saveAllHistory(allHistory); + } + + /** + * Get finetune data from a specific entry + * @param {string} id - Entry ID + * @param {string} controllerSerialNumber - Serial number of the controller + * @returns {Array|null} Finetune data array or null if not found + */ + static getDataById(id, controllerSerialNumber) { + const entry = this.getById(id, controllerSerialNumber); + return entry ? entry.data : null; + } + + // ==================== PRIVATE METHODS ==================== + + /** + * Get all history from localStorage (for all controllers) + * @private + */ + static _getAllHistory() { + try { + const stored = localStorage.getItem(STORAGE_KEY); + return stored ? JSON.parse(stored) : {}; + } catch (e) { + console.error('Failed to parse finetune history:', e); + return {}; + } + } + + /** + * Save all history to localStorage + * @private + */ + static _saveAllHistory(allHistory) { + try { + localStorage.setItem(STORAGE_KEY, JSON.stringify(allHistory)); + } catch (e) { + console.error('Failed to save finetune history:', e); + } + } + + /** + * Generate unique ID + * @private + */ + static _generateId() { + return `${Date.now()}_${Math.random().toString(36).substr(2, 9)}`; + } + + /** + * Compare two data arrays for equality + * @private + */ + static _dataEquals(data1, data2) { + if (!Array.isArray(data1) || !Array.isArray(data2)) { + return false; + } + if (data1.length !== data2.length) { + return false; + } + return data1.every((val, idx) => val === data2[idx]); + } +} \ No newline at end of file diff --git a/js/modals/calibration-history-modal.js b/js/modals/calibration-history-modal.js new file mode 100644 index 0000000..10d5ab2 --- /dev/null +++ b/js/modals/calibration-history-modal.js @@ -0,0 +1,175 @@ +'use strict'; + +import { FinetuneHistory } from '../finetune-history.js'; +import { formatLocalizedDate } from '../utils.js'; + +export class CalibrationHistoryModal { + static modalElement = null; + static bootstrapModal = null; + static currentFinetuneData = null; + static currentControllerSerialNumber = null; + + static init() { + this.modalElement = document.getElementById('calibrationHistoryModal'); + if (this.modalElement) { + this.bootstrapModal = new bootstrap.Modal(this.modalElement); + } + } + + static async show(currentFinetuneData = null, controllerSerialNumber = null) { + if (!this.bootstrapModal) { + this.init(); + } + this.currentFinetuneData = currentFinetuneData; + this.currentControllerSerialNumber = controllerSerialNumber; + await this._populateHistory(); + this.bootstrapModal.show(); + } + + static hide() { + if (this.bootstrapModal) { + this.bootstrapModal.hide(); + } + } + + /** + * Populate the history list + * @private + */ + static async _populateHistory() { + const history = FinetuneHistory.getAll(this.currentControllerSerialNumber); + const container = document.getElementById('historyListContainer'); + + if (!history || history.length === 0) { + container.innerHTML = 'No saved calibration settings found.
'; + document.getElementById('clearAllBtn').style.display = 'none'; + return; + } + + document.getElementById('clearAllBtn').style.display = 'block'; + + let html = 'Values: ${entry.data.join(', ')}
+