New OLED extension (#742)
* Adding a new OLED extension and updated kyria code to use it. * Fixed some basic errors and improved performance of extension. * Add support for changing LED brightness. * Add support for changing OLED brightness. * Update oled.py * Small bugfix * Take busio.I2C object as Oled parameter * Add a missing statement into deinit * Optionally initialize I2C inside the extension * Implement suggested changes --------- Co-authored-by: Jan Lindblom <janlindblom@fastmail.fm>
This commit is contained in:
		
				
					committed by
					
						
						GitHub
					
				
			
			
				
	
			
			
			
						parent
						
							5448cb4479
						
					
				
				
					commit
					f532a57e9a
				
			
							
								
								
									
										272
									
								
								kmk/extensions/oled.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										272
									
								
								kmk/extensions/oled.py
									
									
									
									
									
										Normal file
									
								
							@@ -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()
 | 
			
		||||
		Reference in New Issue
	
	Block a user