Students make a patrol robot that follows the line and sends a radio alert to a base station when it is blocked, using a true/false variable so it alerts once, then build a snake spotter.
Load these into Waypoints Prepare: choose Load URL and paste the copied address, or download the file and import it. In Prepare, the worksheet's Teacher edition shows the answers; the student copy hides them.
Predict and Run 10 min · Investigate 10 min · Modify 12 min · Make 15 min · Real robot 3 min (optional)
This is the assistance strand: lesson 5's line following plus lesson 7's radio, in the other direction. The robot works on its own and reports back. Two new ideas:
Messages are words (send string, on radio received receivedString). On real hardware the base can be a bare micro:bit (untested whether the robot start block errors without a robot; if so, give the base a copy without it).
An alert in forever is sent every time round unless a flag remembers it. Ask what stops the robot sending again.
| What you will see | A question to ask | The fix |
|---|---|---|
| The base never reacts. | Did you press A+B on the base? Is the box on the other robot's line? | Make one micro:bit the base first, then block the other robot. |
| The base beeps again and again. | What stops the robot sending every time round? | Use alerted: send only if not alerted, then set alerted to true. |
| 'clear' is sent all the time. | Is the send inside if alerted? | Send clear only when alerted is true, then set it to false. |
One possible solution for each task. Other correct programs are fine.

function followLine () {
if (robot.detectLine(RobotLineDetector.Left) && robot.detectLine(RobotLineDetector.Right)) {
robot.motorTank(40, 40)
} else if (robot.detectLine(RobotLineDetector.Left)) {
robot.motorTank(10, 40)
} else if (robot.detectLine(RobotLineDetector.Right)) {
robot.motorTank(40, 10)
} else {
robot.motorStop()
}
}
input.onButtonPressed(Button.AB, function () {
role = "base"
robot.motorStop()
basic.showString("B")
})
radio.onReceivedString(function (receivedString) {
if (role == "base") {
if (receivedString == "clear") {
basic.showIcon(IconNames.Yes)
} else {
basic.showIcon(IconNames.No)
music.play(music.tonePlayable(880, music.beat(BeatFraction.Whole)), music.PlaybackMode.InBackground)
}
}
})
let alerted = false
let role = ""
robot.elecfreaksCuteBot.start()
radio.setGroup(1)
role = "robot"
basic.forever(function () {
if (role == "robot") {
if (robot.obstacleDistance() < 15) {
robot.motorStop()
if (!(alerted)) {
radio.sendString("blocked")
alerted = true
}
} else {
if (alerted) {
radio.sendString("clear")
}
alerted = false
followLine()
}
}
})

function followLine () {
if (robot.detectLine(RobotLineDetector.Left) && robot.detectLine(RobotLineDetector.Right)) {
robot.motorTank(40, 40)
} else if (robot.detectLine(RobotLineDetector.Left)) {
robot.motorTank(10, 40)
} else if (robot.detectLine(RobotLineDetector.Right)) {
robot.motorTank(40, 10)
} else {
robot.motorStop()
}
}
input.onButtonPressed(Button.AB, function () {
role = "base"
robot.motorStop()
basic.showString("B")
})
radio.onReceivedString(function (receivedString) {
if (role == "base") {
if (receivedString == "clear") {
basic.showIcon(IconNames.Yes)
} else {
alerts += 1
basic.showNumber(alerts)
music.play(music.tonePlayable(880, music.beat(BeatFraction.Whole)), music.PlaybackMode.InBackground)
}
}
})
let alerts = 0
let alerted = false
let role = ""
robot.elecfreaksCuteBot.start()
radio.setGroup(1)
role = "robot"
basic.forever(function () {
if (role == "robot") {
if (robot.obstacleDistance() < 15) {
robot.motorStop()
if (!(alerted)) {
radio.sendString("blocked")
alerted = true
}
} else {
if (alerted) {
radio.sendString("clear")
}
alerted = false
followLine()
}
}
})

function followLine () {
if (robot.detectLine(RobotLineDetector.Left) && robot.detectLine(RobotLineDetector.Right)) {
robot.motorTank(40, 40)
} else if (robot.detectLine(RobotLineDetector.Left)) {
robot.motorTank(10, 40)
} else if (robot.detectLine(RobotLineDetector.Right)) {
robot.motorTank(40, 10)
} else {
robot.motorStop()
}
}
input.onButtonPressed(Button.AB, function () {
role = "base"
robot.motorStop()
basic.showString("B")
})
radio.onReceivedString(function (receivedString) {
if (role == "base" && receivedString == "snake") {
basic.showIcon(IconNames.Snake)
music.play(music.stringPlayable("C5 A C5 A C5 A - - ", 240), music.PlaybackMode.InBackground)
}
})
let role = ""
robot.elecfreaksCuteBot.start()
radio.setGroup(1)
role = "robot"
basic.forever(function () {
if (role == "robot") {
if (robot.obstacleDistance() < 15) {
robot.motorStop()
robot.setColor(0xff0000)
radio.sendString("snake")
basic.pause(2000)
robot.motorTank(50, -50, 1200)
robot.setColor(0x000000)
} else {
followLine()
}
}
})
Stretch: Two-way: the base sends 'go' with A, and a robot that has stopped waits for 'go' before it carries on (a mode variable from lesson 6).
Support: Give the base side complete; students add the robot's alert with the alerted variable.
Why does the robot need the alerted variable?
forever checks many times a second. Without alerted it would send the alert every time while the box is there; alerted remembers it has already sent, so it sends once.
Next: the challenge unit. Showcase (performance) and Patrol and alert (assistance) put the course together. Real-world links: pipeline and warehouse inspection robots, wildlife cameras that send an alert, smoke alarms that report to a phone.