Telemetry Wizard
+ +Inputs
+ +Chart widget
+ +Meters widget
+ +Steering widget
+ +Display mode
+Then, press any button to continue.
+ `; + + await this.waitButtonRelease(); + await this.waitButtonClick(); + + return { + ...this.AXES.reduce((options, axis) => { + options[`with${this.capitalize(axis)}`] = document.querySelector(`[name=${axis}-option]`).checked; + return options; + }, {}), + chart: document.querySelector('[name=chart-option]').checked, + history: parseInt(document.querySelector('[name=history-option]').value) * 1000, + meters: document.querySelector('[name=meters-option]').checked, + angle: this.$wizardInstructions.querySelector('input[name="steering-angle-option"]').value, + fps: parseInt(document.querySelector('[name=fps-option]').value) + }; + } + + /** + * Detects activity on any button or axis of the gamepad + * + * @param {string} [type='button'|'axis'|undefined] + * @param {number} [distance=0.5] + * @returns {Promise} + */ + async detectActivity(type = undefined, distance = 0.5) { + const before = this.gamepad.getActive(); + return new Promise((resolve) => { + const interval = window.setInterval(async () => { + const gamepad = this.gamepad.getActive(); + const buttonIndex = ['button', undefined].includes(type) + ? gamepad.buttons.findIndex((button, index) => button.pressed && Math.abs(button.value - before.buttons[index].value) > distance) + : -1; + const axisIndex = ['axis', undefined].includes(type) + ? gamepad.axes.findIndex((axis, index) => Math.abs(axis - before.axes[index]) > distance) + : -1; + if (buttonIndex === -1 && axisIndex === -1) return; + window.clearInterval(interval); + resolve({ + type: buttonIndex === -1 ? 'axis' : 'button', + index: buttonIndex === -1 ? axisIndex : buttonIndex, + }); + }, 100); + }); + } + + /** + * Calibrates a pedal + * + * @param {string} name + * @returns {Promise} + */ + async calibratePedal(name) { + this.$wizardInstructions.innerHTML = ` +Waiting for ${name} activity.
+ `; + const { type, index } = await this.detectActivity(); + if (type === 'button') { + this.$wizardInstructions.innerHTML = ` +Release the ${name} button.
+ `; + await this.waitButtonRelease(index); + return { type, index, releasedValue: 0, pressedValue: 1 }; + } + + this.$wizardInstructions.innerHTML = ` +Press and hold the ${name} axis.
+ `; + const pressedValue = await this.getAxisPush(index); + + this.$wizardInstructions.innerHTML = ` +Release the ${name} axis.
+ `; + const releasedValue = await this.getAxisPush(index, pressedValue); + + return { type, index, releasedValue, pressedValue }; + } + + /** + * Calibrates the steering axis + * + * @returns {Promise} + */ + async calibrateSteering() { + this.$wizardInstructions.innerHTML = ` +Turn the steering axis all the way to the left.
+ `; + const { index } = await this.detectActivity('axis', 0.2); + const leftValue = await this.getAxisPush(index, 0); + + this.$wizardInstructions.innerHTML = ` +Turn the steering axis all the way to the right.
+ `; + const rightValue = await this.getAxisPush(index, leftValue); + + return { index, leftValue, rightValue }; + } + + /** + * Converts an object to a query string, ignoring empty values + * + * @param {Object} options + * @returns {string} + */ + toOptionsParams(options) { + return Object.entries(options) + .filter(([, value]) => value !== undefined && value !== null) + .map(([key, value]) => `${key}=${value}`) + .join('&'); + } + + /** + * Generates the query string for a pedal + * + * @param {string} name + * @param {Object} data + * @returns {string} + */ + toPedalParams(name, { type, index, releasedValue, pressedValue }) { + return `${name}Type=${type}&${name}Index=${index}&${name}Min=${releasedValue}&${name}Max=${pressedValue}`; + } + + /** + * Generates the query string for the steering + * + * @param {Object} data + * @returns {string} + */ + toSteeringParams({ index, leftValue, rightValue }) { + return `steeringIndex=${index}&steeringMin=${leftValue}&steeringMax=${rightValue}`; + } + + /** + * Starts the wizard + * + * @returns {Promise} + */ + async wizard() { + this.$wizard.classList.add('active'); + + const gamepad = this.gamepad.getActive(); + const { withClutch, withBrake, withThrottle, withSteering, chart, history, meters, angle, fps } = await this.askForOptions(); + const clutch = withClutch && await this.calibratePedal('clutch'); + const brake = withBrake && await this.calibratePedal('brake'); + const throttle = withThrottle && await this.calibratePedal('throttle'); + const steering = withSteering && await this.calibrateSteering(); + + window.location.href = [ + `?gamepad=${gamepad.id}&type=telemetry`, + this.toOptionsParams({ chart, history, meters, angle, fps }), + withClutch ? this.toPedalParams('clutch', clutch) : null, + withBrake ? this.toPedalParams('brake', brake) : null, + withThrottle ? this.toPedalParams('throttle', throttle) : null, + withSteering ? this.toSteeringParams(steering) : null + ].filter(e => e !== null).join('&'); + } }; diff --git a/templates/xbox-one/template.css b/templates/xbox-one/template.css index 83a1607..fddebce 100644 --- a/templates/xbox-one/template.css +++ b/templates/xbox-one/template.css @@ -1,17 +1,17 @@ -#gamepad { +.controller { height: 630px; width: 750px; } -#gamepad[data-color="black"] { +#gamepad[data-color="black"] .controller { background-image: url(base-black.svg); } -#gamepad[data-color="white"] { +#gamepad[data-color="white"] .controller { background-image: url(base-white.svg); } -#gamepad.disconnected { +#gamepad.disconnected .controller { background-image: url(disconnected.svg); } @@ -19,42 +19,42 @@ display: none; } -#gamepad .triggers { +.controller .triggers { width: 448px; height: 122px; position: absolute; left: 151px; } -#gamepad .trigger { +.controller .trigger { width: 89px; height: 122px; background: url(trigger.svg); clip-path: inset(100% 0px 0px 0pc); } -#gamepad .trigger[data-value="0"] { +.controller .trigger[data-value="0"] { opacity: 0; } -#gamepad .trigger.left { +.controller .trigger.left { float: left; background-position: 0 0; } -#gamepad .trigger.right { +.controller .trigger.right { float: right; transform: rotateY(180deg); } -#gamepad .bumper { +.controller .bumper { width: 170px; height: 61px; background: url(bumper.svg); opacity: 0; } -#gamepad .bumpers { +.controller .bumpers { position: absolute; width: 536px; height: 61px; @@ -62,41 +62,41 @@ top: 129px; } -#gamepad .bumper[data-pressed="true"] { +.controller .bumper[data-pressed="true"] { opacity: 1; } -#gamepad .bumper.left { +.controller .bumper.left { float: left; } -#gamepad .bumper.right { +.controller .bumper.right { float: right; -webkit-transform: rotateY(180deg); transform: rotateY(180deg); } -#gamepad .p0 { +.controller .p0 { -webkit-transform: rotate(0deg); transform: rotate(0deg); } -#gamepad .p1 { +.controller .p1 { -webkit-transform: rotate(90deg); transform: rotate(90deg); } -#gamepad .p2 { +.controller .p2 { -webkit-transform: rotate(270deg); transform: rotate(270deg); } -#gamepad .p3 { +.controller .p3 { -webkit-transform: rotate(180deg); transform: rotate(180deg); } -#gamepad .arrows { +.controller .arrows { position: absolute; width: 141px; height: 33px; @@ -104,29 +104,29 @@ left: 306px; } -#gamepad .select, -#gamepad .start { +.controller .select, +.controller .start { background: url(start-select.svg); width: 33px; height: 33px; opacity: 0; } -#gamepad .select[data-pressed="true"], -#gamepad .start[data-pressed="true"] { +.controller .select[data-pressed="true"], +.controller .start[data-pressed="true"] { opacity: 1; } -#gamepad .select { +.controller .select { float: left; } -#gamepad .start { +.controller .start { background-position: 33px 0px; float: right; } -#gamepad .buttons { +.controller .buttons { position: absolute; width: 155px; height: 156px; @@ -134,43 +134,43 @@ left: 489px; } -#gamepad .button { +.controller .button { position: absolute; background: url(buttons.svg); width: 53px; height: 53px; } -#gamepad .button[data-pressed="true"] { +.controller .button[data-pressed="true"] { background-position-y: -53px; opacity: 1; } -#gamepad .a { +.controller .a { background-position: 0 0; top: 102px; left: 51px; } -#gamepad .b { +.controller .b { background-position: -53px 0; top: 52px; right: 1px; } -#gamepad .x { +.controller .x { background-position: -106px 0; top: 52px; left: 1px; } -#gamepad .y { +.controller .y { background-position: -159px 0; top: 1px; left: 51px; } -#gamepad .sticks { +.controller .sticks { position: absolute; width: 371px; height: 196px; @@ -178,7 +178,7 @@ left: 144px; } -#gamepad .stick { +.controller .stick { position: absolute; background: url(stick.svg); background-position: -85px 0; @@ -186,21 +186,21 @@ width: 83px; } -#gamepad .stick[data-pressed="true"] { +.controller .stick[data-pressed="true"] { background-position: 0 0; } -#gamepad .stick.left { +.controller .stick.left { top: 0; left: 0; } -#gamepad .stick.right { +.controller .stick.right { top: 113px; left: 288px; } -#gamepad .dpad { +.controller .dpad { position: absolute; width: 110px; height: 111px; @@ -208,17 +208,17 @@ left: 223px; } -#gamepad .face { +.controller .face { background: url(dpad.svg); position: absolute; opacity: 0; } -#gamepad .face[data-pressed="true"] { +.controller .face[data-pressed="true"] { opacity: 1; } -#gamepad .face.up { +.controller .face.up { background-position: 35px 0; left: 38px; top: 1px; @@ -226,14 +226,14 @@ height: 56px; } -#gamepad .face.down { +.controller .face.down { left: 38px; bottom: 0; width: 34px; height: 56px; } -#gamepad .face.left { +.controller .face.left { background-position: 0 -93px; width: 56px; height: 34px; @@ -241,7 +241,7 @@ left: 0; } -#gamepad .face.right { +.controller .face.right { background-position: 0 -57px; width: 56px; height: 34px; diff --git a/templates/xbox-one/template.html b/templates/xbox-one/template.html index b769980..0231256 100644 --- a/templates/xbox-one/template.html +++ b/templates/xbox-one/template.html @@ -1,32 +1,34 @@ -