Skip to content

Complete the "binary" documentation such that anybody can write a "wrapper" #106

Description

@DesiOtaku

Right now, inputmodule-rs is a python + rust project. Great for command line, not so great for developers who wish not to touch either language.

Good news is that there is some binary level documentation available here: https://github.com/FrameworkComputer/inputmodule-rs/blob/main/commands.md

However, it is currently incomplete. Things like how the bytes are oriented for images (left to right then down, or up to down then left to right?), how to make a custom animation, etc. This would be useful for developers who wish to make their own "wrapper" (like a C/C++ wrapper, or somebody wants to write everything in Java) and would not need an external dependency.

Activity

  1. mathsaey commented on Oct 13, 2024

    @mathsaey

    For anybody who stumbles on this issue and is also wondering how the DrawBW call works.

    As @DesiOtaku pointed out, the byte orientation is not immediately obvious; if you write a loop which sets every bit one by one, the activated pixel will first move right to left, after which it suddenly jumps to the next line. The location where this occurs is not the same on each line either, which makes it tricky to figure out what is going on.

    There are two things which cause confusion here:

    1. The LED Matrix is a 34x9 grid, while the DrawBW call accepts 39 bytes (i.e. 39x8 bits) of data. Thus, the dimensions of both "grids" don't match. The real grid has a width of 9 while the binary grid has a width of 8.
    • If you do the loop experiment I mentioned above, you will notice the "jump" to a new line always occurs on a multiple of 8.
    1. The bits in each byte are read right-to-left instead left-to-right. Thus, setting the first bit will activate the 8th pixel on line 0 of the LED matrix.

    Once you know these two things, you can figure out how to map a {x, y} coordinate on the LED matrix to a {x, y} coordinate that identifies which bit you need to set:

    1. "Flatten" the coordinate in the LED Matrix 34x9 grid to an offset in an array:
    offset = y * 9 + x
    
    1. We can now do the inverse and turn this offset into an {x, y} coordinate where y represents the byte to modify and x represents the bit in the byte to modify.
    y = offset % 8
    x = 7 - (offset % 8)
    
  2. mb720 commented on Oct 19, 2024

    @mb720

    For drawing black and white, I think it's helpful to imagine the LED matrix as a list of 306 pixels that are turned on or off with a list of bits. After turning the bits into bytes, we can send those bytes to the LED module:

    def black_white_draw_pixels(dev):
        """ Use a list of 306 bits to draw a pattern of black and white pixels on the LED module."""
    
        # Each bit represents the state of an LED (1 is on, 0 is off)
        bits = [
                0, 0, 0, 0, 1, 0, 0, 0, 0,
                0, 0, 0, 1, 0, 1, 0, 0, 0,
                0, 0, 1, 0, 0, 0, 1, 0, 0,
                0, 1, 0, 0, 0, 0, 0, 1, 0,
                1, 0, 0, 0, 0, 0, 0, 0, 1,
                0, 1, 0, 0, 0, 0, 0, 1, 0,
                0, 0, 1, 0, 0, 0, 1, 0, 0,
                0, 0, 0, 1, 0, 1, 0, 0, 0,
                0, 0, 0, 0, 1, 0, 0, 0, 0,
                0, 0, 0, 0, 0, 0, 0, 0, 0,
    
                0, 0, 0, 0, 0, 0, 0, 0, 0,
                0, 0, 0, 0, 0, 0, 0, 0, 0,
    
                0, 0, 0, 0, 1, 0, 0, 0, 0,
                0, 0, 0, 0, 1, 0, 0, 0, 0,
                0, 0, 0, 0, 1, 0, 0, 0, 0,
                0, 0, 0, 0, 1, 0, 0, 0, 0,
                1, 1, 1, 1, 1, 1, 1, 1, 1,
                0, 0, 0, 0, 1, 0, 0, 0, 0,
                0, 0, 0, 0, 1, 0, 0, 0, 0,
                0, 0, 0, 0, 1, 0, 0, 0, 0,
                0, 0, 0, 0, 1, 0, 0, 0, 0,
                0, 0, 0, 0, 1, 0, 0, 0, 0,
    
                0, 0, 0, 0, 0, 0, 0, 0, 0,
                1, 0, 0, 0, 0, 0, 0, 0, 1,
                0, 1, 0, 0, 0, 0, 0, 1, 0,
                0, 0, 1, 0, 0, 0, 1, 0, 0,
                0, 0, 0, 1, 0, 1, 0, 0, 0,
                0, 0, 0, 0, 1, 0, 0, 0, 0,
                0, 0, 0, 1, 0, 1, 0, 0, 0,
                0, 0, 1, 0, 0, 0, 1, 0, 0,
                0, 1, 0, 0, 0, 0, 0, 1, 0,
                1, 0, 0, 0, 0, 0, 0, 0, 1,
    
                0, 0, 0, 0, 0, 0, 0, 0, 0,
                0, 0, 0, 0, 0, 0, 0, 0, 0
              ]
    
        bytes = bits_to_bytes(bits)
        send_draw_command(dev, bytes)
    

    If you define the bits like this, you can actually see the patterns in the code that will end up on the LED matrix.

    Here's the rest of the code:

    def main():
        print("Start of program")
        black_white_draw_pixels("/dev/ttyACM0")
        print("End of program")
    
    
    def bits_to_bytes(bits):
        """Convert a list of bits to a list of bytes."""
        import math
        nr_of_bytes = math.ceil(len(bits) / 8)
        bytes = [0x00 for _ in range(nr_of_bytes)]
    
        for bit_index, bit in enumerate(bits):
            byte_index = int(bit_index / 8)
            bit_index_within_byte = bit_index % 8
            if bit:
                # Set the byte's bit to one at bit_index_within_byte
                bytes[byte_index] |= 1 << bit_index_within_byte
    
        return bytes
    
    
    def send_draw_command(dev, bytes):
        """Send a draw command to the LED module that turns LEDs on or off.
    
        The draw command (0x06) requires 39 bytes to be sent to the module. With a 34x9 matrix, we need 34*9 = 306 bits to represent the individual LED's state (on or off). 306 bits fit into 39 bytes: 306/8 = 38.25.
    
        The bits in those bytes represent the state of the individual LEDs on the module. The first bit represents the top left LED, the second bit the LED to the right of it, and so forth. The 10th bit represents the LED in the second row, first column, and so forth.
        """
        draw_cmd_byte = 0x06
        send_command(dev, draw_cmd_byte, bytes)
    
    
    def send_command(dev, command, parameters=[], with_response=False):
        """Send a command to the device. Opens a new serial connection every time."""
        fwk_magic = [0x32, 0xAC]
        return send_command_raw(dev, fwk_magic + [command] + parameters, with_response)
    
    
    def send_command_raw(dev, command, with_response=False):
        import serial
    
        """Send a command to the device. Opens a new serial connection every time."""
        try:
            with serial.Serial(dev, 115200) as s:
                s.write(command)
    
                # Response size from here:
                # https://github.com/FrameworkComputer/inputmodule-rs/blob/main/commands.md
    
                response_size = 32
                if with_response:
                    res = s.read(response_size)
                    print(f"Received: {res}")
                    return res
        except (IOError, OSError) as _ex:
            disconnect_dev(dev.device)
            print("Error: ", ex)
    
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions