From 62748c299f408edef00b65c80587359314e825dc Mon Sep 17 00:00:00 2001 From: Murilo Polese Date: Tue, 4 Jul 2023 17:26:19 +0200 Subject: [PATCH 01/23] Improving test suite --- examples/big_file.py | 1134 ++++-------------------------------------- examples/test.py | 8 +- micropython.js | 33 +- test.js | 310 +++++++++--- 4 files changed, 356 insertions(+), 1129 deletions(-) diff --git a/examples/big_file.py b/examples/big_file.py index 9df9021..f9affdf 100644 --- a/examples/big_file.py +++ b/examples/big_file.py @@ -1,1043 +1,95 @@ -"""ILI9341 LCD/Touch module.""" -from time import sleep -from math import cos, sin, pi, radians -from sys import implementation -from framebuf import FrameBuffer, RGB565 # type: ignore -import ustruct # type: ignore - - -def color565(r, g, b): - """Return RGB565 color value. - - Args: - r (int): Red value. - g (int): Green value. - b (int): Blue value. - """ - return (r & 0xf8) << 8 | (g & 0xfc) << 3 | b >> 3 - - -class Display(object): - """Serial interface for 16-bit color (5-6-5 RGB) IL9341 display. - - Note: All coordinates are zero based. - """ - - # Command constants from ILI9341 datasheet - NOP = const(0x00) # No-op - SWRESET = const(0x01) # Software reset - RDDID = const(0x04) # Read display ID info - RDDST = const(0x09) # Read display status - SLPIN = const(0x10) # Enter sleep mode - SLPOUT = const(0x11) # Exit sleep mode - PTLON = const(0x12) # Partial mode on - NORON = const(0x13) # Normal display mode on - RDMODE = const(0x0A) # Read display power mode - RDMADCTL = const(0x0B) # Read display MADCTL - RDPIXFMT = const(0x0C) # Read display pixel format - RDIMGFMT = const(0x0D) # Read display image format - RDSELFDIAG = const(0x0F) # Read display self-diagnostic - INVOFF = const(0x20) # Display inversion off - INVON = const(0x21) # Display inversion on - GAMMASET = const(0x26) # Gamma set - DISPLAY_OFF = const(0x28) # Display off - DISPLAY_ON = const(0x29) # Display on - SET_COLUMN = const(0x2A) # Column address set - SET_PAGE = const(0x2B) # Page address set - WRITE_RAM = const(0x2C) # Memory write - READ_RAM = const(0x2E) # Memory read - PTLAR = const(0x30) # Partial area - VSCRDEF = const(0x33) # Vertical scrolling definition - MADCTL = const(0x36) # Memory access control - VSCRSADD = const(0x37) # Vertical scrolling start address - PIXFMT = const(0x3A) # COLMOD: Pixel format set - WRITE_DISPLAY_BRIGHTNESS = const(0x51) # Brightness hardware dependent! - READ_DISPLAY_BRIGHTNESS = const(0x52) - WRITE_CTRL_DISPLAY = const(0x53) - READ_CTRL_DISPLAY = const(0x54) - WRITE_CABC = const(0x55) # Write Content Adaptive Brightness Control - READ_CABC = const(0x56) # Read Content Adaptive Brightness Control - WRITE_CABC_MINIMUM = const(0x5E) # Write CABC Minimum Brightness - READ_CABC_MINIMUM = const(0x5F) # Read CABC Minimum Brightness - FRMCTR1 = const(0xB1) # Frame rate control (In normal mode/full colors) - FRMCTR2 = const(0xB2) # Frame rate control (In idle mode/8 colors) - FRMCTR3 = const(0xB3) # Frame rate control (In partial mode/full colors) - INVCTR = const(0xB4) # Display inversion control - DFUNCTR = const(0xB6) # Display function control - PWCTR1 = const(0xC0) # Power control 1 - PWCTR2 = const(0xC1) # Power control 2 - PWCTRA = const(0xCB) # Power control A - PWCTRB = const(0xCF) # Power control B - VMCTR1 = const(0xC5) # VCOM control 1 - VMCTR2 = const(0xC7) # VCOM control 2 - RDID1 = const(0xDA) # Read ID 1 - RDID2 = const(0xDB) # Read ID 2 - RDID3 = const(0xDC) # Read ID 3 - RDID4 = const(0xDD) # Read ID 4 - GMCTRP1 = const(0xE0) # Positive gamma correction - GMCTRN1 = const(0xE1) # Negative gamma correction - DTCA = const(0xE8) # Driver timing control A - DTCB = const(0xEA) # Driver timing control B - POSC = const(0xED) # Power on sequence control - ENABLE3G = const(0xF2) # Enable 3 gamma control - PUMPRC = const(0xF7) # Pump ratio control - - ROTATE = { - 0: 0x88, - 90: 0xE8, - 180: 0x48, - 270: 0x28 - } - - def __init__(self, spi, cs, dc, rst, - width=240, height=320, rotation=0): - """Initialize OLED. - - Args: - spi (Class Spi): SPI interface for OLED - cs (Class Pin): Chip select pin - dc (Class Pin): Data/Command pin - rst (Class Pin): Reset pin - width (Optional int): Screen width (default 240) - height (Optional int): Screen height (default 320) - rotation (Optional int): Rotation must be 0 default, 90. 180 or 270 - """ - self.spi = spi - self.cs = cs - self.dc = dc - self.rst = rst - self.width = width - self.height = height - if rotation not in self.ROTATE.keys(): - raise RuntimeError('Rotation must be 0, 90, 180 or 270.') - else: - self.rotation = self.ROTATE[rotation] - - # Initialize GPIO pins and set implementation specific methods - if implementation.name == 'circuitpython': - self.cs.switch_to_output(value=True) - self.dc.switch_to_output(value=False) - self.rst.switch_to_output(value=True) - self.reset = self.reset_cpy - self.write_cmd = self.write_cmd_cpy - self.write_data = self.write_data_cpy - else: - self.cs.init(self.cs.OUT, value=1) - self.dc.init(self.dc.OUT, value=0) - self.rst.init(self.rst.OUT, value=1) - self.reset = self.reset_mpy - self.write_cmd = self.write_cmd_mpy - self.write_data = self.write_data_mpy - self.reset() - # Send initialization commands - self.write_cmd(self.SWRESET) # Software reset - sleep(.1) - self.write_cmd(self.PWCTRB, 0x00, 0xC1, 0x30) # Pwr ctrl B - self.write_cmd(self.POSC, 0x64, 0x03, 0x12, 0x81) # Pwr on seq. ctrl - self.write_cmd(self.DTCA, 0x85, 0x00, 0x78) # Driver timing ctrl A - self.write_cmd(self.PWCTRA, 0x39, 0x2C, 0x00, 0x34, 0x02) # Pwr ctrl A - self.write_cmd(self.PUMPRC, 0x20) # Pump ratio control - self.write_cmd(self.DTCB, 0x00, 0x00) # Driver timing ctrl B - self.write_cmd(self.PWCTR1, 0x23) # Pwr ctrl 1 - self.write_cmd(self.PWCTR2, 0x10) # Pwr ctrl 2 - self.write_cmd(self.VMCTR1, 0x3E, 0x28) # VCOM ctrl 1 - self.write_cmd(self.VMCTR2, 0x86) # VCOM ctrl 2 - self.write_cmd(self.MADCTL, self.rotation) # Memory access ctrl - self.write_cmd(self.VSCRSADD, 0x00) # Vertical scrolling start address - self.write_cmd(self.PIXFMT, 0x55) # COLMOD: Pixel format - self.write_cmd(self.FRMCTR1, 0x00, 0x18) # Frame rate ctrl - self.write_cmd(self.DFUNCTR, 0x08, 0x82, 0x27) - self.write_cmd(self.ENABLE3G, 0x00) # Enable 3 gamma ctrl - self.write_cmd(self.GAMMASET, 0x01) # Gamma curve selected - self.write_cmd(self.GMCTRP1, 0x0F, 0x31, 0x2B, 0x0C, 0x0E, 0x08, 0x4E, - 0xF1, 0x37, 0x07, 0x10, 0x03, 0x0E, 0x09, 0x00) - self.write_cmd(self.GMCTRN1, 0x00, 0x0E, 0x14, 0x03, 0x11, 0x07, 0x31, - 0xC1, 0x48, 0x08, 0x0F, 0x0C, 0x31, 0x36, 0x0F) - self.write_cmd(self.SLPOUT) # Exit sleep - sleep(.1) - self.write_cmd(self.DISPLAY_ON) # Display on - sleep(.1) - self.clear() - - def block(self, x0, y0, x1, y1, data): - """Write a block of data to display. - - Args: - x0 (int): Starting X position. - y0 (int): Starting Y position. - x1 (int): Ending X position. - y1 (int): Ending Y position. - data (bytes): Data buffer to write. - """ - self.write_cmd(self.SET_COLUMN, *ustruct.pack(">HH", x0, x1)) - self.write_cmd(self.SET_PAGE, *ustruct.pack(">HH", y0, y1)) - - self.write_cmd(self.WRITE_RAM) - self.write_data(data) - - def cleanup(self): - """Clean up resources.""" - self.clear() - self.display_off() - self.spi.deinit() - print('display off') - - def clear(self, color=0, hlines=8): - """Clear display. - - Args: - color (Optional int): RGB565 color value (Default: 0 = Black). - hlines (Optional int): # of horizontal lines per chunk (Default: 8) - Note: - hlines was introduced to deal with memory allocation on some - boards. Smaller values allocate less memory but take longer - to execute. hlines must be a factor of the display height. - For example, for a 240 pixel height, valid values for hline - would be 1, 2, 4, 5, 8, 10, 16, 20, 32, 40, 64, 80, 160. - Higher values may result in memory allocation errors. - """ - w = self.width - h = self.height - assert hlines > 0 and h % hlines == 0, ( - "hlines must be a non-zero factor of height.") - # Clear display - if color: - line = color.to_bytes(2, 'big') * (w * hlines) - else: - line = bytearray(w * 2 * hlines) - for y in range(0, h, hlines): - self.block(0, y, w - 1, y + hlines - 1, line) - - def display_off(self): - """Turn display off.""" - self.write_cmd(self.DISPLAY_OFF) - - def display_on(self): - """Turn display on.""" - self.write_cmd(self.DISPLAY_ON) - - def draw_circle(self, x0, y0, r, color): - """Draw a circle. - - Args: - x0 (int): X coordinate of center point. - y0 (int): Y coordinate of center point. - r (int): Radius. - color (int): RGB565 color value. - """ - f = 1 - r - dx = 1 - dy = -r - r - x = 0 - y = r - self.draw_pixel(x0, y0 + r, color) - self.draw_pixel(x0, y0 - r, color) - self.draw_pixel(x0 + r, y0, color) - self.draw_pixel(x0 - r, y0, color) - while x < y: - if f >= 0: - y -= 1 - dy += 2 - f += dy - x += 1 - dx += 2 - f += dx - self.draw_pixel(x0 + x, y0 + y, color) - self.draw_pixel(x0 - x, y0 + y, color) - self.draw_pixel(x0 + x, y0 - y, color) - self.draw_pixel(x0 - x, y0 - y, color) - self.draw_pixel(x0 + y, y0 + x, color) - self.draw_pixel(x0 - y, y0 + x, color) - self.draw_pixel(x0 + y, y0 - x, color) - self.draw_pixel(x0 - y, y0 - x, color) - - def draw_ellipse(self, x0, y0, a, b, color): - """Draw an ellipse. - - Args: - x0, y0 (int): Coordinates of center point. - a (int): Semi axis horizontal. - b (int): Semi axis vertical. - color (int): RGB565 color value. - Note: - The center point is the center of the x0,y0 pixel. - Since pixels are not divisible, the axes are integer rounded - up to complete on a full pixel. Therefore the major and - minor axes are increased by 1. - """ - a2 = a * a - b2 = b * b - twoa2 = a2 + a2 - twob2 = b2 + b2 - x = 0 - y = b - px = 0 - py = twoa2 * y - # Plot initial points - self.draw_pixel(x0 + x, y0 + y, color) - self.draw_pixel(x0 - x, y0 + y, color) - self.draw_pixel(x0 + x, y0 - y, color) - self.draw_pixel(x0 - x, y0 - y, color) - # Region 1 - p = round(b2 - (a2 * b) + (0.25 * a2)) - while px < py: - x += 1 - px += twob2 - if p < 0: - p += b2 + px - else: - y -= 1 - py -= twoa2 - p += b2 + px - py - self.draw_pixel(x0 + x, y0 + y, color) - self.draw_pixel(x0 - x, y0 + y, color) - self.draw_pixel(x0 + x, y0 - y, color) - self.draw_pixel(x0 - x, y0 - y, color) - # Region 2 - p = round(b2 * (x + 0.5) * (x + 0.5) + - a2 * (y - 1) * (y - 1) - a2 * b2) - while y > 0: - y -= 1 - py -= twoa2 - if p > 0: - p += a2 - py - else: - x += 1 - px += twob2 - p += a2 - py + px - self.draw_pixel(x0 + x, y0 + y, color) - self.draw_pixel(x0 - x, y0 + y, color) - self.draw_pixel(x0 + x, y0 - y, color) - self.draw_pixel(x0 - x, y0 - y, color) - - def draw_hline(self, x, y, w, color): - """Draw a horizontal line. - - Args: - x (int): Starting X position. - y (int): Starting Y position. - w (int): Width of line. - color (int): RGB565 color value. - """ - if self.is_off_grid(x, y, x + w - 1, y): - return - line = color.to_bytes(2, 'big') * w - self.block(x, y, x + w - 1, y, line) - - def draw_image(self, path, x=0, y=0, w=320, h=240): - """Draw image from flash. - - Args: - path (string): Image file path. - x (int): X coordinate of image left. Default is 0. - y (int): Y coordinate of image top. Default is 0. - w (int): Width of image. Default is 320. - h (int): Height of image. Default is 240. - """ - x2 = x + w - 1 - y2 = y + h - 1 - if self.is_off_grid(x, y, x2, y2): - return - with open(path, "rb") as f: - chunk_height = 1024 // w - chunk_count, remainder = divmod(h, chunk_height) - chunk_size = chunk_height * w * 2 - chunk_y = y - if chunk_count: - for c in range(0, chunk_count): - buf = f.read(chunk_size) - self.block(x, chunk_y, - x2, chunk_y + chunk_height - 1, - buf) - chunk_y += chunk_height - if remainder: - buf = f.read(remainder * w * 2) - self.block(x, chunk_y, - x2, chunk_y + remainder - 1, - buf) - - def draw_letter(self, x, y, letter, font, color, background=0, - landscape=False): - """Draw a letter. - - Args: - x (int): Starting X position. - y (int): Starting Y position. - letter (string): Letter to draw. - font (XglcdFont object): Font. - color (int): RGB565 color value. - background (int): RGB565 background color (default: black). - landscape (bool): Orientation (default: False = portrait) - """ - buf, w, h = font.get_letter(letter, color, background, landscape) - # Check for errors (Font could be missing specified letter) - if w == 0: - return w, h - - if landscape: - y -= w - if self.is_off_grid(x, y, x + h - 1, y + w - 1): - return 0, 0 - self.block(x, y, - x + h - 1, y + w - 1, - buf) - else: - if self.is_off_grid(x, y, x + w - 1, y + h - 1): - return 0, 0 - self.block(x, y, - x + w - 1, y + h - 1, - buf) - return w, h - - def draw_line(self, x1, y1, x2, y2, color): - """Draw a line using Bresenham's algorithm. - - Args: - x1, y1 (int): Starting coordinates of the line - x2, y2 (int): Ending coordinates of the line - color (int): RGB565 color value. - """ - # Check for horizontal line - if y1 == y2: - if x1 > x2: - x1, x2 = x2, x1 - self.draw_hline(x1, y1, x2 - x1 + 1, color) - return - # Check for vertical line - if x1 == x2: - if y1 > y2: - y1, y2 = y2, y1 - self.draw_vline(x1, y1, y2 - y1 + 1, color) - return - # Confirm coordinates in boundary - if self.is_off_grid(min(x1, x2), min(y1, y2), - max(x1, x2), max(y1, y2)): - return - # Changes in x, y - dx = x2 - x1 - dy = y2 - y1 - # Determine how steep the line is - is_steep = abs(dy) > abs(dx) - # Rotate line - if is_steep: - x1, y1 = y1, x1 - x2, y2 = y2, x2 - # Swap start and end points if necessary - if x1 > x2: - x1, x2 = x2, x1 - y1, y2 = y2, y1 - # Recalculate differentials - dx = x2 - x1 - dy = y2 - y1 - # Calculate error - error = dx >> 1 - ystep = 1 if y1 < y2 else -1 - y = y1 - for x in range(x1, x2 + 1): - # Had to reverse HW ???? - if not is_steep: - self.draw_pixel(x, y, color) - else: - self.draw_pixel(y, x, color) - error -= abs(dy) - if error < 0: - y += ystep - error += dx - - def draw_lines(self, coords, color): - """Draw multiple lines. - - Args: - coords ([[int, int],...]): Line coordinate X, Y pairs - color (int): RGB565 color value. - """ - # Starting point - x1, y1 = coords[0] - # Iterate through coordinates - for i in range(1, len(coords)): - x2, y2 = coords[i] - self.draw_line(x1, y1, x2, y2, color) - x1, y1 = x2, y2 - - def draw_pixel(self, x, y, color): - """Draw a single pixel. - - Args: - x (int): X position. - y (int): Y position. - color (int): RGB565 color value. - """ - if self.is_off_grid(x, y, x, y): - return - self.block(x, y, x, y, color.to_bytes(2, 'big')) - - def draw_polygon(self, sides, x0, y0, r, color, rotate=0): - """Draw an n-sided regular polygon. - - Args: - sides (int): Number of polygon sides. - x0, y0 (int): Coordinates of center point. - r (int): Radius. - color (int): RGB565 color value. - rotate (Optional float): Rotation in degrees relative to origin. - Note: - The center point is the center of the x0,y0 pixel. - Since pixels are not divisible, the radius is integer rounded - up to complete on a full pixel. Therefore diameter = 2 x r + 1. - """ - coords = [] - theta = radians(rotate) - n = sides + 1 - for s in range(n): - t = 2.0 * pi * s / sides + theta - coords.append([int(r * cos(t) + x0), int(r * sin(t) + y0)]) - - # Cast to python float first to fix rounding errors - self.draw_lines(coords, color=color) - - def draw_rectangle(self, x, y, w, h, color): - """Draw a rectangle. - - Args: - x (int): Starting X position. - y (int): Starting Y position. - w (int): Width of rectangle. - h (int): Height of rectangle. - color (int): RGB565 color value. - """ - x2 = x + w - 1 - y2 = y + h - 1 - self.draw_hline(x, y, w, color) - self.draw_hline(x, y2, w, color) - self.draw_vline(x, y, h, color) - self.draw_vline(x2, y, h, color) - - def draw_sprite(self, buf, x, y, w, h): - """Draw a sprite (optimized for horizontal drawing). - - Args: - buf (bytearray): Buffer to draw. - x (int): Starting X position. - y (int): Starting Y position. - w (int): Width of drawing. - h (int): Height of drawing. - """ - x2 = x + w - 1 - y2 = y + h - 1 - if self.is_off_grid(x, y, x2, y2): - return - self.block(x, y, x2, y2, buf) - - def draw_text(self, x, y, text, font, color, background=0, - landscape=False, spacing=1): - """Draw text. - - Args: - x (int): Starting X position. - y (int): Starting Y position. - text (string): Text to draw. - font (XglcdFont object): Font. - color (int): RGB565 color value. - background (int): RGB565 background color (default: black). - landscape (bool): Orientation (default: False = portrait) - spacing (int): Pixels between letters (default: 1) - """ - for letter in text: - # Get letter array and letter dimensions - w, h = self.draw_letter(x, y, letter, font, color, background, - landscape) - # Stop on error - if w == 0 or h == 0: - print('Invalid width {0} or height {1}'.format(w, h)) - return - - if landscape: - # Fill in spacing - if spacing: - self.fill_hrect(x, y - w - spacing, h, spacing, background) - # Position y for next letter - y -= (w + spacing) - else: - # Fill in spacing - if spacing: - self.fill_hrect(x + w, y, spacing, h, background) - # Position x for next letter - x += (w + spacing) - - # # Fill in spacing - # if spacing: - # self.fill_vrect(x + w, y, spacing, h, background) - # # Position x for next letter - # x += w + spacing - - def draw_text8x8(self, x, y, text, color, background=0, - rotate=0): - """Draw text using built-in MicroPython 8x8 bit font. - - Args: - x (int): Starting X position. - y (int): Starting Y position. - text (string): Text to draw. - color (int): RGB565 color value. - background (int): RGB565 background color (default: black). - rotate(int): 0, 90, 180, 270 - """ - w = len(text) * 8 - h = 8 - # Confirm coordinates in boundary - if self.is_off_grid(x, y, x + 7, y + 7): - return - # Rearrange color - r = (color & 0xF800) >> 8 - g = (color & 0x07E0) >> 3 - b = (color & 0x1F) << 3 - buf = bytearray(w * 16) - fbuf = FrameBuffer(buf, w, h, RGB565) - if background != 0: - bg_r = (background & 0xF800) >> 8 - bg_g = (background & 0x07E0) >> 3 - bg_b = (background & 0x1F) << 3 - fbuf.fill(color565(bg_b, bg_r, bg_g)) - fbuf.text(text, 0, 0, color565(b, r, g)) - if rotate == 0: - self.block(x, y, x + w - 1, y + (h - 1), buf) - elif rotate == 90: - buf2 = bytearray(w * 16) - fbuf2 = FrameBuffer(buf2, h, w, RGB565) - for y1 in range(h): - for x1 in range(w): - fbuf2.pixel(y1, x1, - fbuf.pixel(x1, (h - 1) - y1)) - self.block(x, y, x + (h - 1), y + w - 1, buf2) - elif rotate == 180: - buf2 = bytearray(w * 16) - fbuf2 = FrameBuffer(buf2, w, h, RGB565) - for y1 in range(h): - for x1 in range(w): - fbuf2.pixel(x1, y1, - fbuf.pixel((w - 1) - x1, (h - 1) - y1)) - self.block(x, y, x + w - 1, y + (h - 1), buf2) - elif rotate == 270: - buf2 = bytearray(w * 16) - fbuf2 = FrameBuffer(buf2, h, w, RGB565) - for y1 in range(h): - for x1 in range(w): - fbuf2.pixel(y1, x1, - fbuf.pixel((w - 1) - x1, y1)) - self.block(x, y, x + (h - 1), y + w - 1, buf2) - - def draw_vline(self, x, y, h, color): - """Draw a vertical line. - - Args: - x (int): Starting X position. - y (int): Starting Y position. - h (int): Height of line. - color (int): RGB565 color value. - """ - # Confirm coordinates in boundary - if self.is_off_grid(x, y, x, y + h - 1): - return - line = color.to_bytes(2, 'big') * h - self.block(x, y, x, y + h - 1, line) - - def fill_circle(self, x0, y0, r, color): - """Draw a filled circle. - - Args: - x0 (int): X coordinate of center point. - y0 (int): Y coordinate of center point. - r (int): Radius. - color (int): RGB565 color value. - """ - f = 1 - r - dx = 1 - dy = -r - r - x = 0 - y = r - self.draw_vline(x0, y0 - r, 2 * r + 1, color) - while x < y: - if f >= 0: - y -= 1 - dy += 2 - f += dy - x += 1 - dx += 2 - f += dx - self.draw_vline(x0 + x, y0 - y, 2 * y + 1, color) - self.draw_vline(x0 - x, y0 - y, 2 * y + 1, color) - self.draw_vline(x0 - y, y0 - x, 2 * x + 1, color) - self.draw_vline(x0 + y, y0 - x, 2 * x + 1, color) - - def fill_ellipse(self, x0, y0, a, b, color): - """Draw a filled ellipse. - - Args: - x0, y0 (int): Coordinates of center point. - a (int): Semi axis horizontal. - b (int): Semi axis vertical. - color (int): RGB565 color value. - Note: - The center point is the center of the x0,y0 pixel. - Since pixels are not divisible, the axes are integer rounded - up to complete on a full pixel. Therefore the major and - minor axes are increased by 1. - """ - a2 = a * a - b2 = b * b - twoa2 = a2 + a2 - twob2 = b2 + b2 - x = 0 - y = b - px = 0 - py = twoa2 * y - # Plot initial points - self.draw_line(x0, y0 - y, x0, y0 + y, color) - # Region 1 - p = round(b2 - (a2 * b) + (0.25 * a2)) - while px < py: - x += 1 - px += twob2 - if p < 0: - p += b2 + px - else: - y -= 1 - py -= twoa2 - p += b2 + px - py - self.draw_line(x0 + x, y0 - y, x0 + x, y0 + y, color) - self.draw_line(x0 - x, y0 - y, x0 - x, y0 + y, color) - # Region 2 - p = round(b2 * (x + 0.5) * (x + 0.5) + - a2 * (y - 1) * (y - 1) - a2 * b2) - while y > 0: - y -= 1 - py -= twoa2 - if p > 0: - p += a2 - py - else: - x += 1 - px += twob2 - p += a2 - py + px - self.draw_line(x0 + x, y0 - y, x0 + x, y0 + y, color) - self.draw_line(x0 - x, y0 - y, x0 - x, y0 + y, color) - - def fill_hrect(self, x, y, w, h, color): - """Draw a filled rectangle (optimized for horizontal drawing). - - Args: - x (int): Starting X position. - y (int): Starting Y position. - w (int): Width of rectangle. - h (int): Height of rectangle. - color (int): RGB565 color value. - """ - if self.is_off_grid(x, y, x + w - 1, y + h - 1): - return - chunk_height = 1024 // w - chunk_count, remainder = divmod(h, chunk_height) - chunk_size = chunk_height * w - chunk_y = y - if chunk_count: - buf = color.to_bytes(2, 'big') * chunk_size - for c in range(0, chunk_count): - self.block(x, chunk_y, - x + w - 1, chunk_y + chunk_height - 1, - buf) - chunk_y += chunk_height - - if remainder: - buf = color.to_bytes(2, 'big') * remainder * w - self.block(x, chunk_y, - x + w - 1, chunk_y + remainder - 1, - buf) - - def fill_rectangle(self, x, y, w, h, color): - """Draw a filled rectangle. - - Args: - x (int): Starting X position. - y (int): Starting Y position. - w (int): Width of rectangle. - h (int): Height of rectangle. - color (int): RGB565 color value. - """ - if self.is_off_grid(x, y, x + w - 1, y + h - 1): - return - if w > h: - self.fill_hrect(x, y, w, h, color) +# stepper.py + +# A micropython driver for 4-phase, unipolar stepper motors such as +# the 28BYJ-48 + +# Relesed to the Public Domain by Nicko van Someren, 2020 + +# The constructor for the Stepper class takes as arguments the four +# pins for driving the motor phases, in phase order, and optionally a +# timer. The pins can be passed as pin numbers or machine.Pin objects +# and the timer can be a machine.Timer object or a timer index. Note +# that if two stepper motors use the same timer then they will not be +# able to run at the same time. +# +# The run() method takes a number of steps and an optional delay (in +# seconds) between driving the steps (the default is 1ms). A negative +# step count will drive the motor in the oposite direction to a +# positive count. The count represents "half steps" since the driver +# alternates driving single coils and driving pairs of adjacent coils. +# Calls to run() return immediately; the motor runs on a timer in the +# background. Calling run() again before the previous command has +# finished adds the new count to the old count, so the destination +# position is the sum of the requests; the delay is set to the new +# value if stepper is not already at its final location. +# +# The stop() method will stop the rotation of the motor. It returns +# the number of un-taken steps that would be needed to perform the +# outstanding requests from previous calls to run(). +# +# The is_running property returns true if the motor is running, +# i.e. stop() would return a non-zero value, and false otherwise. + +import machine +import time + +# When the following number is sampled at four consecutive +# even-numbered bits it will have two bits set, but sampling at four +# consecutive odd-numbered bits will only yield one bit set. + +_WAVE_MAGIC = 0b0000011100000111 + +class Stepper: + def __init__(self, A, B, C, D, T=1): + if not isinstance(T, machine.Timer): + T = machine.Timer(T) + self._timer = T + l = [] + for p in (A, B, C, D): + if not isinstance(p, machine.Pin): + p = machine.Pin(p, machine.Pin.OUT) + l.append(p) + self._pins = l + self._phase = 0 + self._stop() + self._run_remaining = 0 + + def _stop(self): + [p.off() for p in self._pins] + + # Note: This is called on an interrupt on some platforms, so it must not use the heap + def _callback(self, t): + if self._run_remaining != 0: + direction = 1 if self._run_remaining > 0 else -1 + self._phase = (self._phase + direction) % 8 + wave = _WAVE_MAGIC >> self._phase + for i in range(4): + self._pins[i].value((wave >> (i*2)) & 1) + self._run_remaining -= direction else: - self.fill_vrect(x, y, w, h, color) - - def fill_polygon(self, sides, x0, y0, r, color, rotate=0): - """Draw a filled n-sided regular polygon. - - Args: - sides (int): Number of polygon sides. - x0, y0 (int): Coordinates of center point. - r (int): Radius. - color (int): RGB565 color value. - rotate (Optional float): Rotation in degrees relative to origin. - Note: - The center point is the center of the x0,y0 pixel. - Since pixels are not divisible, the radius is integer rounded - up to complete on a full pixel. Therefore diameter = 2 x r + 1. - """ - # Determine side coordinates - coords = [] - theta = radians(rotate) - n = sides + 1 - for s in range(n): - t = 2.0 * pi * s / sides + theta - coords.append([int(r * cos(t) + x0), int(r * sin(t) + y0)]) - # Starting point - x1, y1 = coords[0] - # Minimum Maximum X dict - xdict = {y1: [x1, x1]} - # Iterate through coordinates - for row in coords[1:]: - x2, y2 = row - xprev, yprev = x2, y2 - # Calculate perimeter - # Check for horizontal side - if y1 == y2: - if x1 > x2: - x1, x2 = x2, x1 - if y1 in xdict: - xdict[y1] = [min(x1, xdict[y1][0]), max(x2, xdict[y1][1])] - else: - xdict[y1] = [x1, x2] - x1, y1 = xprev, yprev - continue - # Non horizontal side - # Changes in x, y - dx = x2 - x1 - dy = y2 - y1 - # Determine how steep the line is - is_steep = abs(dy) > abs(dx) - # Rotate line - if is_steep: - x1, y1 = y1, x1 - x2, y2 = y2, x2 - # Swap start and end points if necessary - if x1 > x2: - x1, x2 = x2, x1 - y1, y2 = y2, y1 - # Recalculate differentials - dx = x2 - x1 - dy = y2 - y1 - # Calculate error - error = dx >> 1 - ystep = 1 if y1 < y2 else -1 - y = y1 - # Calcualte minimum and maximum x values - for x in range(x1, x2 + 1): - if is_steep: - if x in xdict: - xdict[x] = [min(y, xdict[x][0]), max(y, xdict[x][1])] - else: - xdict[x] = [y, y] - else: - if y in xdict: - xdict[y] = [min(x, xdict[y][0]), max(x, xdict[y][1])] - else: - xdict[y] = [x, x] - error -= abs(dy) - if error < 0: - y += ystep - error += dx - x1, y1 = xprev, yprev - # Fill polygon - for y, x in xdict.items(): - self.draw_hline(x[0], y, x[1] - x[0] + 2, color) - - def fill_vrect(self, x, y, w, h, color): - """Draw a filled rectangle (optimized for vertical drawing). - - Args: - x (int): Starting X position. - y (int): Starting Y position. - w (int): Width of rectangle. - h (int): Height of rectangle. - color (int): RGB565 color value. - """ - if self.is_off_grid(x, y, x + w - 1, y + h - 1): - return - chunk_width = 1024 // h - chunk_count, remainder = divmod(w, chunk_width) - chunk_size = chunk_width * h - chunk_x = x - if chunk_count: - buf = color.to_bytes(2, 'big') * chunk_size - for c in range(0, chunk_count): - self.block(chunk_x, y, - chunk_x + chunk_width - 1, y + h - 1, - buf) - chunk_x += chunk_width - - if remainder: - buf = color.to_bytes(2, 'big') * remainder * h - self.block(chunk_x, y, - chunk_x + remainder - 1, y + h - 1, - buf) - - def is_off_grid(self, xmin, ymin, xmax, ymax): - """Check if coordinates extend past display boundaries. - - Args: - xmin (int): Minimum horizontal pixel. - ymin (int): Minimum vertical pixel. - xmax (int): Maximum horizontal pixel. - ymax (int): Maximum vertical pixel. - Returns: - boolean: False = Coordinates OK, True = Error. - """ - if xmin < 0: - print('x-coordinate: {0} below minimum of 0.'.format(xmin)) - return True - if ymin < 0: - print('y-coordinate: {0} below minimum of 0.'.format(ymin)) - return True - if xmax >= self.width: - print('x-coordinate: {0} above maximum of {1}.'.format( - xmax, self.width - 1)) - return True - if ymax >= self.height: - print('y-coordinate: {0} above maximum of {1}.'.format( - ymax, self.height - 1)) - return True - return False - - def load_sprite(self, path, w, h): - """Load sprite image. - - Args: - path (string): Image file path. - w (int): Width of image. - h (int): Height of image. - Notes: - w x h cannot exceed 2048 - """ - buf_size = w * h * 2 - with open(path, "rb") as f: - return f.read(buf_size) - - def reset_cpy(self): - """Perform reset: Low=initialization, High=normal operation. - - Notes: CircuitPython implemntation - """ - self.rst.value = False - sleep(.05) - self.rst.value = True - sleep(.05) - - def reset_mpy(self): - """Perform reset: Low=initialization, High=normal operation. - - Notes: MicroPython implemntation - """ - self.rst(0) - sleep(.05) - self.rst(1) - sleep(.05) - - def scroll(self, y): - """Scroll display vertically. - - Args: - y (int): Number of pixels to scroll display. - """ - self.write_cmd(self.VSCRSADD, y >> 8, y & 0xFF) - - def set_scroll(self, top, bottom): - """Set the height of the top and bottom scroll margins. - - Args: - top (int): Height of top scroll margin - bottom (int): Height of bottom scroll margin - """ - if top + bottom <= self.height: - middle = self.height - (top + bottom) - print(top, middle, bottom) - self.write_cmd(self.VSCRDEF, - top >> 8, - top & 0xFF, - middle >> 8, - middle & 0xFF, - bottom >> 8, - bottom & 0xFF) - - def sleep(self, enable=True): - """Enters or exits sleep mode. - - Args: - enable (bool): True (default)=Enter sleep mode, False=Exit sleep - """ - if enable: - self.write_cmd(self.SLPIN) + self._timer.deinit() + self._stop() + + def run(self, count, delay=0.001): + tick_hz=1000000 + period = int(delay*tick_hz) + if period < 500: + period = 500 + self._run_remaining += count + if self._run_remaining != 0: + self._timer.init(period=period, tick_hz=tick_hz, + mode=machine.Timer.PERIODIC, callback=self._callback) else: - self.write_cmd(self.SLPOUT) - - - def write_cmd_mpy(self, command, *args): - """Write command to OLED (MicroPython). - - Args: - command (byte): ILI9341 command code. - *args (optional bytes): Data to transmit. - """ - self.dc(0) - self.cs(0) - self.spi.write(bytearray([command])) - self.cs(1) - # Handle any passed data - if len(args) > 0: - self.write_data(bytearray(args)) - - def write_cmd_cpy(self, command, *args): - """Write command to OLED (CircuitPython). - - Args: - command (byte): ILI9341 command code. - *args (optional bytes): Data to transmit. - """ - self.dc.value = False - self.cs.value = False - # Confirm SPI locked before writing - while not self.spi.try_lock(): - pass - self.spi.write(bytearray([command])) - self.spi.unlock() - self.cs.value = True - # Handle any passed data - if len(args) > 0: - self.write_data(bytearray(args)) - - def write_data_mpy(self, data): - """Write data to OLED (MicroPython). - - Args: - data (bytes): Data to transmit. - """ - self.dc(1) - self.cs(0) - self.spi.write(data) - self.cs(1) - - def write_data_cpy(self, data): - """Write data to OLED (CircuitPython). - - Args: - data (bytes): Data to transmit. - """ - self.dc.value = True - self.cs.value = False - # Confirm SPI locked before writing - while not self.spi.try_lock(): - pass - self.spi.write(data) - self.spi.unlock() - self.cs.value = True - - -#xxx \ No newline at end of file + self._timer.deinit() + self._stop() + + def stop(self): + remaining = self._run_remaining + self._run_remaining = 0 + self._timer.deinit() + self._stop() + return remaining + + @property + def is_running(self): + return self._run_remaining != 0 diff --git a/examples/test.py b/examples/test.py index d130bc9..62f1fe9 100644 --- a/examples/test.py +++ b/examples/test.py @@ -3,12 +3,8 @@ """ from time import sleep -from machine import Pin -pin = Pin(2, Pin.OUT) + print("start OK \r\n") for i in range(0, 10): - print('duh') - pin.on() - sleep(0.1) - pin.off() + print('...') sleep(0.1) diff --git a/micropython.js b/micropython.js index 96424b3..d24820b 100644 --- a/micropython.js +++ b/micropython.js @@ -28,6 +28,7 @@ class MicroPythonBoard { constructor() { this.port = null this.serial = null + this.reject_run = null } list_ports() { @@ -113,7 +114,10 @@ class MicroPythonBoard { } async get_prompt() { - const out = await this.write_and_read_until(`\r\x02\x03`, '\r\n>>>') + await sleep(100) + await this.stop() + await sleep(100) + const out = await this.write_and_read_until(`\r\x03\x02`, '\r\n>>>') return Promise.resolve(out) } @@ -147,10 +151,21 @@ class MicroPythonBoard { async run(code, data_consumer) { data_consumer = data_consumer || function() {} - await this.enter_raw_repl() - const output = await this.exec_raw(code || '#', data_consumer) - await this.exit_raw_repl() - return Promise.resolve(output) + return new Promise(async (resolve, reject) => { + if (this.reject_run) { + this.reject_run('re run') + this.reject_run = null + } + this.reject_run = reject + try { + await this.enter_raw_repl() + const output = await this.exec_raw(code || '#', data_consumer) + await this.exit_raw_repl() + return resolve(output) + } catch (e) { + reject(e) + } + }) } async eval(k) { @@ -159,12 +174,20 @@ class MicroPythonBoard { } async stop() { + if (this.reject_run) { + this.reject_run('pre stop') + this.reject_run = null + } // Dismiss any data with ctrl-C await this.serial.write(Buffer.from(`\x03`)) return Promise.resolve() } async reset() { + if (this.reject_run) { + this.reject_run('pre reset') + this.reject_run = null + } // Dismiss any data with ctrl-C await this.serial.write(Buffer.from(`\x03`)) // Soft reboot diff --git a/test.js b/test.js index dc6f76e..e82749f 100644 --- a/test.js +++ b/test.js @@ -1,6 +1,6 @@ const Board = require('./micropython.js') - -let out +const assert = require('assert') +const fs = require('fs') function sleep(millis) { return new Promise((resolve, reject) => { @@ -10,88 +10,244 @@ function sleep(millis) { }) } - -async function main() { +async function before(port) { const board = new Board() - await board.open(process.env.PORT || '/dev/ttyACM0') - console.log('connected') - - await board.get_prompt() - console.log('has prompt') - - const fn = async () => { - let o = await board.serial.read() - console.log('DATA', o.toString()) - } - board.serial.on('readable', fn) - await board.eval('pri') - await sleep(10) - await board.eval('nt(1') - await sleep(10) - await board.eval('23)') - await board.eval('\r') - await sleep(10) - board.serial.removeListener('readable', fn) - - await board.enter_raw_repl() - console.log('in raw repl') - - const code = `from time import sleep\nfor i in range(0, 10):\n print('.')\n sleep(0.1)\n` - let i = 0 - out = await board.exec_raw(code, async (d) => { - console.log('->', d) - // i += 1; if (i > 3) await board.stop() - }) - console.log('executed', out) - - await board.exit_raw_repl() - console.log('out raw repl') - - out = await board.fs_exists('boot.py') - console.log('boot.py exists', out) - out = await board.fs_exists('this_is_not_a_file.py') - console.log('nope.py exists', out) - - out = await board.fs_ls('./') - console.log('root files', out) - out = await board.fs_ls('./lib') - console.log('lib files', out) - - out = await board.fs_ils('./') - console.log('root files', out) - out = await board.fs_ils('./lib') - console.log('lib files', out) - - out = await board.fs_put( - './examples/test.py', 'test.py', (d) => console.log('progress', d) - ) - console.log('send file to board', out) + await board.open(port) + return Promise.resolve(board) +} - out = await board.fs_cat('test.py') - console.log('get test.py content', out) +async function after(board) { + await board.close() + return Promise.resolve() +} - out = await board.fs_save( - '# overrides test file', 'test.py', (d) => console.log('progress', d) - ) - console.log('save test.py content', out) +const testCases = { + "get prompt": async (board) => { + // From unknown state + let output = await board.get_prompt() + assert.notEqual(output.indexOf('>>>'), -1) + // From raw repl + await board.enter_raw_repl() + output = await board.get_prompt() + assert.notEqual(output.indexOf('>>>'), -1) + // Running code + board.run(`from time import sleep\nwhile True:\n print('.')\nsleep(1)\n`) + .catch(() => null) + output = await board.get_prompt() + assert.notEqual(output.indexOf('>>>'), -1) + + return Promise.resolve() + }, + "real time repl": async (board) => { + let output = '' + const fn = async () => { + let o = await board.serial.read() + output += o.toString() + } + board.serial.on('readable', fn) + await board.eval('pri') + await sleep(10) + await board.eval('nt(1') + await sleep(10) + await board.eval('23)') + await board.eval('\r') + await sleep(10) + board.serial.removeListener('readable', fn) + assert(output, 'print(123)\r\n123\r\n>>> ') + return Promise.resolve() + }, + "enter raw repl": async (board) => { + let output = await board.enter_raw_repl() + assert.notEqual(output.indexOf('raw REPL; CTRL-B to exit'), -1) + // reenter raw repl + output = await board.enter_raw_repl() + assert.notEqual(output.indexOf('raw REPL; CTRL-B to exit'), -1) + + return Promise.resolve() + }, + "exit raw repl": async (board) => { + let output = await board.exit_raw_repl() + assert.notEqual(output.indexOf('>>>'), -1) + + await board.enter_raw_repl() + output = await board.exit_raw_repl() + assert.notEqual(output.indexOf('>>>'), -1) + + return Promise.resolve() + }, + "execute raw small": async (board) => { + await board.enter_raw_repl() + const output = await board.exec_raw('print(123)') + await board.exit_raw_repl() + assert.equal(output, 'OK123\r\n\x04\x04>') + return Promise.resolve() + }, + "execute raw big": async (board) => { + let bigFile = fs.readFileSync('./examples/big_file.py') + await board.enter_raw_repl() + const output = await board.exec_raw(bigFile.toString()) + assert.equal(output, 'OK\x04\x04>') + await board.exit_raw_repl() + return Promise.resolve() + }, + "run small code": async (board) => { + const output = await board.run('print(123)') + assert.equal(output, 'OK123\r\n\x04\x04>') + return Promise.resolve() + }, + "run big code": async (board) => { + let bigFile = fs.readFileSync('./examples/big_file.py') + const output = await board.run(bigFile.toString()) + assert.equal(output, 'OK\x04\x04>') + return Promise.resolve() + }, + "run code after stop": async (board) => { + board.run( + `from time import sleep\nfor i in range(0, 10):\n print('.')\n sleep(1)\n` + ).catch(e => { + // console.log('stopped') + }) + await (new Promise((r) => setTimeout(r, 100))) + await board.stop() + await (new Promise((r) => setTimeout(r, 100))) + + const output = await board.run('print(123)') + assert.equal(output, 'OK123\r\n\x04\x04>') + + }, + "run code after run": async (board) => { + board.run( + `from time import sleep\nfor i in range(0, 10):\n print('.')\n sleep(1)\n`, + // (o) => console.log('board run outputs', o) + ).catch(e => { + // console.log('stopped') + }) + await board.get_prompt() + const output = await board.run('print(123)') + assert.equal(output, 'OK123\r\n\x04\x04>') + + }, + // "foo": async (board) => Promise.reject() +} - out = await board.fs_cat('test.py') - console.log('get test.py content', out) +// SKIP LONG RUNNERS +delete testCases['execute raw big'] +delete testCases['run big code'] - out = await board.fs_rm('test.py') - console.log('removing test.py', out) +async function main() { + let errors = [] + const board = new Board() + let ports = await board.list_ports() + ports = ports.filter(p => p.vendorId && p.productId).map(p => p.path) + for (port of ports) { + console.log('') + console.log('πŸ”Š Running test for', port) + for (const [name, test] of Object.entries(testCases)) { + console.log('>', name) + const board = await before(port) + try { + const result = await test(board) + console.log("🟩 success") + } catch(e) { + console.log('πŸ’” error', e) + errors.push([port, name]) + } + await after(board) + } + } + console.log('') + console.log('Errors:', errors.length) + if (errors.length > 0) { + for (const [port, name] of errors) { + console.log(name, 'failed on', port) + } + } +} - out = await board.fs_mkdir('test_dir') - console.log('created test_dir', out) - out = await board.fs_ils() - console.log('files at ./', out) - out = await board.fs_rmdir('test_dir') - console.log('removed test_dir', out) - out = await board.fs_ils() - console.log('files at ./', out) - board.close() -} + // + // async function main() { + // const board = new Board() + // await board.open(process.env.PORT || '/dev/ttyACM0') + // console.log('connected') + // + // await board.get_prompt() + // console.log('has prompt') + // + // const fn = async () => { + // let o = await board.serial.read() + // console.log('DATA', o.toString()) + // } + // board.serial.on('readable', fn) + // await board.eval('pri') + // await sleep(10) + // await board.eval('nt(1') + // await sleep(10) + // await board.eval('23)') + // await board.eval('\r') + // await sleep(10) + // board.serial.removeListener('readable', fn) + // + // await board.enter_raw_repl() + // console.log('in raw repl') + // + // const code = `from time import sleep\nfor i in range(0, 10):\n print('.')\n sleep(0.1)\n` + // let i = 0 + // out = await board.exec_raw(code, async (d) => { + // console.log('->', d) + // // i += 1; if (i > 3) await board.stop() + // }) + // console.log('executed', out) + // + // await board.exit_raw_repl() + // console.log('out raw repl') + // + // out = await board.fs_exists('boot.py') + // console.log('boot.py exists', out) + // out = await board.fs_exists('this_is_not_a_file.py') + // console.log('nope.py exists', out) + // + // out = await board.fs_ls('./') + // console.log('root files', out) + // out = await board.fs_ls('./lib') + // console.log('lib files', out) + // + // out = await board.fs_ils('./') + // console.log('root files', out) + // out = await board.fs_ils('./lib') + // console.log('lib files', out) + // + // out = await board.fs_put( + // './examples/big_file.py', 'test.py', (d) => console.log('progress', d) + // ) + // console.log('send file to board', out) + // + // out = await board.fs_cat('test.py') + // console.log('get test.py content', out) + // + // out = await board.fs_save( + // '# overrides test file', 'test.py', (d) => console.log('progress', d) + // ) + // console.log('save test.py content', out) + // + // out = await board.fs_cat('test.py') + // console.log('get test.py content', out) + // + // out = await board.fs_rm('test.py') + // console.log('removing test.py', out) + // + // out = await board.fs_mkdir('test_dir') + // console.log('created test_dir', out) + // out = await board.fs_ils() + // console.log('files at ./', out) + // + // out = await board.fs_rmdir('test_dir') + // console.log('removed test_dir', out) + // out = await board.fs_ils() + // console.log('files at ./', out) + // + // board.close() + // } main() From dbc11bf5dc2b6d837155be3440f7842a10112491 Mon Sep 17 00:00:00 2001 From: Murilo Polese Date: Tue, 4 Jul 2023 19:10:57 +0200 Subject: [PATCH 02/23] Labeling reference implementation --- test.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test.js b/test.js index e82749f..74b49ce 100644 --- a/test.js +++ b/test.js @@ -166,7 +166,7 @@ async function main() { - // + // REFERENCE OF A FULL FEATURE TEST: // async function main() { // const board = new Board() // await board.open(process.env.PORT || '/dev/ttyACM0') From 24d1bfc4449002fac35e8e3462e07cff379670d8 Mon Sep 17 00:00:00 2001 From: Murilo Polese Date: Wed, 5 Jul 2023 11:49:44 +0200 Subject: [PATCH 03/23] Increasing timeout --- micropython.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/micropython.js b/micropython.js index d24820b..d561337 100644 --- a/micropython.js +++ b/micropython.js @@ -114,9 +114,9 @@ class MicroPythonBoard { } async get_prompt() { - await sleep(100) + await sleep(150) await this.stop() - await sleep(100) + await sleep(150) const out = await this.write_and_read_until(`\r\x03\x02`, '\r\n>>>') return Promise.resolve(out) } From 6aa0f4b6fd6999f222c6e270d00e3030a4269af8 Mon Sep 17 00:00:00 2001 From: Murilo Polese Date: Wed, 5 Jul 2023 11:49:57 +0200 Subject: [PATCH 04/23] Full feature test --- test.js | 181 +++++++++++++++++++++++++++++--------------------------- 1 file changed, 94 insertions(+), 87 deletions(-) diff --git a/test.js b/test.js index 74b49ce..ddff552 100644 --- a/test.js +++ b/test.js @@ -1,6 +1,7 @@ const Board = require('./micropython.js') const assert = require('assert') const fs = require('fs') +const path = require('path') function sleep(millis) { return new Promise((resolve, reject) => { @@ -102,6 +103,7 @@ const testCases = { return Promise.resolve() }, "run code after stop": async (board) => { + debugger board.run( `from time import sleep\nfor i in range(0, 10):\n print('.')\n sleep(1)\n` ).catch(e => { @@ -110,7 +112,7 @@ const testCases = { await (new Promise((r) => setTimeout(r, 100))) await board.stop() await (new Promise((r) => setTimeout(r, 100))) - + await board.get_prompt() const output = await board.run('print(123)') assert.equal(output, 'OK123\r\n\x04\x04>') @@ -127,12 +129,103 @@ const testCases = { assert.equal(output, 'OK123\r\n\x04\x04>') }, + "upload file": async (board) => { + const diskFilePath = path.resolve('./examples/test.py') + const serialFilePath = '/test.py' + await board.fs_put(diskFilePath, serialFilePath) + const diskFileContent = fs.readFileSync(diskFilePath) + const boardFileContent = await board.fs_cat(serialFilePath) + assert.equal(diskFileContent.toString(), boardFileContent) + await board.fs_rm(serialFilePath) + }, + "upload big file": async (board) => { + const diskFilePath = path.resolve('./examples/big_file.py') + const serialFilePath = '/big_file.py' + await board.fs_put( + diskFilePath, serialFilePath, + (e) => console.log('uploading big file', e) + ) + const diskFileContent = fs.readFileSync(diskFilePath) + const boardFileContent = await board.fs_cat(serialFilePath) + assert.equal(diskFileContent.toString(), boardFileContent) + await board.fs_rm(serialFilePath) + }, + "create folder": async (board) => { + const folderPath = '/test_folder' + await board.fs_mkdir(folderPath) + const ls = await board.fs_ils('/') + const folder = ls.find(f => f[0] === 'test_folder' && f[1] === 16384) + assert.ok(folder) + await board.fs_rmdir(folderPath) + }, + "list files and folders": async (board) => { + const file = [ 'test.py', 32768 ] + const test_folder = 'test_folder' + await board.fs_put(path.resolve('examples', file[0]), '/'+file[0]) + await board.fs_mkdir('/'+test_folder) + const ls = await board.fs_ils('/') + const createdFile = ls.find(f => f[0] === file[0] && f[1] === file[1]) + const createdFolder = ls.find(f => f[0] === test_folder && f[1] === 16384) + assert.ok(createdFile) + assert.ok(createdFolder) + }, + "check if file exists": async (board) => { + const filePath = '/test_exist'+parseInt(Math.random()*99999) + await board.fs_save('.', filePath) + const fileExists = await board.fs_exists(filePath) + assert(fileExists) + const fileDoesNotExist = await board.fs_exists('/xxx'+parseInt(Math.random()*99999)) + assert.ok(!fileDoesNotExist) + await board.fs_rm(filePath) + }, + "save file content": async (board) => { + const filePath = '/test.py' + const content = `.` + await board.fs_save(content, filePath) + const boardContent = await board.fs_cat(filePath) + assert.equal(content, boardContent) + }, + "save big file content": async (board) => { + const filePath = '/test.py' + const content = fs.readFileSync(path.resolve('./examples/big_file.py')) + await board.fs_save(content.toString(), filePath) + const boardContent = await board.fs_cat(filePath) + assert.equal(content, boardContent) + }, + "get file": async (board) => { + const filePath = '/test.py' + const content = `.` + await board.fs_save(content, filePath) + const boardContent = await board.fs_cat(filePath) + assert.equal(content, boardContent) + }, + "remove file": async (board) => { + const filePath = '/test_remove'+parseInt(Math.random()*99999) + await board.fs_save('.', filePath) + const fileExists = await board.fs_exists(filePath) + assert(fileExists) + await board.fs_rm(filePath) + const fileDoesNotExist = await board.fs_exists(filePath) + assert.ok(!fileDoesNotExist) + }, + "remove folder": async (board) => { + const folderPath = '/test_remove'+parseInt(Math.random()*99999) + await board.fs_mkdir(folderPath) + const ls = await board.fs_ils('/') + const foundFolder = ls.find(f => f[0] === folderPath.slice(1) && f[1] === 16384) + assert.ok(foundFolder) + await board.fs_rmdir(folderPath) + const notFoundFolder = ls.find(f => f[0] === folderPath.slice(1) && f[1] === 16384) + assert.ok(!notFoundFolder) + }, // "foo": async (board) => Promise.reject() } // SKIP LONG RUNNERS delete testCases['execute raw big'] delete testCases['run big code'] +delete testCases['upload big file'] +delete testCases['save big file content'] async function main() { let errors = [] @@ -164,90 +257,4 @@ async function main() { } } - - - // REFERENCE OF A FULL FEATURE TEST: - // async function main() { - // const board = new Board() - // await board.open(process.env.PORT || '/dev/ttyACM0') - // console.log('connected') - // - // await board.get_prompt() - // console.log('has prompt') - // - // const fn = async () => { - // let o = await board.serial.read() - // console.log('DATA', o.toString()) - // } - // board.serial.on('readable', fn) - // await board.eval('pri') - // await sleep(10) - // await board.eval('nt(1') - // await sleep(10) - // await board.eval('23)') - // await board.eval('\r') - // await sleep(10) - // board.serial.removeListener('readable', fn) - // - // await board.enter_raw_repl() - // console.log('in raw repl') - // - // const code = `from time import sleep\nfor i in range(0, 10):\n print('.')\n sleep(0.1)\n` - // let i = 0 - // out = await board.exec_raw(code, async (d) => { - // console.log('->', d) - // // i += 1; if (i > 3) await board.stop() - // }) - // console.log('executed', out) - // - // await board.exit_raw_repl() - // console.log('out raw repl') - // - // out = await board.fs_exists('boot.py') - // console.log('boot.py exists', out) - // out = await board.fs_exists('this_is_not_a_file.py') - // console.log('nope.py exists', out) - // - // out = await board.fs_ls('./') - // console.log('root files', out) - // out = await board.fs_ls('./lib') - // console.log('lib files', out) - // - // out = await board.fs_ils('./') - // console.log('root files', out) - // out = await board.fs_ils('./lib') - // console.log('lib files', out) - // - // out = await board.fs_put( - // './examples/big_file.py', 'test.py', (d) => console.log('progress', d) - // ) - // console.log('send file to board', out) - // - // out = await board.fs_cat('test.py') - // console.log('get test.py content', out) - // - // out = await board.fs_save( - // '# overrides test file', 'test.py', (d) => console.log('progress', d) - // ) - // console.log('save test.py content', out) - // - // out = await board.fs_cat('test.py') - // console.log('get test.py content', out) - // - // out = await board.fs_rm('test.py') - // console.log('removing test.py', out) - // - // out = await board.fs_mkdir('test_dir') - // console.log('created test_dir', out) - // out = await board.fs_ils() - // console.log('files at ./', out) - // - // out = await board.fs_rmdir('test_dir') - // console.log('removed test_dir', out) - // out = await board.fs_ils() - // console.log('files at ./', out) - // - // board.close() - // } - main() From d0bc67ff07b883f6e72beb0fe40c7d598075d559 Mon Sep 17 00:00:00 2001 From: Murilo Polese Date: Wed, 5 Jul 2023 12:15:21 +0200 Subject: [PATCH 05/23] Fix test List files again after removing folder --- test.js | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/test.js b/test.js index ddff552..7cb694c 100644 --- a/test.js +++ b/test.js @@ -211,10 +211,11 @@ const testCases = { "remove folder": async (board) => { const folderPath = '/test_remove'+parseInt(Math.random()*99999) await board.fs_mkdir(folderPath) - const ls = await board.fs_ils('/') + let ls = await board.fs_ils('/') const foundFolder = ls.find(f => f[0] === folderPath.slice(1) && f[1] === 16384) assert.ok(foundFolder) await board.fs_rmdir(folderPath) + ls = await board.fs_ils('/') const notFoundFolder = ls.find(f => f[0] === folderPath.slice(1) && f[1] === 16384) assert.ok(!notFoundFolder) }, From 3f0c7f63dcbc0785f9434a8284de38f850a118e5 Mon Sep 17 00:00:00 2001 From: Murilo Polese Date: Wed, 5 Jul 2023 12:15:30 +0200 Subject: [PATCH 06/23] WIP --- TESTS.md | 36 ++++++++++++++++++++++++++++++++++++ examples/test_out | 12 ++++++++++++ 2 files changed, 48 insertions(+) create mode 100644 TESTS.md create mode 100644 examples/test_out diff --git a/TESTS.md b/TESTS.md new file mode 100644 index 0000000..4656a26 --- /dev/null +++ b/TESTS.md @@ -0,0 +1,36 @@ +# Test cases: + +Get prompt +Write to repl and get data as you type / real time repl +Enter raw repl from prompt + +Get prompt when you are on raw repl +Re-enter raw repl + +Exit raw repl + +Execute raw and get final output +Execute raw and get data as it executes and the leave raw repl + +List files on root +Make directory +Put small file on root +Put big file on root +Get root file content + +Put small file on directory +Put big file on directory +Get directory file content + +Check for a file that exists on root folder +Check for a file that exists on folder + +Remove file +Remove folder + +Check for a file that doesn't exist + +Creates a file from string +Overrides a file content with string +Rename file on root +Rename file on folder diff --git a/examples/test_out b/examples/test_out new file mode 100644 index 0000000..2ccd7e4 --- /dev/null +++ b/examples/test_out @@ -0,0 +1,12 @@ +start OK + +... +... +... +... +... +... +... +... +... +... From d2649a855eeb1735e0ac8837a22d8231150e3d5f Mon Sep 17 00:00:00 2001 From: Murilo Polese Date: Thu, 6 Jul 2023 15:12:47 +0200 Subject: [PATCH 07/23] Increase sleep time when getting a prompt --- micropython.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/micropython.js b/micropython.js index e11b534..a0bb3a8 100644 --- a/micropython.js +++ b/micropython.js @@ -114,9 +114,9 @@ class MicroPythonBoard { } async get_prompt() { - await sleep(100) + await sleep(150) await this.stop() - await sleep(100) + await sleep(150) const out = await this.write_and_read_until(`\r\x03\x02`, '\r\n>>>') return Promise.resolve(out) } From dd922a80bfcc44b521b9912bcd79e8caf06fedff Mon Sep 17 00:00:00 2001 From: Murilo Polese Date: Thu, 6 Jul 2023 15:13:06 +0200 Subject: [PATCH 08/23] Fixing line breaks --- micropython.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/micropython.js b/micropython.js index a0bb3a8..86d1133 100644 --- a/micropython.js +++ b/micropython.js @@ -257,7 +257,7 @@ class MicroPythonBoard { ) await this.exit_raw_repl() output = extract(output) - return Promise.resolve(output) + return Promise.resolve(fixLineBreak(output)) } return Promise.reject(new Error(`Path to file was not specified`)) } From 5d8619be1fe16e5f2c75d313135749b51d412236 Mon Sep 17 00:00:00 2001 From: Murilo Polese Date: Thu, 6 Jul 2023 15:13:26 +0200 Subject: [PATCH 09/23] Skip trimming when extracting data from serial --- micropython.js | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/micropython.js b/micropython.js index 86d1133..5916aec 100644 --- a/micropython.js +++ b/micropython.js @@ -19,9 +19,10 @@ function fixLineBreak(str) { function extract(out) { /* * Message ($msg) will come out following this template: - * "OK${msg}\x04\x04>" + * "OK${msg}\x04${err}\x04>" + * TODO: consider error handling */ - return out.slice(2, -3).trim() + return out.slice(2, -3) } class MicroPythonBoard { @@ -206,7 +207,7 @@ class MicroPythonBoard { await this.enter_raw_repl() let output = await this.exec_raw(command) await this.exit_raw_repl() - const exists = extract(output) == '1' + const exists = output[2] == '1' return Promise.resolve(exists) } From 1aaaf5df407411e4e5acee0d67b04e3577143b83 Mon Sep 17 00:00:00 2001 From: Murilo Polese Date: Thu, 6 Jul 2023 15:15:11 +0200 Subject: [PATCH 10/23] Bump version --- package-lock.json | 4 ++-- package.json | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/package-lock.json b/package-lock.json index f8e6b1c..08f69b3 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "micropython.js", - "version": "1.3.5", + "version": "1.4.2", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "micropython.js", - "version": "1.3.5", + "version": "1.4.2", "dependencies": { "serialport": "^10.4.0" }, diff --git a/package.json b/package.json index cbcf564..adcbe36 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "micropython.js", - "version": "1.4.1", + "version": "1.4.2", "description": "Interpretation of pyboard.py in javascript", "main": "micropython.js", "scripts": { From b38f8ff33f393f9b0dfe1dbeac3089d0ff59ffc8 Mon Sep 17 00:00:00 2001 From: Murilo Polese Date: Fri, 12 Jan 2024 12:17:54 +0100 Subject: [PATCH 11/23] Check if serial object exists before calling it --- micropython.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/micropython.js b/micropython.js index 5916aec..1317687 100644 --- a/micropython.js +++ b/micropython.js @@ -69,7 +69,7 @@ class MicroPythonBoard { } close() { - if (this.serial.isOpen) { + if (this.serial && this.serial.isOpen) { return this.serial.close() } else { return Promise.resolve() From a8db719e0cb6bc11d0ed776129a6c3d88f5cab51 Mon Sep 17 00:00:00 2001 From: Murilo Polese Date: Fri, 12 Jan 2024 12:23:00 +0100 Subject: [PATCH 12/23] Bump version --- package-lock.json | 4 ++-- package.json | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/package-lock.json b/package-lock.json index 08f69b3..623bd6c 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "micropython.js", - "version": "1.4.2", + "version": "1.4.3", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "micropython.js", - "version": "1.4.2", + "version": "1.4.3", "dependencies": { "serialport": "^10.4.0" }, diff --git a/package.json b/package.json index adcbe36..b46b237 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "micropython.js", - "version": "1.4.2", + "version": "1.4.3", "description": "Interpretation of pyboard.py in javascript", "main": "micropython.js", "scripts": { From ec191df8809c6da463fcc195966afb4fdf872263 Mon Sep 17 00:00:00 2001 From: ubi de feo Date: Sun, 10 Mar 2024 15:02:23 +0100 Subject: [PATCH 13/23] Implemented binary transfer to handle emoji and other non-ascii characters. Signed-off-by: ubi de feo --- examples/07_save_file.js | 1 + examples/big_file.py | 2 +- examples/test.py | 2 +- micropython.js | 35 +++++++++++++---------------------- 4 files changed, 16 insertions(+), 24 deletions(-) diff --git a/examples/07_save_file.js b/examples/07_save_file.js index c0f806e..5729b0a 100644 --- a/examples/07_save_file.js +++ b/examples/07_save_file.js @@ -4,6 +4,7 @@ let content = ` """ Test """ +# Emoji TEST πŸ’©πŸ€―πŸ«ΆπŸΌ from time import sleep from machine import Pin diff --git a/examples/big_file.py b/examples/big_file.py index f9affdf..8994a6c 100644 --- a/examples/big_file.py +++ b/examples/big_file.py @@ -36,7 +36,7 @@ # When the following number is sampled at four consecutive # even-numbered bits it will have two bits set, but sampling at four # consecutive odd-numbered bits will only yield one bit set. - +# 😌πŸ₯²πŸ™πŸΌπŸ’© _WAVE_MAGIC = 0b0000011100000111 class Stepper: diff --git a/examples/test.py b/examples/test.py index 62f1fe9..a110c7e 100644 --- a/examples/test.py +++ b/examples/test.py @@ -1,7 +1,7 @@ """ Test """ - +# πŸ™πŸΌ from time import sleep print("start OK \r\n") diff --git a/micropython.js b/micropython.js index 1317687..d8959da 100644 --- a/micropython.js +++ b/micropython.js @@ -266,22 +266,17 @@ class MicroPythonBoard { async fs_put(src, dest, data_consumer) { data_consumer = data_consumer || function() {} if (src && dest) { - const contentBuffer = fs.readFileSync(path.resolve(src)) - let contentString = contentBuffer.toString() - contentString = fixLineBreak(contentString) - const hexArray = contentString.split('').map( - c => c.charCodeAt(0).toString(16).padStart(2, '0') - ) + const fileContent = fs.readFileSync(path.resolve(src), 'binary') + const contentBuffer = Buffer.from(fileContent, 'binary') let out = '' out += await this.enter_raw_repl() - out += await this.exec_raw(`f=open('${dest}','w')\nw=f.write`) + out += await this.exec_raw(`f=open('${dest}','wb')\nw=f.write`) const chunkSize = 48 - for (let i = 0; i < hexArray.length; i+= chunkSize) { - let slice = hexArray.slice(i, i+chunkSize) - let bytes = slice.map(h => `0x${h}`) - let line = `w(bytes([${bytes.join(',')}]))` + for (let i = 0; i < contentBuffer.length; i+= chunkSize) { + let slice = Uint8Array.from(contentBuffer.subarray(i, i+chunkSize)) + let line = `w(bytes([${slice}]))` out += await this.exec_raw(line) - data_consumer( parseInt((i / hexArray.length) * 100) + '%') + data_consumer( parseInt((i / contentBuffer.length) * 100) + '%') } out += await this.exec_raw(`f.close()`) out += await this.exit_raw_repl() @@ -293,20 +288,16 @@ class MicroPythonBoard { async fs_save(content, dest, data_consumer) { data_consumer = data_consumer || function() {} if (content && dest) { - let contentString = fixLineBreak(content) - const hexArray = contentString.split('').map( - c => c.charCodeAt(0).toString(16).padStart(2, '0') - ) + const contentBuffer = Buffer.from(content, 'utf-8') let out = '' out += await this.enter_raw_repl() - out += await this.exec_raw(`f=open('${dest}','w')\nw=f.write`) + out += await this.exec_raw(`f=open('${dest}','wb')\nw=f.write`) const chunkSize = 48 - for (let i = 0; i < hexArray.length; i+= chunkSize) { - let slice = hexArray.slice(i, i+chunkSize) - let bytes = slice.map(h => `0x${h}`) - let line = `w(bytes([${bytes.join(',')}]))` + for (let i = 0; i < contentBuffer.length; i+= chunkSize) { + let slice = Uint8Array.from(contentBuffer.subarray(i, i+chunkSize)) + let line = `w(bytes([${slice}]))` out += await this.exec_raw(line) - data_consumer( parseInt((i / hexArray.length) * 100) + '%') + data_consumer( parseInt((i / contentBuffer.length) * 100) + '%') } out += await this.exec_raw(`f.close()`) out += await this.exit_raw_repl() From 1200053178d9be82ec47cbde26d7f1f7fece6f85 Mon Sep 17 00:00:00 2001 From: ubi de feo Date: Sun, 10 Mar 2024 15:24:28 +0100 Subject: [PATCH 14/23] Version update to 1.4.4 --- package-lock.json | 4 ++-- package.json | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/package-lock.json b/package-lock.json index 623bd6c..beab231 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "micropython.js", - "version": "1.4.3", + "version": "1.4.4", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "micropython.js", - "version": "1.4.3", + "version": "1.4.4", "dependencies": { "serialport": "^10.4.0" }, diff --git a/package.json b/package.json index b46b237..8f1e408 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "micropython.js", - "version": "1.4.3", + "version": "1.4.4", "description": "Interpretation of pyboard.py in javascript", "main": "micropython.js", "scripts": { From f3e2c58e2138b6fce8f7784f2e65791a8aca4c7b Mon Sep 17 00:00:00 2001 From: ubi de feo Date: Thu, 14 Nov 2024 07:55:05 +0100 Subject: [PATCH 15/23] Tentative: Implemented binary output from fs_cat. Signed-off-by: ubi de feo --- micropython.js | 25 ++++++++++++++++++++++--- 1 file changed, 22 insertions(+), 3 deletions(-) diff --git a/micropython.js b/micropython.js index d8959da..bf4b0ca 100644 --- a/micropython.js +++ b/micropython.js @@ -25,6 +25,12 @@ function extract(out) { return out.slice(2, -3) } +function extractBytes(out, cut_before = 2, cut_after = 3) { + bytes = out.slice(cut_before, -cut_after) + bytes = bytes.split(',').map(Number) + return bytes +} + class MicroPythonBoard { constructor() { this.port = null @@ -254,15 +260,28 @@ class MicroPythonBoard { if (filePath) { await this.enter_raw_repl() let output = await this.exec_raw( - `with open('${filePath}','r') as f:\n while 1:\n b=f.read(256)\n if not b:break\n print(b,end='')` + `with open('${filePath}','rb') as f:\n while 1:\n b=f.read(256)\n if not b:break\n print(",".join(str(e) for e in b),end=',')` ) await this.exit_raw_repl() - output = extract(output) - return Promise.resolve(fixLineBreak(output)) + output = extractBytes(output, 2, 4) + return Promise.resolve((output)) } return Promise.reject(new Error(`Path to file was not specified`)) } + // async fs_cat(filePath) { + // if (filePath) { + // await this.enter_raw_repl() + // let output = await this.exec_raw( + // `with open('${filePath}','r') as f:\n while 1:\n b=f.read(256)\n if not b:break\n print(b,end='')` + // ) + // await this.exit_raw_repl() + // output = extract(output) + // return Promise.resolve(fixLineBreak(output)) + // } + // return Promise.reject(new Error(`Path to file was not specified`)) + // } + async fs_put(src, dest, data_consumer) { data_consumer = data_consumer || function() {} if (src && dest) { From ab3070fed89a86ea4fb8374f34bfa7698ef66ea9 Mon Sep 17 00:00:00 2001 From: ubi de feo Date: Thu, 14 Nov 2024 13:31:22 +0100 Subject: [PATCH 16/23] Restored the text-based fs_cat method and implemented fs_cat_binary. Signed-off-by: ubi de feo --- micropython.js | 26 +++++++++++++------------- 1 file changed, 13 insertions(+), 13 deletions(-) diff --git a/micropython.js b/micropython.js index bf4b0ca..173f67b 100644 --- a/micropython.js +++ b/micropython.js @@ -256,7 +256,7 @@ class MicroPythonBoard { return Promise.resolve(files) } - async fs_cat(filePath) { + async fs_cat_binary(filePath) { if (filePath) { await this.enter_raw_repl() let output = await this.exec_raw( @@ -269,18 +269,18 @@ class MicroPythonBoard { return Promise.reject(new Error(`Path to file was not specified`)) } - // async fs_cat(filePath) { - // if (filePath) { - // await this.enter_raw_repl() - // let output = await this.exec_raw( - // `with open('${filePath}','r') as f:\n while 1:\n b=f.read(256)\n if not b:break\n print(b,end='')` - // ) - // await this.exit_raw_repl() - // output = extract(output) - // return Promise.resolve(fixLineBreak(output)) - // } - // return Promise.reject(new Error(`Path to file was not specified`)) - // } + async fs_cat(filePath) { + if (filePath) { + await this.enter_raw_repl() + let output = await this.exec_raw( + `with open('${filePath}','r') as f:\n while 1:\n b=f.read(256)\n if not b:break\n print(b,end='')` + ) + await this.exit_raw_repl() + output = extract(output) + return Promise.resolve(fixLineBreak(output)) + } + return Promise.reject(new Error(`Path to file was not specified`)) + } async fs_put(src, dest, data_consumer) { data_consumer = data_consumer || function() {} From 3518a3dfde4a92369d7abc42a51894f20584ca63 Mon Sep 17 00:00:00 2001 From: ubi de feo Date: Thu, 14 Nov 2024 15:08:33 +0100 Subject: [PATCH 17/23] Bump version to 1.5.0 Signed-off-by: ubi de feo --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 8f1e408..e554810 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "micropython.js", - "version": "1.4.4", + "version": "1.5.0", "description": "Interpretation of pyboard.py in javascript", "main": "micropython.js", "scripts": { From 1db1b60361a8505ab23d9e2694d5e0a0b7570005 Mon Sep 17 00:00:00 2001 From: ubi de feo Date: Fri, 15 Nov 2024 07:15:18 +0100 Subject: [PATCH 18/23] Updated cli.js getfile method to leverage binary transfer. Signed-off-by: ubi de feo --- cli.js | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/cli.js b/cli.js index 124d37a..e7c7c7a 100755 --- a/cli.js +++ b/cli.js @@ -140,7 +140,8 @@ const getFile = (args, port, dataConsumer) => { return board.open(port) .then(async () => { try { - let output = await board.fs_cat(boardFilename, consumer) + let output = await board.fs_cat_binary(boardFilename, consumer) + output = Buffer.from(output); fs.writeFileSync(diskFilename, output) log('output') log(output) From 63a140244f5f88438c92d0893f5aa2f660f3f2c9 Mon Sep 17 00:00:00 2001 From: ubi de feo Date: Sat, 7 Dec 2024 08:14:11 +0100 Subject: [PATCH 19/23] Refactored commands. Delete f,w,l leftover symbols. Signed-off-by: ubi de feo --- cli.js | 2 +- micropython.js | 21 +++++++++++++++------ 2 files changed, 16 insertions(+), 7 deletions(-) diff --git a/cli.js b/cli.js index e7c7c7a..7a13be2 100755 --- a/cli.js +++ b/cli.js @@ -82,7 +82,7 @@ const executeString = (args, port, dataConsumer) => { const code = args[0] || '' return board.open(port) .then(() => board.enter_raw_repl()) - .then(() => board.exec_raw({ command: code, data_consumer: dataConsumer })) + .then(() => board.exec_raw(code, dataConsumer)) .then(async (out) => { await board.exit_raw_repl() await board.close() diff --git a/micropython.js b/micropython.js index 173f67b..a6418aa 100644 --- a/micropython.js +++ b/micropython.js @@ -210,6 +210,7 @@ class MicroPythonBoard { command += ` print(1)\n` command += `except OSError:\n` command += ` print(0)\n` + command += `del f\n` await this.enter_raw_repl() let output = await this.exec_raw(command) await this.exit_raw_repl() @@ -245,6 +246,7 @@ class MicroPythonBoard { command += ` print(l)\n` command += `except OSError:\n` command += ` print([])\n` + command += `del l\n` await this.enter_raw_repl() let output = await this.exec_raw(command) await this.exit_raw_repl() @@ -259,9 +261,16 @@ class MicroPythonBoard { async fs_cat_binary(filePath) { if (filePath) { await this.enter_raw_repl() - let output = await this.exec_raw( - `with open('${filePath}','rb') as f:\n while 1:\n b=f.read(256)\n if not b:break\n print(",".join(str(e) for e in b),end=',')` - ) + const chunkSize = 256 + let command = `with open('${filePath}','rb') as f:\n` + command += ` while 1:\n` + command += ` b=f.read(${chunkSize})\n` + command += ` if not b:break\n` + command += ` print(",".join(str(e) for e in b),end=',')\n` + command += `del f\n` + command += `del b\n` + let output = await this.exec_raw(command) + await this.exit_raw_repl() output = extractBytes(output, 2, 4) return Promise.resolve((output)) @@ -273,7 +282,7 @@ class MicroPythonBoard { if (filePath) { await this.enter_raw_repl() let output = await this.exec_raw( - `with open('${filePath}','r') as f:\n while 1:\n b=f.read(256)\n if not b:break\n print(b,end='')` + `with open('${filePath}','r') as f:\n while 1:\n b=f.read(256)\n if not b:break\n print(b,end='')\ndel f\ndel b\n` ) await this.exit_raw_repl() output = extract(output) @@ -297,7 +306,7 @@ class MicroPythonBoard { out += await this.exec_raw(line) data_consumer( parseInt((i / contentBuffer.length) * 100) + '%') } - out += await this.exec_raw(`f.close()`) + out += await this.exec_raw(`f.close()\ndel f\ndel w\n`) out += await this.exit_raw_repl() return Promise.resolve(out) } @@ -318,7 +327,7 @@ class MicroPythonBoard { out += await this.exec_raw(line) data_consumer( parseInt((i / contentBuffer.length) * 100) + '%') } - out += await this.exec_raw(`f.close()`) + out += await this.exec_raw(`f.close()\ndel f\ndel w\n`) out += await this.exit_raw_repl() return Promise.resolve(out) } else { From 2c26a586ec935f6b7f3406f87a21a45b4a4254b3 Mon Sep 17 00:00:00 2001 From: ubi de feo Date: Mon, 9 Dec 2024 12:13:17 +0100 Subject: [PATCH 20/23] Removed leftover 'file' and renamed variable to 'f'. Signed-off-by: ubi de feo --- micropython.js | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/micropython.js b/micropython.js index a6418aa..5bec72b 100644 --- a/micropython.js +++ b/micropython.js @@ -241,12 +241,13 @@ class MicroPythonBoard { let command = `import uos\n` command += `try:\n` command += ` l=[]\n` - command += ` for file in uos.ilistdir("${folderPath}"):\n` - command += ` l.append(list(file))\n` + command += ` for f in uos.ilistdir("${folderPath}"):\n` + command += ` l.append(list(f))\n` command += ` print(l)\n` command += `except OSError:\n` command += ` print([])\n` command += `del l\n` + command += `del f\n` await this.enter_raw_repl() let output = await this.exec_raw(command) await this.exit_raw_repl() From 050d300875153bc551dcfb16b6800cb3ca3a61a4 Mon Sep 17 00:00:00 2001 From: ubi de feo Date: Mon, 9 Dec 2024 12:23:01 +0100 Subject: [PATCH 21/23] Updated package version. Signed-off-by: ubi de feo --- package-lock.json | 4 ++-- package.json | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/package-lock.json b/package-lock.json index beab231..b5256b3 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "micropython.js", - "version": "1.4.4", + "version": "1.5.1", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "micropython.js", - "version": "1.4.4", + "version": "1.5.1", "dependencies": { "serialport": "^10.4.0" }, diff --git a/package.json b/package.json index e554810..62cae7b 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "micropython.js", - "version": "1.5.0", + "version": "1.5.1", "description": "Interpretation of pyboard.py in javascript", "main": "micropython.js", "scripts": { From bc4cc1c3a8e8d3a20f65b407e478804613f7d873 Mon Sep 17 00:00:00 2001 From: ubi de feo Date: Tue, 10 Dec 2024 07:53:51 +0100 Subject: [PATCH 22/23] Added contribution guidelines. Signed-off-by: ubi de feo --- CONTRIBUTING.md | 71 +++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 71 insertions(+) create mode 100644 CONTRIBUTING.md diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md new file mode 100644 index 0000000..70880ca --- /dev/null +++ b/CONTRIBUTING.md @@ -0,0 +1,71 @@ +# Contributing to [Project Name] + +This document provides guidelines and instructions for contributing to the project. + + +## Getting Started + +1. Fork the repository +2. Clone your fork: + ```bash + git clone https://github.com/your-username/project-name.git + cd project-name + ``` +3. Create a new branch, be descriptive: + ```bash + git checkout -b feature/your-feature-name + + # OR + + git checkout -b bugfix/bug-this-pr-fixes + + # OR + + git checkout -b docs/documenting-something + ``` + +## Development Workflow + +### Testing + +- Implement your feature/changes +- Test any code changes thoroughly +- Make sure your PR contains all the information for any developer to checkout your branch and validate it + + +### Commit Guidelines (not enforced but nice) + +- Your commit message should be under 72 characters +- Your commit message should start with a topic (e.g.: "Docs: ") +- Sign-off your commits with `git commit -sm "Feature: Commit subject under 72 characters."` + +## Submitting Changes + +1. Push your changes to your fork +2. Submit a pull request towards the `development` branch of this repository +3. Ensure the PR description clearly describes the problem and solution +4. Include the relevant issue number if applicable + +### Pull Request Process + +1. Update documentation if needed: anyone should be able to checkout your branch and validate the changes +2. Add tests for new features +3. Wait for review +4. Upon approval, if you have write access you can merge your PR + +### New version/release (maintainers only) + +The branch `main` is always assumed to be stable, and new versions are created from there. +To create a new version of this module: + +1. Create a new branch from `main` and name it as the new version (e.g.: `v1.5.5`) +1. Update the `"version"` field in `package.json` +1. Run `npm install` to update `package-lock.json` +1. Validate the above +1. Commit and push these changes +1. Open a PR from this new branch to `main` to align version numbers + + +## License + +By contributing, you agree that your contributions will be licensed under the project's license. From 0b44596434a52c0f188050e3ca8f7a6646aa997e Mon Sep 17 00:00:00 2001 From: ubi de feo Date: Tue, 10 Dec 2024 08:09:39 +0100 Subject: [PATCH 23/23] Fixed executeString parameters. Signed-off-by: ubi de feo --- cli.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cli.js b/cli.js index e7c7c7a..7a13be2 100755 --- a/cli.js +++ b/cli.js @@ -82,7 +82,7 @@ const executeString = (args, port, dataConsumer) => { const code = args[0] || '' return board.open(port) .then(() => board.enter_raw_repl()) - .then(() => board.exec_raw({ command: code, data_consumer: dataConsumer })) + .then(() => board.exec_raw(code, dataConsumer)) .then(async (out) => { await board.exit_raw_repl() await board.close()