def send(self): if not self.connection: if os.environ.has_key('DOTSTAR_HOST'): host = os.environ.get('DOTSTAR_HOST') else: host = config.get("HOST") if os.environ.has_key('DOTSTAR_PORT'): port = os.environ.get('DOTSTAR_PORT') else: port = config.get("PORT") self.connection = Client((host, port)) # Start out_buffer = bytearray() out_buffer += bytearray((0x00, 0x00, 0x00, 0x00)) out_buffer += self.data if self.pixel_count: footerLen = (self.pixel_count + 15) / 16 else: footerLen = ((len(self.data) / 4) + 15) / 16 fBuf = bytearray() for i in range(int(footerLen)): # This is different than AdaFruit library, which uses zero's in the xfer[2] spi_ioc_transfer struct. out_buffer.append(0xFF) # End Frame self.connection.send(out_buffer)
def on_render(self): self.surface.fill(config.get("BRAND_BOX_BG")) font = globals.current_app.get_font(22) c = config.get("BRAND_TITLE") text = font.render(config.get("WINDOW_CAPTION"), True, c) text_pos = text.get_rect() text_pos.center = self.surface.get_rect().center self.surface.blit(text, text_pos)
def initialize_grid(self): """ Step through all of the calculations to determine the grid size and led size. :return: None """ # Store Grid Size self.grid_size = Vector2(config.get("GRID_SIZE")) if self.draw_borders: self.perimeter_border_size = Vector2(config.get("PERIMETER_BORDER_SIZE")) else: self.perimeter_border_size = Vector2(0, 0) # Calculate cell_size self.calculate_cell_size(self.max_grid_pixel_size()) # Create Background self.create_background() # Calculate LED Size self.calculate_led_size() log.info("LED size calculated %s, with borders %s", self.led_size, self.cell_size) # store led rects, of just the LED rect self.led_rects = [[None for x in range(int(self.grid_size.y))] for y in range(int(self.grid_size.x))] # each grid cells rect, includes led borders self.grid_rects = [[None for x in range(int(self.grid_size.y))] for y in range(int(self.grid_size.x))] for x in range(int(self.grid_size.x)): for y in range(int(self.grid_size.y)): top = self.grid_background_position.y + (self.cell_size.y * y) left = self.grid_background_position.x + (self.cell_size.x * x) top += self.border_size.y left += self.border_size.x top += self.perimeter_border_size.y left += self.perimeter_border_size.x width = self.cell_size.x - (self.border_size.x * 2) height = self.cell_size.y - (self.border_size.y * 2) rect = pygame.Rect(left, top, width, height) self.led_rects[x][y] = rect top = self.grid_background_position.y + (self.cell_size.y * y) + self.perimeter_border_size.y left = self.grid_background_position.x + (self.cell_size.x * x) + self.perimeter_border_size.x width = self.cell_size.x height = self.cell_size.y rect = Rect(left, top, width, height) self.grid_rects[x][y] = rect
def on_render(self): self.surface.fill((0, 0, 0, 0)) # if self.draw_borders > 0: self.surface.blit(self.grid_background_surface, self.grid_background_position) bg_color = config.get("DRAW_BORDERS_COLORS")[self.draw_borders - 1] # adjust font size for pixel index rendering based on size of the pixel if self.led_size.x >= 30 and self.led_size.y >= 30: font = globals.current_app.get_font(18) else: font = globals.current_app.get_font(8) for y in range(int(self.grid_size.y)): for x in range(int(self.grid_size.x)): index = globals.mapping_data.get(x, y) if index is not None: c, b, g, r = globals.strip_data.get(index) color = (r, g, b) else: color = bg_color self.led_surface.fill(color) if index is not None and self.draw_indexes > 0: text = font.render(str(index), True, self.draw_indexes_colors[self.draw_indexes - 1]) text_pos = text.get_rect() text_pos.center = self.led_surface.get_rect().center self.led_surface.blit(text, text_pos) self.surface.blit(self.led_surface, self.led_rects[x][y])
def __init__(self): """ Data model of a strip of LED's and also handles processes received data packets. :return: """ self.grid_size = Vector2(config.get("GRID_SIZE")) # self.pixel_count = int(self.grid_size.x * self.grid_size.y) self.pixel_count = globals.mapping_data.pixel_count # setup initial pixel data size = self.pixel_count * 4 self.data = bytearray(size) self.clear_data() self.spi_index = 0 # current pixel index of the spi_in function self.updated = None # datetime of last time start frame was received self.packet_length = 0 # count of number of individual bytes received since last start frame was received. self._dirty = True # keep track if data has been changed since last update call # cache blinker signals self._signal_startrecv = blinker.signal("stripdata.startrecv") self._signal_updated = blinker.signal("stripdata.updated") self.header_bytes_found = 0 self.buffer_count = 0 self.buffer = bytearray((0xFF, 0xFF, 0xFF, 0xFF))
def calculate_cell_size(self, max_grid_pixel_size): """ Given the max grid pixel size, determine the size of the individual grid cells. :param max_grid_pixel_size: pygame.math.Vector2 of the max size of the whole grid. :return: None """ width = max_grid_pixel_size.x / float(self.grid_size.x) height = max_grid_pixel_size.y / float(self.grid_size.y) # Check that the LED size is at least 1px if width <= 1.0 or height <= 1.0: log.error( "Calculated LED cell size less than 1 pixel. The WINDOW_SIZE is to small to fit " "the configured GRID_SIZE") raise Exception("LED size less than 1 pixel") # Sqaure up the LED if config.get("SQUARE_LED"): if width < height: height = width else: width = height self.cell_size = vector2_to_floor(Vector2(width, height))
def calculate_cell_size(self, max_grid_pixel_size): """ Given the max grid pixel size, determine the size of the individual grid cells. :param max_grid_pixel_size: pygame.math.Vector2 of the max size of the whole grid. :return: None """ width = max_grid_pixel_size.x / float(self.grid_size.x) height = max_grid_pixel_size.y / float(self.grid_size.y) # Check that the LED size is at least 1px if width <= 1.0 or height <= 1.0: log.error("Calculated LED cell size less than 1 pixel. The WINDOW_SIZE is to small to fit " "the configured GRID_SIZE") raise Exception("LED size less than 1 pixel") # Sqaure up the LED if config.get("SQUARE_LED"): if width < height: height = width else: width = height self.cell_size = vector2_to_floor(Vector2(width, height))
def create_background(self): """ Calculates the background size and position, and creates the surface. :return: None """ # Initial background Size background_size_px = Vector2(self.grid_size.x * self.cell_size.x, self.grid_size.y * self.cell_size.y) # add in perimeter border size background_size_px.x += (self.perimeter_border_size.x * 2) background_size_px.y += (self.perimeter_border_size.y * 2) self.grid_background_surface = pygame.Surface(vector2_to_int(background_size_px)) if self.draw_borders > 0: color = config.get("DRAW_BORDERS_COLORS")[self.draw_borders - 1] self.grid_background_surface.fill(color) grid_background_position_rect = self.grid_background_surface.get_rect() grid_background_position_rect.centerx = self.layout.size.x / 2 grid_background_position_rect.centery = self.layout.size.y / 2 # Center the grid background in its space self.grid_background_position = Vector2(grid_background_position_rect.topleft)
def calculate_led_size(self): """ Calculate the led size for each cell. :return: None """ if self.draw_borders: self.border_size = Vector2(config.get("BORDER_SIZE")) else: self.border_size = Vector2(0, 0) width = self.cell_size.x - (self.border_size.x * 2) height = self.cell_size.y - (self.border_size.y * 2) self.led_size = vector2_to_floor(Vector2(width, height)) self.led_surface = pygame.Surface(self.led_size) # Check that the LED size is at least 1px if self.led_size.x <= 1.0 or self.led_size.y <= 1.0: log.error( "LED border size is to large for calculated LED size '(%s, %s) pixels'", self.cell_size.x, self.cell_size.y) raise Exception("LED border size is to large.")
def __init__(self, args): self.args = args self.mapping_data = MappingData() self.grid_size = Vector2(config.get("GRID_SIZE")) self.pixel_count = self.mapping_data.pixel_count size = self.pixel_count * 4 # data does not include start and end bytes self.data = bytearray(size) self.connection = None print("Data Type:", self.data_type) if self.args.rate: self.repeat_mode = "rate" self.repeat_rate = float(self.args.rate) print("Repeat Mode: Frequency") print('Frequency Set:', self.repeat_rate) else: self.repeat_mode = "loop" if self.args.loop: self.range = range(int(args.loop)) print("Repeat Mode: Loop") print("Loops Count:", args.loop) else: print("Repeat Mode: None, send once") self.range = range(1)
def on_render(self): self.surface.fill((0, 0, 0, 0)) # if self.draw_borders > 0: self.surface.blit(self.grid_background_surface, self.grid_background_position) bg_color = config.get("DRAW_BORDERS_COLORS")[self.draw_borders - 1] # adjust font size for pixel index rendering based on size of the pixel if self.led_size.x >= 30 and self.led_size.y >= 30: font = globals.current_app.get_font(18) else: font = globals.current_app.get_font(8) for y in range(int(self.grid_size.y)): for x in range(int(self.grid_size.x)): index = globals.mapping_data.get(x, y) if index is not None: c, b, g, r = globals.strip_data.get(index) color = (r, g, b) else: color = bg_color self.led_surface.fill(color) if index is not None and self.draw_indexes > 0: text = font.render( str(index), True, self.draw_indexes_colors[self.draw_indexes - 1]) text_pos = text.get_rect() text_pos.center = self.led_surface.get_rect().center self.led_surface.blit(text, text_pos) self.surface.blit(self.led_surface, self.led_rects[x][y])
def create_background(self): """ Calculates the background size and position, and creates the surface. :return: None """ # Initial background Size background_size_px = Vector2(self.grid_size.x * self.cell_size.x, self.grid_size.y * self.cell_size.y) # add in perimeter border size background_size_px.x += (self.perimeter_border_size.x * 2) background_size_px.y += (self.perimeter_border_size.y * 2) self.grid_background_surface = pygame.Surface( vector2_to_int(background_size_px)) if self.draw_borders > 0: color = config.get("DRAW_BORDERS_COLORS")[self.draw_borders - 1] self.grid_background_surface.fill(color) grid_background_position_rect = self.grid_background_surface.get_rect() grid_background_position_rect.centerx = self.layout.size.x / 2 grid_background_position_rect.centery = self.layout.size.y / 2 # Center the grid background in its space self.grid_background_position = Vector2( grid_background_position_rect.topleft)
def __init__(self): """ Provide the mapping from a single series strip of DotStar pixels to an x, y grid :return: """ self.grid_size = Vector2(config.get("GRID_SIZE")) self.pixel_count = 0 self.data = [[None for x in range(int(self.grid_size.y))] for y in range(int(self.grid_size.x))] pixel_mapping = config.get("PIXEL_MAPPING") if pixel_mapping is not None: self.custom_mapping(pixel_mapping) else: self.prebuilt_mappings() self.pixel_count = int(self.grid_size.x * self.grid_size.y)
def __init__(self): """ Widget to show information about the running screen. :return: """ super(RunningInfo, self).__init__(x=145) self.layout.margin.set(0, 5, 10, 5) # LED information widgets self.led_grid_position_txt = [] # value of grid position self.led_strip_index_txt = [] # value of strip index self.led_value_txt = [] # value of strip index color self.led_color = [] # widget to show color block of strip index color row_size = 14 left = SizedRows(row_size) right = SizedRows(row_size) self.set_left(left) self.set_right(right) left.set(self.hd_txt("Information"), 0) i = 1 left.set(self.lbl_text("Emulator FPS:"), i) self.emulator_fps_txt = self.val_text("") right.set(self.emulator_fps_txt, i) i += 1 left.set(self.lbl_text("Grid size:"), i) grid_size = Vector2(config.get("GRID_SIZE")) pixel_count = globals.mapping_data.pixel_count self.grid_size_txt = self.val_text("({:.0f}, {:.0f}), {:.0f}".format(grid_size.x, grid_size.y, pixel_count)) right.set(self.grid_size_txt, i) i += 1 left.set(self.lbl_text("Packet Updated:"), i) self.packet_updated_txt = self.val_text("") right.set(self.packet_updated_txt, i) i += 1 left.set(self.lbl_text("Packet length:"), i) self.packet_length_txt = self.val_text("") right.set(self.packet_length_txt, i) i += 1 left.set(self.lbl_text("Packet Rate:"), i) self.packet_rate = self.val_text("") right.set(self.packet_rate, i) i += 2 self.pixel_info_count = 4 self.create_pixel_info(left, right, i) blinker.signal("dotgrid.select.set").connect(self.on_dotgrid_select_set) blinker.signal("stripdata.updated").connect(self.on_data_updated) blinker.signal("ratecounter.updated").connect(self.on_ratecounter_updated)
def __init__(self, use_surface=True): """ Main display of DotStar Grid. Renders each LED in a x, y grid fashion. Each grid cell is a LED. Each grid cell can have its own borders. The main grid can also have a grid. Configuration [ 'GRID_SIZE': (x, y), # Grid size 'BORDER_SIZE': (x, y), # Pixel size of the border drawn around each LED 'PERIMETER_BORDER_SIZE': (x, y) # Pixel size of the border drawn around the whole grid 'GRID_BACKGROUND_COLOR': (r, g, b) or pygame.Color # Color of the border/background of the grid 'SQUARE_LED': True or False # if True the LED's will be square, else fit to screen. ] :param use_surface: `bool`, default=True, use a surface to render the widget :return: """ super(DotGridWidget, self).__init__(use_surface=use_surface) # Add a margin around the grid self.layout.margin.set(*[5 for i in range(4)]) self.grid_size = None # Number Columns, Rows of the grid self.cell_size = None # Pixel size of each grid cell self.led_surface = None # Reusable surface to render LEDs self.led_size = None # Pixel size of the colored led inside the grid cell self.border_size = None # Pixel size of the border to draw around the LED inside the grid cell self.grid_background_position = None # pixel offset of background surface self.grid_background_surface = None # background surface self.perimeter_border_size = None # pixel size of perimeter border self.led_rects = None # Cache the Rects of the LEDs, positioned to this widget self.grid_rects = None # Cache the Rects of the grid Rects, position to this widget self.draw_borders = config.get("DRAW_BORDERS") # Borders around each LED can be toggled on and off self.draw_indexes = config.get("DRAW_INDEXES") # Borders around each LED can be toggled on and off self.draw_indexes_colors = config.get("DRAW_INDEXES_COLORS") # connect to signal used to toggle the borders. blinker.signal("dotgrid.drawborders").connect(self.on_drawborders) blinker.signal("dotgrid.drawindexes").connect(self.on_drawindexs) blinker.signal("stripdata.updated").connect(self.on_data_updated)
def prebuilt_mappings(self): zero_location = config.get("ZERO_LOCATION") # Top Left if zero_location == 0: cardinal = False left_to_right = True # Top Right elif zero_location == 1: cardinal = False left_to_right = False # Bottom Left elif zero_location == 2: cardinal = True left_to_right = True # Bottom Right elif zero_location == 3: cardinal = True left_to_right = False # Configuration Error else: raise AttributeError( "invalid ZERO_LOCATION configuration '{}'".format( zero_location)) # Render the correct pattern pattern = config.get("PATTERN") if pattern == 0: self.vertical(cardinal, left_to_right) elif pattern == 1: self.vertical_daisy(cardinal, left_to_right) elif pattern == 2: self.horizontal(cardinal, left_to_right) elif pattern == 3: self.horizontal_daisy(cardinal, left_to_right) else: raise AttributeError( "invlude PATTERN configuration '{}'".format(pattern))
def __init__(self, dotgrid): super(DotGridSelect, self).__init__() self.dotgrid = dotgrid self.clear_event = blinker.signal("dotgrid.select.clear") self.set_event = blinker.signal("dotgrid.select.set") self.step = 0 self.elapsed = 0 self.speed = config.get("DOT_GRID_SELECT_SPEED") self.colors = config.get("DOT_GRID_SELECT_COLORS") self.x = 0 self.y = 0 self.rect = None self.selected = False blinker.signal("event.mousebuttondown").connect(self.on_mousebuttondown) blinker.signal("event.keydown").connect(self.on_keydown)
def confirm_customer_size(pixel_mapping): rows = len(pixel_mapping) if rows != int(Vector2(config.get("GRID_SIZE")).y): raise Exception("Custom PIXEL_MAPPING rows '{}' does not match GRID_SIZE.y '{}'".format(rows, int( Vector2(config.get("GRID_SIZE")).y))) columns = [] for y in range(len(pixel_mapping)): x = len(pixel_mapping[y]) if x not in columns: columns.append(x) if len(columns) != 1: raise Exception("Custom PIXEL_MAPPING columns malformed, all are not of equal length.") if columns[0] != int(Vector2(config.get("GRID_SIZE")).x): raise Exception("Custom PIXEL_MAPPING columns '{}' does not match GRID_SIZE.x '{}'".format(columns[0], int( Vector2(config.get("GRID_SIZE")).x))) return columns[0], rows
def prebuilt_mappings(self): zero_location = config.get("ZERO_LOCATION") # Top Left if zero_location == 0: cardinal = False left_to_right = True # Top Right elif zero_location == 1: cardinal = False left_to_right = False # Bottom Left elif zero_location == 2: cardinal = True left_to_right = True # Bottom Right elif zero_location == 3: cardinal = True left_to_right = False # Configuration Error else: raise AttributeError("invalid ZERO_LOCATION configuration '{}'".format(zero_location)) # Render the correct pattern pattern = config.get("PATTERN") if pattern == 0: self.vertical(cardinal, left_to_right) elif pattern == 1: self.vertical_daisy(cardinal, left_to_right) elif pattern == 2: self.horizontal(cardinal, left_to_right) elif pattern == 3: self.horizontal_daisy(cardinal, left_to_right) else: raise AttributeError("invlude PATTERN configuration '{}'".format(pattern))
def __init__(self, rate): self.mapping_data = MappingData() self.grid_size = Vector2(config.get("GRID_SIZE")) self.pixel_count = self.mapping_data.pixel_count size = self.pixel_count * 4 # data does not include start and end bytes self.data = bytearray(size) self.connection = None self.is_stopped = False print("Data Type:", self.data_type)
def __init__(self): """ Read emulator strip SPI data in a TCP socket. TCPReader runs in its own thread, and will acquire a lock from strip_data before writing data. HOST and PORT set in config. :return: """ super(TCPReader, self).__init__() self.host = config.get("HOST") self.port = config.get("PORT") self.listener = None # Thread Loop self.running = True # Report back to the main thread if we could bind to the port or not. Main thread will not continue # if port was not bound to. self.startup = threading.Event() self.startup_success = False
def text_panel(): panel = panels.CenterSingle(25) rows_panel = panels.SizedRows(18, use_surface=True, color=(40, 40, 40)) rows_panel.layout.requested_size = Vector2(500, 400) panel.set(rows_panel) filename = os.path.join(MEDIA_PATH, 'about.txt') with open(filename, 'r') as f: text = f.read() text_split = text.split("\n") widget = TextLabelWidget(config.get("WINDOW_CAPTION"), 18, config.get("BRAND_TITLE")) widget.layout.margin.set(0, 0, 0, 5) rows_panel.set(widget, 0) for i, line in enumerate(text_split): widget = TextLabelWidget(line, 12, (255, 255, 255)) widget.layout.margin.set(0, 0, 0, 5) rows_panel.set(widget, i+2) return panel
def __init__(self): super(RunningScene, self).__init__() self.draw_borders = config.get("DRAW_BORDERS") self.draw_indexes = config.get("DRAW_INDEXES") self.updated_txt = None self.led_grid_position_txt = None self.led_strip_index_txt = None self.led_value_txt = None self.led_color = None two_columns = panels.TwoColumns(config.get("EMU_RUNNING_MIN_RIGHT_COL_WIDTH")) self.set_panel(two_columns) self.dotgrid = DotGridWidget() two_columns.set_left(self.dotgrid) right = panels.TwoRows(40) two_columns.set_right(right) right.set_top(LogoWidget()) right2 = panels.TwoRows(-60) right.set_bottom(right2) running_info = RunningInfo() right2.set_top(running_info) right2.set_bottom(self.bottom_right_panel()) self.fit() select = DotGridSelect(self.dotgrid) self.add_entity(select) rect = running_info.emulator_fps_txt.layout.global_rect self.add_entity(RunningFPS(rect))
def text_panel(): panel = panels.CenterSingle(25) rows_panel = panels.SizedRows(18, use_surface=True, color=(40, 40, 40)) rows_panel.layout.requested_size = Vector2(500, 400) panel.set(rows_panel) filename = os.path.join(MEDIA_PATH, 'about.txt') with open(filename, 'r') as f: text = f.read() text_split = text.split("\n") widget = TextLabelWidget(config.get("WINDOW_CAPTION"), 18, config.get("BRAND_TITLE")) widget.layout.margin.set(0, 0, 0, 5) rows_panel.set(widget, 0) for i, line in enumerate(text_split): widget = TextLabelWidget(line, 12, (255, 255, 255)) widget.layout.margin.set(0, 0, 0, 5) rows_panel.set(widget, i + 2) return panel
def __init__(self, rect): """ Entity to draw the FPS value to the screen. This is an entity, so that it can be updated at a high rate without redrawing the entire GUI. :param rect: pygame.Rect on where the FPS should be drawn. :return: """ super(RunningFPS, self).__init__() self.rect = rect self.font = globals.current_app.get_font(13) self.text = None self.text_pos = None self.update_rate = config.get("FPS_UPDATE_RATE") self.elapsed = self.update_rate + 1
def on_draw_indexes(self, sender): """ Callback for the Draw Indexes button pressed event. :param sender: blinker sender :return: None """ self.draw_indexes += 1 if self.draw_indexes > len(config.get("DRAW_INDEXES_COLORS")): self.draw_indexes = 0 sender.text = self.draw_indexes_text() sender.redraw() # Send signal to dotgrid to set the draw_indexes blinker.signal("dotgrid.drawindexes").send(None, draw_indexes=self.draw_indexes)
def bottom_right_panel(self): """ Bottom panel of the right side, menu / buttons. :return: gui Panel """ panel = panels.TwoRows(16, use_surface=True, color=config.get("BRAND_BOX_BG")) panel.layout.margin.set(0, 5, 0, 5) text = TextLabelWidget("Menu", 10, (255, 227, 43)) text.layout.margin.left = 4 panel.set_top(text) bottom = panels.Grid(3, 2) bottom.layout.margin.set(0, 2, 2, 2) panel.set_bottom(bottom) # Buttons fps_button = ButtonWidget(self.fps_text(), key=pygame.K_f) bottom.set(fps_button, 0, 0) blinker.signal("gui.button.pressed").connect(self.on_fps, sender=fps_button) borders_button = ButtonWidget(self.draw_borders_text(), key=pygame.K_b) bottom.set(borders_button, 0, 1) blinker.signal("gui.button.pressed").connect(self.on_border, sender=borders_button) # edit_button = ButtonWidget("[E] Edit Grid", key=pygame.K_e) # bottom.set(edit_button, 2, 0) # blinker.signal("gui.button.pressed").connect(self.on_edit, sender=edit_button) edit_button = ButtonWidget(self.draw_indexes_text(), key=pygame.K_i) bottom.set(edit_button, 1, 0) blinker.signal("gui.button.pressed").connect(self.on_draw_indexes, sender=edit_button) exit_button = ButtonWidget("[X] Exit", key=pygame.K_x) bottom.set(exit_button, 2, 1) blinker.signal("gui.button.pressed").connect(self.on_exit, sender=exit_button, weak=True) about_button = ButtonWidget("[F1] About", key=pygame.K_F1) blinker.signal("gui.button.pressed").connect(self.on_about, sender=about_button, weak=True) bottom.set(about_button, 1, 1) return panel
def on_border(self, sender): """ Callback for the Draw border button pressed event. :param sender: blinker sender :return: None """ # self.draw_borders = not self.draw_borders self.draw_borders += 1 if self.draw_borders > len(config.get("DRAW_BORDERS_COLORS")): self.draw_borders = 0 sender.text = self.draw_borders_text() sender.redraw() # Send signal to dotgrid to set the draw_borders blinker.signal("dotgrid.drawborders").send(None, draw_borders=self.draw_borders)
def calculate_led_size(self): """ Calculate the led size for each cell. :return: None """ if self.draw_borders: self.border_size = Vector2(config.get("BORDER_SIZE")) else: self.border_size = Vector2(0, 0) width = self.cell_size.x - (self.border_size.x * 2) height = self.cell_size.y - (self.border_size.y * 2) self.led_size = vector2_to_floor(Vector2(width, height)) self.led_surface = pygame.Surface(self.led_size) # Check that the LED size is at least 1px if self.led_size.x <= 1.0 or self.led_size.y <= 1.0: log.error("LED border size is to large for calculated LED size '(%s, %s) pixels'", self.cell_size.x, self.cell_size.y) raise Exception("LED border size is to large.")