⋯ 13 lines Show the 13 lines above this hunk @@ -14,13 +14,13 @@ export interface SimulatedControlInput { 14 14 rateCapturedAtMs : number 15 15 target : { positionM : SensorVector , yawRad : number , issuedAtMs : number } 16 16 } 17 - interface LiftProfile { massKg : number , gravityMps2 : number , rateScale : SensorVector , allocate ( wrench : Wrench , previous : Motors , dtS : number ) : { motorsN : Motors , achieved : Wrench , limited : boolean } } 17 + interface LiftProfile { massKg : number , gravityMps2 : number , rateScale : SensorVector , attitudeControl : 'quaternion-error-v1' | 'thrust-heading-v2' , allocate ( wrench : Wrench , previous : Motors , dtS : number ) : { motorsN : Motors , achieved : Wrench , limited : boolean } } 18 18 19 19 export function createSimulatedLiftController ( options : { headingAlignment : 'initial-simulation-north' } , profile : LiftProfile ) { 20 20 if ( options . headingAlignment !== 'initial-simulation-north' ) throw new TypeError ( 'An explicit simulation heading alignment is required' ) 21 - const model = { massKg : profile . massKg , gravityMps2 : profile . gravityMps2 } , allocate = profile . allocate 21 + const model = { massKg : profile . massKg , gravityMps2 : profile . gravityMps2 } , allocate = profile . allocate , attitudeControl = profile . attitudeControl 22 22 const rateScale = Array . isArray ( profile . rateScale ) ? [ ... profile . rateScale ] : [ ] 23 - if ( ! Number . isFinite ( model . massKg ) || model . massKg <= 0 || ! Number . isFinite ( model . gravityMps2 ) || model . gravityMps2 <= 0 || ! validVector ( rateScale ) || rateScale . some ( n => n <= 0 ) || typeof allocate !== 'function' ) throw new RangeError ( 'Invalid simulated lift profile' ) 23 + if ( ! Number . isFinite ( model . massKg ) || model . massKg <= 0 || ! Number . isFinite ( model . gravityMps2 ) || model . gravityMps2 <= 0 || ! validVector ( rateScale ) || rateScale . some ( n => n <= 0 ) || ! [ 'quaternion-error-v1' , 'thrust-heading-v2' ] . includes ( attitudeControl ) || typeof allocate !== 'function' ) throw new RangeError ( 'Invalid simulated lift profile' ) 24 24 const velocityPid = [ 0 , 1 , 2 ] . map ( ( ) => createBoundedPid ( { kp : 1.6 , ki : 0.25 , kd : 0 , integralLimit : 1.5 , outputLimit : 3 , derivativeTauS : 0.03 , trackingGain : 1 } ) ) 25 25 const ratePid = [ 0 , 1 , 2 ] . map ( i => createBoundedPid ( { kp : ( i === 2 ? 0.08 : 0.12 ) * rateScale [ i ] ! , ki : ( i === 2 ? 0.04 : 0.08 ) * rateScale [ i ] ! , kd : ( i === 2 ? 0.001 : 0.002 ) * rateScale [ i ] ! , integralLimit : 0.12 * rateScale [ i ] ! , outputLimit : ( i === 2 ? 0.25 : 0.6 ) * rateScale [ i ] ! , derivativeTauS : 0.03 , trackingGain : 4 } ) ) 26 26 let previous : Motors = [ 0 , 0 , 0 , 0 ] , lastNow : number | null = null , reference : string | null = null , fault : string | null = null ⋯ 40 lines Show the 40 lines above this hunk @@ -67,18 +67,27 @@ export function createSimulatedLiftController(options: { headingAlignment: 'init 67 67 const yaw = target . yawRad , ax = Math . cos ( yaw ) * acceleration [ 0 ] + Math . sin ( yaw ) * acceleration [ 1 ] , ay = - Math . sin ( yaw ) * acceleration [ 0 ] + Math . cos ( yaw ) * acceleration [ 1 ] 68 68 const vertical = model . gravityMps2 - acceleration [ 2 ] 69 69 const desired = multiplyQuaternion ( [ Math . cos ( yaw / 2 ) , 0 , 0 , Math . sin ( yaw / 2 ) ] , quaternionFromTilt ( Math . atan2 ( ay , Math . hypot ( ax , vertical ) ) , Math . atan2 ( - ax , vertical ) ) ) 70 - const q = normalizeQuaternion ( attitude . quaternionWxyz ) 71 - 72 - 73 - const from = rotateBodyToNed ( q , [ 0 , 0 , 1 ] ) , to = rotateBodyToNed ( desired , [ 0 , 0 , 1 ] ) 74 - const dot = Math . max ( - 1 , Math . min ( 1 , from . reduce ( ( sum , n , i ) => sum + n * to [ i ] ! , 0 ) ) ) 75 - if ( 1 + dot <= 1e-8 ) return inhibit ( 'antipodal-thrust-direction' ) 76 - const alignment = normalizeQuaternion ( [ 1 + dot , from [ 1 ] * to [ 2 ] - from [ 2 ] * to [ 1 ] , from [ 2 ] * to [ 0 ] - from [ 0 ] * to [ 2 ] , from [ 0 ] * to [ 1 ] - from [ 1 ] * to [ 0 ] ] ) 77 - const reduced = normalizeQuaternion ( multiplyQuaternion ( alignment , q ) ) 78 - const tiltError = multiplyQuaternion ( [ q [ 0 ] , - q [ 1 ] , - q [ 2 ] , - q [ 3 ] ] , reduced ) 79 - const yawError = multiplyQuaternion ( [ reduced [ 0 ] , - reduced [ 1 ] , - reduced [ 2 ] , - reduced [ 3 ] ] , desired ) 80 - const correction = [ tiltError [ 1 ] * ( tiltError [ 0 ] < 0 ? - 1 : 1 ) , tiltError [ 2 ] * ( tiltError [ 0 ] < 0 ? - 1 : 1 ) , yawError [ 3 ] * ( yawError [ 0 ] < 0 ? - 1 : 1 ) ] 81 - const rates = correction . map ( ( n , i ) => clampControl ( 8 * n , i === 2 ? 1.5 : 3 ) ) as SensorVector 70 + let rates : SensorVector 71 + if ( attitudeControl === 'quaternion-error-v1' ) { 72 + 73 + const q = attitude . quaternionWxyz 74 + const error = multiplyQuaternion ( [ q [ 0 ] , - q [ 1 ] , - q [ 2 ] , - q [ 3 ] ] , desired ) 75 + rates = error . slice ( 1 ) . map ( ( n , i ) => clampControl ( 8 * n * ( error [ 0 ] < 0 ? - 1 : 1 ) , i === 2 ? 1.5 : 3 ) ) as SensorVector 76 + } 77 + else { 78 + const q = normalizeQuaternion ( attitude . quaternionWxyz ) 79 + 80 + 81 + const from = rotateBodyToNed ( q , [ 0 , 0 , 1 ] ) , to = rotateBodyToNed ( desired , [ 0 , 0 , 1 ] ) 82 + const dot = Math . max ( - 1 , Math . min ( 1 , from . reduce ( ( sum , n , i ) => sum + n * to [ i ] ! , 0 ) ) ) 83 + if ( 1 + dot <= 1e-8 ) return inhibit ( 'antipodal-thrust-direction' ) 84 + const alignment = normalizeQuaternion ( [ 1 + dot , from [ 1 ] * to [ 2 ] - from [ 2 ] * to [ 1 ] , from [ 2 ] * to [ 0 ] - from [ 0 ] * to [ 2 ] , from [ 0 ] * to [ 1 ] - from [ 1 ] * to [ 0 ] ] ) 85 + const reduced = normalizeQuaternion ( multiplyQuaternion ( alignment , q ) ) 86 + const tiltError = multiplyQuaternion ( [ q [ 0 ] , - q [ 1 ] , - q [ 2 ] , - q [ 3 ] ] , reduced ) 87 + const yawError = multiplyQuaternion ( [ reduced [ 0 ] , - reduced [ 1 ] , - reduced [ 2 ] , - reduced [ 3 ] ] , desired ) 88 + const correction = [ tiltError [ 1 ] * ( tiltError [ 0 ] < 0 ? - 1 : 1 ) , tiltError [ 2 ] * ( tiltError [ 0 ] < 0 ? - 1 : 1 ) , yawError [ 3 ] * ( yawError [ 0 ] < 0 ? - 1 : 1 ) ] 89 + rates = correction . map ( ( n , i ) => clampControl ( 8 * n , i === 2 ? 1.5 : 3 ) ) as SensorVector 90 + } 82 91 const torque = rates . map ( ( n , i ) => ratePid [ i ] ! . step ( n - input . angularRateRadS [ i ] ! , input . angularRateRadS [ i ] ! , dt ) ) as SensorVector 83 92 const desiredWrench = { thrustN : model . massKg * Math . hypot ( ... [ acceleration [ 0 ] , acceleration [ 1 ] , vertical ] ) , torqueNm : torque } 84 93 const allocation = allocate ( desiredWrench , previous , dt )