PragmaticPhil
Published © GPL3+

IoT-Based Quiz App for Multiple micro:bits

Run a quiz by connecting a bunch of BBC micro:bits, via Wi-Fi, to the Ubidots IoT platform using a single XinaBox IoT Starter kit.

BeginnerFull instructions provided45 minutes818
IoT-Based Quiz App for Multiple micro:bits

Things used in this project

Hardware components

CW01
XinaBox CW01
Wi-Fi Core (ESP8266/ESP-12F)
×1
BM01
XinaBox BM01
A bridge to connect your XinaBox xChips to a micro:bit
×1
IP01
XinaBox IP01
An interface module equipped to power and program other modules via a USB A connector.
×1
XC10
XinaBox XC10
This SKU is a 10 pack - we only need 2 for this project, although I use 3. These are standard uBus connectors.
×1
BBC micro:bit board
BBC micro:bit board
You need 1 to act as a gateway then 1 or more others to send data to the gateway
×2

Software apps and online services

Ubidots
Ubidots
You will need an UbiDots account - I use a free 30 day trial account in this blog

Story

Read more

Code

Code for the Classroom Micro:bit

MicroPython
Edit then flash this microPython code onto one of the classroom micro:bits you are using
from microbit import*
import radio
radio.on()
radio.config(length=64, queue=8, channel=11, power=6)
#   NOTE - radio settings are identical to those in the gatewayMicrobit.py code

thisMicrobitID = 0
# EDIT GUIDELINES: remember to name each classroom microbit with a unique integer.  Start from 0 and count upwards.

while True:

    display.show(str(thisMicrobitID))

    if(button_a.was_pressed()):
        radio.send(str(thisMicrobitID) + "_a")
        display.show("a")
        sleep(500)

    if(button_b.was_pressed()):
        radio.send(str(thisMicrobitID) + "_b")
        display.show("b")
        sleep(500)

Code for Micro:bit Gateway

MicroPython
Work through the code and edit is where you see "# EDIT GUIDELINES:~. Then flash it on to your micro:bit gateway.
from microbit import *
import radio
radio.on()
radio.config(length=64, queue=8, channel=11, power=6)
#   NOTE: radio settings are identical to those in the classroomMicrobit.py code

uart.init(baudrate=9600, bits=8, parity=None, stop=1, tx=pin20, rx=pin19)
# NOTE: gets the uart (serial / USB) port on the micro:bit ready to communicate with the CW01

numberOfClassroomMicrobits = 10
#   EDIT GUIDELINES: change the '10' above - enter the number of classroom microbits in your network
#   NOTE: start numbering your clssroom microbits at 0.  so, if you have 3 in total their 'thisMicrobitName' will be 0, 1 and 2 and you must enter 3 above.

global countOfButton_A      #   NOTE: Count the number of classroom mirobits that have sent a message indicating their a-button was pressed. Similar below.
global countOfButton_B      #   NOTE: For these counts we restrict each microbit to 1 'vote'.  Counts and votes are reset when the A-button is clicked.
countOfButton_A = 0
countOfButton_B = 0

global voteRecorded         #   NOTE: Use this list to keep a check on who has voted & prevent repeat.
voteRecorded = [numberOfClassroomMicrobits]
for i in range(0, int(numberOfClassroomMicrobits)):      voteRecorded.append(False)

global testMode
testMode = True
#   NOTE: each classroom micro:bit can only vote one time per separate quiz... only 1 time until the a-button is clicked.
#   NOTE: but, for testing purposes and to set up our dashboard we want lots of data to be sent to Ubidots
#   NOTE: so, when testMode = True we allow each classroom micro:bit to vote as many times as they like.
#   NOTE: set this to False to limit each to 1 vote per quiz.


def resetVoting(numberOfClassroomMicrobits):    #   NOTE: when the a-button on the gateway is clicked voting resets.
    global voteRecorded
    
    #   NOTE - there is no point resetting if no votes have been cast;
    if((countOfButton_A + countOfButton_B) == 0):   return
    
    sendMessageToCW01("+4@YOURMICROBIT@resetVote@1$")        #   NOTE: tells Ubidots a new vote is starting
    sendMessageToCW01("+4@YOURMICROBIT@CountOf_A@0$")
    sendMessageToCW01("+4@YOURMICROBIT@CountOf_B@0$")
    
    #   NOTE: below is a workaround - in Ubidots when we trigger an event it is easy to show the last value receieved.
    #   NOTE: so we send it a value we can use in our email alert:
    percVotesForA = countOfButton_A / (countOfButton_A + countOfButton_B) *100
    sendMessageToCW01("+4@YOURMICROBIT@SummaryOfVote@" + str(percVotesForA) + "$")
    sendMessageToCW01("+4@YOURMICROBIT@SummaryOfVote@-1$")      #   NOTE: reset the value that triggers our email.
    for i in range(0, int(numberOfClassroomMicrobits)): voteRecorded[i] = False


def sendMessageToCW01(parm):
    display.clear()
    uart.write(parm)
    sleep(500)
    data = uart.readline()
    while(data is None):
        data = uart.readline()
    
    if(len(str(data)) >0):
        uartMessageID = getIntegerFromString(data[:1])
        if(uartMessageID == 1):     display.show(Image.YES)     # NOTE: a tick is displayed when data is being transmitted correctly to the CW01
        else:                       display.show(Image.NO)      # NOTE: this means that a cross is displayed when an attempt to send data fails

    sleep(500)


