MicroBlocks and Python Communication with Messages

Issue #294 resolved
Wenjie Wu created an issue

MicroBlocks and Snap! Communication with Messages is really cool ! It is so easy and fun !

I want MicroBlocks to interoperate with more other ecologies. and I had some difficulties when I tried Python:

import serial # https://pyserial.readthedocs.io/
ser = serial.Serial('/dev/tty.usbmodem1402')
def microblocks_send(aString):
    global ser
    length = len(aString) + 1
    rList = [251, 27, 0, length % 256, int(length/256)]
    for i in aString:
    rList.append(ord(i))
    rList.append(254)
    ser.write(bytearray(rList)) # https://pyserial.readthedocs.io/en/latest/pyserial_api.html#serial.Serial.write
    print(rList)
    print(bytearray(rList))

microblocks_send('on')
#[251, 27, 0, 3, 0, 111, 110, 254]
# bytearray(b'\xfb\x1b\x00\x03\x00on\xfe')

The above code does not work, But it works fine in Snap! I referenced Microblocks Serial Protocol (version 2.09), but I still couldn't solve the problem

Comments (16)

  1. John Maloney repo owner

    I think you need to set the baud rate to 115200 using something like:

    serial.Serial('/dev/tty.usbmodem1402', 115200)

  2. Wenjie Wu reporter

    The MicroBlocks code

    When I read the message from MicroBlocks, the result is always the same(The problem seems to be the loop variable i):

    import time
    from microblocks_messaging_library import MicroblocksMessage # See below
    
    m = MicroblocksMessage()
    m.connect('/dev/tty.usbmodem1402') # replace the string with micro:bit port
    
    # send message from Python to MicroBlocks
    m.send('happy')
    time.sleep(1)
    m.send('sad')
    
    # receive message from MicroBlocks
    while True:
        message = m.recv()
        print(message)
        # The output is always the same
    

    here is the MicroblocksMessage class

    import serial
    
    
    class MicroblocksMessage:
        def __init__(self, verbose=False):
            self._verbose = verbose  # verbose: Print various debugging information
            self.ser = None
            self._buffer = bytearray()
    
        def connect(self, port, baudrate=115200):
            self.ser = serial.Serial(port, baudrate)
    
        def __anext__(self):
            self.close()
    
        def disconnect(self):
            self.ser.close()
    
        def send(self, aString):
            length = len(aString) + 1
            rList = [251, 27, 0, length % 256, int(length / 256)]
            for i in aString:
                rList.append(ord(i))
            rList.append(254)
            self.ser.write(bytearray(rList))
    
        def _match(self, in_, filter="*"):
            msgLen = None
            end = None
            cmd = None
    
            result = []
            length = len(in_)
            i = 1
            # b2 = None
            while True:
                while not ((i > length) or (in_[(i - 1)] == 250) or (in_[(i - 1)] == 251)):
                    i += 1
                print(i)
                if length - i < 2:
                    in_ = in_[(i - 1) :]
                    return result
                cmd = in_[(i - 1) + 1]
                if in_[(i - 1)] == 250:
                    if (not (type(filter) == int)) or cmd == filter:
                        result.append(in_[(i - 1) + 1 : (i - 1) + 2])
                    i += 3
                else:
                    if in_[(i - 1)] == 251 and length - i > 3:
                        msgLen = 256 * (in_[(i - 1) + 4]) + in_[(i - 1) + 3]
                        end = i + 4 + msgLen  # i-1?
                        if end > length:
                            in_ = in_[(i - 1) :]
                            return result
                        if (not (type(filter) == int)) or cmd == filter:
                            result.append(in_[(i-1) + 1: (end-1)])
                            i = end + 1
                    else:
                        in_ = in_[(i - 1) :]
                        return result
    
        def recv(self):
            # todo callback
            msg = None
            msgType = None
            length = None
            # data = list(self.ser.read())
            data = self.ser.read()
            if data:
                self._buffer = self._buffer + data
                for msgBytes in self._match(self._buffer, 27):
                    length = len(msgBytes)
                    if length > 4:
                        msg = msgBytes[(5 - 1) :]
                        msg = msg.decode("utf-8")
                        return msg
    

    The above code is translated from Snap!

  3. John Maloney repo owner

    Cool idea to make Python interact with MicroBlocks! I'll take a look when I get a chance. What's the MicroBlocks script you're using to test?

  4. John Maloney repo owner

    Here's a Python that sends and receives broadcast messages from MicroBlocks.

    import serial
    import time
    
    class MicroblocksMessage:
        def __init__(self):
            self.ser = None
            self._buffer = bytearray()
    
        def connect(self, port):
            self.ser = serial.Serial(port, 115200)
    
        def disconnect(self):
            self.ser.close()
    
        def sendBroadcast(self, aString):
            utf8 = aString.encode('utf-8')
            length = len(utf8) + 1
            bytes = bytearray([251, 27, 0, length % 256, int(length / 256)]) + utf8 + b'\xfe'
            self.ser.write(bytes)
    
        def receiveBroadcasts(self):
            result = []
            data = self.ser.read()
            if data:
                self._buffer = self._buffer + data
                for msgBytes in self._match(27):
                    result.append(msgBytes[4 :].decode("utf-8"))
            return result
    
        def _match(self, filter="*"):
            buf = self._buffer
            result = []
            bytesRemaining = None
            cmd = None
            msgLen = None
            end = None
            length = len(buf)
            i = 0
            while True:
                while not ((i >= length) or (buf[i] == 250) or (buf[i] == 251)):
                    i += 1 # skip to start of next message
                bytesRemaining = length - i
                if (bytesRemaining < 1): # nothing to process
                    self._buffer = buf[i :]
                    return result
                cmd = buf[i]
                if (cmd == 250) and (bytesRemaining >= 3): # short message (3 bytes)
                    if (filter == '*') or (filter == buf[i + 1]):
                        result.append(buf[i : i + 3])
                    i += 3
                elif (cmd == 251) and (bytesRemaining >= 5): # long message (>= 5 bytes)
                    msgLen = (256 * buf[i + 4]) + buf[i + 3]
                    end = i + 5 + msgLen
                    if end > length: # long message is not yet complete
                        self._buffer = buf[i :]
                        return result
                    if (filter == '*') or (filter == buf[i + 1]):
                        result.append(buf[i : end])
                    i = end
                else:
                    self._buffer = buf[i :]
                    return result
    
    
    m = MicroblocksMessage()
    m.connect('/dev/tty.usbmodem2102') # replace the string with micro:bit port
    
    # broadcast messages from Python to MicroBlocks
    m.sendBroadcast('sad')
    time.sleep(1)
    m.sendBroadcast('happy')
    time.sleep(1)
    m.sendBroadcast('clear')
    
    # receive broadcasts from MicroBlocks
    while True:
        for msg in m.receiveBroadcasts():
            print(msg)
    

    And some MicroBlocks code for testing (you can save this PNG file, then drag it into MicroBlocks to load the scripts):

    allScripts7109149.png

  5. John Maloney repo owner

    Thanks for publishing it. I wonder how many people will use it? Do you have plans for using it in a project?

  6. Wenjie Wu reporter

    I added the message flow diagram

    I wonder how many people will use it?

    It is currently being used at Elite. I recommended it to some users of CodeLab and they seemed to like it.

    In China, Python is the most popular language for programming education , with a large number of elementary and middle schools using it, and I'm trying to get more people to migrate their Python legacy projects to MicroBlocks through the library.

    Recently I may give a talk for Maker teachers on MicroBlocks . For interoperability between different programming platforms I may mention this library.

  7. Wenjie Wu reporter

    Do you have plans for using it in a project?

    I've had several requests for Python-MicroBlocks communication library before.

    The Python community has many automation libraries that can take over the computer(such as pyautogui). If we can make Python and MicroBlocks communicate easily, We can then use MicroBlocks(boards) to interoperate with computers. This will unlock a lot of interesting possibilities. For example

    Using a microbit A to communicate with Python(Python takes over the computer through pyautogui) while microbit A receives messages from microbit B through radio(so we can walk around), we can use microbit B as a Laser pointer with remote control for PPT.

    When speaking, when it comes to how the Laser pointer with remote control is made(with MicroBlocks), people are usually very interested, and then naturally lead to the theme of the speech.

  8. Wenjie Wu reporter

    Another interesting application is making wands 🪄 At CodeLab, we have observed that most children are particularly fascinated by wands. Programming and magic seem to have a natural connection

    I etch a pattern of geometric shapes onto a stone. To the uninitiated, the shapes look mysterious and complex, but I know that when arranged correctly they will give the stone a special power, enabling it to respond to incantations in a language no human being has ever spoken. I will ask the stone questions in this language, and it will answer by showing me a vision: a world created by my spell, a world imagined within the pattern on the stone.

    A few hundred years ago in my native New England, an accurate description of my occupation would have gotten me burned at the stake. Yet my work involves no witchcraft; I design and program computers. The stone is a wafer of silicon, and the incantations are software. -- Daniel Hillis《The Pattern on the Stone》

    Python community has a lot of deep learning libraries(Tensorflow/PyTorch/...), in the previous microbitA-microbitB-Python architecture, we can use Python's deep learning library to compute microbitB's gesture in real time, thus making microbit B into a magic wand. Since Python controls the computer with pyautogui, the effective range of our magic will be very broad.

  9. John Maloney repo owner

    These are cool ideas! I agree, programming and magic have a very natural connection. In fact, programming feels like magic when you use it to make surprising and interesting things happen. Learning how to program is like learning how to cast spells. For me, programming and electronics have alway felt like "real" magic -- magic that actually works and the anyone can learn if they work at it.

    I love the idea of using a pair of micro:bits to control your PPT slides, then show people the actual code so they can see that it's actually quite easy to understand.

  10. Log in to comment