5. Light Sensitive Robot

    

This is the final tutorial using the light sensors to either avoid or follow light. We will use all of the previous code that we’ve developed, add two methods lightMove() and setReference(). It will be possible at select each mode by pressing button A or button B.

First off I would like you to copy and paste the code from the previous example into Mu as a new project. Then delete the program loop,  delete leftLineSenorrightLineSensor variables and the lineDetector() method.

Now we need to create our method lightMove(), this method will have an argument called direction that can be a 1 or a 0. 1 for avoid and 0 for follow. The method is shown below;

The LightMove Function
def lightMove(direction):
if robotType == "classic":
sensorSelect.write_digital(0) # select left sensor
brightnessLeft = lightSensor.read_analog() # read value
sensorSelect.write_digital(1) # select right sensor
brightnessRight = lightSensor.read_analog() # read value
else: # Bit:Bot XL
brightnessLeft = leftLightSensor.read_analog()
brightnessRight = rightLightSensor.read_analog()

avgBrightness = int((brightnessLeft + brightnessRight) / 2)

if (avgBrightness <= refLevel + 10):
stop() # stop Moving
if (refLevel >= 900): # LEDs are blue to indicate very bright room
leftLights(0, setBrightness(1), setBrightness(1))
rightLights(0, setBrightness(1), setBrightness(1))
else: # LEDs are pinkish to indicate a dim room
leftLights(setBrightness(1), 0, setBrightness(1))
rightLights(setBrightness(1), 0, setBrightness(1))

elif(brightnessLeft > brightnessRight - 25) and (brightnessLeft < brightnessRight + 25):
if (direction == 1):
drive(-512) # drive backwards
else:
drive(512) # drive forwards
leftLights(setBrightness(1), setBrightness(1), 0)
rightLights(setBrightness(1), setBrightness(1), 0)

else: # turn the robot
if (direction == 1):
move(brightnessLeft, brightnessRight, direction, direction)
leftLights(setBrightness(1), 0, 0)
rightLights(setBrightness(1), 0, 0)
else:
move(brightnessRight, brightnessLeft, direction, direction)
leftLights(0, setBrightness(1), 0)
rightLights(0, setBrightness(1), 0)

I also added the following method called setReference();

Setting a Reference Light Level
def setReference():
if robotType == "classic":
sensorSelect.write_digital(0) # select left sensor
val1 = lightSensor.read_analog() # read value
sensorSelect.write_digital(1) # select right sensor
val2 = lightSensor.read_analog()
else:
val1 = leftLightSensor.read_analog() # read value
val2 = rightLightSensor.read_analog()
return int((val1 + val2) / 2)

and finally I added the following variable declaration under sensorSelect = pin16

Create a Variable to Store the Reference Level
refLevel = 0

So what does it do. I will describe the setReference() method first; this method will be invoked when Bit:Bot is started. It takes readings from both the left and right sensors adds them together and divides by 2. This calculates an ambient light level that we will use as the reference level.

Now to the lightMove() method; this method will create an average value from the amount of light coming into the sensors, if the amount of light is less than the reference level + 10 then the robot will not move and the following if statement will be evaluated, if the light is bright it will light up the LEDs a cyan colour to indicate that the light levels are too bright and the room needs to be darker for the robot to perform properly. If the room is dark enough the lights on the robot will light a pinkish colour and the robot will wait for a light to be shone on it before it starts to move.

NOTE: elif means else if. You use this instead of the else statement if you have another condition to be evaluated.

If the ambient light is greater than the reference level then the robot will need to do something. Firstly the robot needs to decide if the light is coming from its front or its side. It uses the following if statement to evaluate this;

Checking Where the Light is Coming From
elif(brightnessLeft > brightnessRight - 25) and 
(brightnessLeft < brightnessRight + 25):

This looks at the light coming into each sensor and if the values are within 50 of each other the robot decides that the light is coming directly from its front and either moves forwards in follower mode or backwards in avoid mode and lights the LEDs a yellowish colour.

Otherwise  the robot will turn towards the light in follower mode and away from the light in avoid mode. The LEDs will be red for avoid and green for follow.

The LEDs can be one of five colours these are shown below;

  • Cyan – Room too bright robot will not respond the different light levels
  • Pink – Robot will respond to different light levels
  • Yellow – robot moving forward or backwards because light is originating in front of the robot
  • Green – robot is moving left or right towards the light source
  • Red – robot is moving left or right away from the light source