def processRadioSignal(radioSignal, numberOfClassroomMicrobits):        #   NOTE: a 'valid' signal looks like this "0_a" or this "11_b"
    global testMode
    
    if(len(str(radioSignal)) < 3):   return False                       #   NOTE: valid radio signals are at least 3 or more characters long
    
    locationOfUnderscore = getLocationOfUnderscore(radioSignal)
    if(locationOfUnderscore == -1): return False                        #   NOTE: valid radio signals contain an underscore    

    currentMicrobitID = getIntegerFromString(radioSignal[0:locationOfUnderscore])
    if(currentMicrobitID < 0):    return False                          #   NOTE: valid radio messages begin with an integer starting at 0.
    if(currentMicrobitID >= numberOfClassroomMicrobits): return False   #   NOTE: IDs should go from 0 to (numberOfClassroomMicrobits - 1)

    #   NOTE: In the line below you will see why we named the classroom microbits as integers - it allows us to use currentMicrobitID as a list reference.
    #   NOTE: this also means we need to be very careful using them - the validation above prevents an invalid list reference being passed in.
    if((not testMode) and voteRecorded[currentMicrobitID]): return False    
    #   NOTE: the above line ensures that the same classroom microbit can't vote twice for the same quiz, UNLESS we are in testMode
    
    #   NOTE: If we've reached this point of the code the radioSignal has passed all our validation checks.  It is 'safe' to process it.
    return sendValidMessageToCW01(radioSignal, locationOfUnderscore, currentMicrobitID)

def sendValidMessageToCW01(radioSignal, locationOfUnderscore, currentMicrobitID):
    global voteRecorded
    global countOfButton_A
    global countOfButton_B

    messageType = str(radioSignal[locationOfUnderscore +1 : locationOfUnderscore +2])
    #   NOTE: you could experiment with sending new types of data from the classroom microbit - how about temperature ("0_c_25")
    
    sendMessageToCW01("+4@YOURMICROBIT@VoterID@" + str(currentMicrobitID) + "$")     
    #   NOTE: above we tell Ubidots that someone has voted, but not how.

    if(messageType == "a"):
        countOfButton_A += 1
        sendMessageToCW01("+4@YOURMICROBIT@buttonA@1$")
        sendMessageToCW01("+4@YOURMICROBIT@CountOf_A@" + str(countOfButton_A) + "$")
        voteRecorded[currentMicrobitID] = True
        return True

    if(messageType == "b"):
        countOfButton_B += 1
        sendMessageToCW01("+4@YOURMICROBIT@buttonB@1$")
        sendMessageToCW01("+4@YOURMICROBIT@CountOf_B@" + str(countOfButton_B) + "$")
        voteRecorded[currentMicrobitID] = True
        return True
    
    return False
    

def getLocationOfUnderscore(radioSignal):
    #   NOTE: The underscore can only be in 1 of 2 places in the string, so KISS:
    radioSignalStr = str(radioSignal)
    if(radioSignalStr[1:2] == "_"):    return 1
    if(radioSignalStr[2:3] == "_"):    return 2
    return -1


def getIntegerFromString(uncheckedString):
    try: return int(uncheckedString)
    except ValueError: return -1


#   INIT:

display.show(Image.SQUARE)          # NOTE: the square is shown on your micro:bit while it is connecting to Ubidots
sleep(2000)
uart.write("$")                     # NOTE: Cleans out the serial buffer
sleep(100)
uart.write("+9@?$")                 # NOTE: Reboots the CW01
sleep(5000)                         # NOTE: long delay is necessary - we need to give Wi-Fi time to sort itself out.
uart.write("$")                     # NOTE: Clean out Serial buffer, again.
sleep(500)

sendMessageToCW01("+1@WIFINAME@WIFIPASSWORD$")
# EDIT GUIDELINES: you MUST enter the name and password of the Wi-Fi network you are trying to connect to in the line above.

sendMessageToCW01("+2@DEFAULTTOKEN@?$")
# EDIT GUIDELINES: you MUST enter the DEFAULT TOKEN from Ubidots in the line above.

sendMessageToCW01("+3@things.ubidots.com@1883$")
# NOTE: above line tells the micro;bit where to send the data to - its a Ubidots URL.

sendMessageToCW01("+6@/v1.6/devices/microbit/number/lv$")
# NOTE: above is an ID for our CW01 - you can leave it as is.

sendMessageToCW01("+4@YOURMICROBIT@SummaryOfVote@-1$")
# NOTE: This variable is used to trigger an email sent after the vote - we set it to -1 as any value > -1 triggers the email.

while True:
    if(processRadioSignal(radio.receive(), numberOfClassroomMicrobits)):    
        display.show(Image.HAPPY)
        sleep(100)
    else:
        display.show(Image.SAD)
            
    if(button_a.was_pressed()): 
        resetVoting(numberOfClassroomMicrobits)
        countOfButton_A = 0
        countOfButton_B = 0
        display.show(Image.ARROW_N)
        sleep(200)

    sleep(100)

Credits

PragmaticPhil

PragmaticPhil

17 projects • 17 followers
Pragmatic hobbyist

Comments