Build Argo V2 control mappings and telemetry dashboard
Add configurable Xbox motion and action bindings, frame-aware six-axis MAVLink controls, sensor logging, camera viewing and recording, and a faster startup path. Synchronize both app copies and document companion hardware setup and verification limits.
This commit is contained in:
+73
-1
@@ -10,6 +10,7 @@
|
||||
invertTurn:false,invertForward:true,invertVertical:true,deadZone:.08};
|
||||
}
|
||||
function read(pad,config){
|
||||
if(config?.version===2)return readProfile(pad,config);
|
||||
if(!pad?.connected||!pad.axes)return null;
|
||||
const axis=(index,invert)=>{
|
||||
if(index===-1)return 0;
|
||||
@@ -48,6 +49,77 @@
|
||||
return demand;
|
||||
}
|
||||
}
|
||||
root.ControllerInput={deadZone,defaults,read,Motion};
|
||||
const names=['forward','lateral','vertical','turn','pitch','roll'];
|
||||
const pair=(negative='off',positive='off')=>({negative,positive,scale:1});
|
||||
function preset(name='triggers3'){
|
||||
const bindings=Object.fromEntries(names.map(axis=>[axis,pair()]));
|
||||
bindings.forward=pair('button:6','button:7');
|
||||
bindings.vertical=pair('button:0','button:1');
|
||||
bindings.turn=pair('axis:0:-','axis:0:+');
|
||||
if(name==='sixaxis'){
|
||||
bindings.lateral=pair('axis:2:-','axis:2:+');
|
||||
bindings.pitch=pair('axis:1:-','axis:1:+');
|
||||
bindings.roll=pair('button:4','button:5');
|
||||
}else if(name==='classic'){
|
||||
bindings.forward=pair('axis:1:+','axis:1:-');
|
||||
bindings.vertical=pair('axis:3:+','axis:3:-');
|
||||
}
|
||||
return {version:2,preset:name,deadZone:.08,expo:.15,bindings,
|
||||
actions:{lights:'button:2',stream:'button:3',record:'button:8'}};
|
||||
}
|
||||
function migrate(config,pad){
|
||||
if(config?.version===2)return structuredClone(config);
|
||||
if(!config)return preset(pad&&pad.mapping!=='standard'?'classic':'triggers3');
|
||||
const result=preset('classic');
|
||||
for(const axis of ['forward','vertical','turn']){
|
||||
const index=config[axis],invert=config['invert'+axis[0].toUpperCase()+axis.slice(1)];
|
||||
result.bindings[axis]=Number.isInteger(index)&&index>=0?
|
||||
pair(`axis:${index}:${invert?'+':'-'}`,`axis:${index}:${invert?'-':'+'}`):pair();
|
||||
}
|
||||
result.deadZone=config.deadZone??.08;
|
||||
result.actions={lights:'off',stream:'off',record:'off'};
|
||||
return result;
|
||||
}
|
||||
function validProfile(config){
|
||||
const source=value=>typeof value==='string'&&/^(off|button:(?:[0-9]|[12][0-9]|3[01])|axis:(?:[0-9]|1[0-5]):[+-])$/.test(value);
|
||||
return config?.version===2&&Number.isFinite(config.deadZone)&&config.deadZone>=0&&config.deadZone<=.3&&
|
||||
Number.isFinite(config.expo)&&config.expo>=0&&config.expo<=1&&names.every(axis=>{
|
||||
const item=config.bindings?.[axis];return item&&source(item.negative)&&source(item.positive)&&
|
||||
(item.negative==='off'||item.negative!==item.positive)&&Number.isFinite(item.scale)&&item.scale>=0&&item.scale<=1;
|
||||
})&&['lights','stream','record'].every(action=>source(config.actions?.[action])&&!config.actions[action].startsWith('axis:'));
|
||||
}
|
||||
function inputValue(pad,source,zone){
|
||||
if(source==='off')return 0;
|
||||
const parts=source.split(':'),index=Number(parts[1]);
|
||||
if(parts[0]==='button'){
|
||||
const button=pad.buttons?.[index];if(!button||!Number.isFinite(button.value))return null;
|
||||
return index===6||index===7?Math.max(0,deadZone(button.value,zone)):(button.pressed?1:0);
|
||||
}
|
||||
const value=pad.axes?.[index];
|
||||
return Number.isFinite(value)?Math.max(0,deadZone(value,zone)*(parts[2]==='-'?-1:1)):null;
|
||||
}
|
||||
function readProfile(pad,config){
|
||||
if(!pad?.connected||!validProfile(config))return null;
|
||||
const result={};
|
||||
for(const name of names){
|
||||
const item=config.bindings[name],negative=inputValue(pad,item.negative,config.deadZone),positive=inputValue(pad,item.positive,config.deadZone);
|
||||
if(negative===null||positive===null)return null;
|
||||
const value=clamp(positive-negative);
|
||||
result[name]=((1-config.expo)*value+config.expo*value**3)*item.scale;
|
||||
}
|
||||
return result;
|
||||
}
|
||||
function actions(pad,config){
|
||||
return Object.fromEntries(Object.entries(config?.actions||{}).map(([name,source])=>[name,!!pad&&inputValue(pad,source,0)>.5]));
|
||||
}
|
||||
function sources(pad){
|
||||
const choices=[['off','Off']],axes=['Left X','Left Y','Right X','Right Y'];
|
||||
for(let index=0;index<Math.max(4,pad?.axes?.length||0);index++)for(const sign of ['-','+'])
|
||||
choices.push([`axis:${index}:${sign}`,`${axes[index]||'Axis '+index} ${sign==='-'?'−':'+'}`]);
|
||||
const buttons=['A','B','X','Y','LB','RB','LT','RT','View','Menu','Left click','Right click','D-pad up','D-pad down','D-pad left','D-pad right','Guide'];
|
||||
for(let index=0;index<Math.max(17,pad?.buttons?.length||0);index++)choices.push([`button:${index}`,buttons[index]||'Button '+index]);
|
||||
return choices;
|
||||
}
|
||||
root.ControllerInput={deadZone,defaults,read,Motion,names,preset,migrate,validProfile,actions,sources};
|
||||
if(typeof module!=='undefined')module.exports=root.ControllerInput;
|
||||
})(typeof window!=='undefined'?window:globalThis);
|
||||
|
||||
Reference in New Issue
Block a user