Now we need a way to choose which mode to put the robot in. For this we will use the A and B buttons. Please note that the A and B buttons share the same pins as the IR sensors. When using Bit:Bot in this mode make sure that its on a dark or evenly coloured surface so the buttons aren’t activated by the IR sensors.

The  below code is added to allow for this;

Selecting the Mode
sleep(1000)
refLevel = setReference()
while True:
buttonA = button_a.get_presses() # Get how many times bA was pressed
buttonB = button_b.get_presses() # Get how many times bB was pressed
if (buttonA > buttonB): # If bA was pressed more than bB then mode 1
mode = 1
elif(buttonB > buttonA): # If bB was pressed more go into mode 2
mode = 2
# If no buttons were pressed stay in whatever mode it was in before
if(mode == 1):
lightMove(0) # If in mode 1 call lightFollower method
display.show("F")
elif(mode == 2):
lightMove(1)
display.show("A")
else:
display.show("N")

The above code will set the ambient light levels by calling the setReference() method and assigning the returned value to refLevel. The program loop is entered where the button presses are retrieved and the button with the most presses will dictate which  mode to put the robot in. mode 1 is for light follower and mode 2 is for light avoider. If no buttons are pressed then the robot will stay in its current mode. The Micro:Bits LEDs will display A for avoider, F for follower and N for no mode selected. lightMove() method will be called with either a 1 or 0 in the argument field to decide if its in follow or avoid mode.

The mode variable is declared under the refLevel variable near the start of the program.

The complete program listing is shown below;

Complete Code Listing
# Light avoider/follower for the 4tronix Bit:Bot and BBC Micro:Bit
# Author David Bradshaw 2017

# Will either avoid or follow light

from microbit import *
import neopixel # Neopixel Library so we can control the NeoPixels lights

np = neopixel.NeoPixel(pin13, 12)

robotType = ""

def detectModel(): # Detects which model were using XL or classic
global robotType
try:
value = i2c.read(28, 1, repeat=False) # Read i2c bus
robotType = "XL" # If we can read it then it must be XL
display.show("X")
except:
robotType = "classic" # If we can't read it it must be classic
display.show("C") # or Micro:bit is unplugged
sleep(1000) # Do this so the user can see if the correct model is found

detectModel() # Call the above function

# Motor pins; these tell the motor to go
# forward, backwards or turn
if robotType == "classic":
leftSpeed = pin0
leftDirection = pin8
rightSpeed = pin1
rightDirection = pin12

else: # Bit:Bot XL
leftSpeed = pin16
leftDirection = pin8
rightSpeed = pin14
rightDirection = pin12


# Both light sensors are on the same pin so we also use a select pin
if robotType == "classic":
lightSensor = pin2
sensorSelect = pin16

else: # Bit:Bot XL
leftLightSensor = pin2
rightLightSensor = pin1



refLevel = 0 # Reference light level
mode = 0


def leftLights(Red, Green, Blue):
for pixel_id in range(0, 1): # Start of for loop
np[pixel_id] = (Red, Green, Blue) # Code to be executed in the loop
np.show() # Change the NeoPixels colour


def rightLights(Red, Green, Blue):
for pixel_id in range(6, 7):
np[pixel_id] = (Red, Green, Blue)
np.show()


def setBrightness(minValue):
global robotType
if robotType == "classic":
sensorSelect.write_digital(0)
brightnessLeft = lightSensor.read_analog()
sensorSelect.write_digital(1)
brightnessRight = lightSensor.read_analog()
else: # Bit:Bot XL
brightnessLeft = leftLightSensor.read_analog()
brightnessRight = rightLightSensor.read_analog()

brightness = int((brightnessLeft + brightnessRight) / 2)
brightness = int(brightness / 25)
if(brightness < minValue):
brightness = minValue
return brightness


# Motor control to tell the motor what direction and speed to move
def move(_leftSpeed, _rightSpeed, _leftDirection, _rightDirection):
# speed values between 1 - 1023
# smaller values == faster speed moving backwards
# Smaller values == lower speeds when moving forwards
# direction 0 == forwards, 1 == backwards
leftSpeed.write_analog(_leftSpeed) # Set the speed of left motor
rightSpeed.write_analog(_rightSpeed) # Set the speed of right motor
if (_leftDirection != 2):
leftDirection.write_digital(_leftDirection) # left motor
rightDirection.write_digital(_rightDirection) # right motor


