Complete the "binary" documentation such that anybody can write a "wrapper"
DesiOtaku opened this issue · 2 comments
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.
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:
- 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.
- 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:
- "Flatten" the coordinate in the LED Matrix 34x9 grid to an offset in an array:
offset = y * 9 + x
- We can now do the inverse and turn this offset into an
{x, y}coordinate whereyrepresents the byte to modify andxrepresents the bit in the byte to modify.
y = offset % 8
x = 7 - (offset % 8)
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)