diff --git a/kmk/extensions/oled.py b/kmk/extensions/oled.py new file mode 100644 index 0000000..5a5efec --- /dev/null +++ b/kmk/extensions/oled.py @@ -0,0 +1,272 @@ +import busio +from supervisor import ticks_ms + +import adafruit_displayio_ssd1306 +import displayio +import terminalio +from adafruit_display_text import label + +from kmk.extensions import Extension +from kmk.handlers.stock import passthrough as handler_passthrough +from kmk.keys import make_key +from kmk.kmktime import PeriodicTimer, ticks_diff +from kmk.modules.split import Split, SplitSide +from kmk.utils import clamp + +displayio.release_displays() + + +class TextEntry: + def __init__( + self, + text='', + x=0, + y=0, + x_anchor='L', + y_anchor='T', + direction='LTR', + line_spacing=0.75, + inverted=False, + layer=None, + side=None, + ): + self.text = text + self.direction = direction + self.line_spacing = line_spacing + self.inverted = inverted + self.layer = layer + self.color = 0xFFFFFF + self.background_color = 0x000000 + self.x_anchor = 0.0 + self.y_anchor = 0.0 + if x_anchor == 'L': + self.x_anchor = 0.0 + x = x + 1 + if x_anchor == 'M': + self.x_anchor = 0.5 + if x_anchor == 'R': + self.x_anchor = 1.0 + if y_anchor == 'T': + self.y_anchor = 0.0 + if y_anchor == 'M': + self.y_anchor = 0.5 + if y_anchor == 'B': + self.y_anchor = 1.0 + self.anchor_point = (self.x_anchor, self.y_anchor) + self.anchored_position = (x, y) + if inverted: + self.color = 0x000000 + self.background_color = 0xFFFFFF + self.side = side + if side == 'L': + self.side = SplitSide.LEFT + if side == 'R': + self.side = SplitSide.RIGHT + + +class ImageEntry: + def __init__(self, x=0, y=0, image='', layer=None, side=None): + self.x = x + self.y = y + self.image = image + self.layer = layer + self.side = side + if side == 'L': + self.side = SplitSide.LEFT + if side == 'R': + self.side = SplitSide.RIGHT + + +class Oled(Extension): + def __init__( + self, + i2c=None, + sda=None, + scl=None, + device_address=0x3C, + entries=[], + width=128, + height=32, + flip: bool = False, + flip_left: bool = False, + flip_right: bool = False, + brightness=0.8, + brightness_step=0.1, + dim_time=20, + dim_target=0.1, + off_time=60, + powersave_dim_time=10, + powersave_dim_target=0.1, + powersave_off_time=30, + ): + self.device_address = device_address + self.flip = flip + self.flip_left = flip_left + self.flip_right = flip_right + self.entries = entries + self.width = width + self.height = height + self.prev_layer = None + self.brightness = brightness + self.brightness_step = brightness_step + self.timer_start = ticks_ms() + self.powersave = False + self.dim_time_ms = dim_time * 1000 + self.dim_target = dim_target + self.off_time_ms = off_time * 1000 + self.powersavedim_time_ms = powersave_dim_time * 1000 + self.powersave_dim_target = powersave_dim_target + self.powersave_off_time_ms = powersave_off_time * 1000 + self.dim_period = PeriodicTimer(50) + self.split_side = None + # i2c initialization + self.i2c = i2c + if self.i2c is None: + self.i2c = busio.I2C(scl, sda) + + make_key( + names=('OLED_BRI',), + on_press=self.oled_brightness_increase, + on_release=handler_passthrough, + ) + make_key( + names=('OLED_BRD',), + on_press=self.oled_brightness_decrease, + on_release=handler_passthrough, + ) + + def render(self, layer): + splash = displayio.Group() + + for entry in self.entries: + if entry.layer != layer and entry.layer is not None: + continue + if isinstance(entry, TextEntry): + splash.append( + label.Label( + terminalio.FONT, + text=entry.text, + color=entry.color, + background_color=entry.background_color, + anchor_point=entry.anchor_point, + anchored_position=entry.anchored_position, + label_direction=entry.direction, + line_spacing=entry.line_spacing, + padding_left=1, + ) + ) + elif isinstance(entry, ImageEntry): + splash.append( + displayio.TileGrid( + entry.image, + pixel_shader=entry.image.pixel_shader, + x=entry.x, + y=entry.y, + ) + ) + self.display.show(splash) + + def on_runtime_enable(self, sandbox): + return + + def on_runtime_disable(self, sandbox): + return + + def during_bootup(self, keyboard): + + for module in keyboard.modules: + if isinstance(module, Split): + self.split_side = module.split_side + + if self.split_side == SplitSide.LEFT: + self.flip = self.flip_left + elif self.split_side == SplitSide.RIGHT: + self.flip = self.flip_right + + for idx, entry in enumerate(self.entries): + if entry.side != self.split_side and entry.side is not None: + del self.entries[idx] + + self.display = adafruit_displayio_ssd1306.SSD1306( + displayio.I2CDisplay(self.i2c, device_address=self.device_address), + width=self.width, + height=self.height, + rotation=180 if self.flip else 0, + brightness=self.brightness, + ) + + def before_matrix_scan(self, sandbox): + if self.dim_period.tick(): + self.dim() + if sandbox.active_layers[0] != self.prev_layer: + self.prev_layer = sandbox.active_layers[0] + self.render(sandbox.active_layers[0]) + + def after_matrix_scan(self, sandbox): + if sandbox.matrix_update or sandbox.secondary_matrix_update: + self.timer_start = ticks_ms() + + def before_hid_send(self, sandbox): + return + + def after_hid_send(self, sandbox): + return + + def on_powersave_enable(self, sandbox): + self.powersave = True + + def on_powersave_disable(self, sandbox): + self.powersave = False + + def deinit(self, sandbox): + displayio.release_displays() + self.i2c.deinit() + + def oled_brightness_increase(self): + self.display.brightness = clamp( + self.display.brightness + self.brightness_step, 0, 1 + ) + self.brightness = self.display.brightness # Save current brightness + + def oled_brightness_decrease(self): + self.display.brightness = clamp( + self.display.brightness - self.brightness_step, 0, 1 + ) + self.brightness = self.display.brightness # Save current brightness + + def dim(self): + if self.powersave: + + if ( + self.powersave_off_time_ms + and ticks_diff(ticks_ms(), self.timer_start) + > self.powersave_off_time_ms + ): + self.display.sleep() + + elif ( + self.powersave_dim_time_ms + and ticks_diff(ticks_ms(), self.timer_start) + > self.powersave_dim_time_ms + ): + self.display.brightness = self.powersave_dim_target + + else: + self.display.brightness = self.brightness + self.display.wake() + + elif ( + self.off_time_ms + and ticks_diff(ticks_ms(), self.timer_start) > self.off_time_ms + ): + self.display.sleep() + + elif ( + self.dim_time_ms + and ticks_diff(ticks_ms(), self.timer_start) > self.dim_time_ms + ): + self.display.brightness = self.dim_target + + else: + self.display.brightness = self.brightness + self.display.wake()