Micro::Bit Beacon Detection Activity
Description
You will learn about radio signals through beacon detection. We have provided the code for the beacon(s), which simply sends their ID over their specified group/channel. You will implement scanners that listen for beacons and leads you to the location of the beacon by measuring signal strength.
Features
-
Estimated Time: 1 Hour (basic; base lab) - 4 Hours (advanced; enhancements)
-
Appropriate Grades: 6-12
-
Topic Alignment: ( Limited, Medium, High )
- GenCyber Cybersecurity First Principles:
GenCyber CFPs Alignment Data Hiding L Least Privilege L Abstraction M Domain Separation L Resource Encapsulation M Simplicity M Modularity H Process Isolation H Layering H Minimization L - GenCyber Cybersecurity Concepts:
GenCyber CCs Alignment Defense in Depth H Confidentiality M Integrity M Availability H Think Like an Adversary H Keep It Simple L
Step 0: The Basics
- Micro::Bit Coding Options
- [Click here to code with JavaScript/Blocks] (https://makecode.microbit.org)
- Click here to code with MicroPython
- Radio Signal Basics
- Click here for information on radio signals
- [Click here for information on beacons] (https://kontakt.io/beacon-basics/what-is-a-beacon/)
- Click here for Micro::Bit radio basics
Step 1: Gather Materials
- Micro::Bit
- Micro-USB Cable
- Battery Pack (Equivalent to two AA batteries)
Step 2: Get Familiar With Beacons
Beacons are rarely moved after they are set up. Beacons send out one-way messages that receivers can acquire if they are within range. Below is an example of the most basic implementation of a beacon on a Micro::Bit in Javascript, shown with Blocks as well as the code.
radio.setGroup(1)
basic.forever(() => {
radio.sendNumber(0)
basic.showLeds(`
. . # . .
. # # # .
# # . # #
. # # # .
. . # . .
`)
})
Groups are very similar to channels. Even if you are in range of a beacon, you cannot receive their messages if you are not on the same channel as the beacon. The beacon shown above will set the group to 1, so it can be heard from any in-range receiver listening to group 1. Then, the beacon above repeatedly sends the number 0 over the radio and shows a diamond pattern on the screen. It is not necessary to display the diamond pattern, however it is useful for debugging purposes in more complex beacons.
Step 3: Download The Files
-
Download (click on) the following file to get started: BeaconLab.zip
-
Unzip BeaconLab.zip to your desired folder. For instructions on how to unzip click here
Step 4: Open your editor
This base code is also available in the directory that you downloaded as well as this repository. If you don't remember how to open code in your editor, see the instructions here.
Step 5: Flash the base code
Here is a .hex file with the base code: Base.hex.
This base code is also available in the directory that you downloaded as well as this repository. If you don't remember how to flash code to your Micro::Bit, see the instructions here.
Step 6: Programming!
-
Test the base code. This code should start by displaying a diamond. When you are in range of the beacon, it will show a check mark. Then, open up the base code by doing the following:
- Go to the files you downloaded from above
- Double-click on the JavaScript or Python folder
- Right-click on Base.js (JavaScript) or Base.py (Python)
- Select "Open with Visual Code", "Open with Notepad++", or "Open with Notepad"
- Highlight all of the text and copy it (right-click and copy or ctrl+c)
- If you are using JavaScript/Blocks, in the editor, select the JavaScript button in the center-top of the page. If you are using Python, simply go to your editor.
- Delete all of the existing code in the editor
- Paste your code into the editor
- If you are using Blocks, to get back to blocks, you can select the Blocks button in the center-top of the page.
-
Complete the standard receiver. The receiver sets the group to 1, shows a diamond icon to indicate that the receiver is on, determines the strength of a received signal (on a scale from 1 to 5), and displays icons to show how strong the signal is. You need to add the displays for the different signal strengths Add the following to the base code:
- Display an image for the strongest signal strength (or leave as a check-mark)
- Display an image for the second strongest signal strength
- Display an image for the medium signal strength
- Display an image for the second weakest signal strength
- Display an image for the weakest signal strength (in the else statement)
-
Generate your .hex file. From your editor, click on the "Download" button in the bottom right hand corner. This will download your .hex file. You can find it in the Downloads folder on your computer.
If you are using Mu (Python editor), you simply need to press the "flash" button. In other words, if you are working with Python, you don't have to generate a .hex file, you just need to press the "flash" button.
-
Flash and test your Micro::Bit with beacon at the front of the room (which should be set on group 1). Your Micro::Bit should show the strength of the signal with the displays you chose. To help you in the debugging process, we have provided a potential solution. The completed code for these solutions (as well as pictures of the completed code blocks) is included in the directory you downloaded during setup.
Potential JavaScript Solution (Click to Expand)
radio.onDataPacketReceived( ({ signal, receivedNumber }) => { if (signal > -50) { basic.showIcon(IconNames.Yes) } else if (signal > -60) { basic.showLeds(` . # # # . # # # # # # # # # # # # # # # . # # # . `) } else if (signal > -80) { basic.showLeds(` . . # . . . # # # . # # # # # . # # # . . . # . . `) } else if (signal > -120) { basic.showLeds(` . . . . . . . # . . . # # # . . . # . . . . . . . `) } else { basic.showLeds(` . . . . . . . . . . . . # . . . . . . . . . . . . `) } }) radio.setGroup(1) basic.showIcon(IconNames.Diamond)
Potential Python Solution (Click to Expand)
from microbit import * import radio
strength_1 = Image("00000:" "00000:" "00900:" "00000:" "00000")
strength_2 = Image("00000:" "00900:" "09990:" "00900:" "00000")
strength_3 = Image("00900:" "09990:" "99999:" "09990:" "00900")
strength_4 = Image("09990:" "99999:" "99999:" "99999:" "09990")
display.show(Image.DIAMOND) radio.on() radio.config(group = 1)
while True: details = radio.receive_full() if details is not None: x, rssi, y = details if (rssi>-50): display.show(Image.YES) elif (rssi>-60): display.show(strength_4) elif (rssi>-80): display.show(strength_3) elif (rssi>-120): display.show(strength_2) else: display.show(strength_1)
Covering the radio receiver while you are holding your Micro::Bit can skew the signal strength from the beacon and make your code harder to debug.
-
Now that we have our basic receiver done, we can start adding better features. What if there are multiple beacons on your radio group? Can you find them all? Can you keep track of which ones you have seen? Can you keep track of how many you have seen? To find out, let's add to our program.
JavaScript
All of the changes are in the if block for the strongest signal (when you find a beacon).
- Create a variable called found and set it to false. This will be the variable that tells us if the received ID was found in the list/array.
- Create a for-loop that iterates over each value in the list/array.
- Inside the for-loop, set found to true if the value in the list/array equals the receivedNumber (if the received ID matches one of the entries in the list).
- Outside of the for-loop but still inside the block for the strongest signal, add the receivedNumber to the list if found is false (if the ID wasn't in the list, add it).
- Also if found is false, display the length of the list/array.
Python
- Before the "while True:" loop, create a list for the recorded IDs.
- Inside the "while True:" loop, in the if-statement for the strongest signal (when you find a beacon), sleep for 100 milliseconds after you show your strongest display.
- After the sleep command, check to see if the ID is in the list using:
if id[-4:] not in recordedIds:
- Inside the if-statement, append the ID (id[-4:]) to your list of recorded IDs.
- Also inside the if statment and after you append the id, display the length of your list and sleep for 500 milliseconds.
-
Flash and test Micro:Bit. Debug as needed. To help you in the debugging process, we have provided potential solutions for both JavaScript and Python. The completed code is also included in the directory you downloaded earlier. You're Micro::Bit should do the following:
- Keep track of each beacon that the receiver has found
- Avoid double counting any beacon
- Display the number of unique beacons that the receiver has found each time a new beacon is found.
Potential JavaScript Solution (Click to Expand)
let list: number[] = [] let found = false radio.onDataPacketReceived( ({ signal, receivedNumber }) => { if (signal > -50) { basic.showIcon(IconNames.Yes) found = false for (let value of list) { if (value == receivedNumber) { found = true } } if (!(found)) { list.push(receivedNumber) basic.showNumber(list.length) } } else if (signal > -60) { basic.showLeds( . # # # . # # # # # # # # # # # # # # # . # # # . ) } else if (signal > -80) { basic.showLeds( . . # . . . # # # . # # # # # . # # # . . . # . . ) } else if (signal > -120) { basic.showLeds( . . . . . . . # . . . # # # . . . # . . . . . . . ) } else { basic.showLeds( . . . . . . . . . . . . # . . . . . . . . . . . . ) } }) radio.setGroup(1) basic.showIcon(IconNames.Diamond)
Potential Python Solution (Click to Expand)
from microbit import * import radio strength_1 = Image("00000:" "00000:" "00900:" "00000:" "00000") strength_2 = Image("00000:" "00900:" "09990:" "00900:" "00000") strength_3 = Image("00900:" "09990:" "99999:" "09990:" "00900") strength_4 = Image("09990:" "99999:" "99999:" "99999:" "09990") display.show(Image.DIAMOND) radio.on() radio.config(group = 1) recordedIds = [] while True: details = radio.receive_full() if details is not None: id, rssi, y = details if (rssi > -50): display.show(Image.YES) sleep(100) if id[-4:] not in recordedIds: recordedIds.append(id[-4:]) display.show(str(len(recordedIds))) sleep(500) elif (rssi>-60): display.show(strength_4) elif (rssi>-80): display.show(strength_3) elif (rssi>-120): display.show(strength_2) else: display.show(strength_1)
-
So far, we have built a standard receiver as well as a receiver that can keep track of which beacons it has seen. Now consider how good our receiver is at finding beacons when there are multiple beacons broadcasting over multiple different radio groups. Since our receiver only listens on group 1, it can only ever find beacons that are broadcasting on group 1. We want to be able to control the group that the receiver is searching on. To do this, set the A button to decrement the group and the B button to increment the group. For this lab, there will only be ten groups, 0 through 9.
JavaScript
- Create a variable called group and initialize it to 1 on start.
- Create an input blocks for "on button A pressed" and "on button B pressed"
- In the A button pressed block, if group is greater than zero, set group to group-1 and then set the radio group to group.
- In the B button pressed block, if group is less than nine, set group to group+1 and then set the radio group to group.
- At the end of each button block (outside of the if-statement), display the group.
Python
- Before the "while True:" loop, create an variable called group and initialize it to 1.
- Inside the "while True:" loop, before you set details using receive_full(), create two if-statements, one for if button A was pressed and the other for if button B was pressed.
- In the A button if-statement, if group is greater than zero, set group to group-1 and then set the radio group to group.
- In the B button if-statement, if group is less than nine, set group to group+1 and then set the radio group to group.
- At the end of each button block (outside fo the if-statement), display the gorup and sleep for 500 milliseconds.
-
Flash and test Micro:Bit. Debug as needed. To help you in the debugging process, we have provided potential solutions for both JavaScript and Python. The completed code is also included in the directory you downloaded earlier. You're Micro::Bit should do the following:
- Be able to change groups from 0 to 9, but not less than zero or more than nine
- Be able to find beacons on any of the ten groups
- Be able to continue changing the group after it detects a beacon
Potential JavaScript Solution (Click to Expand)
let list: number[] = [] let found = false let group = 0 radio.onDataPacketReceived( ({ signal, receivedNumber }) => { if (signal > -50) { basic.showIcon(IconNames.Yes) found = false for (let value of list) { if (value == receivedNumber) { found = true } } if (!(found)) { list.push(receivedNumber) basic.showNumber(list.length) } } else if (signal > -60) { basic.showLeds( . # # # . # # # # # # # # # # # # # # # . # # # . ) } else if (signal > -80) { basic.showLeds( . . # . . . # # # . # # # # # . # # # . . . # . . ) } else if (signal > -120) { basic.showLeds( . . . . . . . # . . . # # # . . . # . . . . . . . ) } else { basic.showLeds( . . . . . . . . . . . . # . . . . . . . . . . . . ) } }) input.onButtonPressed(Button.A, () => { if (group > 0) { group = group - 1 radio.setGroup(group) } basic.showNumber(group) }) input.onButtonPressed(Button.B, () => { if (group < 9) { group = group + 1 radio.setGroup(group) } basic.showNumber(group) }) radio.setGroup(1) basic.showIcon(IconNames.Diamond) group = 1
Potential Python Solution (Click to Expand)
from microbit import * import radio strength_1 = Image("00000:" "00000:" "00900:" "00000:" "00000") strength_2 = Image("00000:" "00900:" "09990:" "00900:" "00000") strength_3 = Image("00900:" "09990:" "99999:" "09990:" "00900") strength_4 = Image("09990:" "99999:" "99999:" "99999:" "09990") display.show(Image.DIAMOND) radio.on() radio.config(group=1) group = 1 recordedIds = [] while True: if button_a.was_pressed(): if group > 0: group = group - 1 radio.config(group=group) display.show(str(group)) sleep(500) if button_b.was_pressed(): if group < 9: group = group + 1 radio.config(group=group) display.show(str(group)) sleep(500) details = radio.receive_full() if details is not None: id, rssi, y = details if (rssi > -50): display.show(Image.YES) sleep(100) if id[-4:] not in recordedIds: recordedIds.append(id[-4:]) display.show(str(len(recordedIds))) sleep(500) elif (rssi>-60): display.show(strength_4) elif (rssi>-80): display.show(strength_3) elif (rssi>-120): display.show(strength_2) else: display.show(strength_1)
- Once everyone is feeling comfortable with their receivers, we will have a competition to see who can find the most beacons. There are ~5-10 beacons. You will use your receiver to see how many beacons you can find before time runs out.
Step 7: Further Exploration
- Can you think of a better receiver than the tune-able one? What if your receiver scanned all of the possible group ids for you? What if the receiver always tracked the strongest signal? What else can you think of? Spend some time exploring with your Micro::Bit and see what kind of improvements you can make. The sky is the limit. Below is the JavaScript and blocks of a scanning receiver that we built in the CEDAR lab (Cybersecurity EDucation And Research) and it is also available in the directory you downloaded.
Potential JavaScript Solution (Click to Expand)
let found = 0 let list: number[] = [] let strongest = 0 let beacon_id = 0 let already_found = false let scanned = false let index = 0 let id = 0 radio.onDataPacketReceived( ({ signal, receivedNumber: received_number }) => { if (!(scanned)) { if (strongest < signal) { already_found = false for (let index = 0; index <= list.length; index++) { if (list[index] == received_number + 1) { already_found = true } } if (!(already_found)) { strongest = signal beacon_id = received_number } } } else { if (signal > -60) { basic.showIcon(IconNames.Yes) beacon_id = 999 list.insertAt(found, received_number + 1) found = found + 1 strongest = -128 scanned = false } else if (signal > -65) { basic.showLeds(` . # # # . # # # # # # # # # # # # # # # . # # # . `) } else if (signal > -75) { basic.showLeds(` . . # . . . # # # . # # # # # . # # # . . . # . . `) } else if (signal > -95) { basic.showLeds(` . . . . . . . # . . . # # # . . . # . . . . . . . `) } else { basic.showLeds(` . . . . . . . . . . . . # . . . . . . . . . . . . `) } } }) function set_id() { if (!(scanned)) { for (let id = 0; id <= 9; id++) { already_found = false for (let index = 0; index <= list.length; index++) { if (list[index] == id + 1) { already_found = true } } if (!(already_found)) { radio.setGroup(id) basic.showNumber(id) basic.pause(200) basic.clearScreen() } } if (beacon_id != 999) { scanned = true } } else { radio.setGroup(beacon_id) } } index = 0 radio.setGroup(0) scanned = false id = 0 strongest = -128 found = 0 beacon_id = 999 basic.forever(() => { set_id() })
Potential Python Solution (Click to Expand)
from microbit import * import radio import time strength_1 = Image("00000:" "00000:" "00900:" "00000:" "00000")strength_2 = Image("00000:" "00900:" "09990:" "00900:" "00000") strength_3 = Image("00900:" "09990:" "99999:" "09990:" "00900") strength_4 = Image("09990:" "99999:" "99999:" "99999:" "09990") strength_5 = Image("99999:" "99999:" "99999:" "99999:" "99999") id = 0 display.show(Image.DIAMOND) radio.on() while True: radio.config(group=id) if id is 10: id = 0 else: id = id + 1 details = radio.receive_full() if details is not None: x, rssi, y = details if (rssi>-60): display.show(strength_5) sleep(300) display.show(Image.YES) sleep(500) display.clear() elif (rssi>-65): display.show(strength_4) sleep(300) elif (rssi>-75): display.show(strength_3) sleep(300) elif (rssi>-95): display.show(strength_2) sleep(300) else: display.show(strength_1) sleep(300) sleep(100) </pre> </details>
- Are you able to interfere with the beacons communications?
- What happens when multiple beacons are transmitting on the same group in the same relative location?
- How would you imitate a beacon?
The GenCyber Wyoming COWPOKES program is supported by the National Security Agency and the National Science Foundation through Award #H98230-18-1-0095. Any opinions, findings, and conclusions or recommendations expressed in this material are those of the author(s) and do not necessarily reflect the views of the National Science Foundation, the National Security Agency, or the U.S. government.