Back-and-Forth Demo with changing button contexts

Greetings!

This project’s goal was to:

  • - Explore the "calibration" of the micro:bot. (*i.e.* How much delay is required to make a turn of angle "X".)
  • - Explore the possibility of making the "A" and "B" buttons change context based on situation.
  • - Explore the possibility of changing a critical variable, (the delay during turn in ms), while the program is running instead of having to change the code, re-compile and re-download.
  • The result is this program:

    let delay = 0
    let FirstRun = 1
    //  Button "A" starts the micro:bot moving
    //  Button "A+B" enters/exits change delay mode
    input.onButtonPressed(Button.A, function () {
        motobit.enable(MotorPower.Off)
        motobit.setMotorSpeed(Motor.Left, MotorDirection.Forward, 50)
        motobit.setMotorSpeed(Motor.Right, MotorDirection.Forward, 50)
        motobit.enable(MotorPower.On)
        basic.pause(1000)
        motobit.enable(MotorPower.Off)
        basic.pause(1000)  // stop before making turn
        motobit.setMotorSpeed(Motor.Left, MotorDirection.Reverse, 50)
        motobit.setMotorSpeed(Motor.Right, MotorDirection.Forward, 50)
        motobit.enable(MotorPower.On)
        basic.pause(delay)
        motobit.enable(MotorPower.Off)
        basic.pause(1000)  // stop before moving back again
        motobit.setMotorSpeed(Motor.Left, MotorDirection.Forward, 50)
        motobit.setMotorSpeed(Motor.Right, MotorDirection.Forward, 50)
        motobit.enable(MotorPower.On)
        basic.pause(1000)
        motobit.enable(MotorPower.Off)
    })
    input.onButtonPressed(Button.AB, function () {
        SetDelay()
        basic.showString("A=Start")
    })
    // Function SetDelay allows the length of the turn to be adjusted
    // while the device is being used, without having to re-download
    // corrected values - i.e. /in vivo/ results.
    function SetDelay() {
        basic.showString("Set Delay")
        // FirstRun detects if this is the first time the delay adjust
        // has been called, if so, display instructions    
        if (FirstRun == 1) {
            basic.showString("A=- B=+")
            basic.showString("A+B=Exit")
            FirstRun = 0
        }
        // Button "A" subtracts 50 ms from the turn time
        // Button "B" adds 50 ms to the turn time
        // Button "A+B" exits and returns to "run" mode
        while (true) {
            if (input.buttonIsPressed(Button.A)) {
                delay += -50
            }
            if (input.buttonIsPressed(Button.B)) {
                delay += 50
            }
            if (input.buttonIsPressed(Button.AB)) {
                basic.showString("Exit")
                return
            }
            basic.showNumber(delay)
            basic.showString("*")
        }
    }
    motobit.invert(Motor.Left, false)
    motobit.invert(Motor.Right, false)
    delay = 700
    basic.showString("A=Start")
    basic.showString("A+B=Change Delay")
    

    The first part is the top couple of lines, and the lines at the very bottom:

    let delay = 0
    let FirstRun = 1
       . . . . . .
    motobit.invert(Motor.Left, false)
    motobit.invert(Motor.Right, false)
    delay = 700
    basic.showString("A=Start")
    basic.showString("A+B=Change Delay")
    

    This initializes the two local variables, “delay” (the amount of the radial delay in milliseconds) and FirstRun, a flag to indicate if the SetDelay function should display instructions, since it displays instructions only when run the first time.

    Note that I could get rid of the one line of code “delay = 700” by pre-setting it at the top of the program. I already did that with FirstRun.

    This is the part of the program that actually moves the micro:bot.

    //  Button "A" starts the micro:bot moving
    //  Button "A+B" enters/exits change delay mode
    input.onButtonPressed(Button.A, function () {
        motobit.enable(MotorPower.Off)
        motobit.setMotorSpeed(Motor.Left, MotorDirection.Forward, 50)
        motobit.setMotorSpeed(Motor.Right, MotorDirection.Forward, 50)
        motobit.enable(MotorPower.On)
        basic.pause(1000)
        motobit.enable(MotorPower.Off)
        basic.pause(1000)  // stop before making turn
        motobit.setMotorSpeed(Motor.Left, MotorDirection.Reverse, 50)
        motobit.setMotorSpeed(Motor.Right, MotorDirection.Forward, 50)
        motobit.enable(MotorPower.On)
        basic.pause(delay)
        motobit.enable(MotorPower.Off)
        basic.pause(1000)  // stop before moving back again
        motobit.setMotorSpeed(Motor.Left, MotorDirection.Forward, 50)
        motobit.setMotorSpeed(Motor.Right, MotorDirection.Forward, 50)
        motobit.enable(MotorPower.On)
        basic.pause(1000)
        motobit.enable(MotorPower.Off)
    })
    

    Button “A” starts this section when pressed which is a very valuable technique that I saw in some of the exercises. Waiting for a button press allows whoever’s playing with the 'bot to put it down and get it ready before launching it. It’s also safer that way.

    Once started this routine:

  • - Makes sure everything is shut off.
  • - Moves the micro:bot forward, (hopefully in a straight line), for one second.
  • - It then stops and waits for one second so that different sections of this routine are easily seen.
  • - It then executes a left-turn in-place timed by the value of "delay" - which is the variable we're testing.
  • - There's another one second pause. . . .
  • - And finally, it goes forward again for one second, hopefully returning to where it started from.
  • input.onButtonPressed(Button.AB, function () {
        SetDelay()
        basic.showString("A=Start")
    })
    

    This detects the A+B button press and calls SetDelay().

    Note that up to this point, all the button actions have been top-level button events using the “On Button [A]” or [A+B] button handler events. Once the SetDelay function starts the meaning and operation of the buttons can change.

    / Function SetDelay allows the length of the turn to be adjusted
    // while the device is being used, without having to re-download
    // corrected values - i.e. /in vivo/ results.
    function SetDelay() {
        basic.showString("Set Delay")
        // FirstRun detects if this is the first time the delay adjust
        // has been called, if so, display instructions    
        if (FirstRun == 1) {
            basic.showString("A=- B=+")
            basic.showString("A+B=Exit")
            FirstRun = 0
        }
        // Button "A" subtracts 50 ms from the turn time
        // Button "B" adds 50 ms to the turn time
        // Button "A+B" exits and returns to "run" mode
        while (true) {
            if (input.buttonIsPressed(Button.A)) {
                delay += -50
            }
            if (input.buttonIsPressed(Button.B)) {
                delay += 50
            }
            if (input.buttonIsPressed(Button.AB)) {
                basic.showString("Exit")
                return
            }
            basic.showNumber(delay)
            basic.showString("*")
        }
    }
    

    Here, the behavior of the A, B, and A+B button actions change. It also introduces the concept of “modes” of operation.

    As mentioned before, the variable FirstRun is a simple toggle to detect if the operator has already seen the instructions. If so, don’t bother showing them again. (a good programming practice)

    Here’s where the action is:

        // Button "A" subtracts 50 ms from the turn time
        // Button "B" adds 50 ms to the turn time
        // Button "A+B" exits and returns to "run" mode
        while (true) {
            if (input.buttonIsPressed(Button.A)) {
                delay += -50
            }
            if (input.buttonIsPressed(Button.B)) {
                delay += 50
            }
            if (input.buttonIsPressed(Button.AB)) {
                basic.showString("Exit")
                return
            }
            basic.showNumber(delay)
            basic.showString("*")
        }
    

    This detects the button presses inside the function. Note that the function “traps” the button presses and prevents them from being passed to the generic button-press handlers at the top level of the code. Note also that, because we can trap the button events, we can use them to do whatever we want.

    Additionally, note the “return” statement at the bottom. This is not a statement included in the blockly programming interface within MakeCode, (though it should be).

    Note also that this part of the code could have been implemented with an “if - elseif - else” or a “switch-case” block. Note that, like the “return” statement, “switch-case” is legal JavaScript and can be used here, but it must be entered by hand.

    The “while (true)” block sets up an endless loop that is used to poll for button presses inside the function. Since this will never exit, the return statement is used to jump out once the A+B button press is detected.

    The function of this section is like this:

  • - Has button "A" been pressed? If so, subtract 50 from the delay variable which starts at 700 ms. (0.7 second)
  • - Has button "B" been pressed? If so, add 50 to the delay variable.
  • - Have both buttons been pressed at the same time? If so, indicate this by showing "Exit" and terminate the function.
  • - If we have not exited the loop, display the current value of "delay" a "pip" (*) that indicates the bottom of the loop, and go back around again. Functionally, you need to have selected one of the three conditions prior to the "pip" for it to be recognized.
  • Once this function has exited, control of the buttons returns to the top-level button event handlers. The board prints “A to start” and waits for the user to press the “A” button to launch the robot.

    At this point, we’re back to where we started at the beginning except that the delay variable for the turn has been changed.

    Comments and suggestions are always welcome.

    Jim “JR”