aboutsummaryrefslogtreecommitdiffstats
path: root/klippy/extras/display
diff options
context:
space:
mode:
authorTomasz Kramkowski <tomasz@kramkow.ski>2025-08-06 17:50:53 +0100
committerTomasz Kramkowski <tomasz@kramkow.ski>2025-08-06 17:50:53 +0100
commit581208b2ffeeb2a2128aee0741fa3fd9e46358e2 (patch)
treef0d7e0e5cb574b235a5495b0f40f79e3ded7aa4d /klippy/extras/display
parente1176e4dfb9018e712d4fa86daf41e9e762a1698 (diff)
downloadkutter-581208b2ffeeb2a2128aee0741fa3fd9e46358e2.tar.gz
kutter-581208b2ffeeb2a2128aee0741fa3fd9e46358e2.tar.xz
kutter-581208b2ffeeb2a2128aee0741fa3fd9e46358e2.zip
Run black on all first party python code
Diffstat (limited to 'klippy/extras/display')
-rw-r--r--klippy/extras/display/__init__.py10
-rw-r--r--klippy/extras/display/aip31068_spi.py151
-rw-r--r--klippy/extras/display/display.py176
-rw-r--r--klippy/extras/display/font8x14.py512
-rw-r--r--klippy/extras/display/hd44780.py109
-rw-r--r--klippy/extras/display/hd44780_spi.py80
-rw-r--r--klippy/extras/display/menu.py344
-rw-r--r--klippy/extras/display/menu_keys.py73
-rw-r--r--klippy/extras/display/st7920.py165
-rw-r--r--klippy/extras/display/uc1701.py180
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)