diff options
Diffstat (limited to 'klippy/extras/display')
-rw-r--r-- | klippy/extras/display/__init__.py | 10 | ||||
-rw-r--r-- | klippy/extras/display/aip31068_spi.py | 151 | ||||
-rw-r--r-- | klippy/extras/display/display.py | 176 | ||||
-rw-r--r-- | klippy/extras/display/font8x14.py | 512 | ||||
-rw-r--r-- | klippy/extras/display/hd44780.py | 109 | ||||
-rw-r--r-- | klippy/extras/display/hd44780_spi.py | 80 | ||||
-rw-r--r-- | klippy/extras/display/menu.py | 344 | ||||
-rw-r--r-- | klippy/extras/display/menu_keys.py | 73 | ||||
-rw-r--r-- | klippy/extras/display/st7920.py | 165 | ||||
-rw-r--r-- | klippy/extras/display/uc1701.py | 180 |
10 files changed, 999 insertions, 801 deletions
diff --git a/klippy/extras/display/__init__.py b/klippy/extras/display/__init__.py index da0e861c..b4650180 100644 --- a/klippy/extras/display/__init__.py +++ b/klippy/extras/display/__init__.py @@ -5,17 +5,21 @@ # This file may be distributed under the terms of the GNU GPLv3 license. from . import display + def load_config(config): return display.load_config(config) + def load_config_prefix(config): - if not config.has_section('display'): + if not config.has_section("display"): raise config.error( "A primary [display] section must be defined in printer.cfg " - "to use auxiliary displays") + "to use auxiliary displays" + ) name = config.get_name().split()[-1] if name == "display": raise config.error( "Section name [display display] is not valid. " - "Please choose a different postfix.") + "Please choose a different postfix." + ) return display.load_config(config) diff --git a/klippy/extras/display/aip31068_spi.py b/klippy/extras/display/aip31068_spi.py index 75f4978f..9bbbbdaa 100644 --- a/klippy/extras/display/aip31068_spi.py +++ b/klippy/extras/display/aip31068_spi.py @@ -16,10 +16,11 @@ from .. import bus -LINE_LENGTH_DEFAULT=20 -LINE_LENGTH_OPTIONS={16:16, 20:20} +LINE_LENGTH_DEFAULT = 20 +LINE_LENGTH_OPTIONS = {16: 16, 20: 20} + +TextGlyphs = {"right_arrow": b"\x7e"} -TextGlyphs = { 'right_arrow': b'\x7e' } # Each command is 9 bits long: # 1 bit for RS (Register Select) - 0 for command, 1 for data @@ -30,66 +31,79 @@ TextGlyphs = { 'right_arrow': b'\x7e' } # or just by OR with enabled flags: # cmd = CMND | flg_CMND.param1 class CMND: - CLR = 1 # Clear display - HOME = 2 # Return home + CLR = 1 # Clear display + HOME = 2 # Return home ENTERY_MODE = 2**2 # Entry mode set - DISPLAY = 2**3 # Display on/off control - SHIFT = 2**4 # Cursor or display shift - FUNCTION = 2**5 # Function set - CGRAM = 2**6 # Character Generator RAM - DDRAM = 2**7 # Display Data RAM - WRITE_RAM = 2**8 # Write to RAM + DISPLAY = 2**3 # Display on/off control + SHIFT = 2**4 # Cursor or display shift + FUNCTION = 2**5 # Function set + CGRAM = 2**6 # Character Generator RAM + DDRAM = 2**7 # Display Data RAM + WRITE_RAM = 2**8 # Write to RAM + # Define flags for all commands: class flg_ENTERY_MODE: - INC = 2**1 # Increment - SHIFT = 2**0 # Shift display + INC = 2**1 # Increment + SHIFT = 2**0 # Shift display + class flg_DISPLAY: - ON = 2**2 # Display ON - CURSOR = 2**1 # Cursor ON - BLINK = 2**0 # Blink ON + ON = 2**2 # Display ON + CURSOR = 2**1 # Cursor ON + BLINK = 2**0 # Blink ON + class flg_SHIFT: - WHOLE_DISPLAY = 2**3 # Shift whole display - RIGHT = 2**2 # Shift right + WHOLE_DISPLAY = 2**3 # Shift whole display + RIGHT = 2**2 # Shift right + class flg_FUNCTION: - TWO_LINES = 2**3 # 2-line display mode - FIVE_BY_ELEVEN = 2**2 # 5x11 dot character font + TWO_LINES = 2**3 # 2-line display mode + FIVE_BY_ELEVEN = 2**2 # 5x11 dot character font + class flg_CGRAM: - MASK = 0b00111111 # CGRAM address mask + MASK = 0b00111111 # CGRAM address mask + class flg_DDRAM: - MASK = 0b01111111 # DDRAM address mask + MASK = 0b01111111 # DDRAM address mask + class flg_WRITE_RAM: - MASK = 0b11111111 # Write RAM mask + MASK = 0b11111111 # Write RAM mask -DISPLAY_INIT_CMNDS= [ + +DISPLAY_INIT_CMNDS = [ # CMND.CLR - no need as framebuffer will rewrite all - CMND.HOME, # move cursor to home (0x00) - CMND.ENTERY_MODE | flg_ENTERY_MODE.INC, # increment cursor and no shift - CMND.DISPLAY | flg_DISPLAY.ON, # keep cursor and blinking off - CMND.SHIFT | flg_SHIFT.RIGHT, # shift right cursor only - CMND.FUNCTION | flg_FUNCTION.TWO_LINES, # 2-line display mode, 5x8 dots + CMND.HOME, # move cursor to home (0x00) + CMND.ENTERY_MODE | flg_ENTERY_MODE.INC, # increment cursor and no shift + CMND.DISPLAY | flg_DISPLAY.ON, # keep cursor and blinking off + CMND.SHIFT | flg_SHIFT.RIGHT, # shift right cursor only + CMND.FUNCTION | flg_FUNCTION.TWO_LINES, # 2-line display mode, 5x8 dots ] + class aip31068_spi: def __init__(self, config): self.printer = config.get_printer() # spi config self.spi = bus.MCU_SPI_from_config( - config, 0x00, pin_option="latch_pin") # (config, mode, cs_name) + config, 0x00, pin_option="latch_pin" + ) # (config, mode, cs_name) self.mcu = self.spi.get_mcu() self.icons = {} - self.line_length = config.getchoice('line_length', LINE_LENGTH_OPTIONS, - LINE_LENGTH_DEFAULT) + self.line_length = config.getchoice( + "line_length", LINE_LENGTH_OPTIONS, LINE_LENGTH_DEFAULT + ) # each controller's line is 2 lines on the display and hence twice # line length - self.text_framebuffers = [bytearray(b' '*2*self.line_length), - bytearray(b' '*2*self.line_length)] + self.text_framebuffers = [ + bytearray(b" " * 2 * self.line_length), + bytearray(b" " * 2 * self.line_length), + ] self.glyph_framebuffer = bytearray(64) # all_framebuffers - list of tuples per buffer type. # Each tuple contains: @@ -102,23 +116,33 @@ class aip31068_spi: # (immutable tuple is allowed to store mutable bytearray) self.all_framebuffers = [ # Text framebuffers - (self.text_framebuffers[0], bytearray(b'~'*2*self.line_length), - CMND.DDRAM | (flg_DDRAM.MASK & 0x00) ), - (self.text_framebuffers[1], bytearray(b'~'*2*self.line_length), - CMND.DDRAM | (flg_DDRAM.MASK & 0x40) ), + ( + self.text_framebuffers[0], + bytearray(b"~" * 2 * self.line_length), + CMND.DDRAM | (flg_DDRAM.MASK & 0x00), + ), + ( + self.text_framebuffers[1], + bytearray(b"~" * 2 * self.line_length), + CMND.DDRAM | (flg_DDRAM.MASK & 0x40), + ), # Glyph framebuffer - (self.glyph_framebuffer, bytearray(b'~'*64), - CMND.CGRAM | (flg_CGRAM.MASK & 0x00) ) ] + ( + self.glyph_framebuffer, + bytearray(b"~" * 64), + CMND.CGRAM | (flg_CGRAM.MASK & 0x00), + ), + ] + @staticmethod - def encode(data, width = 9): + def encode(data, width=9): encoded_bytes = [] accumulator = 0 # To accumulate bits acc_bits = 0 # Count of bits in the accumulator for num in data: # check that num will fit in width bits if num >= (1 << width): - raise ValueError("Number {} does not fit in {} bits". - format(num, width)) + raise ValueError("Number {} does not fit in {} bits".format(num, width)) # Shift the current number into the accumulator from the right accumulator = (accumulator << width) | num acc_bits += width # Update the count of bits in the accumulator @@ -135,6 +159,7 @@ class aip31068_spi: last_byte = accumulator << (8 - acc_bits) encoded_bytes.append(last_byte) return encoded_bytes + def send(self, data, minclock=0): # different commands have different processing time # to avoid timing violation pad with some fast command, e.g. ENTRY_MODE @@ -142,56 +167,63 @@ class aip31068_spi: pad = CMND.ENTERY_MODE | flg_ENTERY_MODE.INC for i in range(0, len(data), 8): # Take a slice of 8 numbers - group = data[i:i+8] + group = data[i : i + 8] # Pad the group if it has fewer than 8 elements if len(group) < 8: group.extend([pad] * (8 - len(group))) self.spi.spi_send(self.encode(group), minclock) + def flush(self): # Find all differences in the framebuffers and send them to the chip for new_data, old_data, fb_cmnd in self.all_framebuffers: if new_data == old_data: continue # Find the position of all changed bytes in this framebuffer - diffs = [[i, 1] for i, (n, o) in enumerate(zip(new_data, old_data)) - if n != o] + diffs = [ + [i, 1] for i, (n, o) in enumerate(zip(new_data, old_data)) if n != o + ] # Batch together changes that are close to each other - for i in range(len(diffs)-2, -1, -1): + for i in range(len(diffs) - 2, -1, -1): pos, count = diffs[i] - nextpos, nextcount = diffs[i+1] + nextpos, nextcount = diffs[i + 1] if pos + 4 >= nextpos and nextcount < 16: diffs[i][1] = nextcount + (nextpos - pos) - del diffs[i+1] + del diffs[i + 1] # Transmit changes for pos, count in diffs: chip_pos = pos self.send([fb_cmnd + chip_pos]) - self.send([CMND.WRITE_RAM | byte for byte in - new_data[pos:pos+count]]) + self.send( + [CMND.WRITE_RAM | byte for byte in new_data[pos : pos + count]] + ) old_data[:] = new_data + def init(self): curtime = self.printer.get_reactor().monotonic() print_time = self.mcu.estimated_print_time(curtime) for i, cmds in enumerate(DISPLAY_INIT_CMNDS): - minclock = self.mcu.print_time_to_clock(print_time + i * .100) + minclock = self.mcu.print_time_to_clock(print_time + i * 0.100) self.send([cmds], minclock=minclock) self.flush() + def write_text(self, x, y, data): if x + len(data) > self.line_length: - data = data[:self.line_length - min(x, self.line_length)] + data = data[: self.line_length - min(x, self.line_length)] pos = x + ((y & 0x02) >> 1) * self.line_length - self.text_framebuffers[y & 1][pos:pos+len(data)] = data + self.text_framebuffers[y & 1][pos : pos + len(data)] = data + def set_glyphs(self, glyphs): for glyph_name, glyph_data in glyphs.items(): - data = glyph_data.get('icon5x8') + data = glyph_data.get("icon5x8") if data is not None: self.icons[glyph_name] = data + def write_glyph(self, x, y, glyph_name): data = self.icons.get(glyph_name) if data is not None: slot, bits = data self.write_text(x, y, [slot]) - self.glyph_framebuffer[slot * 8:(slot + 1) * 8] = bits + self.glyph_framebuffer[slot * 8 : (slot + 1) * 8] = bits return 1 char = TextGlyphs.get(glyph_name) if char is not None: @@ -199,11 +231,14 @@ class aip31068_spi: self.write_text(x, y, char) return 1 return 0 + def write_graphics(self, x, y, data): - pass # this display supports only hardcoded or 8 user defined glyphs + pass # this display supports only hardcoded or 8 user defined glyphs + def clear(self): - spaces = b' ' * 2*self.line_length + spaces = b" " * 2 * self.line_length self.text_framebuffers[0][:] = spaces self.text_framebuffers[1][:] = spaces + def get_dimensions(self): return (self.line_length, 4) diff --git a/klippy/extras/display/display.py b/klippy/extras/display/display.py index e9ba31d6..c381cf09 100644 --- a/klippy/extras/display/display.py +++ b/klippy/extras/display/display.py @@ -14,77 +14,90 @@ REDRAW_TIME = 0.500 REDRAW_MIN_TIME = 0.100 LCD_chips = { - 'st7920': st7920.ST7920, 'emulated_st7920': st7920.EmulatedST7920, - 'hd44780': hd44780.HD44780, 'uc1701': uc1701.UC1701, - 'ssd1306': uc1701.SSD1306, 'sh1106': uc1701.SH1106, - 'hd44780_spi': hd44780_spi.hd44780_spi, - 'aip31068_spi':aip31068_spi.aip31068_spi + "st7920": st7920.ST7920, + "emulated_st7920": st7920.EmulatedST7920, + "hd44780": hd44780.HD44780, + "uc1701": uc1701.UC1701, + "ssd1306": uc1701.SSD1306, + "sh1106": uc1701.SH1106, + "hd44780_spi": hd44780_spi.hd44780_spi, + "aip31068_spi": aip31068_spi.aip31068_spi, } + # Storage of [display_template my_template] config sections class DisplayTemplate: def __init__(self, config): self.printer = config.get_printer() name_parts = config.get_name().split() if len(name_parts) != 2: - raise config.error("Section name '%s' is not valid" - % (config.get_name(),)) + raise config.error("Section name '%s' is not valid" % (config.get_name(),)) self.name = name_parts[1] self.params = {} - for option in config.get_prefix_options('param_'): + for option in config.get_prefix_options("param_"): try: self.params[option] = ast.literal_eval(config.get(option)) except ValueError as e: raise config.error( - "Option '%s' in section '%s' is not a valid literal" % ( - option, config.get_name())) - gcode_macro = self.printer.load_object(config, 'gcode_macro') - self.template = gcode_macro.load_template(config, 'text') + "Option '%s' in section '%s' is not a valid literal" + % (option, config.get_name()) + ) + gcode_macro = self.printer.load_object(config, "gcode_macro") + self.template = gcode_macro.load_template(config, "text") + def get_params(self): return self.params + def render(self, context, **kwargs): params = dict(self.params) params.update(**kwargs) if len(params) != len(self.params): raise self.printer.command_error( - "Invalid parameter to display_template %s" % (self.name,)) + "Invalid parameter to display_template %s" % (self.name,) + ) context = dict(context) context.update(params) return self.template.render(context) + # Store [display_data my_group my_item] sections (one instance per group name) class DisplayGroup: def __init__(self, config, name, data_configs): # Load and parse the position of display_data items items = [] for c in data_configs: - pos = c.get('position') + pos = c.get("position") try: - row, col = [int(v.strip()) for v in pos.split(',')] + row, col = [int(v.strip()) for v in pos.split(",")] except: - raise config.error("Unable to parse 'position' in section '%s'" - % (c.get_name(),)) + raise config.error( + "Unable to parse 'position' in section '%s'" % (c.get_name(),) + ) items.append((row, col, c.get_name())) # Load all templates and store sorted by display position configs_by_name = {c.get_name(): c for c in data_configs} printer = config.get_printer() - gcode_macro = printer.load_object(config, 'gcode_macro') + gcode_macro = printer.load_object(config, "gcode_macro") self.data_items = [] for row, col, name in sorted(items): c = configs_by_name[name] - if c.get('text'): - template = gcode_macro.load_template(c, 'text') + if c.get("text"): + template = gcode_macro.load_template(c, "text") self.data_items.append((row, col, template)) + def show(self, display, templates, eventtime): context = self.data_items[0][2].create_template_context(eventtime) - context['draw_progress_bar'] = display.draw_progress_bar + context["draw_progress_bar"] = display.draw_progress_bar + def render(name, **kwargs): return templates[name].render(context, **kwargs) - context['render'] = render + + context["render"] = render for row, col, template in self.data_items: text = template.render(context) - display.draw_text(row, col, text.replace('\n', ''), eventtime) - context.clear() # Remove circular references for better gc + display.draw_text(row, col, text.replace("\n", ""), eventtime) + context.clear() # Remove circular references for better gc + # Global cache of DisplayTemplate, DisplayGroup, and glyphs class PrinterDisplayTemplate: @@ -94,76 +107,89 @@ class PrinterDisplayTemplate: self.display_data_groups = {} self.display_glyphs = {} self.load_config(config) + def get_display_templates(self): return self.display_templates + def get_display_data_groups(self): return self.display_data_groups + def get_display_glyphs(self): return self.display_glyphs + def _parse_glyph(self, config, glyph_name, data, width, height): glyph_data = [] - for line in data.split('\n'): - line = line.strip().replace('.', '0').replace('*', '1') + for line in data.split("\n"): + line = line.strip().replace(".", "0").replace("*", "1") if not line: continue - if len(line) != width or line.replace('0', '').replace('1', ''): + if len(line) != width or line.replace("0", "").replace("1", ""): raise config.error("Invalid glyph line in %s" % (glyph_name,)) glyph_data.append(int(line, 2)) if len(glyph_data) != height: raise config.error("Glyph %s incorrect lines" % (glyph_name,)) return glyph_data + def load_config(self, config): # Load default display config file - pconfig = self.printer.lookup_object('configfile') - filename = os.path.join(os.path.dirname(__file__), 'display.cfg') + pconfig = self.printer.lookup_object("configfile") + filename = os.path.join(os.path.dirname(__file__), "display.cfg") try: dconfig = pconfig.read_config(filename) except Exception: - raise self.printer.config_error("Cannot load config '%s'" - % (filename,)) + raise self.printer.config_error("Cannot load config '%s'" % (filename,)) # Load display_template sections - dt_main = config.get_prefix_sections('display_template ') - dt_main_names = { c.get_name(): 1 for c in dt_main } - dt_def = [c for c in dconfig.get_prefix_sections('display_template ') - if c.get_name() not in dt_main_names] + dt_main = config.get_prefix_sections("display_template ") + dt_main_names = {c.get_name(): 1 for c in dt_main} + dt_def = [ + c + for c in dconfig.get_prefix_sections("display_template ") + if c.get_name() not in dt_main_names + ] for c in dt_main + dt_def: dt = DisplayTemplate(c) self.display_templates[dt.name] = dt # Load display_data sections - dd_main = config.get_prefix_sections('display_data ') - dd_main_names = { c.get_name(): 1 for c in dd_main } - dd_def = [c for c in dconfig.get_prefix_sections('display_data ') - if c.get_name() not in dd_main_names] + dd_main = config.get_prefix_sections("display_data ") + dd_main_names = {c.get_name(): 1 for c in dd_main} + dd_def = [ + c + for c in dconfig.get_prefix_sections("display_data ") + if c.get_name() not in dd_main_names + ] groups = {} for c in dd_main + dd_def: name_parts = c.get_name().split() if len(name_parts) != 3: - raise config.error("Section name '%s' is not valid" - % (c.get_name(),)) + raise config.error("Section name '%s' is not valid" % (c.get_name(),)) groups.setdefault(name_parts[1], []).append(c) for group_name, data_configs in groups.items(): dg = DisplayGroup(config, group_name, data_configs) self.display_data_groups[group_name] = dg # Load display glyphs - dg_prefix = 'display_glyph ' + dg_prefix = "display_glyph " self.display_glyphs = icons = {} dg_main = config.get_prefix_sections(dg_prefix) dg_main_names = {c.get_name(): 1 for c in dg_main} - dg_def = [c for c in dconfig.get_prefix_sections(dg_prefix) - if c.get_name() not in dg_main_names] + dg_def = [ + c + for c in dconfig.get_prefix_sections(dg_prefix) + if c.get_name() not in dg_main_names + ] for dg in dg_main + dg_def: - glyph_name = dg.get_name()[len(dg_prefix):] - data = dg.get('data', None) + glyph_name = dg.get_name()[len(dg_prefix) :] + data = dg.get("data", None) if data is not None: idata = self._parse_glyph(config, glyph_name, data, 16, 16) - icon1 = [(bits >> 8) & 0xff for bits in idata] - icon2 = [bits & 0xff for bits in idata] - icons.setdefault(glyph_name, {})['icon16x16'] = (icon1, icon2) - data = dg.get('hd44780_data', None) + icon1 = [(bits >> 8) & 0xFF for bits in idata] + icon2 = [bits & 0xFF for bits in idata] + icons.setdefault(glyph_name, {})["icon16x16"] = (icon1, icon2) + data = dg.get("hd44780_data", None) if data is not None: - slot = dg.getint('hd44780_slot', minval=0, maxval=7) + slot = dg.getint("hd44780_slot", minval=0, maxval=7) idata = self._parse_glyph(config, glyph_name, data, 5, 8) - icons.setdefault(glyph_name, {})['icon5x8'] = (slot, idata) + icons.setdefault(glyph_name, {})["icon5x8"] = (slot, idata) + def lookup_display_templates(config): printer = config.get_printer() @@ -173,16 +199,17 @@ def lookup_display_templates(config): printer.add_object("display_template", dt) return dt + class PrinterLCD: def __init__(self, config): self.printer = config.get_printer() self.reactor = self.printer.get_reactor() # Load low-level lcd handler - self.lcd_chip = config.getchoice('lcd_type', LCD_chips)(config) + self.lcd_chip = config.getchoice("lcd_type", LCD_chips)(config) # Load menu and display_status self.menu = None name = config.get_name() - if name == 'display': + if name == "display": # only load menu for primary display self.menu = menu.MenuManager(config, self) self.printer.load_object(config, "display_status") @@ -194,30 +221,37 @@ class PrinterLCD: dgroup = "_default_16x4" if self.lcd_chip.get_dimensions()[0] == 20: dgroup = "_default_20x4" - dgroup = config.get('display_group', dgroup) + dgroup = config.get("display_group", dgroup) self.show_data_group = self.display_data_groups.get(dgroup) if self.show_data_group is None: raise config.error("Unknown display_data group '%s'" % (dgroup,)) # Screen updating self.printer.register_event_handler("klippy:ready", self.handle_ready) - self.screen_update_timer = self.reactor.register_timer( - self.screen_update_event) + self.screen_update_timer = self.reactor.register_timer(self.screen_update_event) self.redraw_request_pending = False - self.redraw_time = 0. + self.redraw_time = 0.0 # Register g-code commands gcode = self.printer.lookup_object("gcode") - gcode.register_mux_command('SET_DISPLAY_GROUP', 'DISPLAY', name, - self.cmd_SET_DISPLAY_GROUP, - desc=self.cmd_SET_DISPLAY_GROUP_help) - if name == 'display': - gcode.register_mux_command('SET_DISPLAY_GROUP', 'DISPLAY', None, - self.cmd_SET_DISPLAY_GROUP) + gcode.register_mux_command( + "SET_DISPLAY_GROUP", + "DISPLAY", + name, + self.cmd_SET_DISPLAY_GROUP, + desc=self.cmd_SET_DISPLAY_GROUP_help, + ) + if name == "display": + gcode.register_mux_command( + "SET_DISPLAY_GROUP", "DISPLAY", None, self.cmd_SET_DISPLAY_GROUP + ) + def get_dimensions(self): return self.lcd_chip.get_dimensions() + def handle_ready(self): self.lcd_chip.init() # Start screen update timer self.reactor.update_timer(self.screen_update_timer, self.reactor.NOW) + # Screen updating def screen_update_event(self, eventtime): if self.redraw_request_pending: @@ -237,14 +271,16 @@ class PrinterLCD: logging.exception("Error during display screen update") self.lcd_chip.flush() return eventtime + REDRAW_TIME + def request_redraw(self): if self.redraw_request_pending: return self.redraw_request_pending = True self.reactor.update_timer(self.screen_update_timer, self.redraw_time) + def draw_text(self, row, col, mixed_text, eventtime): pos = col - for i, text in enumerate(mixed_text.split('~')): + for i, text in enumerate(mixed_text.split("~")): if i & 1 == 0: # write text self.lcd_chip.write_text(pos, row, text.encode()) @@ -253,20 +289,24 @@ class PrinterLCD: # write glyph pos += self.lcd_chip.write_glyph(pos, row, text) return pos + def draw_progress_bar(self, row, col, width, value): - pixels = -1 << int(width * 8 * (1. - value) + .5) + pixels = -1 << int(width * 8 * (1.0 - value) + 0.5) pixels |= (1 << (width * 8 - 1)) | 1 for i in range(width): - data = [0xff] + [(pixels >> (i * 8)) & 0xff] * 14 + [0xff] + data = [0xFF] + [(pixels >> (i * 8)) & 0xFF] * 14 + [0xFF] self.lcd_chip.write_graphics(col + width - 1 - i, row, data) return "" + cmd_SET_DISPLAY_GROUP_help = "Set the active display group" + def cmd_SET_DISPLAY_GROUP(self, gcmd): - group = gcmd.get('GROUP') + group = gcmd.get("GROUP") new_dg = self.display_data_groups.get(group) if new_dg is None: raise gcmd.error("Unknown display_data group '%s'" % (group,)) self.show_data_group = new_dg + def load_config(config): return PrinterLCD(config) diff --git a/klippy/extras/display/font8x14.py b/klippy/extras/display/font8x14.py index 66592edb..b79d177d 100644 --- a/klippy/extras/display/font8x14.py +++ b/klippy/extras/display/font8x14.py @@ -17,260 +17,260 @@ ###################################################################### VGA_FONT = [ - b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00', - b'\x00\x00\x00\x7e\x81\xa5\x81\x81\xbd\x99\x81\x7e\x00\x00\x00\x00', - b'\x00\x00\x00\x7e\xff\xdb\xff\xff\xc3\xe7\xff\x7e\x00\x00\x00\x00', - b'\x00\x00\x00\x00\x6c\xfe\xfe\xfe\xfe\x7c\x38\x10\x00\x00\x00\x00', - b'\x00\x00\x00\x00\x10\x38\x7c\xfe\x7c\x38\x10\x00\x00\x00\x00\x00', - b'\x00\x00\x00\x18\x3c\x3c\xe7\xe7\xe7\x18\x18\x3c\x00\x00\x00\x00', - b'\x00\x00\x00\x18\x3c\x7e\xff\xff\x7e\x18\x18\x3c\x00\x00\x00\x00', - b'\x00\x00\x00\x00\x00\x00\x18\x3c\x3c\x18\x00\x00\x00\x00\x00\x00', - b'\x00\xff\xff\xff\xff\xff\xe7\xc3\xc3\xe7\xff\xff\xff\xff\xff\x00', - b'\x00\x00\x00\x00\x00\x3c\x66\x42\x42\x66\x3c\x00\x00\x00\x00\x00', - b'\x00\xff\xff\xff\xff\xc3\x99\xbd\xbd\x99\xc3\xff\xff\xff\xff\x00', - b'\x00\x00\x00\x1e\x0e\x1a\x32\x78\xcc\xcc\xcc\x78\x00\x00\x00\x00', - b'\x00\x00\x00\x3c\x66\x66\x66\x3c\x18\x7e\x18\x18\x00\x00\x00\x00', - b'\x00\x00\x00\x3f\x33\x3f\x30\x30\x30\x70\xf0\xe0\x00\x00\x00\x00', - b'\x00\x00\x00\x7f\x63\x7f\x63\x63\x63\x67\xe7\xe6\xc0\x00\x00\x00', - b'\x00\x00\x00\x18\x18\xdb\x3c\xe7\x3c\xdb\x18\x18\x00\x00\x00\x00', - b'\x00\x00\x00\x80\xc0\xe0\xf8\xfe\xf8\xe0\xc0\x80\x00\x00\x00\x00', - b'\x00\x00\x00\x02\x06\x0e\x3e\xfe\x3e\x0e\x06\x02\x00\x00\x00\x00', - b'\x00\x00\x00\x18\x3c\x7e\x18\x18\x18\x7e\x3c\x18\x00\x00\x00\x00', - b'\x00\x00\x00\x66\x66\x66\x66\x66\x66\x00\x66\x66\x00\x00\x00\x00', - b'\x00\x00\x00\x7f\xdb\xdb\xdb\x7b\x1b\x1b\x1b\x1b\x00\x00\x00\x00', - b'\x00\x00\x7c\xc6\x60\x38\x6c\xc6\xc6\x6c\x38\x0c\xc6\x7c\x00\x00', - b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\xfe\xfe\xfe\x00\x00\x00\x00', - b'\x00\x00\x00\x18\x3c\x7e\x18\x18\x18\x7e\x3c\x18\x7e\x00\x00\x00', - b'\x00\x00\x00\x18\x3c\x7e\x18\x18\x18\x18\x18\x18\x00\x00\x00\x00', - b'\x00\x00\x00\x18\x18\x18\x18\x18\x18\x7e\x3c\x18\x00\x00\x00\x00', - b'\x00\x00\x00\x00\x00\x18\x0c\xfe\x0c\x18\x00\x00\x00\x00\x00\x00', - b'\x00\x00\x00\x00\x00\x30\x60\xfe\x60\x30\x00\x00\x00\x00\x00\x00', - b'\x00\x00\x00\x00\x00\x00\xc0\xc0\xc0\xfe\x00\x00\x00\x00\x00\x00', - b'\x00\x00\x00\x00\x00\x28\x6c\xfe\x6c\x28\x00\x00\x00\x00\x00\x00', - b'\x00\x00\x00\x00\x10\x38\x38\x7c\x7c\xfe\xfe\x00\x00\x00\x00\x00', - b'\x00\x00\x00\x00\xfe\xfe\x7c\x7c\x38\x38\x10\x00\x00\x00\x00\x00', - b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00', - b'\x00\x00\x00\x18\x3c\x3c\x3c\x18\x18\x00\x18\x18\x00\x00\x00\x00', - b'\x00\x00\x66\x66\x66\x24\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00', - b'\x00\x00\x00\x6c\x6c\xfe\x6c\x6c\x6c\xfe\x6c\x6c\x00\x00\x00\x00', - b'\x00\x18\x18\x7c\xc6\xc2\xc0\x7c\x06\x86\xc6\x7c\x18\x18\x00\x00', - b'\x00\x00\x00\x00\x00\xc2\xc6\x0c\x18\x30\x66\xc6\x00\x00\x00\x00', - b'\x00\x00\x00\x38\x6c\x6c\x38\x76\xdc\xcc\xcc\x76\x00\x00\x00\x00', - b'\x00\x00\x30\x30\x30\x60\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00', - b'\x00\x00\x00\x0c\x18\x30\x30\x30\x30\x30\x18\x0c\x00\x00\x00\x00', - b'\x00\x00\x00\x30\x18\x0c\x0c\x0c\x0c\x0c\x18\x30\x00\x00\x00\x00', - b'\x00\x00\x00\x00\x00\x66\x3c\xff\x3c\x66\x00\x00\x00\x00\x00\x00', - b'\x00\x00\x00\x00\x00\x18\x18\x7e\x18\x18\x00\x00\x00\x00\x00\x00', - b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x18\x18\x18\x30\x00\x00\x00', - b'\x00\x00\x00\x00\x00\x00\x00\xfe\x00\x00\x00\x00\x00\x00\x00\x00', - b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x18\x18\x00\x00\x00\x00', - b'\x00\x00\x00\x02\x06\x0c\x18\x30\x60\xc0\x80\x00\x00\x00\x00\x00', - b'\x00\x00\x00\x7c\xc6\xce\xde\xf6\xe6\xc6\xc6\x7c\x00\x00\x00\x00', - b'\x00\x00\x00\x18\x38\x78\x18\x18\x18\x18\x18\x7e\x00\x00\x00\x00', - b'\x00\x00\x00\x7c\xc6\x06\x0c\x18\x30\x60\xc6\xfe\x00\x00\x00\x00', - b'\x00\x00\x00\x7c\xc6\x06\x06\x3c\x06\x06\xc6\x7c\x00\x00\x00\x00', - b'\x00\x00\x00\x0c\x1c\x3c\x6c\xcc\xfe\x0c\x0c\x1e\x00\x00\x00\x00', - b'\x00\x00\x00\xfe\xc0\xc0\xc0\xfc\x06\x06\xc6\x7c\x00\x00\x00\x00', - b'\x00\x00\x00\x38\x60\xc0\xc0\xfc\xc6\xc6\xc6\x7c\x00\x00\x00\x00', - b'\x00\x00\x00\xfe\xc6\x06\x0c\x18\x30\x30\x30\x30\x00\x00\x00\x00', - b'\x00\x00\x00\x7c\xc6\xc6\xc6\x7c\xc6\xc6\xc6\x7c\x00\x00\x00\x00', - b'\x00\x00\x00\x7c\xc6\xc6\xc6\x7e\x06\x06\x0c\x78\x00\x00\x00\x00', - b'\x00\x00\x00\x00\x18\x18\x00\x00\x00\x18\x18\x00\x00\x00\x00\x00', - b'\x00\x00\x00\x00\x18\x18\x00\x00\x00\x18\x18\x30\x00\x00\x00\x00', - b'\x00\x00\x00\x06\x0c\x18\x30\x60\x30\x18\x0c\x06\x00\x00\x00\x00', - b'\x00\x00\x00\x00\x00\x00\x7e\x00\x00\x7e\x00\x00\x00\x00\x00\x00', - b'\x00\x00\x00\x60\x30\x18\x0c\x06\x0c\x18\x30\x60\x00\x00\x00\x00', - b'\x00\x00\x00\x7c\xc6\xc6\x0c\x18\x18\x00\x18\x18\x00\x00\x00\x00', - b'\x00\x00\x00\x7c\xc6\xc6\xde\xde\xde\xdc\xc0\x7c\x00\x00\x00\x00', - b'\x00\x00\x00\x10\x38\x6c\xc6\xc6\xfe\xc6\xc6\xc6\x00\x00\x00\x00', - b'\x00\x00\x00\xfc\x66\x66\x66\x7c\x66\x66\x66\xfc\x00\x00\x00\x00', - b'\x00\x00\x00\x3c\x66\xc2\xc0\xc0\xc0\xc2\x66\x3c\x00\x00\x00\x00', - b'\x00\x00\x00\xf8\x6c\x66\x66\x66\x66\x66\x6c\xf8\x00\x00\x00\x00', - b'\x00\x00\x00\xfe\x66\x62\x68\x78\x68\x62\x66\xfe\x00\x00\x00\x00', - b'\x00\x00\x00\xfe\x66\x62\x68\x78\x68\x60\x60\xf0\x00\x00\x00\x00', - b'\x00\x00\x00\x3c\x66\xc2\xc0\xc0\xde\xc6\x66\x3a\x00\x00\x00\x00', - b'\x00\x00\x00\xc6\xc6\xc6\xc6\xfe\xc6\xc6\xc6\xc6\x00\x00\x00\x00', - b'\x00\x00\x00\x3c\x18\x18\x18\x18\x18\x18\x18\x3c\x00\x00\x00\x00', - b'\x00\x00\x00\x1e\x0c\x0c\x0c\x0c\x0c\xcc\xcc\x78\x00\x00\x00\x00', - b'\x00\x00\x00\xe6\x66\x6c\x6c\x78\x6c\x6c\x66\xe6\x00\x00\x00\x00', - b'\x00\x00\x00\xf0\x60\x60\x60\x60\x60\x62\x66\xfe\x00\x00\x00\x00', - b'\x00\x00\x00\xc6\xee\xfe\xfe\xd6\xc6\xc6\xc6\xc6\x00\x00\x00\x00', - b'\x00\x00\x00\xc6\xe6\xf6\xfe\xde\xce\xc6\xc6\xc6\x00\x00\x00\x00', - b'\x00\x00\x00\x38\x6c\xc6\xc6\xc6\xc6\xc6\x6c\x38\x00\x00\x00\x00', - b'\x00\x00\x00\xfc\x66\x66\x66\x7c\x60\x60\x60\xf0\x00\x00\x00\x00', - b'\x00\x00\x00\x7c\xc6\xc6\xc6\xc6\xd6\xde\x7c\x0c\x0e\x00\x00\x00', - b'\x00\x00\x00\xfc\x66\x66\x66\x7c\x6c\x66\x66\xe6\x00\x00\x00\x00', - b'\x00\x00\x00\x7c\xc6\xc6\x60\x38\x0c\xc6\xc6\x7c\x00\x00\x00\x00', - b'\x00\x00\x00\x7e\x7e\x5a\x18\x18\x18\x18\x18\x3c\x00\x00\x00\x00', - b'\x00\x00\x00\xc6\xc6\xc6\xc6\xc6\xc6\xc6\xc6\x7c\x00\x00\x00\x00', - b'\x00\x00\x00\xc6\xc6\xc6\xc6\xc6\xc6\x6c\x38\x10\x00\x00\x00\x00', - b'\x00\x00\x00\xc6\xc6\xc6\xc6\xd6\xd6\xfe\x7c\x6c\x00\x00\x00\x00', - b'\x00\x00\x00\xc6\xc6\x6c\x38\x38\x38\x6c\xc6\xc6\x00\x00\x00\x00', - b'\x00\x00\x00\x66\x66\x66\x66\x3c\x18\x18\x18\x3c\x00\x00\x00\x00', - b'\x00\x00\x00\xfe\xc6\x8c\x18\x30\x60\xc2\xc6\xfe\x00\x00\x00\x00', - b'\x00\x00\x00\x3c\x30\x30\x30\x30\x30\x30\x30\x3c\x00\x00\x00\x00', - b'\x00\x00\x00\x80\xc0\xe0\x70\x38\x1c\x0e\x06\x02\x00\x00\x00\x00', - b'\x00\x00\x00\x3c\x0c\x0c\x0c\x0c\x0c\x0c\x0c\x3c\x00\x00\x00\x00', - b'\x00\x10\x38\x6c\xc6\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00', - b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xff\x00\x00', - b'\x00\x30\x30\x18\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00', - b'\x00\x00\x00\x00\x00\x00\x78\x0c\x7c\xcc\xcc\x76\x00\x00\x00\x00', - b'\x00\x00\x00\xe0\x60\x60\x78\x6c\x66\x66\x66\x7c\x00\x00\x00\x00', - b'\x00\x00\x00\x00\x00\x00\x7c\xc6\xc0\xc0\xc6\x7c\x00\x00\x00\x00', - b'\x00\x00\x00\x1c\x0c\x0c\x3c\x6c\xcc\xcc\xcc\x76\x00\x00\x00\x00', - b'\x00\x00\x00\x00\x00\x00\x7c\xc6\xfe\xc0\xc6\x7c\x00\x00\x00\x00', - b'\x00\x00\x00\x38\x6c\x64\x60\xf0\x60\x60\x60\xf0\x00\x00\x00\x00', - b'\x00\x00\x00\x00\x00\x00\x76\xcc\xcc\xcc\x7c\x0c\xcc\x78\x00\x00', - b'\x00\x00\x00\xe0\x60\x60\x6c\x76\x66\x66\x66\xe6\x00\x00\x00\x00', - b'\x00\x00\x00\x18\x18\x00\x38\x18\x18\x18\x18\x3c\x00\x00\x00\x00', - b'\x00\x00\x00\x06\x06\x00\x0e\x06\x06\x06\x06\x66\x66\x3c\x00\x00', - b'\x00\x00\x00\xe0\x60\x60\x66\x6c\x78\x6c\x66\xe6\x00\x00\x00\x00', - b'\x00\x00\x00\x38\x18\x18\x18\x18\x18\x18\x18\x3c\x00\x00\x00\x00', - b'\x00\x00\x00\x00\x00\x00\xec\xfe\xd6\xd6\xd6\xc6\x00\x00\x00\x00', - b'\x00\x00\x00\x00\x00\x00\xdc\x66\x66\x66\x66\x66\x00\x00\x00\x00', - b'\x00\x00\x00\x00\x00\x00\x7c\xc6\xc6\xc6\xc6\x7c\x00\x00\x00\x00', - b'\x00\x00\x00\x00\x00\x00\xdc\x66\x66\x66\x7c\x60\x60\xf0\x00\x00', - b'\x00\x00\x00\x00\x00\x00\x76\xcc\xcc\xcc\x7c\x0c\x0c\x1e\x00\x00', - b'\x00\x00\x00\x00\x00\x00\xdc\x76\x66\x60\x60\xf0\x00\x00\x00\x00', - b'\x00\x00\x00\x00\x00\x00\x7c\xc6\x70\x1c\xc6\x7c\x00\x00\x00\x00', - b'\x00\x00\x00\x10\x30\x30\xfc\x30\x30\x30\x36\x1c\x00\x00\x00\x00', - b'\x00\x00\x00\x00\x00\x00\xcc\xcc\xcc\xcc\xcc\x76\x00\x00\x00\x00', - b'\x00\x00\x00\x00\x00\x00\x66\x66\x66\x66\x3c\x18\x00\x00\x00\x00', - b'\x00\x00\x00\x00\x00\x00\xc6\xc6\xd6\xd6\xfe\x6c\x00\x00\x00\x00', - b'\x00\x00\x00\x00\x00\x00\xc6\x6c\x38\x38\x6c\xc6\x00\x00\x00\x00', - b'\x00\x00\x00\x00\x00\x00\xc6\xc6\xc6\xc6\x7e\x06\x0c\xf8\x00\x00', - b'\x00\x00\x00\x00\x00\x00\xfe\xcc\x18\x30\x66\xfe\x00\x00\x00\x00', - b'\x00\x00\x00\x0e\x18\x18\x18\x70\x18\x18\x18\x0e\x00\x00\x00\x00', - b'\x00\x00\x00\x18\x18\x18\x18\x00\x18\x18\x18\x18\x00\x00\x00\x00', - b'\x00\x00\x00\x70\x18\x18\x18\x0e\x18\x18\x18\x70\x00\x00\x00\x00', - b'\x00\x00\x00\x76\xdc\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00', - b'\x00\x00\x00\x00\x00\x10\x38\x6c\xc6\xc6\xfe\x00\x00\x00\x00\x00', - b'\x00\x00\x00\x3c\x66\xc2\xc0\xc0\xc2\x66\x3c\x0c\x06\x7c\x00\x00', - b'\x00\x00\x00\xcc\xcc\x00\xcc\xcc\xcc\xcc\xcc\x76\x00\x00\x00\x00', - b'\x00\x00\x0c\x18\x30\x00\x7c\xc6\xfe\xc0\xc6\x7c\x00\x00\x00\x00', - b'\x00\x00\x10\x38\x6c\x00\x78\x0c\x7c\xcc\xcc\x76\x00\x00\x00\x00', - b'\x00\x00\x00\xcc\xcc\x00\x78\x0c\x7c\xcc\xcc\x76\x00\x00\x00\x00', - b'\x00\x00\x60\x30\x18\x00\x78\x0c\x7c\xcc\xcc\x76\x00\x00\x00\x00', - b'\x00\x00\x38\x6c\x38\x00\x78\x0c\x7c\xcc\xcc\x76\x00\x00\x00\x00', - b'\x00\x00\x00\x00\x00\x3c\x66\x60\x66\x3c\x0c\x06\x3c\x00\x00\x00', - b'\x00\x00\x10\x38\x6c\x00\x7c\xc6\xfe\xc0\xc6\x7c\x00\x00\x00\x00', - b'\x00\x00\x00\xcc\xcc\x00\x7c\xc6\xfe\xc0\xc6\x7c\x00\x00\x00\x00', - b'\x00\x00\x60\x30\x18\x00\x7c\xc6\xfe\xc0\xc6\x7c\x00\x00\x00\x00', - b'\x00\x00\x00\x66\x66\x00\x38\x18\x18\x18\x18\x3c\x00\x00\x00\x00', - b'\x00\x00\x18\x3c\x66\x00\x38\x18\x18\x18\x18\x3c\x00\x00\x00\x00', - b'\x00\x00\x60\x30\x18\x00\x38\x18\x18\x18\x18\x3c\x00\x00\x00\x00', - b'\x00\x00\xc6\xc6\x10\x38\x6c\xc6\xc6\xfe\xc6\xc6\x00\x00\x00\x00', - b'\x00\x38\x6c\x38\x00\x38\x6c\xc6\xc6\xfe\xc6\xc6\x00\x00\x00\x00', - b'\x00\x18\x30\x60\x00\xfe\x66\x60\x7c\x60\x66\xfe\x00\x00\x00\x00', - b'\x00\x00\x00\x00\x00\xcc\x76\x36\x7e\xd8\xd8\x6e\x00\x00\x00\x00', - b'\x00\x00\x00\x3e\x6c\xcc\xcc\xfe\xcc\xcc\xcc\xce\x00\x00\x00\x00', - b'\x00\x00\x10\x38\x6c\x00\x7c\xc6\xc6\xc6\xc6\x7c\x00\x00\x00\x00', - b'\x00\x00\x00\xc6\xc6\x00\x7c\xc6\xc6\xc6\xc6\x7c\x00\x00\x00\x00', - b'\x00\x00\x60\x30\x18\x00\x7c\xc6\xc6\xc6\xc6\x7c\x00\x00\x00\x00', - b'\x00\x00\x30\x78\xcc\x00\xcc\xcc\xcc\xcc\xcc\x76\x00\x00\x00\x00', - b'\x00\x00\x60\x30\x18\x00\xcc\xcc\xcc\xcc\xcc\x76\x00\x00\x00\x00', - b'\x00\x00\x00\xc6\xc6\x00\xc6\xc6\xc6\xc6\x7e\x06\x0c\x78\x00\x00', - b'\x00\x00\xc6\xc6\x38\x6c\xc6\xc6\xc6\xc6\x6c\x38\x00\x00\x00\x00', - b'\x00\x00\xc6\xc6\x00\xc6\xc6\xc6\xc6\xc6\xc6\x7c\x00\x00\x00\x00', - b'\x00\x00\x18\x18\x3c\x66\x60\x60\x66\x3c\x18\x18\x00\x00\x00\x00', - b'\x00\x00\x38\x6c\x64\x60\xf0\x60\x60\x60\xe6\xfc\x00\x00\x00\x00', - b'\x00\x00\x00\x66\x66\x3c\x18\x7e\x18\x7e\x18\x18\x00\x00\x00\x00', - b'\x00\x00\xf8\xcc\xcc\xf8\xc4\xcc\xde\xcc\xcc\xc6\x00\x00\x00\x00', - b'\x00\x00\x0e\x1b\x18\x18\x18\x7e\x18\x18\x18\x18\xd8\x70\x00\x00', - b'\x00\x00\x18\x30\x60\x00\x78\x0c\x7c\xcc\xcc\x76\x00\x00\x00\x00', - b'\x00\x00\x0c\x18\x30\x00\x38\x18\x18\x18\x18\x3c\x00\x00\x00\x00', - b'\x00\x00\x18\x30\x60\x00\x7c\xc6\xc6\xc6\xc6\x7c\x00\x00\x00\x00', - b'\x00\x00\x18\x30\x60\x00\xcc\xcc\xcc\xcc\xcc\x76\x00\x00\x00\x00', - b'\x00\x00\x00\x76\xdc\x00\xdc\x66\x66\x66\x66\x66\x00\x00\x00\x00', - b'\x00\x76\xdc\x00\xc6\xe6\xf6\xfe\xde\xce\xc6\xc6\x00\x00\x00\x00', - b'\x00\x00\x3c\x6c\x6c\x3e\x00\x7e\x00\x00\x00\x00\x00\x00\x00\x00', - b'\x00\x00\x38\x6c\x6c\x38\x00\x7c\x00\x00\x00\x00\x00\x00\x00\x00', - b'\x00\x00\x00\x30\x30\x00\x30\x30\x60\xc6\xc6\x7c\x00\x00\x00\x00', - b'\x00\x00\x00\x00\x00\x00\x00\xfe\xc0\xc0\xc0\x00\x00\x00\x00\x00', - b'\x00\x00\x00\x00\x00\x00\x00\xfe\x06\x06\x06\x00\x00\x00\x00\x00', - b'\x00\x00\xc0\xc0\xc6\xcc\xd8\x30\x60\xdc\x86\x0c\x18\x3e\x00\x00', - b'\x00\x00\xc0\xc0\xc6\xcc\xd8\x30\x66\xce\x9e\x3e\x06\x06\x00\x00', - b'\x00\x00\x00\x18\x18\x00\x18\x18\x3c\x3c\x3c\x18\x00\x00\x00\x00', - b'\x00\x00\x00\x00\x00\x36\x6c\xd8\x6c\x36\x00\x00\x00\x00\x00\x00', - b'\x00\x00\x00\x00\x00\xd8\x6c\x36\x6c\xd8\x00\x00\x00\x00\x00\x00', - b'\x00\x11\x44\x11\x44\x11\x44\x11\x44\x11\x44\x11\x44\x11\x44\x00', - b'\x00\x55\xaa\x55\xaa\x55\xaa\x55\xaa\x55\xaa\x55\xaa\x55\xaa\x00', - b'\x00\xdd\x77\xdd\x77\xdd\x77\xdd\x77\xdd\x77\xdd\x77\xdd\x77\x00', - b'\x00\x18\x18\x18\x18\x18\x18\x18\x18\x18\x18\x18\x18\x18\x18\x00', - b'\x00\x18\x18\x18\x18\x18\x18\x18\xf8\x18\x18\x18\x18\x18\x18\x00', - b'\x00\x18\x18\x18\x18\x18\xf8\x18\xf8\x18\x18\x18\x18\x18\x18\x00', - b'\x00\x36\x36\x36\x36\x36\x36\x36\xf6\x36\x36\x36\x36\x36\x36\x00', - b'\x00\x00\x00\x00\x00\x00\x00\x00\xfe\x36\x36\x36\x36\x36\x36\x00', - b'\x00\x00\x00\x00\x00\x00\xf8\x18\xf8\x18\x18\x18\x18\x18\x18\x00', - b'\x00\x36\x36\x36\x36\x36\xf6\x06\xf6\x36\x36\x36\x36\x36\x36\x00', - b'\x00\x36\x36\x36\x36\x36\x36\x36\x36\x36\x36\x36\x36\x36\x36\x00', - b'\x00\x00\x00\x00\x00\x00\xfe\x06\xf6\x36\x36\x36\x36\x36\x36\x00', - b'\x00\x36\x36\x36\x36\x36\xf6\x06\xfe\x00\x00\x00\x00\x00\x00\x00', - b'\x00\x36\x36\x36\x36\x36\x36\x36\xfe\x00\x00\x00\x00\x00\x00\x00', - b'\x00\x18\x18\x18\x18\x18\xf8\x18\xf8\x00\x00\x00\x00\x00\x00\x00', - b'\x00\x00\x00\x00\x00\x00\x00\x00\xf8\x18\x18\x18\x18\x18\x18\x00', - b'\x00\x18\x18\x18\x18\x18\x18\x18\x1f\x00\x00\x00\x00\x00\x00\x00', - b'\x00\x18\x18\x18\x18\x18\x18\x18\xff\x00\x00\x00\x00\x00\x00\x00', - b'\x00\x00\x00\x00\x00\x00\x00\x00\xff\x18\x18\x18\x18\x18\x18\x00', - b'\x00\x18\x18\x18\x18\x18\x18\x18\x1f\x18\x18\x18\x18\x18\x18\x00', - b'\x00\x00\x00\x00\x00\x00\x00\x00\xff\x00\x00\x00\x00\x00\x00\x00', - b'\x00\x18\x18\x18\x18\x18\x18\x18\xff\x18\x18\x18\x18\x18\x18\x00', - b'\x00\x18\x18\x18\x18\x18\x1f\x18\x1f\x18\x18\x18\x18\x18\x18\x00', - b'\x00\x36\x36\x36\x36\x36\x36\x36\x37\x36\x36\x36\x36\x36\x36\x00', - b'\x00\x36\x36\x36\x36\x36\x37\x30\x3f\x00\x00\x00\x00\x00\x00\x00', - b'\x00\x00\x00\x00\x00\x00\x3f\x30\x37\x36\x36\x36\x36\x36\x36\x00', - b'\x00\x36\x36\x36\x36\x36\xf7\x00\xff\x00\x00\x00\x00\x00\x00\x00', - b'\x00\x00\x00\x00\x00\x00\xff\x00\xf7\x36\x36\x36\x36\x36\x36\x00', - b'\x00\x36\x36\x36\x36\x36\x37\x30\x37\x36\x36\x36\x36\x36\x36\x00', - b'\x00\x00\x00\x00\x00\x00\xff\x00\xff\x00\x00\x00\x00\x00\x00\x00', - b'\x00\x36\x36\x36\x36\x36\xf7\x00\xf7\x36\x36\x36\x36\x36\x36\x00', - b'\x00\x18\x18\x18\x18\x18\xff\x00\xff\x00\x00\x00\x00\x00\x00\x00', - b'\x00\x36\x36\x36\x36\x36\x36\x36\xff\x00\x00\x00\x00\x00\x00\x00', - b'\x00\x00\x00\x00\x00\x00\xff\x00\xff\x18\x18\x18\x18\x18\x18\x00', - b'\x00\x00\x00\x00\x00\x00\x00\x00\xff\x36\x36\x36\x36\x36\x36\x00', - b'\x00\x36\x36\x36\x36\x36\x36\x36\x3f\x00\x00\x00\x00\x00\x00\x00', - b'\x00\x18\x18\x18\x18\x18\x1f\x18\x1f\x00\x00\x00\x00\x00\x00\x00', - b'\x00\x00\x00\x00\x00\x00\x1f\x18\x1f\x18\x18\x18\x18\x18\x18\x00', - b'\x00\x00\x00\x00\x00\x00\x00\x00\x3f\x36\x36\x36\x36\x36\x36\x00', - b'\x00\x36\x36\x36\x36\x36\x36\x36\xff\x36\x36\x36\x36\x36\x36\x00', - b'\x00\x18\x18\x18\x18\x18\xff\x18\xff\x18\x18\x18\x18\x18\x18\x00', - b'\x00\x18\x18\x18\x18\x18\x18\x18\xf8\x00\x00\x00\x00\x00\x00\x00', - b'\x00\x00\x00\x00\x00\x00\x00\x00\x1f\x18\x18\x18\x18\x18\x18\x00', - b'\x00\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\x00', - b'\x00\x00\x00\x00\x00\x00\x00\x00\xff\xff\xff\xff\xff\xff\xff\x00', - b'\x00\xf0\xf0\xf0\xf0\xf0\xf0\xf0\xf0\xf0\xf0\xf0\xf0\xf0\xf0\x00', - b'\x00\x0f\x0f\x0f\x0f\x0f\x0f\x0f\x0f\x0f\x0f\x0f\x0f\x0f\x0f\x00', - b'\x00\xff\xff\xff\xff\xff\xff\xff\x00\x00\x00\x00\x00\x00\x00\x00', - b'\x00\x00\x00\x00\x00\x00\x76\xdc\xd8\xd8\xdc\x76\x00\x00\x00\x00', - b'\x00\x00\x00\x00\x00\x7c\xc6\xfc\xc6\xc6\xfc\xc0\xc0\x40\x00\x00', - b'\x00\x00\x00\xfe\xc6\xc6\xc0\xc0\xc0\xc0\xc0\xc0\x00\x00\x00\x00', - b'\x00\x00\x00\x00\x00\xfe\x6c\x6c\x6c\x6c\x6c\x6c\x00\x00\x00\x00', - b'\x00\x00\x00\xfe\xc6\x60\x30\x18\x30\x60\xc6\xfe\x00\x00\x00\x00', - b'\x00\x00\x00\x00\x00\x00\x7e\xd8\xd8\xd8\xd8\x70\x00\x00\x00\x00', - b'\x00\x00\x00\x00\x00\x66\x66\x66\x66\x7c\x60\x60\xc0\x00\x00\x00', - b'\x00\x00\x00\x00\x00\x76\xdc\x18\x18\x18\x18\x18\x00\x00\x00\x00', - b'\x00\x00\x00\x7e\x18\x3c\x66\x66\x66\x3c\x18\x7e\x00\x00\x00\x00', - b'\x00\x00\x00\x38\x6c\xc6\xc6\xfe\xc6\xc6\x6c\x38\x00\x00\x00\x00', - b'\x00\x00\x00\x38\x6c\xc6\xc6\xc6\x6c\x6c\x6c\xee\x00\x00\x00\x00', - b'\x00\x00\x00\x1e\x30\x18\x0c\x3e\x66\x66\x66\x3c\x00\x00\x00\x00', - b'\x00\x00\x00\x00\x00\x00\x7e\xdb\xdb\x7e\x00\x00\x00\x00\x00\x00', - b'\x00\x00\x00\x03\x06\x7e\xdb\xdb\xf3\x7e\x60\xc0\x00\x00\x00\x00', - b'\x00\x00\x00\x1c\x30\x60\x60\x7c\x60\x60\x30\x1c\x00\x00\x00\x00', - b'\x00\x00\x00\x00\x7c\xc6\xc6\xc6\xc6\xc6\xc6\xc6\x00\x00\x00\x00', - b'\x00\x00\x00\x00\xfe\x00\x00\xfe\x00\x00\xfe\x00\x00\x00\x00\x00', - b'\x00\x00\x00\x00\x18\x18\x7e\x18\x18\x00\x00\xff\x00\x00\x00\x00', - b'\x00\x00\x00\x30\x18\x0c\x06\x0c\x18\x30\x00\x7e\x00\x00\x00\x00', - b'\x00\x00\x00\x0c\x18\x30\x60\x30\x18\x0c\x00\x7e\x00\x00\x00\x00', - b'\x00\x00\x00\x0e\x1b\x1b\x18\x18\x18\x18\x18\x18\x18\x18\x18\x00', - b'\x00\x18\x18\x18\x18\x18\x18\x18\x18\xd8\xd8\x70\x00\x00\x00\x00', - b'\x00\x00\x00\x00\x18\x18\x00\x7e\x00\x18\x18\x00\x00\x00\x00\x00', - b'\x00\x00\x00\x00\x00\x76\xdc\x00\x76\xdc\x00\x00\x00\x00\x00\x00', - b'\x00\x00\x38\x6c\x6c\x38\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00', - b'\x00\x00\x00\x00\x00\x00\x00\x18\x18\x00\x00\x00\x00\x00\x00\x00', - b'\x00\x00\x00\x00\x00\x00\x00\x00\x18\x00\x00\x00\x00\x00\x00\x00', - b'\x00\x00\x0f\x0c\x0c\x0c\x0c\x0c\xec\x6c\x3c\x1c\x00\x00\x00\x00', - b'\x00\x00\xd8\x6c\x6c\x6c\x6c\x6c\x00\x00\x00\x00\x00\x00\x00\x00', - b'\x00\x00\x70\xd8\x30\x60\xc8\xf8\x00\x00\x00\x00\x00\x00\x00\x00', - b'\x00\x00\x00\x00\x00\x7c\x7c\x7c\x7c\x7c\x7c\x00\x00\x00\x00\x00', - b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00' + b"\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00", + b"\x00\x00\x00\x7e\x81\xa5\x81\x81\xbd\x99\x81\x7e\x00\x00\x00\x00", + b"\x00\x00\x00\x7e\xff\xdb\xff\xff\xc3\xe7\xff\x7e\x00\x00\x00\x00", + b"\x00\x00\x00\x00\x6c\xfe\xfe\xfe\xfe\x7c\x38\x10\x00\x00\x00\x00", + b"\x00\x00\x00\x00\x10\x38\x7c\xfe\x7c\x38\x10\x00\x00\x00\x00\x00", + b"\x00\x00\x00\x18\x3c\x3c\xe7\xe7\xe7\x18\x18\x3c\x00\x00\x00\x00", + b"\x00\x00\x00\x18\x3c\x7e\xff\xff\x7e\x18\x18\x3c\x00\x00\x00\x00", + b"\x00\x00\x00\x00\x00\x00\x18\x3c\x3c\x18\x00\x00\x00\x00\x00\x00", + b"\x00\xff\xff\xff\xff\xff\xe7\xc3\xc3\xe7\xff\xff\xff\xff\xff\x00", + b"\x00\x00\x00\x00\x00\x3c\x66\x42\x42\x66\x3c\x00\x00\x00\x00\x00", + b"\x00\xff\xff\xff\xff\xc3\x99\xbd\xbd\x99\xc3\xff\xff\xff\xff\x00", + b"\x00\x00\x00\x1e\x0e\x1a\x32\x78\xcc\xcc\xcc\x78\x00\x00\x00\x00", + b"\x00\x00\x00\x3c\x66\x66\x66\x3c\x18\x7e\x18\x18\x00\x00\x00\x00", + b"\x00\x00\x00\x3f\x33\x3f\x30\x30\x30\x70\xf0\xe0\x00\x00\x00\x00", + b"\x00\x00\x00\x7f\x63\x7f\x63\x63\x63\x67\xe7\xe6\xc0\x00\x00\x00", + b"\x00\x00\x00\x18\x18\xdb\x3c\xe7\x3c\xdb\x18\x18\x00\x00\x00\x00", + b"\x00\x00\x00\x80\xc0\xe0\xf8\xfe\xf8\xe0\xc0\x80\x00\x00\x00\x00", + b"\x00\x00\x00\x02\x06\x0e\x3e\xfe\x3e\x0e\x06\x02\x00\x00\x00\x00", + b"\x00\x00\x00\x18\x3c\x7e\x18\x18\x18\x7e\x3c\x18\x00\x00\x00\x00", + b"\x00\x00\x00\x66\x66\x66\x66\x66\x66\x00\x66\x66\x00\x00\x00\x00", + b"\x00\x00\x00\x7f\xdb\xdb\xdb\x7b\x1b\x1b\x1b\x1b\x00\x00\x00\x00", + b"\x00\x00\x7c\xc6\x60\x38\x6c\xc6\xc6\x6c\x38\x0c\xc6\x7c\x00\x00", + b"\x00\x00\x00\x00\x00\x00\x00\x00\x00\xfe\xfe\xfe\x00\x00\x00\x00", + b"\x00\x00\x00\x18\x3c\x7e\x18\x18\x18\x7e\x3c\x18\x7e\x00\x00\x00", + b"\x00\x00\x00\x18\x3c\x7e\x18\x18\x18\x18\x18\x18\x00\x00\x00\x00", + b"\x00\x00\x00\x18\x18\x18\x18\x18\x18\x7e\x3c\x18\x00\x00\x00\x00", + b"\x00\x00\x00\x00\x00\x18\x0c\xfe\x0c\x18\x00\x00\x00\x00\x00\x00", + b"\x00\x00\x00\x00\x00\x30\x60\xfe\x60\x30\x00\x00\x00\x00\x00\x00", + b"\x00\x00\x00\x00\x00\x00\xc0\xc0\xc0\xfe\x00\x00\x00\x00\x00\x00", + b"\x00\x00\x00\x00\x00\x28\x6c\xfe\x6c\x28\x00\x00\x00\x00\x00\x00", + b"\x00\x00\x00\x00\x10\x38\x38\x7c\x7c\xfe\xfe\x00\x00\x00\x00\x00", + b"\x00\x00\x00\x00\xfe\xfe\x7c\x7c\x38\x38\x10\x00\x00\x00\x00\x00", + b"\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00", + b"\x00\x00\x00\x18\x3c\x3c\x3c\x18\x18\x00\x18\x18\x00\x00\x00\x00", + b"\x00\x00\x66\x66\x66\x24\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00", + b"\x00\x00\x00\x6c\x6c\xfe\x6c\x6c\x6c\xfe\x6c\x6c\x00\x00\x00\x00", + b"\x00\x18\x18\x7c\xc6\xc2\xc0\x7c\x06\x86\xc6\x7c\x18\x18\x00\x00", + b"\x00\x00\x00\x00\x00\xc2\xc6\x0c\x18\x30\x66\xc6\x00\x00\x00\x00", + b"\x00\x00\x00\x38\x6c\x6c\x38\x76\xdc\xcc\xcc\x76\x00\x00\x00\x00", + b"\x00\x00\x30\x30\x30\x60\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00", + b"\x00\x00\x00\x0c\x18\x30\x30\x30\x30\x30\x18\x0c\x00\x00\x00\x00", + b"\x00\x00\x00\x30\x18\x0c\x0c\x0c\x0c\x0c\x18\x30\x00\x00\x00\x00", + b"\x00\x00\x00\x00\x00\x66\x3c\xff\x3c\x66\x00\x00\x00\x00\x00\x00", + b"\x00\x00\x00\x00\x00\x18\x18\x7e\x18\x18\x00\x00\x00\x00\x00\x00", + b"\x00\x00\x00\x00\x00\x00\x00\x00\x00\x18\x18\x18\x30\x00\x00\x00", + b"\x00\x00\x00\x00\x00\x00\x00\xfe\x00\x00\x00\x00\x00\x00\x00\x00", + b"\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x18\x18\x00\x00\x00\x00", + b"\x00\x00\x00\x02\x06\x0c\x18\x30\x60\xc0\x80\x00\x00\x00\x00\x00", + b"\x00\x00\x00\x7c\xc6\xce\xde\xf6\xe6\xc6\xc6\x7c\x00\x00\x00\x00", + b"\x00\x00\x00\x18\x38\x78\x18\x18\x18\x18\x18\x7e\x00\x00\x00\x00", + b"\x00\x00\x00\x7c\xc6\x06\x0c\x18\x30\x60\xc6\xfe\x00\x00\x00\x00", + b"\x00\x00\x00\x7c\xc6\x06\x06\x3c\x06\x06\xc6\x7c\x00\x00\x00\x00", + b"\x00\x00\x00\x0c\x1c\x3c\x6c\xcc\xfe\x0c\x0c\x1e\x00\x00\x00\x00", + b"\x00\x00\x00\xfe\xc0\xc0\xc0\xfc\x06\x06\xc6\x7c\x00\x00\x00\x00", + b"\x00\x00\x00\x38\x60\xc0\xc0\xfc\xc6\xc6\xc6\x7c\x00\x00\x00\x00", + b"\x00\x00\x00\xfe\xc6\x06\x0c\x18\x30\x30\x30\x30\x00\x00\x00\x00", + b"\x00\x00\x00\x7c\xc6\xc6\xc6\x7c\xc6\xc6\xc6\x7c\x00\x00\x00\x00", + b"\x00\x00\x00\x7c\xc6\xc6\xc6\x7e\x06\x06\x0c\x78\x00\x00\x00\x00", + b"\x00\x00\x00\x00\x18\x18\x00\x00\x00\x18\x18\x00\x00\x00\x00\x00", + b"\x00\x00\x00\x00\x18\x18\x00\x00\x00\x18\x18\x30\x00\x00\x00\x00", + b"\x00\x00\x00\x06\x0c\x18\x30\x60\x30\x18\x0c\x06\x00\x00\x00\x00", + b"\x00\x00\x00\x00\x00\x00\x7e\x00\x00\x7e\x00\x00\x00\x00\x00\x00", + b"\x00\x00\x00\x60\x30\x18\x0c\x06\x0c\x18\x30\x60\x00\x00\x00\x00", + b"\x00\x00\x00\x7c\xc6\xc6\x0c\x18\x18\x00\x18\x18\x00\x00\x00\x00", + b"\x00\x00\x00\x7c\xc6\xc6\xde\xde\xde\xdc\xc0\x7c\x00\x00\x00\x00", + b"\x00\x00\x00\x10\x38\x6c\xc6\xc6\xfe\xc6\xc6\xc6\x00\x00\x00\x00", + b"\x00\x00\x00\xfc\x66\x66\x66\x7c\x66\x66\x66\xfc\x00\x00\x00\x00", + b"\x00\x00\x00\x3c\x66\xc2\xc0\xc0\xc0\xc2\x66\x3c\x00\x00\x00\x00", + b"\x00\x00\x00\xf8\x6c\x66\x66\x66\x66\x66\x6c\xf8\x00\x00\x00\x00", + b"\x00\x00\x00\xfe\x66\x62\x68\x78\x68\x62\x66\xfe\x00\x00\x00\x00", + b"\x00\x00\x00\xfe\x66\x62\x68\x78\x68\x60\x60\xf0\x00\x00\x00\x00", + b"\x00\x00\x00\x3c\x66\xc2\xc0\xc0\xde\xc6\x66\x3a\x00\x00\x00\x00", + b"\x00\x00\x00\xc6\xc6\xc6\xc6\xfe\xc6\xc6\xc6\xc6\x00\x00\x00\x00", + b"\x00\x00\x00\x3c\x18\x18\x18\x18\x18\x18\x18\x3c\x00\x00\x00\x00", + b"\x00\x00\x00\x1e\x0c\x0c\x0c\x0c\x0c\xcc\xcc\x78\x00\x00\x00\x00", + b"\x00\x00\x00\xe6\x66\x6c\x6c\x78\x6c\x6c\x66\xe6\x00\x00\x00\x00", + b"\x00\x00\x00\xf0\x60\x60\x60\x60\x60\x62\x66\xfe\x00\x00\x00\x00", + b"\x00\x00\x00\xc6\xee\xfe\xfe\xd6\xc6\xc6\xc6\xc6\x00\x00\x00\x00", + b"\x00\x00\x00\xc6\xe6\xf6\xfe\xde\xce\xc6\xc6\xc6\x00\x00\x00\x00", + b"\x00\x00\x00\x38\x6c\xc6\xc6\xc6\xc6\xc6\x6c\x38\x00\x00\x00\x00", + b"\x00\x00\x00\xfc\x66\x66\x66\x7c\x60\x60\x60\xf0\x00\x00\x00\x00", + b"\x00\x00\x00\x7c\xc6\xc6\xc6\xc6\xd6\xde\x7c\x0c\x0e\x00\x00\x00", + b"\x00\x00\x00\xfc\x66\x66\x66\x7c\x6c\x66\x66\xe6\x00\x00\x00\x00", + b"\x00\x00\x00\x7c\xc6\xc6\x60\x38\x0c\xc6\xc6\x7c\x00\x00\x00\x00", + b"\x00\x00\x00\x7e\x7e\x5a\x18\x18\x18\x18\x18\x3c\x00\x00\x00\x00", + b"\x00\x00\x00\xc6\xc6\xc6\xc6\xc6\xc6\xc6\xc6\x7c\x00\x00\x00\x00", + b"\x00\x00\x00\xc6\xc6\xc6\xc6\xc6\xc6\x6c\x38\x10\x00\x00\x00\x00", + b"\x00\x00\x00\xc6\xc6\xc6\xc6\xd6\xd6\xfe\x7c\x6c\x00\x00\x00\x00", + b"\x00\x00\x00\xc6\xc6\x6c\x38\x38\x38\x6c\xc6\xc6\x00\x00\x00\x00", + b"\x00\x00\x00\x66\x66\x66\x66\x3c\x18\x18\x18\x3c\x00\x00\x00\x00", + b"\x00\x00\x00\xfe\xc6\x8c\x18\x30\x60\xc2\xc6\xfe\x00\x00\x00\x00", + b"\x00\x00\x00\x3c\x30\x30\x30\x30\x30\x30\x30\x3c\x00\x00\x00\x00", + b"\x00\x00\x00\x80\xc0\xe0\x70\x38\x1c\x0e\x06\x02\x00\x00\x00\x00", + b"\x00\x00\x00\x3c\x0c\x0c\x0c\x0c\x0c\x0c\x0c\x3c\x00\x00\x00\x00", + b"\x00\x10\x38\x6c\xc6\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00", + b"\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xff\x00\x00", + b"\x00\x30\x30\x18\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00", + b"\x00\x00\x00\x00\x00\x00\x78\x0c\x7c\xcc\xcc\x76\x00\x00\x00\x00", + b"\x00\x00\x00\xe0\x60\x60\x78\x6c\x66\x66\x66\x7c\x00\x00\x00\x00", + b"\x00\x00\x00\x00\x00\x00\x7c\xc6\xc0\xc0\xc6\x7c\x00\x00\x00\x00", + b"\x00\x00\x00\x1c\x0c\x0c\x3c\x6c\xcc\xcc\xcc\x76\x00\x00\x00\x00", + b"\x00\x00\x00\x00\x00\x00\x7c\xc6\xfe\xc0\xc6\x7c\x00\x00\x00\x00", + b"\x00\x00\x00\x38\x6c\x64\x60\xf0\x60\x60\x60\xf0\x00\x00\x00\x00", + b"\x00\x00\x00\x00\x00\x00\x76\xcc\xcc\xcc\x7c\x0c\xcc\x78\x00\x00", + b"\x00\x00\x00\xe0\x60\x60\x6c\x76\x66\x66\x66\xe6\x00\x00\x00\x00", + b"\x00\x00\x00\x18\x18\x00\x38\x18\x18\x18\x18\x3c\x00\x00\x00\x00", + b"\x00\x00\x00\x06\x06\x00\x0e\x06\x06\x06\x06\x66\x66\x3c\x00\x00", + b"\x00\x00\x00\xe0\x60\x60\x66\x6c\x78\x6c\x66\xe6\x00\x00\x00\x00", + b"\x00\x00\x00\x38\x18\x18\x18\x18\x18\x18\x18\x3c\x00\x00\x00\x00", + b"\x00\x00\x00\x00\x00\x00\xec\xfe\xd6\xd6\xd6\xc6\x00\x00\x00\x00", + b"\x00\x00\x00\x00\x00\x00\xdc\x66\x66\x66\x66\x66\x00\x00\x00\x00", + b"\x00\x00\x00\x00\x00\x00\x7c\xc6\xc6\xc6\xc6\x7c\x00\x00\x00\x00", + b"\x00\x00\x00\x00\x00\x00\xdc\x66\x66\x66\x7c\x60\x60\xf0\x00\x00", + b"\x00\x00\x00\x00\x00\x00\x76\xcc\xcc\xcc\x7c\x0c\x0c\x1e\x00\x00", + b"\x00\x00\x00\x00\x00\x00\xdc\x76\x66\x60\x60\xf0\x00\x00\x00\x00", + b"\x00\x00\x00\x00\x00\x00\x7c\xc6\x70\x1c\xc6\x7c\x00\x00\x00\x00", + b"\x00\x00\x00\x10\x30\x30\xfc\x30\x30\x30\x36\x1c\x00\x00\x00\x00", + b"\x00\x00\x00\x00\x00\x00\xcc\xcc\xcc\xcc\xcc\x76\x00\x00\x00\x00", + b"\x00\x00\x00\x00\x00\x00\x66\x66\x66\x66\x3c\x18\x00\x00\x00\x00", + b"\x00\x00\x00\x00\x00\x00\xc6\xc6\xd6\xd6\xfe\x6c\x00\x00\x00\x00", + b"\x00\x00\x00\x00\x00\x00\xc6\x6c\x38\x38\x6c\xc6\x00\x00\x00\x00", + b"\x00\x00\x00\x00\x00\x00\xc6\xc6\xc6\xc6\x7e\x06\x0c\xf8\x00\x00", + b"\x00\x00\x00\x00\x00\x00\xfe\xcc\x18\x30\x66\xfe\x00\x00\x00\x00", + b"\x00\x00\x00\x0e\x18\x18\x18\x70\x18\x18\x18\x0e\x00\x00\x00\x00", + b"\x00\x00\x00\x18\x18\x18\x18\x00\x18\x18\x18\x18\x00\x00\x00\x00", + b"\x00\x00\x00\x70\x18\x18\x18\x0e\x18\x18\x18\x70\x00\x00\x00\x00", + b"\x00\x00\x00\x76\xdc\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00", + b"\x00\x00\x00\x00\x00\x10\x38\x6c\xc6\xc6\xfe\x00\x00\x00\x00\x00", + b"\x00\x00\x00\x3c\x66\xc2\xc0\xc0\xc2\x66\x3c\x0c\x06\x7c\x00\x00", + b"\x00\x00\x00\xcc\xcc\x00\xcc\xcc\xcc\xcc\xcc\x76\x00\x00\x00\x00", + b"\x00\x00\x0c\x18\x30\x00\x7c\xc6\xfe\xc0\xc6\x7c\x00\x00\x00\x00", + b"\x00\x00\x10\x38\x6c\x00\x78\x0c\x7c\xcc\xcc\x76\x00\x00\x00\x00", + b"\x00\x00\x00\xcc\xcc\x00\x78\x0c\x7c\xcc\xcc\x76\x00\x00\x00\x00", + b"\x00\x00\x60\x30\x18\x00\x78\x0c\x7c\xcc\xcc\x76\x00\x00\x00\x00", + b"\x00\x00\x38\x6c\x38\x00\x78\x0c\x7c\xcc\xcc\x76\x00\x00\x00\x00", + b"\x00\x00\x00\x00\x00\x3c\x66\x60\x66\x3c\x0c\x06\x3c\x00\x00\x00", + b"\x00\x00\x10\x38\x6c\x00\x7c\xc6\xfe\xc0\xc6\x7c\x00\x00\x00\x00", + b"\x00\x00\x00\xcc\xcc\x00\x7c\xc6\xfe\xc0\xc6\x7c\x00\x00\x00\x00", + b"\x00\x00\x60\x30\x18\x00\x7c\xc6\xfe\xc0\xc6\x7c\x00\x00\x00\x00", + b"\x00\x00\x00\x66\x66\x00\x38\x18\x18\x18\x18\x3c\x00\x00\x00\x00", + b"\x00\x00\x18\x3c\x66\x00\x38\x18\x18\x18\x18\x3c\x00\x00\x00\x00", + b"\x00\x00\x60\x30\x18\x00\x38\x18\x18\x18\x18\x3c\x00\x00\x00\x00", + b"\x00\x00\xc6\xc6\x10\x38\x6c\xc6\xc6\xfe\xc6\xc6\x00\x00\x00\x00", + b"\x00\x38\x6c\x38\x00\x38\x6c\xc6\xc6\xfe\xc6\xc6\x00\x00\x00\x00", + b"\x00\x18\x30\x60\x00\xfe\x66\x60\x7c\x60\x66\xfe\x00\x00\x00\x00", + b"\x00\x00\x00\x00\x00\xcc\x76\x36\x7e\xd8\xd8\x6e\x00\x00\x00\x00", + b"\x00\x00\x00\x3e\x6c\xcc\xcc\xfe\xcc\xcc\xcc\xce\x00\x00\x00\x00", + b"\x00\x00\x10\x38\x6c\x00\x7c\xc6\xc6\xc6\xc6\x7c\x00\x00\x00\x00", + b"\x00\x00\x00\xc6\xc6\x00\x7c\xc6\xc6\xc6\xc6\x7c\x00\x00\x00\x00", + b"\x00\x00\x60\x30\x18\x00\x7c\xc6\xc6\xc6\xc6\x7c\x00\x00\x00\x00", + b"\x00\x00\x30\x78\xcc\x00\xcc\xcc\xcc\xcc\xcc\x76\x00\x00\x00\x00", + b"\x00\x00\x60\x30\x18\x00\xcc\xcc\xcc\xcc\xcc\x76\x00\x00\x00\x00", + b"\x00\x00\x00\xc6\xc6\x00\xc6\xc6\xc6\xc6\x7e\x06\x0c\x78\x00\x00", + b"\x00\x00\xc6\xc6\x38\x6c\xc6\xc6\xc6\xc6\x6c\x38\x00\x00\x00\x00", + b"\x00\x00\xc6\xc6\x00\xc6\xc6\xc6\xc6\xc6\xc6\x7c\x00\x00\x00\x00", + b"\x00\x00\x18\x18\x3c\x66\x60\x60\x66\x3c\x18\x18\x00\x00\x00\x00", + b"\x00\x00\x38\x6c\x64\x60\xf0\x60\x60\x60\xe6\xfc\x00\x00\x00\x00", + b"\x00\x00\x00\x66\x66\x3c\x18\x7e\x18\x7e\x18\x18\x00\x00\x00\x00", + b"\x00\x00\xf8\xcc\xcc\xf8\xc4\xcc\xde\xcc\xcc\xc6\x00\x00\x00\x00", + b"\x00\x00\x0e\x1b\x18\x18\x18\x7e\x18\x18\x18\x18\xd8\x70\x00\x00", + b"\x00\x00\x18\x30\x60\x00\x78\x0c\x7c\xcc\xcc\x76\x00\x00\x00\x00", + b"\x00\x00\x0c\x18\x30\x00\x38\x18\x18\x18\x18\x3c\x00\x00\x00\x00", + b"\x00\x00\x18\x30\x60\x00\x7c\xc6\xc6\xc6\xc6\x7c\x00\x00\x00\x00", + b"\x00\x00\x18\x30\x60\x00\xcc\xcc\xcc\xcc\xcc\x76\x00\x00\x00\x00", + b"\x00\x00\x00\x76\xdc\x00\xdc\x66\x66\x66\x66\x66\x00\x00\x00\x00", + b"\x00\x76\xdc\x00\xc6\xe6\xf6\xfe\xde\xce\xc6\xc6\x00\x00\x00\x00", + b"\x00\x00\x3c\x6c\x6c\x3e\x00\x7e\x00\x00\x00\x00\x00\x00\x00\x00", + b"\x00\x00\x38\x6c\x6c\x38\x00\x7c\x00\x00\x00\x00\x00\x00\x00\x00", + b"\x00\x00\x00\x30\x30\x00\x30\x30\x60\xc6\xc6\x7c\x00\x00\x00\x00", + b"\x00\x00\x00\x00\x00\x00\x00\xfe\xc0\xc0\xc0\x00\x00\x00\x00\x00", + b"\x00\x00\x00\x00\x00\x00\x00\xfe\x06\x06\x06\x00\x00\x00\x00\x00", + b"\x00\x00\xc0\xc0\xc6\xcc\xd8\x30\x60\xdc\x86\x0c\x18\x3e\x00\x00", + b"\x00\x00\xc0\xc0\xc6\xcc\xd8\x30\x66\xce\x9e\x3e\x06\x06\x00\x00", + b"\x00\x00\x00\x18\x18\x00\x18\x18\x3c\x3c\x3c\x18\x00\x00\x00\x00", + b"\x00\x00\x00\x00\x00\x36\x6c\xd8\x6c\x36\x00\x00\x00\x00\x00\x00", + b"\x00\x00\x00\x00\x00\xd8\x6c\x36\x6c\xd8\x00\x00\x00\x00\x00\x00", + b"\x00\x11\x44\x11\x44\x11\x44\x11\x44\x11\x44\x11\x44\x11\x44\x00", + b"\x00\x55\xaa\x55\xaa\x55\xaa\x55\xaa\x55\xaa\x55\xaa\x55\xaa\x00", + b"\x00\xdd\x77\xdd\x77\xdd\x77\xdd\x77\xdd\x77\xdd\x77\xdd\x77\x00", + b"\x00\x18\x18\x18\x18\x18\x18\x18\x18\x18\x18\x18\x18\x18\x18\x00", + b"\x00\x18\x18\x18\x18\x18\x18\x18\xf8\x18\x18\x18\x18\x18\x18\x00", + b"\x00\x18\x18\x18\x18\x18\xf8\x18\xf8\x18\x18\x18\x18\x18\x18\x00", + b"\x00\x36\x36\x36\x36\x36\x36\x36\xf6\x36\x36\x36\x36\x36\x36\x00", + b"\x00\x00\x00\x00\x00\x00\x00\x00\xfe\x36\x36\x36\x36\x36\x36\x00", + b"\x00\x00\x00\x00\x00\x00\xf8\x18\xf8\x18\x18\x18\x18\x18\x18\x00", + b"\x00\x36\x36\x36\x36\x36\xf6\x06\xf6\x36\x36\x36\x36\x36\x36\x00", + b"\x00\x36\x36\x36\x36\x36\x36\x36\x36\x36\x36\x36\x36\x36\x36\x00", + b"\x00\x00\x00\x00\x00\x00\xfe\x06\xf6\x36\x36\x36\x36\x36\x36\x00", + b"\x00\x36\x36\x36\x36\x36\xf6\x06\xfe\x00\x00\x00\x00\x00\x00\x00", + b"\x00\x36\x36\x36\x36\x36\x36\x36\xfe\x00\x00\x00\x00\x00\x00\x00", + b"\x00\x18\x18\x18\x18\x18\xf8\x18\xf8\x00\x00\x00\x00\x00\x00\x00", + b"\x00\x00\x00\x00\x00\x00\x00\x00\xf8\x18\x18\x18\x18\x18\x18\x00", + b"\x00\x18\x18\x18\x18\x18\x18\x18\x1f\x00\x00\x00\x00\x00\x00\x00", + b"\x00\x18\x18\x18\x18\x18\x18\x18\xff\x00\x00\x00\x00\x00\x00\x00", + b"\x00\x00\x00\x00\x00\x00\x00\x00\xff\x18\x18\x18\x18\x18\x18\x00", + b"\x00\x18\x18\x18\x18\x18\x18\x18\x1f\x18\x18\x18\x18\x18\x18\x00", + b"\x00\x00\x00\x00\x00\x00\x00\x00\xff\x00\x00\x00\x00\x00\x00\x00", + b"\x00\x18\x18\x18\x18\x18\x18\x18\xff\x18\x18\x18\x18\x18\x18\x00", + b"\x00\x18\x18\x18\x18\x18\x1f\x18\x1f\x18\x18\x18\x18\x18\x18\x00", + b"\x00\x36\x36\x36\x36\x36\x36\x36\x37\x36\x36\x36\x36\x36\x36\x00", + b"\x00\x36\x36\x36\x36\x36\x37\x30\x3f\x00\x00\x00\x00\x00\x00\x00", + b"\x00\x00\x00\x00\x00\x00\x3f\x30\x37\x36\x36\x36\x36\x36\x36\x00", + b"\x00\x36\x36\x36\x36\x36\xf7\x00\xff\x00\x00\x00\x00\x00\x00\x00", + b"\x00\x00\x00\x00\x00\x00\xff\x00\xf7\x36\x36\x36\x36\x36\x36\x00", + b"\x00\x36\x36\x36\x36\x36\x37\x30\x37\x36\x36\x36\x36\x36\x36\x00", + b"\x00\x00\x00\x00\x00\x00\xff\x00\xff\x00\x00\x00\x00\x00\x00\x00", + b"\x00\x36\x36\x36\x36\x36\xf7\x00\xf7\x36\x36\x36\x36\x36\x36\x00", + b"\x00\x18\x18\x18\x18\x18\xff\x00\xff\x00\x00\x00\x00\x00\x00\x00", + b"\x00\x36\x36\x36\x36\x36\x36\x36\xff\x00\x00\x00\x00\x00\x00\x00", + b"\x00\x00\x00\x00\x00\x00\xff\x00\xff\x18\x18\x18\x18\x18\x18\x00", + b"\x00\x00\x00\x00\x00\x00\x00\x00\xff\x36\x36\x36\x36\x36\x36\x00", + b"\x00\x36\x36\x36\x36\x36\x36\x36\x3f\x00\x00\x00\x00\x00\x00\x00", + b"\x00\x18\x18\x18\x18\x18\x1f\x18\x1f\x00\x00\x00\x00\x00\x00\x00", + b"\x00\x00\x00\x00\x00\x00\x1f\x18\x1f\x18\x18\x18\x18\x18\x18\x00", + b"\x00\x00\x00\x00\x00\x00\x00\x00\x3f\x36\x36\x36\x36\x36\x36\x00", + b"\x00\x36\x36\x36\x36\x36\x36\x36\xff\x36\x36\x36\x36\x36\x36\x00", + b"\x00\x18\x18\x18\x18\x18\xff\x18\xff\x18\x18\x18\x18\x18\x18\x00", + b"\x00\x18\x18\x18\x18\x18\x18\x18\xf8\x00\x00\x00\x00\x00\x00\x00", + b"\x00\x00\x00\x00\x00\x00\x00\x00\x1f\x18\x18\x18\x18\x18\x18\x00", + b"\x00\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\x00", + b"\x00\x00\x00\x00\x00\x00\x00\x00\xff\xff\xff\xff\xff\xff\xff\x00", + b"\x00\xf0\xf0\xf0\xf0\xf0\xf0\xf0\xf0\xf0\xf0\xf0\xf0\xf0\xf0\x00", + b"\x00\x0f\x0f\x0f\x0f\x0f\x0f\x0f\x0f\x0f\x0f\x0f\x0f\x0f\x0f\x00", + b"\x00\xff\xff\xff\xff\xff\xff\xff\x00\x00\x00\x00\x00\x00\x00\x00", + b"\x00\x00\x00\x00\x00\x00\x76\xdc\xd8\xd8\xdc\x76\x00\x00\x00\x00", + b"\x00\x00\x00\x00\x00\x7c\xc6\xfc\xc6\xc6\xfc\xc0\xc0\x40\x00\x00", + b"\x00\x00\x00\xfe\xc6\xc6\xc0\xc0\xc0\xc0\xc0\xc0\x00\x00\x00\x00", + b"\x00\x00\x00\x00\x00\xfe\x6c\x6c\x6c\x6c\x6c\x6c\x00\x00\x00\x00", + b"\x00\x00\x00\xfe\xc6\x60\x30\x18\x30\x60\xc6\xfe\x00\x00\x00\x00", + b"\x00\x00\x00\x00\x00\x00\x7e\xd8\xd8\xd8\xd8\x70\x00\x00\x00\x00", + b"\x00\x00\x00\x00\x00\x66\x66\x66\x66\x7c\x60\x60\xc0\x00\x00\x00", + b"\x00\x00\x00\x00\x00\x76\xdc\x18\x18\x18\x18\x18\x00\x00\x00\x00", + b"\x00\x00\x00\x7e\x18\x3c\x66\x66\x66\x3c\x18\x7e\x00\x00\x00\x00", + b"\x00\x00\x00\x38\x6c\xc6\xc6\xfe\xc6\xc6\x6c\x38\x00\x00\x00\x00", + b"\x00\x00\x00\x38\x6c\xc6\xc6\xc6\x6c\x6c\x6c\xee\x00\x00\x00\x00", + b"\x00\x00\x00\x1e\x30\x18\x0c\x3e\x66\x66\x66\x3c\x00\x00\x00\x00", + b"\x00\x00\x00\x00\x00\x00\x7e\xdb\xdb\x7e\x00\x00\x00\x00\x00\x00", + b"\x00\x00\x00\x03\x06\x7e\xdb\xdb\xf3\x7e\x60\xc0\x00\x00\x00\x00", + b"\x00\x00\x00\x1c\x30\x60\x60\x7c\x60\x60\x30\x1c\x00\x00\x00\x00", + b"\x00\x00\x00\x00\x7c\xc6\xc6\xc6\xc6\xc6\xc6\xc6\x00\x00\x00\x00", + b"\x00\x00\x00\x00\xfe\x00\x00\xfe\x00\x00\xfe\x00\x00\x00\x00\x00", + b"\x00\x00\x00\x00\x18\x18\x7e\x18\x18\x00\x00\xff\x00\x00\x00\x00", + b"\x00\x00\x00\x30\x18\x0c\x06\x0c\x18\x30\x00\x7e\x00\x00\x00\x00", + b"\x00\x00\x00\x0c\x18\x30\x60\x30\x18\x0c\x00\x7e\x00\x00\x00\x00", + b"\x00\x00\x00\x0e\x1b\x1b\x18\x18\x18\x18\x18\x18\x18\x18\x18\x00", + b"\x00\x18\x18\x18\x18\x18\x18\x18\x18\xd8\xd8\x70\x00\x00\x00\x00", + b"\x00\x00\x00\x00\x18\x18\x00\x7e\x00\x18\x18\x00\x00\x00\x00\x00", + b"\x00\x00\x00\x00\x00\x76\xdc\x00\x76\xdc\x00\x00\x00\x00\x00\x00", + b"\x00\x00\x38\x6c\x6c\x38\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00", + b"\x00\x00\x00\x00\x00\x00\x00\x18\x18\x00\x00\x00\x00\x00\x00\x00", + b"\x00\x00\x00\x00\x00\x00\x00\x00\x18\x00\x00\x00\x00\x00\x00\x00", + b"\x00\x00\x0f\x0c\x0c\x0c\x0c\x0c\xec\x6c\x3c\x1c\x00\x00\x00\x00", + b"\x00\x00\xd8\x6c\x6c\x6c\x6c\x6c\x00\x00\x00\x00\x00\x00\x00\x00", + b"\x00\x00\x70\xd8\x30\x60\xc8\xf8\x00\x00\x00\x00\x00\x00\x00\x00", + b"\x00\x00\x00\x00\x00\x7c\x7c\x7c\x7c\x7c\x7c\x00\x00\x00\x00\x00", + b"\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00", ] diff --git a/klippy/extras/display/hd44780.py b/klippy/extras/display/hd44780.py index 2da49c51..8cde1d6d 100644 --- a/klippy/extras/display/hd44780.py +++ b/klippy/extras/display/hd44780.py @@ -6,87 +6,106 @@ # This file may be distributed under the terms of the GNU GPLv3 license. import logging -BACKGROUND_PRIORITY_CLOCK = 0x7fffffff00000000 -LINE_LENGTH_DEFAULT=20 -LINE_LENGTH_OPTIONS=[16, 20] +BACKGROUND_PRIORITY_CLOCK = 0x7FFFFFFF00000000 +LINE_LENGTH_DEFAULT = 20 +LINE_LENGTH_OPTIONS = [16, 20] -TextGlyphs = { 'right_arrow': b'\x7e' } +TextGlyphs = {"right_arrow": b"\x7e"} + +HD44780_DELAY = 0.000040 -HD44780_DELAY = .000040 class HD44780: def __init__(self, config): self.printer = config.get_printer() # pin config - ppins = self.printer.lookup_object('pins') - pins = [ppins.lookup_pin(config.get(name + '_pin')) - for name in ['rs', 'e', 'd4', 'd5', 'd6', 'd7']] - self.hd44780_protocol_init = config.getboolean('hd44780_protocol_init', - True) - self.line_length = config.getchoice('line_length', LINE_LENGTH_OPTIONS, - LINE_LENGTH_DEFAULT) + ppins = self.printer.lookup_object("pins") + pins = [ + ppins.lookup_pin(config.get(name + "_pin")) + for name in ["rs", "e", "d4", "d5", "d6", "d7"] + ] + self.hd44780_protocol_init = config.getboolean("hd44780_protocol_init", True) + self.line_length = config.getchoice( + "line_length", LINE_LENGTH_OPTIONS, LINE_LENGTH_DEFAULT + ) mcu = None for pin_params in pins: - if mcu is not None and pin_params['chip'] != mcu: + if mcu is not None and pin_params["chip"] != mcu: raise ppins.error("hd44780 all pins must be on same mcu") - mcu = pin_params['chip'] - self.pins = [pin_params['pin'] for pin_params in pins] + mcu = pin_params["chip"] + self.pins = [pin_params["pin"] for pin_params in pins] self.mcu = mcu self.oid = self.mcu.create_oid() self.mcu.register_config_callback(self.build_config) self.send_data_cmd = self.send_cmds_cmd = None self.icons = {} # framebuffers - self.text_framebuffers = [bytearray(b' '*2*self.line_length), - bytearray(b' '*2*self.line_length)] + self.text_framebuffers = [ + bytearray(b" " * 2 * self.line_length), + bytearray(b" " * 2 * self.line_length), + ] self.glyph_framebuffer = bytearray(64) self.all_framebuffers = [ # Text framebuffers - (self.text_framebuffers[0], bytearray(b'~'*2*self.line_length), - 0x80), - (self.text_framebuffers[1], bytearray(b'~'*2*self.line_length), - 0xc0), + (self.text_framebuffers[0], bytearray(b"~" * 2 * self.line_length), 0x80), + (self.text_framebuffers[1], bytearray(b"~" * 2 * self.line_length), 0xC0), # Glyph framebuffer - (self.glyph_framebuffer, bytearray(b'~'*64), 0x40) ] + (self.glyph_framebuffer, bytearray(b"~" * 64), 0x40), + ] + def build_config(self): self.mcu.add_config_cmd( "config_hd44780 oid=%d rs_pin=%s e_pin=%s" - " d4_pin=%s d5_pin=%s d6_pin=%s d7_pin=%s delay_ticks=%d" % ( - self.oid, self.pins[0], self.pins[1], - self.pins[2], self.pins[3], self.pins[4], self.pins[5], - self.mcu.seconds_to_clock(HD44780_DELAY))) + " d4_pin=%s d5_pin=%s d6_pin=%s d7_pin=%s delay_ticks=%d" + % ( + self.oid, + self.pins[0], + self.pins[1], + self.pins[2], + self.pins[3], + self.pins[4], + self.pins[5], + self.mcu.seconds_to_clock(HD44780_DELAY), + ) + ) cmd_queue = self.mcu.alloc_command_queue() self.send_cmds_cmd = self.mcu.lookup_command( - "hd44780_send_cmds oid=%c cmds=%*s", cq=cmd_queue) + "hd44780_send_cmds oid=%c cmds=%*s", cq=cmd_queue + ) self.send_data_cmd = self.mcu.lookup_command( - "hd44780_send_data oid=%c data=%*s", cq=cmd_queue) + "hd44780_send_data oid=%c data=%*s", cq=cmd_queue + ) + def send(self, cmds, is_data=False): cmd_type = self.send_cmds_cmd if is_data: cmd_type = self.send_data_cmd cmd_type.send([self.oid, cmds], reqclock=BACKGROUND_PRIORITY_CLOCK) - #logging.debug("hd44780 %d %s", is_data, repr(cmds)) + # logging.debug("hd44780 %d %s", is_data, repr(cmds)) + def flush(self): # Find all differences in the framebuffers and send them to the chip for new_data, old_data, fb_id in self.all_framebuffers: if new_data == old_data: continue # Find the position of all changed bytes in this framebuffer - diffs = [[i, 1] for i, (n, o) in enumerate(zip(new_data, old_data)) - if n != o] + diffs = [ + [i, 1] for i, (n, o) in enumerate(zip(new_data, old_data)) if n != o + ] # Batch together changes that are close to each other - for i in range(len(diffs)-2, -1, -1): + for i in range(len(diffs) - 2, -1, -1): pos, count = diffs[i] - nextpos, nextcount = diffs[i+1] + nextpos, nextcount = diffs[i + 1] if pos + 4 >= nextpos and nextcount < 16: diffs[i][1] = nextcount + (nextpos - pos) - del diffs[i+1] + del diffs[i + 1] # Transmit changes for pos, count in diffs: chip_pos = pos self.send([fb_id + chip_pos]) - self.send(new_data[pos:pos+count], is_data=True) + self.send(new_data[pos : pos + count], is_data=True) old_data[:] = new_data + def init(self): curtime = self.printer.get_reactor().monotonic() print_time = self.mcu.estimated_print_time(curtime) @@ -96,27 +115,30 @@ class HD44780: else: init = [[0x02]] # Reset (set positive direction ; enable display and hide cursor) - init.append([0x06, 0x0c]) + init.append([0x06, 0x0C]) for i, cmds in enumerate(init): - minclock = self.mcu.print_time_to_clock(print_time + i * .100) + minclock = self.mcu.print_time_to_clock(print_time + i * 0.100) self.send_cmds_cmd.send([self.oid, cmds], minclock=minclock) self.flush() + def write_text(self, x, y, data): if x + len(data) > self.line_length: - data = data[:self.line_length - min(x, self.line_length)] + data = data[: self.line_length - min(x, self.line_length)] pos = x + ((y & 0x02) >> 1) * self.line_length - self.text_framebuffers[y & 1][pos:pos+len(data)] = data + self.text_framebuffers[y & 1][pos : pos + len(data)] = data + def set_glyphs(self, glyphs): for glyph_name, glyph_data in glyphs.items(): - data = glyph_data.get('icon5x8') + data = glyph_data.get("icon5x8") if data is not None: self.icons[glyph_name] = data + def write_glyph(self, x, y, glyph_name): data = self.icons.get(glyph_name) if data is not None: slot, bits = data self.write_text(x, y, [slot]) - self.glyph_framebuffer[slot * 8:(slot + 1) * 8] = bits + self.glyph_framebuffer[slot * 8 : (slot + 1) * 8] = bits return 1 char = TextGlyphs.get(glyph_name) if char is not None: @@ -124,11 +146,14 @@ class HD44780: self.write_text(x, y, char) return 1 return 0 + def write_graphics(self, x, y, data): pass + def clear(self): - spaces = b' ' * 2*self.line_length + spaces = b" " * 2 * self.line_length self.text_framebuffers[0][:] = spaces self.text_framebuffers[1][:] = spaces + def get_dimensions(self): return (self.line_length, 4) diff --git a/klippy/extras/display/hd44780_spi.py b/klippy/extras/display/hd44780_spi.py index f21accbb..dd46acd3 100644 --- a/klippy/extras/display/hd44780_spi.py +++ b/klippy/extras/display/hd44780_spi.py @@ -8,43 +8,43 @@ import logging from .. import bus -LINE_LENGTH_DEFAULT=20 -LINE_LENGTH_OPTIONS=[16, 20] - -TextGlyphs = { 'right_arrow': b'\x7e' } +LINE_LENGTH_DEFAULT = 20 +LINE_LENGTH_OPTIONS = [16, 20] +TextGlyphs = {"right_arrow": b"\x7e"} class hd44780_spi: def __init__(self, config): self.printer = config.get_printer() - self.hd44780_protocol_init = config.getboolean('hd44780_protocol_init', - True) + self.hd44780_protocol_init = config.getboolean("hd44780_protocol_init", True) # spi config - self.spi = bus.MCU_SPI_from_config( - config, 0x00, pin_option="latch_pin") + self.spi = bus.MCU_SPI_from_config(config, 0x00, pin_option="latch_pin") self.mcu = self.spi.get_mcu() - #self.spi.spi_send([0x01,0xa0]) - self.data_mask = (1<<1) + # self.spi.spi_send([0x01,0xa0]) + self.data_mask = 1 << 1 self.command_mask = 0 - self.enable_mask = (1<<3) + self.enable_mask = 1 << 3 self.icons = {} - self.line_length = config.getchoice('line_length', LINE_LENGTH_OPTIONS, - LINE_LENGTH_DEFAULT) + self.line_length = config.getchoice( + "line_length", LINE_LENGTH_OPTIONS, LINE_LENGTH_DEFAULT + ) # framebuffers - self.text_framebuffers = [bytearray(b' '*2*self.line_length), - bytearray(b' '*2*self.line_length)] + self.text_framebuffers = [ + bytearray(b" " * 2 * self.line_length), + bytearray(b" " * 2 * self.line_length), + ] self.glyph_framebuffer = bytearray(64) self.all_framebuffers = [ # Text framebuffers - (self.text_framebuffers[0], bytearray(b'~'*2*self.line_length), - 0x80), - (self.text_framebuffers[1], bytearray(b'~'*2*self.line_length), - 0xc0), + (self.text_framebuffers[0], bytearray(b"~" * 2 * self.line_length), 0x80), + (self.text_framebuffers[1], bytearray(b"~" * 2 * self.line_length), 0xC0), # Glyph framebuffer - (self.glyph_framebuffer, bytearray(b'~'*64), 0x40) ] + (self.glyph_framebuffer, bytearray(b"~" * 64), 0x40), + ] + def send_4_bits(self, cmd, is_data, minclock): if is_data: mask = self.data_mask @@ -53,31 +53,35 @@ class hd44780_spi: self.spi.spi_send([(cmd & 0xF0) | mask], minclock) self.spi.spi_send([(cmd & 0xF0) | mask | self.enable_mask], minclock) self.spi.spi_send([(cmd & 0xF0) | mask], minclock) + def send(self, cmds, is_data=False, minclock=0): for data in cmds: - self.send_4_bits(data,is_data,minclock) - self.send_4_bits(data<<4,is_data,minclock) + self.send_4_bits(data, is_data, minclock) + self.send_4_bits(data << 4, is_data, minclock) + def flush(self): # Find all differences in the framebuffers and send them to the chip for new_data, old_data, fb_id in self.all_framebuffers: if new_data == old_data: continue # Find the position of all changed bytes in this framebuffer - diffs = [[i, 1] for i, (n, o) in enumerate(zip(new_data, old_data)) - if n != o] + diffs = [ + [i, 1] for i, (n, o) in enumerate(zip(new_data, old_data)) if n != o + ] # Batch together changes that are close to each other - for i in range(len(diffs)-2, -1, -1): + for i in range(len(diffs) - 2, -1, -1): pos, count = diffs[i] - nextpos, nextcount = diffs[i+1] + nextpos, nextcount = diffs[i + 1] if pos + 4 >= nextpos and nextcount < 16: diffs[i][1] = nextcount + (nextpos - pos) - del diffs[i+1] + del diffs[i + 1] # Transmit changes for pos, count in diffs: chip_pos = pos self.send([fb_id + chip_pos]) - self.send(new_data[pos:pos+count], is_data=True) + self.send(new_data[pos : pos + count], is_data=True) old_data[:] = new_data + def init(self): curtime = self.printer.get_reactor().monotonic() print_time = self.mcu.estimated_print_time(curtime) @@ -87,27 +91,30 @@ class hd44780_spi: else: init = [[0x02]] # Reset (set positive direction ; enable display and hide cursor) - init.append([0x06, 0x0c]) + init.append([0x06, 0x0C]) for i, cmds in enumerate(init): - minclock = self.mcu.print_time_to_clock(print_time + i * .100) + minclock = self.mcu.print_time_to_clock(print_time + i * 0.100) self.send(cmds, minclock=minclock) self.flush() + def write_text(self, x, y, data): if x + len(data) > self.line_length: - data = data[:self.line_length - min(x, self.line_length)] + data = data[: self.line_length - min(x, self.line_length)] pos = x + ((y & 0x02) >> 1) * self.line_length - self.text_framebuffers[y & 1][pos:pos+len(data)] = data + self.text_framebuffers[y & 1][pos : pos + len(data)] = data + def set_glyphs(self, glyphs): for glyph_name, glyph_data in glyphs.items(): - data = glyph_data.get('icon5x8') + data = glyph_data.get("icon5x8") if data is not None: self.icons[glyph_name] = data + def write_glyph(self, x, y, glyph_name): data = self.icons.get(glyph_name) if data is not None: slot, bits = data self.write_text(x, y, [slot]) - self.glyph_framebuffer[slot * 8:(slot + 1) * 8] = bits + self.glyph_framebuffer[slot * 8 : (slot + 1) * 8] = bits return 1 char = TextGlyphs.get(glyph_name) if char is not None: @@ -115,11 +122,14 @@ class hd44780_spi: self.write_text(x, y, char) return 1 return 0 + def write_graphics(self, x, y, data): pass + def clear(self): - spaces = b' ' * 2*self.line_length + spaces = b" " * 2 * self.line_length self.text_framebuffers[0][:] = spaces self.text_framebuffers[1][:] = spaces + def get_dimensions(self): return (self.line_length, 4) diff --git a/klippy/extras/display/menu.py b/klippy/extras/display/menu.py index e7723a7e..717b6084 100644 --- a/klippy/extras/display/menu.py +++ b/klippy/extras/display/menu.py @@ -21,33 +21,33 @@ class error(Exception): class MenuElement(object): def __init__(self, manager, config, **kwargs): if type(self) is MenuElement: - raise error( - 'Abstract MenuElement cannot be instantiated directly') + raise error("Abstract MenuElement cannot be instantiated directly") self._manager = manager - self._cursor = '>' + self._cursor = ">" # set class defaults and attributes from arguments - self._index = kwargs.get('index', None) - self._enable = kwargs.get('enable', True) - self._name = kwargs.get('name', None) + self._index = kwargs.get("index", None) + self._enable = kwargs.get("enable", True) + self._name = kwargs.get("name", None) self._enable_tpl = self._name_tpl = None if config is not None: # overwrite class attributes from config - self._index = config.getint('index', self._index) + self._index = config.getint("index", self._index) self._name_tpl = manager.gcode_macro.load_template( - config, 'name', self._name) + config, "name", self._name + ) try: - self._enable = config.getboolean('enable', self._enable) + self._enable = config.getboolean("enable", self._enable) except config.error: - self._enable_tpl = manager.gcode_macro.load_template( - config, 'enable') + self._enable_tpl = manager.gcode_macro.load_template(config, "enable") # item namespace - used in relative paths - self._ns = str(" ".join(config.get_name().split(' ')[1:])).strip() + self._ns = str(" ".join(config.get_name().split(" ")[1:])).strip() else: # ns - item namespace key, used in item relative paths # $__id - generated id text variable - __id = '__menu_' + hex(id(self)).lstrip("0x").rstrip("L") - self._ns = Template( - 'menu ' + kwargs.get('ns', __id)).safe_substitute(__id=__id) + __id = "__menu_" + hex(id(self)).lstrip("0x").rstrip("L") + self._ns = Template("menu " + kwargs.get("ns", __id)).safe_substitute( + __id=__id + ) self._last_heartbeat = None self.__scroll_pos = None self.__scroll_request_pending = False @@ -71,14 +71,15 @@ class MenuElement(object): """Load script template from config or callback from dict""" if name in self._scripts: logging.info( - "Declaration of '%s' hides " - "previous script declaration" % (name,)) + "Declaration of '%s' hides " "previous script declaration" % (name,) + ) option = option or name if isinstance(config, dict): self._scripts[name] = config.get(option, None) else: self._scripts[name] = self.manager.gcode_macro.load_template( - config, option, '') + config, option, "" + ) # override def is_editing(self): @@ -105,9 +106,7 @@ class MenuElement(object): def get_context(self, cxt=None): # get default menu context context = self.manager.get_context(cxt) - context['menu'].update({ - 'ns': self.get_ns() - }) + context["menu"].update({"ns": self.get_ns()}) return context def eval_enable(self, context): @@ -153,7 +152,7 @@ class MenuElement(object): def __slice_name(self, name, index): chunks = [] - for i, text in enumerate(re.split(r'(\~.*?\~)', name)): + for i, text in enumerate(re.split(r"(\~.*?\~)", name)): if i & 1 == 0: # text chunks += text else: # glyph placeholder @@ -168,18 +167,16 @@ class MenuElement(object): self.__reset_scroller() return name - def get_ns(self, name='.'): + def get_ns(self, name="."): name = str(name).strip() - if name.startswith('..'): - name = ' '.join( - [(' '.join(str(self._ns).split(' ')[:-1])), name[2:]]) - elif name.startswith('.'): - name = ' '.join([str(self._ns), name[1:]]) + if name.startswith(".."): + name = " ".join([(" ".join(str(self._ns).split(" ")[:-1])), name[2:]]) + elif name.startswith("."): + name = " ".join([str(self._ns), name[1:]]) return name.strip() def send_event(self, event, *args): - return self.manager.send_event( - "%s:%s" % (self.get_ns(), str(event)), *args) + return self.manager.send_event("%s:%s" % (self.get_ns(), str(event)), *args) def get_script(self, name): if name in self._scripts: @@ -187,7 +184,7 @@ class MenuElement(object): return None def _run_script(self, name, context): - _render = getattr(self._scripts[name], 'render', None) + _render = getattr(self._scripts[name], "render", None) # check template if _render is not None and callable(_render): return _render(context) @@ -199,16 +196,14 @@ class MenuElement(object): return self._scripts[name] def run_script(self, name, **kwargs): - event = kwargs.get('event', None) - context = kwargs.get('context', None) - render_only = kwargs.get('render_only', False) + event = kwargs.get("event", None) + context = kwargs.get("context", None) + render_only = kwargs.get("render_only", False) result = "" # init context if name in self._scripts: context = self.get_context(context) - context['menu'].update({ - 'event': event or name - }) + context["menu"].update({"event": event or name}) result = self._run_script(name, context) if not render_only: # run result as gcode @@ -234,13 +229,13 @@ class MenuElement(object): class MenuContainer(MenuElement): """Menu container abstract class""" + def __init__(self, manager, config, **kwargs): if type(self) is MenuContainer: - raise error( - 'Abstract MenuContainer cannot be instantiated directly') + raise error("Abstract MenuContainer cannot be instantiated directly") super(MenuContainer, self).__init__(manager, config, **kwargs) - self._populate_cb = kwargs.get('populate', None) - self._cursor = '>' + self._populate_cb = kwargs.get("populate", None) + self._cursor = ">" self.__selected = None self._allitems = [] self._names = [] @@ -307,8 +302,9 @@ class MenuContainer(MenuElement): self._parents.append(parents) def assert_recursive_relation(self, parents=None): - assert self not in (parents or self._parents), \ - "Recursive relation of '%s' container" % (self.get_ns(),) + assert self not in ( + parents or self._parents + ), "Recursive relation of '%s' container" % (self.get_ns(),) def insert_item(self, s, index=None): self._insert_item(s, index) @@ -343,11 +339,10 @@ class MenuContainer(MenuElement): if self._populate_cb is not None and callable(self._populate_cb): self._populate_cb(self) # send populate event - self.send_event('populate', self) + self.send_event("populate", self) def update_items(self): - _a = [(item, name) for item, name in self._allitems - if item.is_enabled()] + _a = [(item, name) for item, name in self._allitems if item.is_enabled()] self._items, self._names = zip(*_a) or ([], []) # select methods @@ -416,7 +411,7 @@ class MenuContainer(MenuElement): class MenuDisabled(MenuElement): def __init__(self, manager, config, **kwargs): - super(MenuDisabled, self).__init__(manager, config, name='') + super(MenuDisabled, self).__init__(manager, config, name="") def is_enabled(self): return False @@ -425,30 +420,32 @@ class MenuDisabled(MenuElement): class MenuCommand(MenuElement): def __init__(self, manager, config, **kwargs): super(MenuCommand, self).__init__(manager, config, **kwargs) - self._load_script(config or kwargs, 'gcode') + self._load_script(config or kwargs, "gcode") class MenuInput(MenuCommand): def __init__(self, manager, config, **kwargs): super(MenuInput, self).__init__(manager, config, **kwargs) # set class defaults and attributes from arguments - self._input = kwargs.get('input', None) - self._input_min = kwargs.get('input_min', -999999.0) - self._input_max = kwargs.get('input_max', 999999.0) - self._input_step = kwargs.get('input_step', 1.0) - self._realtime = kwargs.get('realtime', False) + self._input = kwargs.get("input", None) + self._input_min = kwargs.get("input_min", -999999.0) + self._input_max = kwargs.get("input_max", 999999.0) + self._input_step = kwargs.get("input_step", 1.0) + self._realtime = kwargs.get("realtime", False) self._input_tpl = self._input_min_tpl = self._input_max_tpl = None if config is not None: # overwrite class attributes from config - self._realtime = config.getboolean('realtime', self._realtime) - self._input_tpl = manager.gcode_macro.load_template( - config, 'input') + self._realtime = config.getboolean("realtime", self._realtime) + self._input_tpl = manager.gcode_macro.load_template(config, "input") self._input_min_tpl = manager.gcode_macro.load_template( - config, 'input_min', str(self._input_min)) + config, "input_min", str(self._input_min) + ) self._input_max_tpl = manager.gcode_macro.load_template( - config, 'input_max', str(self._input_max)) + config, "input_max", str(self._input_max) + ) self._input_step = config.getfloat( - 'input_step', self._input_step, above=0.) + "input_step", self._input_step, above=0.0 + ) def init(self): super(MenuInput, self).init() @@ -474,22 +471,25 @@ class MenuInput(MenuCommand): def heartbeat(self, eventtime): super(MenuInput, self).heartbeat(eventtime) - if (self._is_dirty is True - and self.__last_change is not None - and self._input_value is not None - and (eventtime - self.__last_change) > 0.250): + if ( + self._is_dirty is True + and self.__last_change is not None + and self._input_value is not None + and (eventtime - self.__last_change) > 0.250 + ): if self._realtime is True: - self.run_script('gcode', event='change') - self.run_script('change') + self.run_script("gcode", event="change") + self.run_script("change") self._is_dirty = False def get_context(self, cxt=None): context = super(MenuInput, self).get_context(cxt) - value = (self._eval_value(context) if self._input_value is None - else self._input_value) - context['menu'].update({ - 'input': value - }) + value = ( + self._eval_value(context) + if self._input_value is None + else self._input_value + ) + context["menu"].update({"input": value}) return context def is_enabled(self): @@ -499,8 +499,7 @@ class MenuInput(MenuCommand): def _eval_min(self, context): try: if self._input_min_tpl is not None: - return float(ast.literal_eval( - self._input_min_tpl.render(context))) + return float(ast.literal_eval(self._input_min_tpl.render(context))) return float(self._input_min) except ValueError: logging.exception("Input min value evaluation error") @@ -508,8 +507,7 @@ class MenuInput(MenuCommand): def _eval_max(self, context): try: if self._input_max_tpl is not None: - return float(ast.literal_eval( - self._input_max_tpl.render(context))) + return float(ast.literal_eval(self._input_max_tpl.render(context))) return float(self._input_max) except ValueError: logging.exception("Input max value evaluation error") @@ -517,8 +515,7 @@ class MenuInput(MenuCommand): def _eval_value(self, context): try: if self._input_tpl is not None: - return float(ast.literal_eval( - self._input_tpl.render(context))) + return float(ast.literal_eval(self._input_tpl.render(context))) return float(self._input) except ValueError: logging.exception("Input value evaluation error") @@ -532,17 +529,21 @@ class MenuInput(MenuCommand): self._input_value = None self._input_min = self._eval_min(context) self._input_max = self._eval_max(context) - self._input_value = min(self._input_max, max( - self._input_min, self._eval_value(context))) + self._input_value = min( + self._input_max, max(self._input_min, self._eval_value(context)) + ) self._value_changed() def _reset_value(self): self._input_value = None def _get_input_step(self, fast_rate=False): - return ((10.0 * self._input_step) if fast_rate and ( - (self._input_max - self._input_min) / self._input_step > 100.0) - else self._input_step) + return ( + (10.0 * self._input_step) + if fast_rate + and ((self._input_max - self._input_min) / self._input_step > 100.0) + else self._input_step + ) def inc_value(self, fast_rate=False): last_value = self._input_value @@ -551,8 +552,9 @@ class MenuInput(MenuCommand): input_step = self._get_input_step(fast_rate) self._input_value += abs(input_step) - self._input_value = min(self._input_max, max( - self._input_min, self._input_value)) + self._input_value = min( + self._input_max, max(self._input_min, self._input_value) + ) if last_value != self._input_value: self._value_changed() @@ -564,8 +566,9 @@ class MenuInput(MenuCommand): input_step = self._get_input_step(fast_rate) self._input_value -= abs(input_step) - self._input_value = min(self._input_max, max( - self._input_min, self._input_value)) + self._input_value = min( + self._input_max, max(self._input_min, self._input_value) + ) if last_value != self._input_value: self._value_changed() @@ -585,9 +588,9 @@ class MenuList(MenuContainer): def _cb(el, context): el.manager.back() + # create back item - self._itemBack = self.manager.menuitem_from( - 'command', name='..', gcode=_cb) + self._itemBack = self.manager.menuitem_from("command", name="..", gcode=_cb) def _names_aslist(self): return self.manager.lookup_children(self.get_ns()) @@ -619,7 +622,7 @@ class MenuList(MenuContainer): suffix = "" if row < len(self): current = self[row] - selected = (row == selected_row) + selected = row == selected_row if selected: current.heartbeat(eventtime) text = current.render_name(selected) @@ -627,12 +630,12 @@ class MenuList(MenuContainer): if selected and not current.is_editing(): prefix = current.cursor elif selected and current.is_editing(): - prefix = '*' + prefix = "*" else: - prefix = ' ' + prefix = " " # add suffix (folder indicator) if isinstance(current, MenuList): - suffix += '>' + suffix += ">" # draw to display plen = len(prefix) slen = len(suffix) @@ -642,8 +645,7 @@ class MenuList(MenuContainer): # draw item name tpos = display.draw_text(y, ppos, text.ljust(width), eventtime) # check scroller - if (selected and tpos > self.manager.cols - and current.is_scrollable()): + if selected and tpos > self.manager.cols and current.is_scrollable(): # scroll next current.need_scroller(True) else: @@ -651,12 +653,11 @@ class MenuList(MenuContainer): current.need_scroller(None) # draw item suffix if suffix: - display.draw_text( - y, self.manager.cols - slen, suffix, eventtime) + display.draw_text(y, self.manager.cols - slen, suffix, eventtime) # next display row y += 1 except Exception: - logging.exception('List drawing error') + logging.exception("List drawing error") class MenuVSDList(MenuList): @@ -665,20 +666,23 @@ class MenuVSDList(MenuList): def _populate(self): super(MenuVSDList, self)._populate() - sdcard = self.manager.printer.lookup_object('virtual_sdcard', None) + sdcard = self.manager.printer.lookup_object("virtual_sdcard", None) if sdcard is not None: files = sdcard.get_file_list() for fname, fsize in files: - self.insert_item(self.manager.menuitem_from( - 'command', name=repr(fname), gcode='M23 /%s' % str(fname))) + self.insert_item( + self.manager.menuitem_from( + "command", name=repr(fname), gcode="M23 /%s" % str(fname) + ) + ) menu_items = { - 'disabled': MenuDisabled, - 'command': MenuCommand, - 'input': MenuInput, - 'list': MenuList, - 'vsdlist': MenuVSDList + "disabled": MenuDisabled, + "command": MenuCommand, + "input": MenuInput, + "list": MenuList, + "vsdlist": MenuVSDList, } @@ -693,33 +697,32 @@ class MenuManager: self.children = {} self.display = display self.printer = config.get_printer() - self.pconfig = self.printer.lookup_object('configfile') - self.gcode = self.printer.lookup_object('gcode') + self.pconfig = self.printer.lookup_object("configfile") + self.gcode = self.printer.lookup_object("gcode") self.gcode_queue = [] self.context = {} self.root = None - self._root = config.get('menu_root', '__main') + self._root = config.get("menu_root", "__main") self.cols, self.rows = self.display.get_dimensions() - self.timeout = config.getint('menu_timeout', 0) + self.timeout = config.getint("menu_timeout", 0) self.timer = 0 # reverse container navigation - self._reverse_navigation = config.getboolean( - 'menu_reverse_navigation', False) + self._reverse_navigation = config.getboolean("menu_reverse_navigation", False) # load printer objects - self.gcode_macro = self.printer.load_object(config, 'gcode_macro') + self.gcode_macro = self.printer.load_object(config, "gcode_macro") # register itself for printer callbacks - self.printer.add_object('menu', self) + self.printer.add_object("menu", self) self.printer.register_event_handler("klippy:ready", self.handle_ready) # register for key events menu_keys.MenuKeys(config, self.key_event) # Load local config file in same directory as current module - self.load_config(os.path.dirname(__file__), 'menu.cfg') + self.load_config(os.path.dirname(__file__), "menu.cfg") # Load items from main config self.load_menuitems(config) # Load menu root self.root = self.lookup_menuitem(self._root) # send init event - self.send_event('init', self) + self.send_event("init", self) def handle_ready(self): # start timer @@ -731,8 +734,11 @@ class MenuManager: return eventtime + TIMER_DELAY def timeout_check(self, eventtime): - if (self.is_running() and self.timeout > 0 - and isinstance(self.root, MenuContainer)): + if ( + self.is_running() + and self.timeout > 0 + and isinstance(self.root, MenuContainer) + ): if self.timer >= self.timeout: self.exit() else: @@ -751,7 +757,7 @@ class MenuManager: self.timer = 0 if isinstance(self.root, MenuContainer): # send begin event - self.send_event('begin', self) + self.send_event("begin", self) self.update_context(eventtime) if isinstance(self.root, MenuContainer): self.root.init_selection() @@ -764,10 +770,10 @@ class MenuManager: def get_status(self, eventtime): return { - 'timeout': self.timeout, - 'running': self.running, - 'rows': self.rows, - 'cols': self.cols + "timeout": self.timeout, + "running": self.running, + "rows": self.rows, + "cols": self.cols, } def _action_back(self, force=False, update=True): @@ -787,10 +793,10 @@ class MenuManager: def update_context(self, eventtime): # menu default jinja2 context self.context = self.gcode_macro.create_template_context(eventtime) - self.context['menu'] = { - 'eventtime': eventtime, - 'back': self._action_back, - 'exit': self._action_exit + self.context["menu"] = { + "eventtime": eventtime, + "back": self._action_back, + "exit": self._action_exit, } def stack_push(self, container): @@ -800,9 +806,9 @@ class MenuManager: top = self.stack_peek() if top is not None: if isinstance(top, MenuList): - top.run_script('leave') + top.run_script("leave") if isinstance(container, MenuList): - container.run_script('enter') + container.run_script("enter") if not container.is_editing(): container.update_items() container.init_selection() @@ -822,12 +828,12 @@ class MenuManager: top.update_items() top.init_selection() if isinstance(container, MenuList): - container.run_script('leave') + container.run_script("leave") if isinstance(top, MenuList): - top.run_script('enter') + top.run_script("enter") else: if isinstance(container, MenuList): - container.run_script('leave') + container.run_script("leave") return container def stack_size(self): @@ -905,25 +911,26 @@ class MenuManager: if self.running and isinstance(container, MenuContainer): self.timer = 0 current = container.selected_item() - if (not force and isinstance(current, MenuInput) - and current.is_editing()): + if not force and isinstance(current, MenuInput) and current.is_editing(): return if isinstance(container, MenuList): - container.run_script('leave') - self.send_event('exit', self) + container.run_script("leave") + self.send_event("exit", self) self.running = False def push_container(self, menu): container = self.stack_peek() if self.running and isinstance(container, MenuContainer): - if (isinstance(menu, MenuContainer) - and not container.is_editing() - and menu is not container): + if ( + isinstance(menu, MenuContainer) + and not container.is_editing() + and menu is not container + ): self.stack_push(menu) return True return False - def press(self, event='click'): + def press(self, event="click"): container = self.stack_peek() if self.running and isinstance(container, MenuContainer): self.timer = 0 @@ -932,10 +939,10 @@ class MenuManager: self.stack_push(current) elif isinstance(current, MenuInput): if current.is_editing(): - current.run_script('gcode', event=event) + current.run_script("gcode", event=event) current.run_script(event) elif isinstance(current, MenuCommand): - current.run_script('gcode', event=event) + current.run_script("gcode", event=event) current.run_script(event) else: # current is None, no selection. passthru to container @@ -960,8 +967,10 @@ class MenuManager: def menuitem_from(self, type, **kwargs): if type not in menu_items: - raise error("Choice '%s' for option '%s'" - " is not a valid choice" % (type, menu_items)) + raise error( + "Choice '%s' for option '%s'" + " is not a valid choice" % (type, menu_items) + ) return menu_items[type](self, None, **kwargs) def add_menuitem(self, name, item): @@ -969,18 +978,18 @@ class MenuManager: if name in self.menuitems: existing_item = True logging.info( - "Declaration of '%s' hides " - "previous menuitem declaration" % (name,)) + "Declaration of '%s' hides " "previous menuitem declaration" % (name,) + ) self.menuitems[name] = item if isinstance(item, MenuElement): - parent = item.get_ns('..') + parent = item.get_ns("..") if parent and not existing_item: if item.index is not None: self.children.setdefault(parent, []).insert( - item.index, item.get_ns()) + item.index, item.get_ns() + ) else: - self.children.setdefault(parent, []).append( - item.get_ns()) + self.children.setdefault(parent, []).append(item.get_ns()) def lookup_menuitem(self, name, default=sentinel): if name is None: @@ -988,8 +997,7 @@ class MenuManager: if name in self.menuitems: return self.menuitems[name] if default is sentinel: - raise self.printer.config_error( - "Unknown menuitem '%s'" % (name,)) + raise self.printer.config_error("Unknown menuitem '%s'" % (name,)) return default def lookup_children(self, ns): @@ -1003,18 +1011,19 @@ class MenuManager: try: cfg = self.pconfig.read_config(filename) except Exception: - raise self.printer.config_error( - "Cannot load config '%s'" % (filename,)) + raise self.printer.config_error("Cannot load config '%s'" % (filename,)) if cfg: self.load_menuitems(cfg) return cfg def load_menuitems(self, config): - for cfg in config.get_prefix_sections('menu '): - type = cfg.get('type') + for cfg in config.get_prefix_sections("menu "): + type = cfg.get("type") if type not in menu_items: - raise error("Choice '%s' for option '%s'" - " is not a valid choice" % (type, menu_items)) + raise error( + "Choice '%s' for option '%s'" + " is not a valid choice" % (type, menu_items) + ) item = menu_items[type](self, cfg) self.add_menuitem(item.get_ns(), item) @@ -1026,19 +1035,19 @@ class MenuManager: self.begin(eventtime) def key_event(self, key, eventtime): - if key == 'click': + if key == "click": self._click_callback(eventtime, key) - elif key == 'long_click': + elif key == "long_click": self._click_callback(eventtime, key) - elif key == 'up': + elif key == "up": self.up(False) - elif key == 'fast_up': + elif key == "fast_up": self.up(True) - elif key == 'down': + elif key == "down": self.down(False) - elif key == 'fast_down': + elif key == "fast_down": self.down(True) - elif key == 'back': + elif key == "back": self.back() self.display.request_redraw() @@ -1048,8 +1057,9 @@ class MenuManager: def stripliterals(cls, s): """Literals are beginning or ending by the double or single quotes""" s = str(s) - if (s.startswith('"') and s.endswith('"')) or \ - (s.startswith("'") and s.endswith("'")): + if (s.startswith('"') and s.endswith('"')) or ( + s.startswith("'") and s.endswith("'") + ): s = s[1:-1] return s @@ -1058,10 +1068,10 @@ class MenuManager: if isinstance(s, str): return s elif isinstance(s, unicode): - return unicode(s).encode('latin-1', 'ignore') + return unicode(s).encode("latin-1", "ignore") else: return str(s) @classmethod def asflat(cls, s): - return cls.stripliterals(''.join(cls.aslatin(s).splitlines())) + return cls.stripliterals("".join(cls.aslatin(s).splitlines())) diff --git a/klippy/extras/display/menu_keys.py b/klippy/extras/display/menu_keys.py index 8094c996..5da9b05c 100644 --- a/klippy/extras/display/menu_keys.py +++ b/klippy/extras/display/menu_keys.py @@ -7,7 +7,8 @@ # This file may be distributed under the terms of the GNU GPLv3 license. LONG_PRESS_DURATION = 0.800 -TIMER_DELAY = .200 +TIMER_DELAY = 0.200 + class MenuKeys: def __init__(self, config, callback): @@ -16,45 +17,48 @@ class MenuKeys: self.callback = callback buttons = self.printer.load_object(config, "buttons") # Register rotary encoder - encoder_pins = config.get('encoder_pins', None) - encoder_steps_per_detent = config.getchoice('encoder_steps_per_detent', - [2, 4], 4) + encoder_pins = config.get("encoder_pins", None) + encoder_steps_per_detent = config.getchoice( + "encoder_steps_per_detent", [2, 4], 4 + ) if encoder_pins is not None: try: - pin1, pin2 = encoder_pins.split(',') + pin1, pin2 = encoder_pins.split(",") except: raise config.error("Unable to parse encoder_pins") - buttons.register_rotary_encoder(pin1.strip(), pin2.strip(), - self.encoder_cw_callback, - self.encoder_ccw_callback, - encoder_steps_per_detent) - self.encoder_fast_rate = config.getfloat('encoder_fast_rate', - .030, above=0.) + buttons.register_rotary_encoder( + pin1.strip(), + pin2.strip(), + self.encoder_cw_callback, + self.encoder_ccw_callback, + encoder_steps_per_detent, + ) + self.encoder_fast_rate = config.getfloat("encoder_fast_rate", 0.030, above=0.0) self.last_encoder_cw_eventtime = 0 self.last_encoder_ccw_eventtime = 0 # Register click button self.is_short_click = False self.click_timer = self.reactor.register_timer(self.long_click_event) - self.register_button(config, 'click_pin', self.click_callback, False) + self.register_button(config, "click_pin", self.click_callback, False) # Register other buttons - self.register_button(config, 'back_pin', self.back_callback) - self.register_button(config, 'up_pin', self.up_callback) - self.register_button(config, 'down_pin', self.down_callback) - self.register_button(config, 'kill_pin', self.kill_callback) + self.register_button(config, "back_pin", self.back_callback) + self.register_button(config, "up_pin", self.up_callback) + self.register_button(config, "down_pin", self.down_callback) + self.register_button(config, "kill_pin", self.kill_callback) def register_button(self, config, name, callback, push_only=True): pin = config.get(name, None) if pin is None: return buttons = self.printer.lookup_object("buttons") - if config.get('analog_range_' + name, None) is None: + if config.get("analog_range_" + name, None) is None: if push_only: buttons.register_button_push(pin, callback) else: buttons.register_buttons([pin], callback) return - amin, amax = config.getfloatlist('analog_range_' + name, count=2) - pullup = config.getfloat('analog_pullup_resistor', 4700., above=0.) + amin, amax = config.getfloatlist("analog_range_" + name, count=2) + pullup = config.getfloat("analog_pullup_resistor", 4700.0, above=0.0) if push_only: buttons.register_adc_button_push(pin, amin, amax, pullup, callback) else: @@ -62,47 +66,48 @@ class MenuKeys: # Rotary encoder callbacks def encoder_cw_callback(self, eventtime): - fast_rate = ((eventtime - self.last_encoder_cw_eventtime) - <= self.encoder_fast_rate) + fast_rate = ( + eventtime - self.last_encoder_cw_eventtime + ) <= self.encoder_fast_rate self.last_encoder_cw_eventtime = eventtime if fast_rate: - self.callback('fast_up', eventtime) + self.callback("fast_up", eventtime) else: - self.callback('up', eventtime) + self.callback("up", eventtime) def encoder_ccw_callback(self, eventtime): - fast_rate = ((eventtime - self.last_encoder_ccw_eventtime) - <= self.encoder_fast_rate) + fast_rate = ( + eventtime - self.last_encoder_ccw_eventtime + ) <= self.encoder_fast_rate self.last_encoder_ccw_eventtime = eventtime if fast_rate: - self.callback('fast_down', eventtime) + self.callback("fast_down", eventtime) else: - self.callback('down', eventtime) + self.callback("down", eventtime) # Click handling def long_click_event(self, eventtime): self.is_short_click = False - self.callback('long_click', eventtime) + self.callback("long_click", eventtime) return self.reactor.NEVER def click_callback(self, eventtime, state): if state: self.is_short_click = True - self.reactor.update_timer(self.click_timer, - eventtime + LONG_PRESS_DURATION) + self.reactor.update_timer(self.click_timer, eventtime + LONG_PRESS_DURATION) elif self.is_short_click: self.reactor.update_timer(self.click_timer, self.reactor.NEVER) - self.callback('click', eventtime) + self.callback("click", eventtime) # Other button callbacks def back_callback(self, eventtime): - self.callback('back', eventtime) + self.callback("back", eventtime) def up_callback(self, eventtime): - self.callback('up', eventtime) + self.callback("up", eventtime) def down_callback(self, eventtime): - self.callback('down', eventtime) + self.callback("down", eventtime) def kill_callback(self, eventtime): self.printer.invoke_shutdown("Shutdown due to kill button!") diff --git a/klippy/extras/display/st7920.py b/klippy/extras/display/st7920.py index 5b2a943c..9411a064 100644 --- a/klippy/extras/display/st7920.py +++ b/klippy/extras/display/st7920.py @@ -7,46 +7,50 @@ import logging from .. import bus from . import font8x14 -BACKGROUND_PRIORITY_CLOCK = 0x7fffffff00000000 +BACKGROUND_PRIORITY_CLOCK = 0x7FFFFFFF00000000 # Spec says 72us, but faster is possible in practice -ST7920_CMD_DELAY = .000020 -ST7920_SYNC_DELAY = .000045 +ST7920_CMD_DELAY = 0.000020 +ST7920_SYNC_DELAY = 0.000045 + +TextGlyphs = {"right_arrow": b"\x1a"} +CharGlyphs = {"degrees": bytearray(font8x14.VGA_FONT[0xF8])} -TextGlyphs = { 'right_arrow': b'\x1a' } -CharGlyphs = { 'degrees': bytearray(font8x14.VGA_FONT[0xf8]) } class DisplayBase: def __init__(self): # framebuffers - self.text_framebuffer = bytearray(b' '*64) + self.text_framebuffer = bytearray(b" " * 64) self.glyph_framebuffer = bytearray(128) self.graphics_framebuffers = [bytearray(32) for i in range(32)] self.all_framebuffers = [ # Text framebuffer - (self.text_framebuffer, bytearray(b'~'*64), 0x80), + (self.text_framebuffer, bytearray(b"~" * 64), 0x80), # Glyph framebuffer - (self.glyph_framebuffer, bytearray(b'~'*128), 0x40), + (self.glyph_framebuffer, bytearray(b"~" * 128), 0x40), # Graphics framebuffers - ] + [(self.graphics_framebuffers[i], bytearray(b'~'*32), i) - for i in range(32)] + ] + [ + (self.graphics_framebuffers[i], bytearray(b"~" * 32), i) for i in range(32) + ] self.cached_glyphs = {} self.icons = {} + def flush(self): # Find all differences in the framebuffers and send them to the chip for new_data, old_data, fb_id in self.all_framebuffers: if new_data == old_data: continue # Find the position of all changed bytes in this framebuffer - diffs = [[i, 1] for i, (n, o) in enumerate(zip(new_data, old_data)) - if n != o] + diffs = [ + [i, 1] for i, (n, o) in enumerate(zip(new_data, old_data)) if n != o + ] # Batch together changes that are close to each other - for i in range(len(diffs)-2, -1, -1): + for i in range(len(diffs) - 2, -1, -1): pos, count = diffs[i] - nextpos, nextcount = diffs[i+1] + nextpos, nextcount = diffs[i + 1] if pos + 5 >= nextpos and nextcount < 16: diffs[i][1] = nextcount + (nextpos - pos) - del diffs[i+1] + del diffs[i + 1] # Transmit changes for pos, count in diffs: count += pos & 0x01 @@ -58,19 +62,23 @@ class DisplayBase: self.send([0x80 + fb_id, 0x80 + chip_pos], is_extended=True) else: self.send([fb_id + chip_pos]) - self.send(new_data[pos:pos+count], is_data=True) + self.send(new_data[pos : pos + count], is_data=True) old_data[:] = new_data + def init(self): - cmds = [0x24, # Enter extended mode - 0x40, # Clear vertical scroll address - 0x02, # Enable CGRAM access - 0x26, # Enable graphics - 0x22, # Leave extended mode - 0x02, # Home the display - 0x06, # Set positive update direction - 0x0c] # Enable display and hide cursor + cmds = [ + 0x24, # Enter extended mode + 0x40, # Clear vertical scroll address + 0x02, # Enable CGRAM access + 0x26, # Enable graphics + 0x22, # Leave extended mode + 0x02, # Home the display + 0x06, # Set positive update direction + 0x0C, + ] # Enable display and hide cursor self.send(cmds) self.flush() + def cache_glyph(self, glyph_name, base_glyph_name, glyph_id): icon = self.icons.get(glyph_name) base_icon = self.icons.get(base_glyph_name) @@ -79,23 +87,26 @@ class DisplayBase: all_bits = zip(icon[0], icon[1], base_icon[0], base_icon[1]) for i, (ic1, ic2, b1, b2) in enumerate(all_bits): x1, x2 = ic1 ^ b1, ic2 ^ b2 - pos = glyph_id*32 + i*2 - self.glyph_framebuffer[pos:pos+2] = [x1, x2] - self.all_framebuffers[1][1][pos:pos+2] = [x1 ^ 1, x2 ^ 1] - self.cached_glyphs[glyph_name] = (base_glyph_name, (0, glyph_id*2)) + pos = glyph_id * 32 + i * 2 + self.glyph_framebuffer[pos : pos + 2] = [x1, x2] + self.all_framebuffers[1][1][pos : pos + 2] = [x1 ^ 1, x2 ^ 1] + self.cached_glyphs[glyph_name] = (base_glyph_name, (0, glyph_id * 2)) + def set_glyphs(self, glyphs): for glyph_name, glyph_data in glyphs.items(): - icon = glyph_data.get('icon16x16') + icon = glyph_data.get("icon16x16") if icon is not None: self.icons[glyph_name] = icon # Setup animated glyphs - self.cache_glyph('fan2', 'fan1', 0) - self.cache_glyph('bed_heat2', 'bed_heat1', 1) + self.cache_glyph("fan2", "fan1", 0) + self.cache_glyph("bed_heat2", "bed_heat1", 1) + def write_text(self, x, y, data): if x + len(data) > 16: - data = data[:16 - min(x, 16)] + data = data[: 16 - min(x, 16)] pos = [0, 32, 16, 48][y] + x - self.text_framebuffer[pos:pos+len(data)] = data + self.text_framebuffer[pos : pos + len(data)] = data + def write_graphics(self, x, y, data): if x >= 16 or y >= 4 or len(data) != 16: return @@ -105,6 +116,7 @@ class DisplayBase: x += 16 for i, bits in enumerate(data): self.graphics_framebuffers[gfx_fb + i][x] = bits + def write_glyph(self, x, y, glyph_name): glyph_id = self.cached_glyphs.get(glyph_name) if glyph_id is not None and x & 1 == 0: @@ -128,28 +140,33 @@ class DisplayBase: self.write_graphics(x, y, font) return 1 return 0 + def clear(self): - self.text_framebuffer[:] = b' '*64 + self.text_framebuffer[:] = b" " * 64 zeros = bytearray(32) for gfb in self.graphics_framebuffers: gfb[:] = zeros + def get_dimensions(self): return (16, 4) + # Display driver for stock ST7920 displays class ST7920(DisplayBase): def __init__(self, config): printer = config.get_printer() # pin config - ppins = printer.lookup_object('pins') - pins = [ppins.lookup_pin(config.get(name + '_pin')) - for name in ['cs', 'sclk', 'sid']] + ppins = printer.lookup_object("pins") + pins = [ + ppins.lookup_pin(config.get(name + "_pin")) + for name in ["cs", "sclk", "sid"] + ] mcu = None for pin_params in pins: - if mcu is not None and pin_params['chip'] != mcu: + if mcu is not None and pin_params["chip"] != mcu: raise ppins.error("st7920 all pins must be on same mcu") - mcu = pin_params['chip'] - self.pins = [pin_params['pin'] for pin_params in pins] + mcu = pin_params["chip"] + self.pins = [pin_params["pin"] for pin_params in pins] # prepare send functions self.mcu = mcu self.oid = self.mcu.create_oid() @@ -158,19 +175,29 @@ class ST7920(DisplayBase): self.is_extended = False # init display base DisplayBase.__init__(self) + def build_config(self): # configure send functions self.mcu.add_config_cmd( "config_st7920 oid=%u cs_pin=%s sclk_pin=%s sid_pin=%s" - " sync_delay_ticks=%d cmd_delay_ticks=%d" % ( - self.oid, self.pins[0], self.pins[1], self.pins[2], + " sync_delay_ticks=%d cmd_delay_ticks=%d" + % ( + self.oid, + self.pins[0], + self.pins[1], + self.pins[2], self.mcu.seconds_to_clock(ST7920_SYNC_DELAY), - self.mcu.seconds_to_clock(ST7920_CMD_DELAY))) + self.mcu.seconds_to_clock(ST7920_CMD_DELAY), + ) + ) cmd_queue = self.mcu.alloc_command_queue() self.send_cmds_cmd = self.mcu.lookup_command( - "st7920_send_cmds oid=%c cmds=%*s", cq=cmd_queue) + "st7920_send_cmds oid=%c cmds=%*s", cq=cmd_queue + ) self.send_data_cmd = self.mcu.lookup_command( - "st7920_send_data oid=%c data=%*s", cq=cmd_queue) + "st7920_send_data oid=%c data=%*s", cq=cmd_queue + ) + def send(self, cmds, is_data=False, is_extended=False): cmd_type = self.send_cmds_cmd if is_data: @@ -182,26 +209,30 @@ class ST7920(DisplayBase): cmds = [add_cmd] + cmds self.is_extended = is_extended cmd_type.send([self.oid, cmds], reqclock=BACKGROUND_PRIORITY_CLOCK) - #logging.debug("st7920 %d %s", is_data, repr(cmds)) + # logging.debug("st7920 %d %s", is_data, repr(cmds)) + # Helper code for toggling the en pin on startup class EnableHelper: def __init__(self, pin_desc, spi): - self.en_pin = bus.MCU_bus_digital_out(spi.get_mcu(), pin_desc, - spi.get_command_queue()) + self.en_pin = bus.MCU_bus_digital_out( + spi.get_mcu(), pin_desc, spi.get_command_queue() + ) + def init(self): mcu = self.en_pin.get_mcu() curtime = mcu.get_printer().get_reactor().monotonic() print_time = mcu.estimated_print_time(curtime) # Toggle enable pin - minclock = mcu.print_time_to_clock(print_time + .100) + minclock = mcu.print_time_to_clock(print_time + 0.100) self.en_pin.update_digital_out(0, minclock=minclock) - minclock = mcu.print_time_to_clock(print_time + .200) + minclock = mcu.print_time_to_clock(print_time + 0.200) self.en_pin.update_digital_out(1, minclock=minclock) # Force a delay to any subsequent commands on the command queue - minclock = mcu.print_time_to_clock(print_time + .300) + minclock = mcu.print_time_to_clock(print_time + 0.300) self.en_pin.update_digital_out(1, minclock=minclock) + # Display driver for displays that emulate the ST7920 in software. # These displays rely on the CS pin to be toggled in order to initialize the # SPI correctly. This display driver uses a software SPI with an unused pin @@ -209,19 +240,22 @@ class EnableHelper: class EmulatedST7920(DisplayBase): def __init__(self, config): # create software spi - ppins = config.get_printer().lookup_object('pins') - sw_pin_names = ['spi_software_%s_pin' % (name,) - for name in ['miso', 'mosi', 'sclk']] - sw_pin_params = [ppins.lookup_pin(config.get(name), share_type=name) - for name in sw_pin_names] + ppins = config.get_printer().lookup_object("pins") + sw_pin_names = [ + "spi_software_%s_pin" % (name,) for name in ["miso", "mosi", "sclk"] + ] + sw_pin_params = [ + ppins.lookup_pin(config.get(name), share_type=name) for name in sw_pin_names + ] mcu = None for pin_params in sw_pin_params: - if mcu is not None and pin_params['chip'] != mcu: - raise ppins.error("%s: spi pins must be on same mcu" % ( - config.get_name(),)) - mcu = pin_params['chip'] - sw_pins = tuple([pin_params['pin'] for pin_params in sw_pin_params]) - speed = config.getint('spi_speed', 1000000, minval=100000) + if mcu is not None and pin_params["chip"] != mcu: + raise ppins.error( + "%s: spi pins must be on same mcu" % (config.get_name(),) + ) + mcu = pin_params["chip"] + sw_pins = tuple([pin_params["pin"] for pin_params in sw_pin_params]) + speed = config.getint("spi_speed", 1000000, minval=100000) self.spi = bus.MCU_SPI(mcu, None, None, 0, speed, sw_pins) # create enable helper self.en_helper = EnableHelper(config.get("en_pin"), self.spi) @@ -229,11 +263,12 @@ class EmulatedST7920(DisplayBase): # init display base self.is_extended = False DisplayBase.__init__(self) + def send(self, cmds, is_data=False, is_extended=False): # setup sync byte and check for exten mode switch - sync_byte = 0xfa + sync_byte = 0xFA if not is_data: - sync_byte = 0xf8 + sync_byte = 0xF8 if self.is_extended != is_extended: add_cmd = 0x22 if is_extended: @@ -254,4 +289,4 @@ class EmulatedST7920(DisplayBase): self.en_set = True # send data self.spi.spi_send(spi_data, reqclock=BACKGROUND_PRIORITY_CLOCK) - #logging.debug("st7920 %s", repr(spi_data)) + # logging.debug("st7920 %s", repr(spi_data)) diff --git a/klippy/extras/display/uc1701.py b/klippy/extras/display/uc1701.py index 8e877cf2..7d4e4683 100644 --- a/klippy/extras/display/uc1701.py +++ b/klippy/extras/display/uc1701.py @@ -8,9 +8,10 @@ import logging from .. import bus from . import font8x14 -BACKGROUND_PRIORITY_CLOCK = 0x7fffffff00000000 +BACKGROUND_PRIORITY_CLOCK = 0x7FFFFFFF00000000 + +TextGlyphs = {"right_arrow": b"\x1a", "degrees": b"\xf8"} -TextGlyphs = { 'right_arrow': b'\x1a', 'degrees': b'\xf8' } class DisplayBase: def __init__(self, io, columns=128, x_offset=0): @@ -19,37 +20,40 @@ class DisplayBase: self.columns = columns self.x_offset = x_offset self.vram = [bytearray(self.columns) for i in range(8)] - self.all_framebuffers = [(self.vram[i], bytearray(b'~'*self.columns), i) - for i in range(8)] + self.all_framebuffers = [ + (self.vram[i], bytearray(b"~" * self.columns), i) for i in range(8) + ] # Cache fonts and icons in display byte order - self.font = [self._swizzle_bits(bytearray(c)) - for c in font8x14.VGA_FONT] + self.font = [self._swizzle_bits(bytearray(c)) for c in font8x14.VGA_FONT] self.icons = {} + def flush(self): # Find all differences in the framebuffers and send them to the chip for new_data, old_data, page in self.all_framebuffers: if new_data == old_data: continue # Find the position of all changed bytes in this framebuffer - diffs = [[i, 1] for i, (n, o) in enumerate(zip(new_data, old_data)) - if n != o] + diffs = [ + [i, 1] for i, (n, o) in enumerate(zip(new_data, old_data)) if n != o + ] # Batch together changes that are close to each other - for i in range(len(diffs)-2, -1, -1): + for i in range(len(diffs) - 2, -1, -1): pos, count = diffs[i] - nextpos, nextcount = diffs[i+1] + nextpos, nextcount = diffs[i + 1] if pos + 5 >= nextpos and nextcount < 16: diffs[i][1] = nextcount + (nextpos - pos) - del diffs[i+1] + del diffs[i + 1] # Transmit changes for col_pos, count in diffs: # Set Position registers - ra = 0xb0 | (page & 0x0F) + ra = 0xB0 | (page & 0x0F) ca_msb = 0x10 | ((col_pos >> 4) & 0x0F) ca_lsb = col_pos & 0x0F self.send([ra, ca_msb, ca_lsb]) # Send Data - self.send(new_data[col_pos:col_pos+count], is_data=True) + self.send(new_data[col_pos : col_pos + count], is_data=True) old_data[:] = new_data + def _swizzle_bits(self, data): # Convert from "rows of pixels" format to "columns of pixels" top = bot = 0 @@ -58,28 +62,31 @@ class DisplayBase: top |= spaced >> (7 - row) spaced = (data[row + 8] * 0x8040201008040201) & 0x8080808080808080 bot |= spaced >> (7 - row) - bits_top = [(top >> s) & 0xff for s in range(0, 64, 8)] - bits_bot = [(bot >> s) & 0xff for s in range(0, 64, 8)] + bits_top = [(top >> s) & 0xFF for s in range(0, 64, 8)] + bits_bot = [(bot >> s) & 0xFF for s in range(0, 64, 8)] return (bytearray(bits_top), bytearray(bits_bot)) + def set_glyphs(self, glyphs): for glyph_name, glyph_data in glyphs.items(): - icon = glyph_data.get('icon16x16') + icon = glyph_data.get("icon16x16") if icon is not None: top1, bot1 = self._swizzle_bits(icon[0]) top2, bot2 = self._swizzle_bits(icon[1]) self.icons[glyph_name] = (top1 + top2, bot1 + bot2) + def write_text(self, x, y, data): if x + len(data) > 16: - data = data[:16 - min(x, 16)] + data = data[: 16 - min(x, 16)] pix_x = x * 8 pix_x += self.x_offset page_top = self.vram[y * 2] page_bot = self.vram[y * 2 + 1] for c in bytearray(data): bits_top, bits_bot = self.font[c] - page_top[pix_x:pix_x+8] = bits_top - page_bot[pix_x:pix_x+8] = bits_bot + page_top[pix_x : pix_x + 8] = bits_top + page_bot[pix_x : pix_x + 8] = bits_bot pix_x += 8 + def write_graphics(self, x, y, data): if x >= 16 or y >= 4 or len(data) != 16: return @@ -91,6 +98,7 @@ class DisplayBase: for i in range(8): page_top[pix_x + i] ^= bits_top[i] page_bot[pix_x + i] ^= bits_bot[i] + def write_glyph(self, x, y, glyph_name): icon = self.icons.get(glyph_name) if icon is not None and x < 15: @@ -98,8 +106,8 @@ class DisplayBase: pix_x = x * 8 pix_x += self.x_offset page_idx = y * 2 - self.vram[page_idx][pix_x:pix_x+16] = icon[0] - self.vram[page_idx + 1][pix_x:pix_x+16] = icon[1] + self.vram[page_idx][pix_x : pix_x + 16] = icon[0] + self.vram[page_idx + 1][pix_x : pix_x + 16] = icon[1] return 2 char = TextGlyphs.get(glyph_name) if char is not None: @@ -107,30 +115,37 @@ class DisplayBase: self.write_text(x, y, char) return 1 return 0 + def clear(self): zeros = bytearray(self.columns) for page in self.vram: page[:] = zeros + def get_dimensions(self): return (16, 4) + # IO wrapper for "4 wire" spi bus (spi bus with an extra data/control line) class SPI4wire: def __init__(self, config, data_pin_name): self.spi = bus.MCU_SPI_from_config(config, 0, default_speed=10000000) dc_pin = config.get(data_pin_name) - self.mcu_dc = bus.MCU_bus_digital_out(self.spi.get_mcu(), dc_pin, - self.spi.get_command_queue()) + self.mcu_dc = bus.MCU_bus_digital_out( + self.spi.get_mcu(), dc_pin, self.spi.get_command_queue() + ) + def send(self, cmds, is_data=False): - self.mcu_dc.update_digital_out(is_data, - reqclock=BACKGROUND_PRIORITY_CLOCK) + self.mcu_dc.update_digital_out(is_data, reqclock=BACKGROUND_PRIORITY_CLOCK) self.spi.spi_send(cmds, reqclock=BACKGROUND_PRIORITY_CLOCK) + # IO wrapper for i2c bus class I2C: def __init__(self, config, default_addr): - self.i2c = bus.MCU_I2C_from_config(config, default_addr=default_addr, - default_speed=400000) + self.i2c = bus.MCU_I2C_from_config( + config, default_addr=default_addr, default_speed=400000 + ) + def send(self, cmds, is_data=False): if is_data: hdr = 0x40 @@ -140,14 +155,17 @@ class I2C: cmds.insert(0, hdr) self.i2c.i2c_write(cmds, reqclock=BACKGROUND_PRIORITY_CLOCK) + # Helper code for toggling a reset pin on startup class ResetHelper: def __init__(self, pin_desc, io_bus): self.mcu_reset = None if pin_desc is None: return - self.mcu_reset = bus.MCU_bus_digital_out(io_bus.get_mcu(), pin_desc, - io_bus.get_command_queue()) + self.mcu_reset = bus.MCU_bus_digital_out( + io_bus.get_mcu(), pin_desc, io_bus.get_command_queue() + ) + def init(self): if self.mcu_reset is None: return @@ -155,45 +173,50 @@ class ResetHelper: curtime = mcu.get_printer().get_reactor().monotonic() print_time = mcu.estimated_print_time(curtime) # Toggle reset - minclock = mcu.print_time_to_clock(print_time + .100) + minclock = mcu.print_time_to_clock(print_time + 0.100) self.mcu_reset.update_digital_out(0, minclock=minclock) - minclock = mcu.print_time_to_clock(print_time + .200) + minclock = mcu.print_time_to_clock(print_time + 0.200) self.mcu_reset.update_digital_out(1, minclock=minclock) # Force a delay to any subsequent commands on the command queue - minclock = mcu.print_time_to_clock(print_time + .300) + minclock = mcu.print_time_to_clock(print_time + 0.300) self.mcu_reset.update_digital_out(1, minclock=minclock) + # The UC1701 is a "4-wire" SPI display device class UC1701(DisplayBase): def __init__(self, config): io = SPI4wire(config, "a0_pin") DisplayBase.__init__(self, io) - self.contrast = config.getint('contrast', 40, minval=0, maxval=63) + self.contrast = config.getint("contrast", 40, minval=0, maxval=63) self.reset = ResetHelper(config.get("rst_pin", None), io.spi) + def init(self): self.reset.init() - init_cmds = [0xE2, # System reset - 0x40, # Set display to start at line 0 - 0xA0, # Set SEG direction - 0xC8, # Set COM Direction - 0xA2, # Set Bias = 1/9 - 0x2C, # Boost ON - 0x2E, # Voltage regulator on - 0x2F, # Voltage follower on - 0xF8, # Set booster ratio - 0x00, # Booster ratio value (4x) - 0x23, # Set resistor ratio (3) - 0x81, # Set Electronic Volume - self.contrast, # Electronic Volume value - 0xAC, # Set static indicator off - 0x00, # NOP - 0xA6, # Disable Inverse - 0xAF] # Set display enable + init_cmds = [ + 0xE2, # System reset + 0x40, # Set display to start at line 0 + 0xA0, # Set SEG direction + 0xC8, # Set COM Direction + 0xA2, # Set Bias = 1/9 + 0x2C, # Boost ON + 0x2E, # Voltage regulator on + 0x2F, # Voltage follower on + 0xF8, # Set booster ratio + 0x00, # Booster ratio value (4x) + 0x23, # Set resistor ratio (3) + 0x81, # Set Electronic Volume + self.contrast, # Electronic Volume value + 0xAC, # Set static indicator off + 0x00, # NOP + 0xA6, # Disable Inverse + 0xAF, + ] # Set display enable self.send(init_cmds) - self.send([0xA5]) # display all - self.send([0xA4]) # normal display + self.send([0xA5]) # display all + self.send([0xA4]) # normal display self.flush() + # The SSD1306 supports both i2c and "4-wire" spi class SSD1306(DisplayBase): def __init__(self, config, columns=128, x_offset=0): @@ -206,35 +229,46 @@ class SSD1306(DisplayBase): io_bus = io.spi self.reset = ResetHelper(config.get("reset_pin", None), io_bus) DisplayBase.__init__(self, io, columns, x_offset) - self.contrast = config.getint('contrast', 239, minval=0, maxval=255) - self.vcomh = config.getint('vcomh', 0, minval=0, maxval=63) - self.invert = config.getboolean('invert', False) + self.contrast = config.getint("contrast", 239, minval=0, maxval=255) + self.vcomh = config.getint("vcomh", 0, minval=0, maxval=63) + self.invert = config.getboolean("invert", False) + def init(self): self.reset.init() init_cmds = [ - 0xAE, # Display off - 0xD5, 0x80, # Set oscillator frequency - 0xA8, 0x3f, # Set multiplex ratio - 0xD3, 0x00, # Set display offset - 0x40, # Set display start line - 0x8D, 0x14, # Charge pump setting - 0x20, 0x02, # Set Memory addressing mode - 0xA1, # Set Segment re-map - 0xC8, # Set COM output scan direction - 0xDA, 0x12, # Set COM pins hardware configuration - 0x81, self.contrast, # Set contrast control - 0xD9, 0xA1, # Set pre-charge period - 0xDB, self.vcomh, # Set VCOMH deselect level - 0x2E, # Deactivate scroll - 0xA4, # Output ram to display - 0xA7 if self.invert else 0xA6, # Set normal/invert - 0xAF, # Display on + 0xAE, # Display off + 0xD5, + 0x80, # Set oscillator frequency + 0xA8, + 0x3F, # Set multiplex ratio + 0xD3, + 0x00, # Set display offset + 0x40, # Set display start line + 0x8D, + 0x14, # Charge pump setting + 0x20, + 0x02, # Set Memory addressing mode + 0xA1, # Set Segment re-map + 0xC8, # Set COM output scan direction + 0xDA, + 0x12, # Set COM pins hardware configuration + 0x81, + self.contrast, # Set contrast control + 0xD9, + 0xA1, # Set pre-charge period + 0xDB, + self.vcomh, # Set VCOMH deselect level + 0x2E, # Deactivate scroll + 0xA4, # Output ram to display + 0xA7 if self.invert else 0xA6, # Set normal/invert + 0xAF, # Display on ] self.send(init_cmds) self.flush() + # the SH1106 is SSD1306 compatible with up to 132 columns class SH1106(SSD1306): def __init__(self, config): - x_offset = config.getint('x_offset', 0, minval=0, maxval=3) + x_offset = config.getint("x_offset", 0, minval=0, maxval=3) SSD1306.__init__(self, config, 132, x_offset=x_offset) |