/**
 * Power Functions IR Sender
 * Control your Power Functions motors using your micro:bit or Calliope-Mini, an infrared LED and MakeCode.
 *
 * (c) 2017-2020, Philipp Henkel
 */ enum PowerFunctionsChannel { One = 0 , Two = 1 , Three = 2 , Four = 3 } enum PowerFunctionsDirection { Left = 1 , Right = - 1 } enum PowerFunctionsOutput { Red = 0 , Blue = 1 } enum PowerFunctionsMotor { Red1 = 0 , Red2 = 1 , Red3 = 2 , Red4 = 3 , Blue1 = 4 , Blue2 = 5 , Blue3 = 6 , Blue4 = 7 } enum PowerFunctionsCommand { Float = 0 , Forward = 1 , Backward = 2 , Brake = 3 } namespace powerfunctions { interface PowerFunctionsState { irDevice : InfraredDevice ; motorDirections : PowerFunctionsDirection [ ] ; } let state ; function getChannel ( motor ) { const MOTOR_TO_CHANNEL = [ PowerFunctionsChannel . One , PowerFunctionsChannel . Two , PowerFunctionsChannel . Three , PowerFunctionsChannel . Four , PowerFunctionsChannel . One , PowerFunctionsChannel . Two , PowerFunctionsChannel . Three , PowerFunctionsChannel . Four , ] ; return MOTOR_TO_CHANNEL [ motor ] ; } function getOutput ( motor ) { const MOTOR_TO_OUTPUT = [ PowerFunctionsOutput . Red , PowerFunctionsOutput . Red , PowerFunctionsOutput . Red , PowerFunctionsOutput . Red , PowerFunctionsOutput . Blue , PowerFunctionsOutput . Blue , PowerFunctionsOutput . Blue , PowerFunctionsOutput . Blue , ] ; return MOTOR_TO_OUTPUT [ motor ] ; } function sendSingleOutputCommand ( channel , output , speed ) { const msg = message . createSingleOutputPwmMessage ( channel , output , speed ) ; if ( state ) { state . irDevice . sendMessage ( msg ) ; } } param pin export function connectIrLed ( pin ) { state = { irDevice : new InfraredDevice ( pin ) , motorDirections : [ PowerFunctionsDirection . Left , PowerFunctionsDirection . Left , PowerFunctionsDirection . Left , PowerFunctionsDirection . Left , PowerFunctionsDirection . Left , PowerFunctionsDirection . Left , PowerFunctionsDirection . Left , PowerFunctionsDirection . Left , ] , } ; } param motor param speed export function setSpeed ( motor , speed ) { speed = Math . max ( - 7 , Math . min ( 7 , speed ) ) ; if ( state ) { sendSingleOutputCommand ( getChannel ( motor ) , getOutput ( motor ) , speed * state . motorDirections [ motor ] ) ; } } param motor export function brake ( motor ) { setSpeed ( motor , 0 ) ; } param motor export function float ( motor ) { if ( state ) { sendSingleOutputCommand ( getChannel ( motor ) , getOutput ( motor ) , 8 ) ; } } param motor param direction export function setMotorDirection ( motor , direction ) { if ( state ) { state . motorDirections [ motor ] = direction ; } } namespace message { function mapValueToPwmElseFloat ( value ) { switch ( value ) { case 7 : return 0b0111 ; case 6 : return 0b0110 ; case 5 : return 0b0101 ; case 4 : return 0b0100 ; case 3 : return 0b0011 ; case 2 : return 0b0010 ; case 1 : return 0b0001 ; case 0 : return 0b1000 ; case - 1 : return 0b1111 ; case - 2 : return 0b1110 ; case - 3 : return 0b1101 ; case - 4 : return 0b1100 ; case - 5 : return 0b1011 ; case - 6 : return 0b1010 ; case - 7 : return 0b1001 ; default : return 0b0000 ; } } function createMessageFromNibbles ( nibble1 , nibble2 , nibble3 ) { const lrc = 0xf ^ nibble1 ^ nibble2 ^ nibble3 ; return ( nibble1 << 12 ) | ( nibble2 << 8 ) | ( nibble3 << 4 ) | lrc ; } export function createSingleOutputPwmMessage ( channel , output , value ) { const nibble1 = 0b0000 + channel ; const nibble2 = 0b0100 + output ; const nibble3 = mapValueToPwmElseFloat ( value ) ; return createMessageFromNibbles ( nibble1 , nibble2 , nibble3 ) ; } export function createComboDirectMessage ( channel , outputRed , outputBlue ) { const nibble1 = 0b0000 + channel ; const nibble2 = 0b0001 ; const nibble3 = ( outputBlue << 2 ) + outputRed ; return createMessageFromNibbles ( nibble1 , nibble2 , nibble3 ) ; } export function createComboPwmMessage ( channel , outputRed , outputBlue ) { const nibble1 = 0b0100 + channel ; const nibble2 = mapValueToPwmElseFloat ( outputBlue ) ; const nibble3 = mapValueToPwmElseFloat ( outputRed ) ; return createMessageFromNibbles ( nibble1 , nibble2 , nibble3 ) ; } } const IR_MARK = Math . idiv ( 6 * 1000000 , 38000 ) ; const START_STOP_PAUSE = Math . idiv ( ( 45 - 6 ) * 1000000 , 38000 ) ; const LOW_PAUSE = Math . idiv ( ( 16 - 6 ) * 1000000 , 38000 ) ; const HIGH_PAUSE = Math . idiv ( ( 27 - 6 ) * 1000000 , 38000 ) ; export class InfraredDevice { private pin : AnalogPin ; private waitCorrection : number ; constructor ( pin , pwmPeriod = 26 ) { this . pin = pin ; pins . analogWritePin ( this . pin , 0 ) ; pins . analogSetPeriod ( this . pin , pwmPeriod ) ; { const start = input . runningTimeMicros ( ) ; const runs = 8 ; for ( let i = 0 ; i < runs ; i ++ ) { this . transmitBit ( 1 , 1 ) ; } const end = input . runningTimeMicros ( ) ; this . waitCorrection = Math . idiv ( end - start - runs * 2 , runs * 2 ) ; } control . waitMicros ( 2000 ) ; } public transmitBit ( highMicros , lowMicros ) { pins . analogWritePin ( this . pin , 511 ) ; control . waitMicros ( highMicros ) ; pins . analogWritePin ( this . pin , 1 ) ; control . waitMicros ( lowMicros ) ; } public sendMessage ( message ) { const MAX_LENGTH_MS = 16 ; const channel = 1 + ( ( message >> 12 ) & 0b0011 ) ; const ir_mark = IR_MARK - this . waitCorrection ; const high_pause = HIGH_PAUSE - this . waitCorrection ; const low_pause = LOW_PAUSE - this . waitCorrection ; const start_stop_pause = START_STOP_PAUSE - this . waitCorrection ; for ( let sendCount = 0 ; sendCount < 5 ; sendCount ++ ) { const MESSAGE_BITS = 16 ; let mask = 1 << ( MESSAGE_BITS - 1 ) ; this . transmitBit ( ir_mark , start_stop_pause ) ; while ( mask > 0 ) { if ( message & mask ) { this . transmitBit ( ir_mark , high_pause ) ; } else { this . transmitBit ( ir_mark , low_pause ) ; } mask >>= 1 ; } this . transmitBit ( ir_mark , start_stop_pause ) ; if ( sendCount == 0 || sendCount == 1 ) { basic . pause ( 5 * MAX_LENGTH_MS ) ; } else { basic . pause ( ( 6 + 2 * channel ) * MAX_LENGTH_MS ) ; } } } } export function runTests ( ) { { const c1RedFullForward = message . createSingleOutputPwmMessage ( PowerFunctionsChannel . One , PowerFunctionsOutput . Red , 7 ) ; const expectedC1RedFullForward = 0b0000010001111100 ; control . assert ( c1RedFullForward === expectedC1RedFullForward , "createSingleOutputPwmMessage motor Red1 with speed 7" ) ; } { const c1ComboRedForwardBlueBackward = message . createComboDirectMessage ( PowerFunctionsChannel . One , PowerFunctionsCommand . Forward , PowerFunctionsCommand . Backward ) ; const expectedC1ComboRedForwardBlueBackward = 0b0000000110010111 ; control . assert ( c1ComboRedForwardBlueBackward === expectedC1ComboRedForwardBlueBackward , "createComboDirectMessage Red1 forward, Blue1 backward full speed" ) ; } { const c1ComboRedFloatBlueBrake = message . createComboPwmMessage ( PowerFunctionsChannel . One , 8 , 0 ) ; const expectedC1ComboRedFloatBlueBrake = 0b0100100000000011 ; control . assert ( c1ComboRedFloatBlueBrake === expectedC1ComboRedFloatBlueBrake , "createComboPwmMessage Red1 float, Blue1 brake" ) ; } } }