def drive(speed):
if (speed > 0):
move(speed, speed, 0, 0) # move the motors forwards
else:
speed = 1023 + speed
move(speed, speed, 1, 1) # move the motors backwards


def sharpRight():
move(100, 1023 + -200, 0, 1)


def sharpLeft():
move(1023 + -200, 100, 1, 0)


def gentleRight():
move(200, 0, 0, 0)


def gentleLeft():
move(0, 200, 0, 0)


def coast():
move(0, 0, 2, 2)


def stop():
move(0, 0, 0, 0)


def lightMove(direction):
global robotType
if robotType == "classic":
sensorSelect.write_digital(0) # select left sensor
brightnessLeft = lightSensor.read_analog() # read value
sensorSelect.write_digital(1) # select right sensor
brightnessRight = lightSensor.read_analog() # read value
else: # Bit:Bot XL
brightnessLeft = leftLightSensor.read_analog()
brightnessRight = rightLightSensor.read_analog()

avgBrightness = int((brightnessLeft + brightnessRight) / 2)

if (avgBrightness <= refLevel + 10):
stop() # stop Moving
if (refLevel >= 900): # LEDs are blue to indicate very bright room
leftLights(0, setBrightness(1), setBrightness(1))
rightLights(0, setBrightness(1), setBrightness(1))
else: # LEDs are pinkish to indicate a dim room
leftLights(setBrightness(1), 0, setBrightness(1))
rightLights(setBrightness(1), 0, setBrightness(1))

elif(brightnessLeft > brightnessRight - 25) and (brightnessLeft < brightnessRight + 25):
if (direction == 1):
drive(-512) # drive backwards
else:
drive(512) # drive forwards
leftLights(setBrightness(1), setBrightness(1), 0)
rightLights(setBrightness(1), setBrightness(1), 0)

else: # turn the robot
if (direction == 1):
move(brightnessLeft, brightnessRight, direction, direction)
leftLights(setBrightness(1), 0, 0)
rightLights(setBrightness(1), 0, 0)
else:
move(brightnessRight, brightnessLeft, direction, direction)
leftLights(0, setBrightness(1), 0)
rightLights(0, setBrightness(1), 0)


def setReference():
global robotType
if robotType == "classic":
sensorSelect.write_digital(0) # select left sensor
val1 = lightSensor.read_analog() # read value
sensorSelect.write_digital(1) # select right sensor
val2 = lightSensor.read_analog()
else:
val1 = leftLightSensor.read_analog() # read value
val2 = rightLightSensor.read_analog()
return int((val1 + val2) / 2)


sleep(1000)
refLevel = setReference()

while True:
buttonA = button_a.get_presses() # Get how many times bA was pressed
buttonB = button_b.get_presses() # Get how many times bB was pressed
if (buttonA > buttonB): # If bA was pressed more than bB then mode 1
mode = 1
elif(buttonB > buttonA): # If bB was pressed more go into mode 2
mode = 2
# If no buttons were pressed stay in whatever mode it was in before
if(mode == 1):
lightMove(0) # If in mode 1 call lightFollower method
display.show("F")
elif(mode == 2):
lightMove(1)
display.show("A")
else:
display.show("N")

The above solution isn’t the most elegant however it works and it gave us an opportunity to cover elif statements, button presses, displaying letters on the Micro:Bit and to use the code developed from the previous examples.

Now upload the code to your robot, put it in a room that’s not too bright and use a torch to make the robot follow or avoid the light. Play with the code and see how Bit:Bot behaves.

You’ve done really well and should understand how to program the Bit:Bot to do stuff. All of these examples can be improved and expanded to give Bit:Bot more functionality. You can purchase an ultrasonic sensor for Bit:Bot from the 4Tronix website that will allow you to develop an obstacle sensing robot. Use the experience you’ve gained from these examples and expand on by coming up with your own ideas and coding them. Micro:Bits are good bits of hardware to cut your teeth with coding, when you get more experience maybe you could have a look at the Arduino family of products. This will let you do much more with many different types of micro-controllers from simple 8-bit AVR type controllers to more complex 32-bit ARM Cortex and even the Atheros AR9331 (Arduino Yun) that can run a small distro of the Linux operating system.

I have put the code files below, if you have any comments, questions or code it can be posted in the comments section below.

Code Files

The below zip archive contains the code file from this example.

LightAvoidFollowerAUTO.zip