mirror of
https://github.com/jayofelony/pwnagotchi.git
synced 2025-07-01 18:37:27 -04:00
@ -260,9 +260,21 @@ class Display(View):
|
||||
def is_pirateaudio(self):
|
||||
return self._implementation.name == 'pirateaudio'
|
||||
|
||||
def gfxhat(self):
|
||||
return self._implementation.name == 'gfxhat'
|
||||
|
||||
def is_argonpod(self):
|
||||
return self._implementation.name == 'argonpod'
|
||||
|
||||
def is_pitft(self):
|
||||
return self._implementation.name == 'pitft'
|
||||
|
||||
def is_minipitft(self):
|
||||
return self._implementation.name == 'minipitft'
|
||||
|
||||
def is_minipitft2(self):
|
||||
return self._implementation.name == 'minipitft2'
|
||||
|
||||
def is_tftbonnet(self):
|
||||
return self._implementation.name == 'tftbonnet'
|
||||
|
||||
|
@ -96,10 +96,26 @@ def display_for(config):
|
||||
from pwnagotchi.ui.hw.pirateaudio import PirateAudio
|
||||
return PirateAudio(config)
|
||||
|
||||
elif config['ui']['display']['type'] == 'gfxhat':
|
||||
from pwnagotchi.ui.hw.gfxhat import GfxHat
|
||||
return GfxHat(config)
|
||||
|
||||
elif config['ui']['display']['type'] == 'argonpod':
|
||||
from pwnagotchi.ui.hw.argonpod import ArgonPod
|
||||
return ArgonPod(config)
|
||||
|
||||
elif config['ui']['display']['type'] == 'pitft':
|
||||
from pwnagotchi.ui.hw.pitft import Pitft
|
||||
return Pitft(config)
|
||||
|
||||
elif config['ui']['display']['type'] == 'minipitft':
|
||||
from pwnagotchi.ui.hw.minipitft import MiniPitft
|
||||
return MiniPitft(config)
|
||||
|
||||
elif config['ui']['display']['type'] == 'minipitft2':
|
||||
from pwnagotchi.ui.hw.minipitft2 import MiniPitft2
|
||||
return MiniPitft2(config)
|
||||
|
||||
elif config['ui']['display']['type'] == 'tftbonnet':
|
||||
from pwnagotchi.ui.hw.tftbonnet import TftBonnet
|
||||
return TftBonnet(config)
|
||||
|
54
pwnagotchi/ui/hw/argonpod.py
Normal file
54
pwnagotchi/ui/hw/argonpod.py
Normal file
@ -0,0 +1,54 @@
|
||||
# board GPIO:
|
||||
# Key1:
|
||||
# Key2:
|
||||
# Key3:
|
||||
# Key4:
|
||||
#
|
||||
# Touch chipset:
|
||||
# HW info: https://argon40.com/products/pod-display-2-8inch
|
||||
# HW datasheet:
|
||||
|
||||
import logging
|
||||
|
||||
import pwnagotchi.ui.fonts as fonts
|
||||
from pwnagotchi.ui.hw.base import DisplayImpl
|
||||
|
||||
|
||||
class ArgonPod(DisplayImpl):
|
||||
def __init__(self, config):
|
||||
super(ArgonPod, self).__init__(config, 'argonpod')
|
||||
self._display = None
|
||||
|
||||
def layout(self):
|
||||
fonts.setup(12, 10, 12, 70, 25, 9)
|
||||
self._layout['width'] = 320
|
||||
self._layout['height'] = 240
|
||||
self._layout['face'] = (0, 36)
|
||||
self._layout['name'] = (150, 36)
|
||||
self._layout['channel'] = (0, 0)
|
||||
self._layout['aps'] = (40, 0)
|
||||
self._layout['uptime'] = (240, 0)
|
||||
self._layout['line1'] = [0, 14, 320, 14]
|
||||
self._layout['line2'] = [0, 220, 320, 220]
|
||||
self._layout['friend_face'] = (0, 130)
|
||||
self._layout['friend_name'] = (40, 135)
|
||||
self._layout['shakes'] = (0, 220)
|
||||
self._layout['mode'] = (280, 220)
|
||||
self._layout['status'] = {
|
||||
'pos': (150, 48),
|
||||
'font': fonts.status_font(fonts.Medium),
|
||||
'max': 20
|
||||
}
|
||||
|
||||
return self._layout
|
||||
|
||||
def initialize(self):
|
||||
logging.info("Initializing Argon Pod display")
|
||||
from pwnagotchi.ui.hw.libs.argon.argonpod.ILI9341 import ILI9341
|
||||
self._display = ILI9341(0, 0, 22, 18)
|
||||
|
||||
def render(self, canvas):
|
||||
self._display.display(canvas)
|
||||
|
||||
def clear(self):
|
||||
self._display.clear()
|
@ -7,7 +7,7 @@ from pwnagotchi.ui.hw.base import DisplayImpl
|
||||
class DisplayHatMini(DisplayImpl):
|
||||
def __init__(self, config):
|
||||
super(DisplayHatMini, self).__init__(config, 'displayhatmini')
|
||||
self.mode = "RGB" # its actually BGR;16 5,6,5 bit, but display lib converts it
|
||||
self._display = None
|
||||
|
||||
def layout(self):
|
||||
fonts.setup(12, 10, 12, 70, 25, 9)
|
||||
@ -35,10 +35,10 @@ class DisplayHatMini(DisplayImpl):
|
||||
def initialize(self):
|
||||
logging.info("initializing Display Hat Mini")
|
||||
from pwnagotchi.ui.hw.libs.pimoroni.displayhatmini.ST7789 import ST7789
|
||||
self._display = ST7789(0, 1, 9, 13, width=self._layout['width'], height=self._layout['height'], rotation=0)
|
||||
self._display = ST7789(0,1,9,13)
|
||||
|
||||
def render(self, canvas):
|
||||
self._display.display(canvas)
|
||||
|
||||
def clear(self):
|
||||
pass
|
||||
self._display.clear()
|
||||
|
63
pwnagotchi/ui/hw/gfxhat.py
Normal file
63
pwnagotchi/ui/hw/gfxhat.py
Normal file
@ -0,0 +1,63 @@
|
||||
# Created for the Pwnagotchi project by RasTacsko
|
||||
# HW libraries are based on the pimoroni gfx-hat repo:
|
||||
# https://github.com/pimoroni/gfx-hat/tree/master
|
||||
#
|
||||
# Contrast and Backlight color are imported from config.toml
|
||||
#
|
||||
# ui.display.contrast = 40
|
||||
# ui.display.blcolor = "olive"
|
||||
#
|
||||
# Contrast should be between 30-50, default is 40
|
||||
# Backlight are predefined in the epd.py
|
||||
# Available backlight colors:
|
||||
# white, grey, maroon, red, purple, fuchsia, green,
|
||||
# lime, olive, yellow, navy, blue, teal, aqua
|
||||
|
||||
import logging
|
||||
|
||||
import pwnagotchi.ui.fonts as fonts
|
||||
from pwnagotchi.ui.hw.base import DisplayImpl
|
||||
|
||||
class GfxHat(DisplayImpl):
|
||||
def __init__(self, config):
|
||||
self._config = config['ui']['display']
|
||||
super(GfxHat, self).__init__(config, 'gfxhat')
|
||||
|
||||
def layout(self):
|
||||
fonts.setup(8, 8, 8, 10, 10, 8)
|
||||
self._layout['width'] = 128
|
||||
self._layout['height'] = 64
|
||||
self._layout['face'] = (0, 30)
|
||||
self._layout['name'] = (0, 10)
|
||||
self._layout['channel'] = (72, 10)
|
||||
self._layout['aps'] = (0, 0)
|
||||
self._layout['uptime'] = (87, 0)
|
||||
self._layout['line1'] = [0, 9, 128, 9]
|
||||
self._layout['line2'] = [0, 54, 128, 54]
|
||||
self._layout['friend_face'] = (0, 41)
|
||||
self._layout['friend_name'] = (40, 43)
|
||||
self._layout['shakes'] = (0, 55)
|
||||
self._layout['mode'] = (107, 10)
|
||||
self._layout['status'] = {
|
||||
'pos': (37, 19),
|
||||
'font': fonts.status_font(fonts.Small),
|
||||
'max': 18
|
||||
}
|
||||
return self._layout
|
||||
|
||||
def initialize(self):
|
||||
contrast = self._config['contrast'] if 'contrast' in self._config else 40
|
||||
blcolor = self._config['blcolor'] if 'blcolor' in self._config else 'OLIVE'
|
||||
logging.info("initializing Pimoroni GfxHat")
|
||||
logging.info("initializing Pimoroni GfxHat - Contrast: %d Backlight color: %s" % (contrast, blcolor))
|
||||
from pwnagotchi.ui.hw.libs.pimoroni.gfxhat.epd import EPD
|
||||
self._display = EPD(contrast=contrast)
|
||||
self._display.Init(color_name=blcolor)
|
||||
self._display.Clear()
|
||||
|
||||
|
||||
def render(self, canvas):
|
||||
self._display.Display(canvas)
|
||||
|
||||
def clear(self):
|
||||
self._display.Clear()
|
360
pwnagotchi/ui/hw/libs/adafruit/minipitft/ST7789.py
Normal file
360
pwnagotchi/ui/hw/libs/adafruit/minipitft/ST7789.py
Normal file
@ -0,0 +1,360 @@
|
||||
# Copyright (c) 2014 Adafruit Industries
|
||||
# Author: Tony DiCola
|
||||
#
|
||||
# Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
# of this software and associated documentation files (the "Software"), to deal
|
||||
# in the Software without restriction, including without limitation the rights
|
||||
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
# copies of the Software, and to permit persons to whom the Software is
|
||||
# furnished to do so, subject to the following conditions:
|
||||
#
|
||||
# The above copyright notice and this permission notice shall be included in
|
||||
# all copies or substantial portions of the Software.
|
||||
#
|
||||
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
# THE SOFTWARE.
|
||||
import numbers
|
||||
import time
|
||||
import numpy as np
|
||||
|
||||
import spidev
|
||||
import RPi.GPIO as GPIO
|
||||
|
||||
|
||||
__version__ = '0.0.4'
|
||||
|
||||
BG_SPI_CS_BACK = 0
|
||||
BG_SPI_CS_FRONT = 1
|
||||
|
||||
SPI_CLOCK_HZ = 16000000
|
||||
|
||||
ST7789_NOP = 0x00
|
||||
ST7789_SWRESET = 0x01
|
||||
ST7789_RDDID = 0x04
|
||||
ST7789_RDDST = 0x09
|
||||
|
||||
ST7789_SLPIN = 0x10
|
||||
ST7789_SLPOUT = 0x11
|
||||
ST7789_PTLON = 0x12
|
||||
ST7789_NORON = 0x13
|
||||
|
||||
ST7789_INVOFF = 0x20
|
||||
ST7789_INVON = 0x21
|
||||
ST7789_DISPOFF = 0x28
|
||||
ST7789_DISPON = 0x29
|
||||
|
||||
ST7789_CASET = 0x2A
|
||||
ST7789_RASET = 0x2B
|
||||
ST7789_RAMWR = 0x2C
|
||||
ST7789_RAMRD = 0x2E
|
||||
|
||||
ST7789_PTLAR = 0x30
|
||||
ST7789_MADCTL = 0x36
|
||||
ST7789_COLMOD = 0x3A
|
||||
|
||||
ST7789_FRMCTR1 = 0xB1
|
||||
ST7789_FRMCTR2 = 0xB2
|
||||
ST7789_FRMCTR3 = 0xB3
|
||||
ST7789_INVCTR = 0xB4
|
||||
ST7789_DISSET5 = 0xB6
|
||||
|
||||
ST7789_GCTRL = 0xB7
|
||||
ST7789_GTADJ = 0xB8
|
||||
ST7789_VCOMS = 0xBB
|
||||
|
||||
ST7789_LCMCTRL = 0xC0
|
||||
ST7789_IDSET = 0xC1
|
||||
ST7789_VDVVRHEN = 0xC2
|
||||
ST7789_VRHS = 0xC3
|
||||
ST7789_VDVS = 0xC4
|
||||
ST7789_VMCTR1 = 0xC5
|
||||
ST7789_FRCTRL2 = 0xC6
|
||||
ST7789_CABCCTRL = 0xC7
|
||||
|
||||
ST7789_RDID1 = 0xDA
|
||||
ST7789_RDID2 = 0xDB
|
||||
ST7789_RDID3 = 0xDC
|
||||
ST7789_RDID4 = 0xDD
|
||||
|
||||
ST7789_GMCTRP1 = 0xE0
|
||||
ST7789_GMCTRN1 = 0xE1
|
||||
|
||||
ST7789_PWCTR6 = 0xFC
|
||||
|
||||
|
||||
class ST7789(object):
|
||||
"""Representation of an ST7789 TFT LCD."""
|
||||
|
||||
def __init__(self, port, cs, dc, backlight, rst=None, width=240,
|
||||
height=240, rotation=90, invert=True, spi_speed_hz=60 * 1000 * 1000,
|
||||
offset_left=0,
|
||||
offset_top=0):
|
||||
"""Create an instance of the display using SPI communication.
|
||||
|
||||
Must provide the GPIO pin number for the D/C pin and the SPI driver.
|
||||
|
||||
Can optionally provide the GPIO pin number for the reset pin as the rst parameter.
|
||||
|
||||
:param port: SPI port number
|
||||
:param cs: SPI chip-select number (0 or 1 for BCM
|
||||
:param backlight: Pin for controlling backlight
|
||||
:param rst: Reset pin for ST7789
|
||||
:param width: Width of display connected to ST7789
|
||||
:param height: Height of display connected to ST7789
|
||||
:param rotation: Rotation of display connected to ST7789
|
||||
:param invert: Invert display
|
||||
:param spi_speed_hz: SPI speed (in Hz)
|
||||
|
||||
"""
|
||||
if rotation not in [0, 90, 180, 270]:
|
||||
raise ValueError("Invalid rotation {}".format(rotation))
|
||||
|
||||
if width != height and rotation in [90, 270]:
|
||||
raise ValueError("Invalid rotation {} for {}x{} resolution".format(rotation, width, height))
|
||||
|
||||
GPIO.setwarnings(False)
|
||||
GPIO.setmode(GPIO.BCM)
|
||||
|
||||
self._spi = spidev.SpiDev(port, cs)
|
||||
self._spi.mode = 0
|
||||
self._spi.lsbfirst = False
|
||||
self._spi.max_speed_hz = spi_speed_hz
|
||||
|
||||
self._dc = dc
|
||||
self._rst = rst
|
||||
self._width = width
|
||||
self._height = height
|
||||
self._rotation = rotation
|
||||
self._invert = invert
|
||||
|
||||
self._offset_left = offset_left
|
||||
self._offset_top = offset_top
|
||||
|
||||
# Set DC as output.
|
||||
GPIO.setup(dc, GPIO.OUT)
|
||||
|
||||
# Setup backlight as output (if provided).
|
||||
self._backlight = backlight
|
||||
if backlight is not None:
|
||||
GPIO.setup(backlight, GPIO.OUT)
|
||||
GPIO.output(backlight, GPIO.LOW)
|
||||
time.sleep(0.1)
|
||||
GPIO.output(backlight, GPIO.HIGH)
|
||||
|
||||
# Setup reset as output (if provided).
|
||||
if rst is not None:
|
||||
GPIO.setup(self._rst, GPIO.OUT)
|
||||
self.reset()
|
||||
self._init()
|
||||
|
||||
def send(self, data, is_data=True, chunk_size=4096):
|
||||
"""Write a byte or array of bytes to the display. Is_data parameter
|
||||
controls if byte should be interpreted as display data (True) or command
|
||||
data (False). Chunk_size is an optional size of bytes to write in a
|
||||
single SPI transaction, with a default of 4096.
|
||||
"""
|
||||
# Set DC low for command, high for data.
|
||||
GPIO.output(self._dc, is_data)
|
||||
# Convert scalar argument to list so either can be passed as parameter.
|
||||
if isinstance(data, numbers.Number):
|
||||
data = [data & 0xFF]
|
||||
# Write data a chunk at a time.
|
||||
for start in range(0, len(data), chunk_size):
|
||||
end = min(start + chunk_size, len(data))
|
||||
self._spi.xfer(data[start:end])
|
||||
|
||||
def set_backlight(self, value):
|
||||
"""Set the backlight on/off."""
|
||||
if self._backlight is not None:
|
||||
GPIO.output(self._backlight, value)
|
||||
|
||||
@property
|
||||
def width(self):
|
||||
return self._width if self._rotation == 0 or self._rotation == 180 else self._height
|
||||
|
||||
@property
|
||||
def height(self):
|
||||
return self._height if self._rotation == 0 or self._rotation == 180 else self._width
|
||||
|
||||
def command(self, data):
|
||||
"""Write a byte or array of bytes to the display as command data."""
|
||||
self.send(data, False)
|
||||
|
||||
def data(self, data):
|
||||
"""Write a byte or array of bytes to the display as display data."""
|
||||
self.send(data, True)
|
||||
|
||||
def reset(self):
|
||||
"""Reset the display, if reset pin is connected."""
|
||||
if self._rst is not None:
|
||||
GPIO.output(self._rst, 1)
|
||||
time.sleep(0.500)
|
||||
GPIO.output(self._rst, 0)
|
||||
time.sleep(0.500)
|
||||
GPIO.output(self._rst, 1)
|
||||
time.sleep(0.500)
|
||||
|
||||
def _init(self):
|
||||
# Initialize the display.
|
||||
|
||||
self.command(ST7789_SWRESET) # Software reset
|
||||
time.sleep(0.150) # delay 150 ms
|
||||
|
||||
self.command(ST7789_MADCTL)
|
||||
self.data(0x70)
|
||||
|
||||
self.command(ST7789_FRMCTR2) # Frame rate ctrl - idle mode
|
||||
self.data(0x0C)
|
||||
self.data(0x0C)
|
||||
self.data(0x00)
|
||||
self.data(0x33)
|
||||
self.data(0x33)
|
||||
|
||||
self.command(ST7789_COLMOD)
|
||||
self.data(0x05)
|
||||
|
||||
self.command(ST7789_GCTRL)
|
||||
self.data(0x14)
|
||||
|
||||
self.command(ST7789_VCOMS)
|
||||
self.data(0x37)
|
||||
|
||||
self.command(ST7789_LCMCTRL) # Power control
|
||||
self.data(0x2C)
|
||||
|
||||
self.command(ST7789_VDVVRHEN) # Power control
|
||||
self.data(0x01)
|
||||
|
||||
self.command(ST7789_VRHS) # Power control
|
||||
self.data(0x12)
|
||||
|
||||
self.command(ST7789_VDVS) # Power control
|
||||
self.data(0x20)
|
||||
|
||||
self.command(0xD0)
|
||||
self.data(0xA4)
|
||||
self.data(0xA1)
|
||||
|
||||
self.command(ST7789_FRCTRL2)
|
||||
self.data(0x0F)
|
||||
|
||||
self.command(ST7789_GMCTRP1) # Set Gamma
|
||||
self.data(0xD0)
|
||||
self.data(0x04)
|
||||
self.data(0x0D)
|
||||
self.data(0x11)
|
||||
self.data(0x13)
|
||||
self.data(0x2B)
|
||||
self.data(0x3F)
|
||||
self.data(0x54)
|
||||
self.data(0x4C)
|
||||
self.data(0x18)
|
||||
self.data(0x0D)
|
||||
self.data(0x0B)
|
||||
self.data(0x1F)
|
||||
self.data(0x23)
|
||||
|
||||
self.command(ST7789_GMCTRN1) # Set Gamma
|
||||
self.data(0xD0)
|
||||
self.data(0x04)
|
||||
self.data(0x0C)
|
||||
self.data(0x11)
|
||||
self.data(0x13)
|
||||
self.data(0x2C)
|
||||
self.data(0x3F)
|
||||
self.data(0x44)
|
||||
self.data(0x51)
|
||||
self.data(0x2F)
|
||||
self.data(0x1F)
|
||||
self.data(0x1F)
|
||||
self.data(0x20)
|
||||
self.data(0x23)
|
||||
|
||||
if self._invert:
|
||||
self.command(ST7789_INVON) # Invert display
|
||||
else:
|
||||
self.command(ST7789_INVOFF) # Don't invert display
|
||||
|
||||
self.command(ST7789_SLPOUT)
|
||||
|
||||
self.command(ST7789_DISPON) # Display on
|
||||
time.sleep(0.100) # 100 ms
|
||||
|
||||
def begin(self):
|
||||
"""Set up the display
|
||||
|
||||
Deprecated. Included in __init__.
|
||||
|
||||
"""
|
||||
pass
|
||||
|
||||
def set_window(self, x0=0, y0=0, x1=None, y1=None):
|
||||
"""Set the pixel address window for proceeding drawing commands. x0 and
|
||||
x1 should define the minimum and maximum x pixel bounds. y0 and y1
|
||||
should define the minimum and maximum y pixel bound. If no parameters
|
||||
are specified the default will be to update the entire display from 0,0
|
||||
to width-1,height-1.
|
||||
"""
|
||||
if x1 is None:
|
||||
x1 = self._width - 1
|
||||
|
||||
if y1 is None:
|
||||
y1 = self._height - 1
|
||||
|
||||
y0 += self._offset_top
|
||||
y1 += self._offset_top
|
||||
|
||||
x0 += self._offset_left
|
||||
x1 += self._offset_left
|
||||
|
||||
self.command(ST7789_CASET) # Column addr set
|
||||
self.data(x0 >> 8)
|
||||
self.data(x0 & 0xFF) # XSTART
|
||||
self.data(x1 >> 8)
|
||||
self.data(x1 & 0xFF) # XEND
|
||||
self.command(ST7789_RASET) # Row addr set
|
||||
self.data(y0 >> 8)
|
||||
self.data(y0 & 0xFF) # YSTART
|
||||
self.data(y1 >> 8)
|
||||
self.data(y1 & 0xFF) # YEND
|
||||
self.command(ST7789_RAMWR) # write to RAM
|
||||
|
||||
def display(self, image):
|
||||
"""Write the provided image to the hardware.
|
||||
|
||||
:param image: Should be RGB format and the same dimensions as the display hardware.
|
||||
|
||||
"""
|
||||
# Set address bounds to entire display.
|
||||
self.set_window()
|
||||
|
||||
# Convert image to 16bit RGB565 format and
|
||||
# flatten into bytes.
|
||||
pixelbytes = self.image_to_data(image, self._rotation)
|
||||
|
||||
# Write data to hardware.
|
||||
for i in range(0, len(pixelbytes), 4096):
|
||||
self.data(pixelbytes[i:i + 4096])
|
||||
|
||||
def image_to_data(self, image, rotation=0):
|
||||
if not isinstance(image, np.ndarray):
|
||||
image = np.array(image.convert('RGB'))
|
||||
|
||||
# Rotate the image
|
||||
pb = np.rot90(image, rotation // 90).astype('uint16')
|
||||
|
||||
# Mask and shift the 888 RGB into 565 RGB
|
||||
red = (pb[..., [0]] & 0xf8) << 8
|
||||
green = (pb[..., [1]] & 0xfc) << 3
|
||||
blue = (pb[..., [2]] & 0xf8) >> 3
|
||||
|
||||
# Stick 'em together
|
||||
result = red | green | blue
|
||||
|
||||
# Output the raw bytes
|
||||
return result.byteswap().tobytes()
|
360
pwnagotchi/ui/hw/libs/adafruit/minipitft2/ST7789.py
Normal file
360
pwnagotchi/ui/hw/libs/adafruit/minipitft2/ST7789.py
Normal file
@ -0,0 +1,360 @@
|
||||
# Copyright (c) 2014 Adafruit Industries
|
||||
# Author: Tony DiCola
|
||||
#
|
||||
# Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
# of this software and associated documentation files (the "Software"), to deal
|
||||
# in the Software without restriction, including without limitation the rights
|
||||
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
# copies of the Software, and to permit persons to whom the Software is
|
||||
# furnished to do so, subject to the following conditions:
|
||||
#
|
||||
# The above copyright notice and this permission notice shall be included in
|
||||
# all copies or substantial portions of the Software.
|
||||
#
|
||||
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
# THE SOFTWARE.
|
||||
import numbers
|
||||
import time
|
||||
import numpy as np
|
||||
|
||||
import spidev
|
||||
import RPi.GPIO as GPIO
|
||||
|
||||
|
||||
__version__ = '0.0.4'
|
||||
|
||||
BG_SPI_CS_BACK = 0
|
||||
BG_SPI_CS_FRONT = 1
|
||||
|
||||
SPI_CLOCK_HZ = 16000000
|
||||
|
||||
ST7789_NOP = 0x00
|
||||
ST7789_SWRESET = 0x01
|
||||
ST7789_RDDID = 0x04
|
||||
ST7789_RDDST = 0x09
|
||||
|
||||
ST7789_SLPIN = 0x10
|
||||
ST7789_SLPOUT = 0x11
|
||||
ST7789_PTLON = 0x12
|
||||
ST7789_NORON = 0x13
|
||||
|
||||
ST7789_INVOFF = 0x20
|
||||
ST7789_INVON = 0x21
|
||||
ST7789_DISPOFF = 0x28
|
||||
ST7789_DISPON = 0x29
|
||||
|
||||
ST7789_CASET = 0x2A
|
||||
ST7789_RASET = 0x2B
|
||||
ST7789_RAMWR = 0x2C
|
||||
ST7789_RAMRD = 0x2E
|
||||
|
||||
ST7789_PTLAR = 0x30
|
||||
ST7789_MADCTL = 0x36
|
||||
ST7789_COLMOD = 0x3A
|
||||
|
||||
ST7789_FRMCTR1 = 0xB1
|
||||
ST7789_FRMCTR2 = 0xB2
|
||||
ST7789_FRMCTR3 = 0xB3
|
||||
ST7789_INVCTR = 0xB4
|
||||
ST7789_DISSET5 = 0xB6
|
||||
|
||||
ST7789_GCTRL = 0xB7
|
||||
ST7789_GTADJ = 0xB8
|
||||
ST7789_VCOMS = 0xBB
|
||||
|
||||
ST7789_LCMCTRL = 0xC0
|
||||
ST7789_IDSET = 0xC1
|
||||
ST7789_VDVVRHEN = 0xC2
|
||||
ST7789_VRHS = 0xC3
|
||||
ST7789_VDVS = 0xC4
|
||||
ST7789_VMCTR1 = 0xC5
|
||||
ST7789_FRCTRL2 = 0xC6
|
||||
ST7789_CABCCTRL = 0xC7
|
||||
|
||||
ST7789_RDID1 = 0xDA
|
||||
ST7789_RDID2 = 0xDB
|
||||
ST7789_RDID3 = 0xDC
|
||||
ST7789_RDID4 = 0xDD
|
||||
|
||||
ST7789_GMCTRP1 = 0xE0
|
||||
ST7789_GMCTRN1 = 0xE1
|
||||
|
||||
ST7789_PWCTR6 = 0xFC
|
||||
|
||||
|
||||
class ST7789(object):
|
||||
"""Representation of an ST7789 TFT LCD."""
|
||||
|
||||
def __init__(self, port, cs, dc, backlight, rst=None, width=240,
|
||||
height=135, rotation=0, invert=True, spi_speed_hz=60 * 1000 * 1000,
|
||||
offset_left=0,
|
||||
offset_top=0):
|
||||
"""Create an instance of the display using SPI communication.
|
||||
|
||||
Must provide the GPIO pin number for the D/C pin and the SPI driver.
|
||||
|
||||
Can optionally provide the GPIO pin number for the reset pin as the rst parameter.
|
||||
|
||||
:param port: SPI port number
|
||||
:param cs: SPI chip-select number (0 or 1 for BCM
|
||||
:param backlight: Pin for controlling backlight
|
||||
:param rst: Reset pin for ST7789
|
||||
:param width: Width of display connected to ST7789
|
||||
:param height: Height of display connected to ST7789
|
||||
:param rotation: Rotation of display connected to ST7789
|
||||
:param invert: Invert display
|
||||
:param spi_speed_hz: SPI speed (in Hz)
|
||||
|
||||
"""
|
||||
if rotation not in [0, 90, 180, 270]:
|
||||
raise ValueError("Invalid rotation {}".format(rotation))
|
||||
|
||||
if width != height and rotation in [90, 270]:
|
||||
raise ValueError("Invalid rotation {} for {}x{} resolution".format(rotation, width, height))
|
||||
|
||||
GPIO.setwarnings(False)
|
||||
GPIO.setmode(GPIO.BCM)
|
||||
|
||||
self._spi = spidev.SpiDev(port, cs)
|
||||
self._spi.mode = 0
|
||||
self._spi.lsbfirst = False
|
||||
self._spi.max_speed_hz = spi_speed_hz
|
||||
|
||||
self._dc = dc
|
||||
self._rst = rst
|
||||
self._width = width
|
||||
self._height = height
|
||||
self._rotation = rotation
|
||||
self._invert = invert
|
||||
|
||||
self._offset_left = offset_left
|
||||
self._offset_top = offset_top
|
||||
|
||||
# Set DC as output.
|
||||
GPIO.setup(dc, GPIO.OUT)
|
||||
|
||||
# Setup backlight as output (if provided).
|
||||
self._backlight = backlight
|
||||
if backlight is not None:
|
||||
GPIO.setup(backlight, GPIO.OUT)
|
||||
GPIO.output(backlight, GPIO.LOW)
|
||||
time.sleep(0.1)
|
||||
GPIO.output(backlight, GPIO.HIGH)
|
||||
|
||||
# Setup reset as output (if provided).
|
||||
if rst is not None:
|
||||
GPIO.setup(self._rst, GPIO.OUT)
|
||||
self.reset()
|
||||
self._init()
|
||||
|
||||
def send(self, data, is_data=True, chunk_size=4096):
|
||||
"""Write a byte or array of bytes to the display. Is_data parameter
|
||||
controls if byte should be interpreted as display data (True) or command
|
||||
data (False). Chunk_size is an optional size of bytes to write in a
|
||||
single SPI transaction, with a default of 4096.
|
||||
"""
|
||||
# Set DC low for command, high for data.
|
||||
GPIO.output(self._dc, is_data)
|
||||
# Convert scalar argument to list so either can be passed as parameter.
|
||||
if isinstance(data, numbers.Number):
|
||||
data = [data & 0xFF]
|
||||
# Write data a chunk at a time.
|
||||
for start in range(0, len(data), chunk_size):
|
||||
end = min(start + chunk_size, len(data))
|
||||
self._spi.xfer(data[start:end])
|
||||
|
||||
def set_backlight(self, value):
|
||||
"""Set the backlight on/off."""
|
||||
if self._backlight is not None:
|
||||
GPIO.output(self._backlight, value)
|
||||
|
||||
@property
|
||||
def width(self):
|
||||
return self._width if self._rotation == 0 or self._rotation == 180 else self._height
|
||||
|
||||
@property
|
||||
def height(self):
|
||||
return self._height if self._rotation == 0 or self._rotation == 180 else self._width
|
||||
|
||||
def command(self, data):
|
||||
"""Write a byte or array of bytes to the display as command data."""
|
||||
self.send(data, False)
|
||||
|
||||
def data(self, data):
|
||||
"""Write a byte or array of bytes to the display as display data."""
|
||||
self.send(data, True)
|
||||
|
||||
def reset(self):
|
||||
"""Reset the display, if reset pin is connected."""
|
||||
if self._rst is not None:
|
||||
GPIO.output(self._rst, 1)
|
||||
time.sleep(0.500)
|
||||
GPIO.output(self._rst, 0)
|
||||
time.sleep(0.500)
|
||||
GPIO.output(self._rst, 1)
|
||||
time.sleep(0.500)
|
||||
|
||||
def _init(self):
|
||||
# Initialize the display.
|
||||
|
||||
self.command(ST7789_SWRESET) # Software reset
|
||||
time.sleep(0.150) # delay 150 ms
|
||||
|
||||
self.command(ST7789_MADCTL)
|
||||
self.data(0x70)
|
||||
|
||||
self.command(ST7789_FRMCTR2) # Frame rate ctrl - idle mode
|
||||
self.data(0x0C)
|
||||
self.data(0x0C)
|
||||
self.data(0x00)
|
||||
self.data(0x33)
|
||||
self.data(0x33)
|
||||
|
||||
self.command(ST7789_COLMOD)
|
||||
self.data(0x05)
|
||||
|
||||
self.command(ST7789_GCTRL)
|
||||
self.data(0x14)
|
||||
|
||||
self.command(ST7789_VCOMS)
|
||||
self.data(0x37)
|
||||
|
||||
self.command(ST7789_LCMCTRL) # Power control
|
||||
self.data(0x2C)
|
||||
|
||||
self.command(ST7789_VDVVRHEN) # Power control
|
||||
self.data(0x01)
|
||||
|
||||
self.command(ST7789_VRHS) # Power control
|
||||
self.data(0x12)
|
||||
|
||||
self.command(ST7789_VDVS) # Power control
|
||||
self.data(0x20)
|
||||
|
||||
self.command(0xD0)
|
||||
self.data(0xA4)
|
||||
self.data(0xA1)
|
||||
|
||||
self.command(ST7789_FRCTRL2)
|
||||
self.data(0x0F)
|
||||
|
||||
self.command(ST7789_GMCTRP1) # Set Gamma
|
||||
self.data(0xD0)
|
||||
self.data(0x04)
|
||||
self.data(0x0D)
|
||||
self.data(0x11)
|
||||
self.data(0x13)
|
||||
self.data(0x2B)
|
||||
self.data(0x3F)
|
||||
self.data(0x54)
|
||||
self.data(0x4C)
|
||||
self.data(0x18)
|
||||
self.data(0x0D)
|
||||
self.data(0x0B)
|
||||
self.data(0x1F)
|
||||
self.data(0x23)
|
||||
|
||||
self.command(ST7789_GMCTRN1) # Set Gamma
|
||||
self.data(0xD0)
|
||||
self.data(0x04)
|
||||
self.data(0x0C)
|
||||
self.data(0x11)
|
||||
self.data(0x13)
|
||||
self.data(0x2C)
|
||||
self.data(0x3F)
|
||||
self.data(0x44)
|
||||
self.data(0x51)
|
||||
self.data(0x2F)
|
||||
self.data(0x1F)
|
||||
self.data(0x1F)
|
||||
self.data(0x20)
|
||||
self.data(0x23)
|
||||
|
||||
if self._invert:
|
||||
self.command(ST7789_INVON) # Invert display
|
||||
else:
|
||||
self.command(ST7789_INVOFF) # Don't invert display
|
||||
|
||||
self.command(ST7789_SLPOUT)
|
||||
|
||||
self.command(ST7789_DISPON) # Display on
|
||||
time.sleep(0.100) # 100 ms
|
||||
|
||||
def begin(self):
|
||||
"""Set up the display
|
||||
|
||||
Deprecated. Included in __init__.
|
||||
|
||||
"""
|
||||
pass
|
||||
|
||||
def set_window(self, x0=0, y0=0, x1=None, y1=None):
|
||||
"""Set the pixel address window for proceeding drawing commands. x0 and
|
||||
x1 should define the minimum and maximum x pixel bounds. y0 and y1
|
||||
should define the minimum and maximum y pixel bound. If no parameters
|
||||
are specified the default will be to update the entire display from 0,0
|
||||
to width-1,height-1.
|
||||
"""
|
||||
if x1 is None:
|
||||
x1 = self._width - 1
|
||||
|
||||
if y1 is None:
|
||||
y1 = self._height - 1
|
||||
|
||||
y0 += self._offset_top
|
||||
y1 += self._offset_top
|
||||
|
||||
x0 += self._offset_left
|
||||
x1 += self._offset_left
|
||||
|
||||
self.command(ST7789_CASET) # Column addr set
|
||||
self.data(x0 >> 8)
|
||||
self.data(x0 & 0xFF) # XSTART
|
||||
self.data(x1 >> 8)
|
||||
self.data(x1 & 0xFF) # XEND
|
||||
self.command(ST7789_RASET) # Row addr set
|
||||
self.data(y0 >> 8)
|
||||
self.data(y0 & 0xFF) # YSTART
|
||||
self.data(y1 >> 8)
|
||||
self.data(y1 & 0xFF) # YEND
|
||||
self.command(ST7789_RAMWR) # write to RAM
|
||||
|
||||
def display(self, image):
|
||||
"""Write the provided image to the hardware.
|
||||
|
||||
:param image: Should be RGB format and the same dimensions as the display hardware.
|
||||
|
||||
"""
|
||||
# Set address bounds to entire display.
|
||||
self.set_window()
|
||||
|
||||
# Convert image to 16bit RGB565 format and
|
||||
# flatten into bytes.
|
||||
pixelbytes = self.image_to_data(image, self._rotation)
|
||||
|
||||
# Write data to hardware.
|
||||
for i in range(0, len(pixelbytes), 4096):
|
||||
self.data(pixelbytes[i:i + 4096])
|
||||
|
||||
def image_to_data(self, image, rotation=0):
|
||||
if not isinstance(image, np.ndarray):
|
||||
image = np.array(image.convert('RGB'))
|
||||
|
||||
# Rotate the image
|
||||
pb = np.rot90(image, rotation // 90).astype('uint16')
|
||||
|
||||
# Mask and shift the 888 RGB into 565 RGB
|
||||
red = (pb[..., [0]] & 0xf8) << 8
|
||||
green = (pb[..., [1]] & 0xfc) << 3
|
||||
blue = (pb[..., [2]] & 0xf8) >> 3
|
||||
|
||||
# Stick 'em together
|
||||
result = red | green | blue
|
||||
|
||||
# Output the raw bytes
|
||||
return result.byteswap().tobytes()
|
362
pwnagotchi/ui/hw/libs/argon/argonpod/ILI9341.py
Normal file
362
pwnagotchi/ui/hw/libs/argon/argonpod/ILI9341.py
Normal file
@ -0,0 +1,362 @@
|
||||
# Copyright (c) 2014 Adafruit Industries
|
||||
# Author: Tony DiCola
|
||||
#
|
||||
# Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
# of this software and associated documentation files (the "Software"), to deal
|
||||
# in the Software without restriction, including without limitation the rights
|
||||
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
# copies of the Software, and to permit persons to whom the Software is
|
||||
# furnished to do so, subject to the following conditions:
|
||||
#
|
||||
# The above copyright notice and this permission notice shall be included in
|
||||
# all copies or substantial portions of the Software.
|
||||
#
|
||||
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
# THE SOFTWARE.
|
||||
|
||||
# Modified for Pwnagotchi by RasTacsko
|
||||
# Based on ST7899 driver for pimoroni displayhatmini by Do-Ki
|
||||
|
||||
import numbers
|
||||
import time
|
||||
import numpy as np
|
||||
|
||||
from PIL import Image
|
||||
from PIL import ImageDraw
|
||||
|
||||
import spidev
|
||||
import RPi.GPIO as GPIO
|
||||
|
||||
__version__ = '0.0.1'
|
||||
|
||||
# Constants for interacting with display registers.
|
||||
ILI9341_TFTWIDTH = 320
|
||||
ILI9341_TFTHEIGHT = 240
|
||||
|
||||
ILI9341_NOP = 0x00
|
||||
ILI9341_SWRESET = 0x01
|
||||
ILI9341_RDDID = 0x04
|
||||
ILI9341_RDDST = 0x09
|
||||
|
||||
ILI9341_SLPIN = 0x10
|
||||
ILI9341_SLPOUT = 0x11
|
||||
ILI9341_PTLON = 0x12
|
||||
ILI9341_NORON = 0x13
|
||||
|
||||
ILI9341_RDMODE = 0x0A
|
||||
ILI9341_RDMADCTL = 0x0B
|
||||
ILI9341_RDPIXFMT = 0x0C
|
||||
ILI9341_RDIMGFMT = 0x0A
|
||||
ILI9341_RDSELFDIAG = 0x0F
|
||||
|
||||
ILI9341_INVOFF = 0x20
|
||||
ILI9341_INVON = 0x21
|
||||
ILI9341_GAMMASET = 0x26
|
||||
ILI9341_DISPOFF = 0x28
|
||||
ILI9341_DISPON = 0x29
|
||||
|
||||
ILI9341_CASET = 0x2A
|
||||
ILI9341_PASET = 0x2B
|
||||
ILI9341_RAMWR = 0x2C
|
||||
ILI9341_RAMRD = 0x2E
|
||||
|
||||
ILI9341_PTLAR = 0x30
|
||||
ILI9341_MADCTL = 0x36
|
||||
ILI9341_PIXFMT = 0x3A
|
||||
|
||||
ILI9341_FRMCTR1 = 0xB1
|
||||
ILI9341_FRMCTR2 = 0xB2
|
||||
ILI9341_FRMCTR3 = 0xB3
|
||||
ILI9341_INVCTR = 0xB4
|
||||
ILI9341_DFUNCTR = 0xB6
|
||||
|
||||
ILI9341_PWCTR1 = 0xC0
|
||||
ILI9341_PWCTR2 = 0xC1
|
||||
ILI9341_PWCTR3 = 0xC2
|
||||
ILI9341_PWCTR4 = 0xC3
|
||||
ILI9341_PWCTR5 = 0xC4
|
||||
ILI9341_VMCTR1 = 0xC5
|
||||
ILI9341_VMCTR2 = 0xC7
|
||||
|
||||
ILI9341_RDID1 = 0xDA
|
||||
ILI9341_RDID2 = 0xDB
|
||||
ILI9341_RDID3 = 0xDC
|
||||
ILI9341_RDID4 = 0xDD
|
||||
|
||||
ILI9341_GMCTRP1 = 0xE0
|
||||
ILI9341_GMCTRN1 = 0xE1
|
||||
|
||||
ILI9341_PWCTR6 = 0xFC
|
||||
|
||||
|
||||
class ILI9341(object):
|
||||
"""Representation of an ILI9341 TFT LCD."""
|
||||
|
||||
def __init__(self, port, cs, dc, backlight, rst=None,
|
||||
width=ILI9341_TFTWIDTH, height=ILI9341_TFTHEIGHT,
|
||||
rotation=270, invert=False, spi_speed_hz=64000000,
|
||||
offset_left=0, offset_top=0):
|
||||
"""Create an instance of the display using SPI communication.
|
||||
Must provide the GPIO pin number for the D/C pin and the SPI driver.
|
||||
Can optionally provide the GPIO pin number for the reset pin as the rst parameter.
|
||||
:param port: SPI port number -> 0
|
||||
:param cs: SPI chip-select number (0 or 1 for BCM) -> 1
|
||||
:param backlight: Pin for controlling backlight -> 18
|
||||
:param rst: Reset pin for ILI9341 -> 24?
|
||||
:param width: Width of display connected to ILI9341 -> 240
|
||||
:param height: Height of display connected to ILI9341 -> 320
|
||||
:param rotation: Rotation of display connected to ILI9341
|
||||
:param invert: Invert display
|
||||
:param spi_speed_hz: SPI speed (in Hz)
|
||||
"""
|
||||
|
||||
if rotation not in [0, 90, 180, 270]:
|
||||
raise ValueError("Invalid rotation {}".format(rotation))
|
||||
|
||||
GPIO.setwarnings(False)
|
||||
GPIO.setmode(GPIO.BCM)
|
||||
|
||||
self._spi = spidev.SpiDev(port, cs)
|
||||
self._spi.mode = 0
|
||||
self._spi.lsbfirst = False
|
||||
self._spi.max_speed_hz = spi_speed_hz
|
||||
|
||||
self._dc = dc
|
||||
self._rst = rst
|
||||
self._width = width
|
||||
self._height = height
|
||||
self._rotation = rotation
|
||||
self._invert = invert
|
||||
|
||||
self._offset_left = offset_left
|
||||
self._offset_top = offset_top
|
||||
|
||||
# Set DC as output.
|
||||
GPIO.setup(dc, GPIO.OUT)
|
||||
|
||||
# Setup backlight as output (if provided).
|
||||
self._backlight = backlight
|
||||
if backlight is not None:
|
||||
GPIO.setup(backlight, GPIO.OUT)
|
||||
GPIO.output(backlight, GPIO.LOW)
|
||||
time.sleep(0.05)
|
||||
GPIO.output(backlight, GPIO.HIGH)
|
||||
|
||||
# Setup reset as output (if provided).
|
||||
if rst is not None:
|
||||
GPIO.setup(self._rst, GPIO.OUT)
|
||||
self.reset()
|
||||
|
||||
# Create an image buffer.
|
||||
self.buffer = Image.new('RGB', (width, height))
|
||||
|
||||
self._init()
|
||||
|
||||
def send(self, data, is_data=True, chunk_size=4096):
|
||||
"""Write a byte or array of bytes to the display. Is_data parameter
|
||||
controls if byte should be interpreted as display data (True) or command
|
||||
data (False). Chunk_size is an optional size of bytes to write in a
|
||||
single SPI transaction, with a default of 4096.
|
||||
"""
|
||||
# Set DC low for command, high for data.
|
||||
GPIO.output(self._dc, is_data)
|
||||
# Convert scalar argument to list so either can be passed as parameter.
|
||||
if isinstance(data, numbers.Number):
|
||||
data = [data & 0xFF]
|
||||
# Write data a chunk at a time.
|
||||
for start in range(0, len(data), chunk_size):
|
||||
end = min(start+chunk_size, len(data))
|
||||
self._spi.xfer(data[start:end])
|
||||
|
||||
def set_backlight(self, value):
|
||||
"""Set the backlight on/off."""
|
||||
if self._backlight is not None:
|
||||
GPIO.output(self._backlight, value)
|
||||
|
||||
@property
|
||||
def width(self):
|
||||
return self._width if self._rotation == 0 or self._rotation == 180 else self._height
|
||||
|
||||
@property
|
||||
def height(self):
|
||||
return self._height if self._rotation == 0 or self._rotation == 180 else self._width
|
||||
|
||||
def command(self, data):
|
||||
"""Write a byte or array of bytes to the display as command data."""
|
||||
self.send(data, False)
|
||||
|
||||
def data(self, data):
|
||||
"""Write a byte or array of bytes to the display as display data."""
|
||||
self.send(data, True)
|
||||
|
||||
def reset(self):
|
||||
"""Reset the display, if reset pin is connected."""
|
||||
if self._rst is not None:
|
||||
GPIO.output(self._rst, 1)
|
||||
time.sleep(0.005)
|
||||
GPIO.output(self._rst, 0)
|
||||
time.sleep(0.02)
|
||||
GPIO.output(self._rst, 1)
|
||||
time.sleep(0.150)
|
||||
|
||||
def _init(self):
|
||||
# Initialize the display. Broken out as a separate function so it can
|
||||
# be overridden by other displays in the future.
|
||||
self.command(0xEF)
|
||||
self.data(0x03)
|
||||
self.data(0x80)
|
||||
self.data(0x02)
|
||||
self.command(0xCF)
|
||||
self.data(0x00)
|
||||
self.data(0XC1)
|
||||
self.data(0X30)
|
||||
self.command(0xED)
|
||||
self.data(0x64)
|
||||
self.data(0x03)
|
||||
self.data(0X12)
|
||||
self.data(0X81)
|
||||
self.command(0xE8)
|
||||
self.data(0x85)
|
||||
self.data(0x00)
|
||||
self.data(0x78)
|
||||
self.command(0xCB)
|
||||
self.data(0x39)
|
||||
self.data(0x2C)
|
||||
self.data(0x00)
|
||||
self.data(0x34)
|
||||
self.data(0x02)
|
||||
self.command(0xF7)
|
||||
self.data(0x20)
|
||||
self.command(0xEA)
|
||||
self.data(0x00)
|
||||
self.data(0x00)
|
||||
self.command(ILI9341_PWCTR1) # Power control
|
||||
self.data(0x23) # VRH[5:0]
|
||||
self.command(ILI9341_PWCTR2) # Power control
|
||||
self.data(0x10) # SAP[2:0];BT[3:0]
|
||||
self.command(ILI9341_VMCTR1) # VCM control
|
||||
self.data(0x3e)
|
||||
self.data(0x28)
|
||||
self.command(ILI9341_VMCTR2) # VCM control2
|
||||
self.data(0x86) # --
|
||||
self.command(ILI9341_MADCTL) # Memory Access Control
|
||||
self.data(0x48)
|
||||
self.command(ILI9341_PIXFMT)
|
||||
self.data(0x55)
|
||||
self.command(ILI9341_FRMCTR1)
|
||||
self.data(0x00)
|
||||
self.data(0x18)
|
||||
self.command(ILI9341_DFUNCTR) # Display Function Control
|
||||
self.data(0x08)
|
||||
self.data(0x82)
|
||||
self.data(0x27)
|
||||
self.command(0xF2) # 3Gamma Function Disable
|
||||
self.data(0x00)
|
||||
self.command(ILI9341_GAMMASET) # Gamma curve selected
|
||||
self.data(0x01)
|
||||
self.command(ILI9341_GMCTRP1) # Set Gamma
|
||||
self.data(0x0F)
|
||||
self.data(0x31)
|
||||
self.data(0x2B)
|
||||
self.data(0x0C)
|
||||
self.data(0x0E)
|
||||
self.data(0x08)
|
||||
self.data(0x4E)
|
||||
self.data(0xF1)
|
||||
self.data(0x37)
|
||||
self.data(0x07)
|
||||
self.data(0x10)
|
||||
self.data(0x03)
|
||||
self.data(0x0E)
|
||||
self.data(0x09)
|
||||
self.data(0x00)
|
||||
self.command(ILI9341_GMCTRN1) # Set Gamma
|
||||
self.data(0x00)
|
||||
self.data(0x0E)
|
||||
self.data(0x14)
|
||||
self.data(0x03)
|
||||
self.data(0x11)
|
||||
self.data(0x07)
|
||||
self.data(0x31)
|
||||
self.data(0xC1)
|
||||
self.data(0x48)
|
||||
self.data(0x08)
|
||||
self.data(0x0F)
|
||||
self.data(0x0C)
|
||||
self.data(0x31)
|
||||
self.data(0x36)
|
||||
self.data(0x0F)
|
||||
if self._invert:
|
||||
self.command(ILI9341_INVON) # Invert display
|
||||
else:
|
||||
self.command(ILI9341_INVOFF) # Don't invert display
|
||||
self.command(ILI9341_SLPOUT) # Exit Sleep
|
||||
time.sleep(0.120)
|
||||
self.command(ILI9341_DISPON) # Display on
|
||||
|
||||
def begin(self):
|
||||
"""Set up the display deprecated.
|
||||
Included in __init__. """
|
||||
pass
|
||||
|
||||
def set_window(self, x0=0, y0=0, x1=None, y1=None):
|
||||
"""Set the pixel address window for proceeding drawing commands. x0 and
|
||||
x1 should define the minimum and maximum x pixel bounds. y0 and y1
|
||||
should define the minimum and maximum y pixel bound. If no parameters
|
||||
are specified the default will be to update the entire display from 0,0
|
||||
to 239,319.
|
||||
"""
|
||||
if x1 is None:
|
||||
x1 = self.width-1
|
||||
if y1 is None:
|
||||
y1 = self.height-1
|
||||
|
||||
self.command(ILI9341_CASET) # Column addr set
|
||||
self.data(x0 >> 8)
|
||||
self.data(x0 & 0xFF) # XSTART
|
||||
self.data(x1 >> 8)
|
||||
self.data(x1 & 0xFF) # XEND
|
||||
self.command(ILI9341_PASET) # Row addr set
|
||||
self.data(y0 >> 8)
|
||||
self.data(y0 & 0xFF) # YSTART
|
||||
self.data(y1 >> 8)
|
||||
self.data(y1 & 0xFF) # YEND
|
||||
self.command(ILI9341_RAMWR) # write to RAM
|
||||
|
||||
def display(self, image):
|
||||
"""Write the provided image to the hardware.
|
||||
:param image: Should be RGB format and the same dimensions as the display hardware.
|
||||
"""
|
||||
# Set address bounds to entire display.
|
||||
self.set_window()
|
||||
|
||||
# Convert image to 16bit RGB565 format and
|
||||
# flatten into bytes.
|
||||
pixelbytes = self.image_to_data(image, self._rotation)
|
||||
|
||||
# Write data to hardware.
|
||||
for i in range(0, len(pixelbytes), 4096):
|
||||
self.data(pixelbytes[i:i + 4096])
|
||||
|
||||
def image_to_data(self, image, rotation=0):
|
||||
if not isinstance(image, np.ndarray):
|
||||
image = np.array(image.convert('RGB'))
|
||||
|
||||
# Rotate the image
|
||||
pb = np.rot90(image, rotation // 90).astype('uint16')
|
||||
|
||||
# Mask and shift the 888 RGB into 565 RGB
|
||||
red = (pb[..., [0]] & 0xf8) << 8
|
||||
green = (pb[..., [1]] & 0xfc) << 3
|
||||
blue = (pb[..., [2]] & 0xf8) >> 3
|
||||
|
||||
# Stick 'em together
|
||||
result = red | green | blue
|
||||
|
||||
# Output the raw bytes
|
||||
return result.byteswap().tobytes()
|
@ -11,7 +11,7 @@ EPD_HEIGHT = 64
|
||||
|
||||
class EPD(object):
|
||||
|
||||
def __init__(self, address=0x3D, width=EPD_WIDTH, height=EPD_HEIGHT):
|
||||
def __init__(self, address=0x3C, width=EPD_WIDTH, height=EPD_HEIGHT):
|
||||
self.width = width
|
||||
self.height = height
|
||||
|
||||
|
@ -90,8 +90,8 @@ ST7789_PWCTR6 = 0xFC
|
||||
class ST7789(object):
|
||||
"""Representation of an ST7789 TFT LCD."""
|
||||
|
||||
def __init__(self, port, cs, dc, backlight=None, rst=None, width=240,
|
||||
height=240, rotation=90, invert=True, spi_speed_hz=4000000,
|
||||
def __init__(self, port, cs, dc, backlight, rst=None, width=320,
|
||||
height=240, rotation=0, invert=True, spi_speed_hz=60 * 1000 * 1000,
|
||||
offset_left=0,
|
||||
offset_top=0):
|
||||
"""Create an instance of the display using SPI communication.
|
||||
|
84
pwnagotchi/ui/hw/libs/pimoroni/gfxhat/backlight.py
Normal file
84
pwnagotchi/ui/hw/libs/pimoroni/gfxhat/backlight.py
Normal file
@ -0,0 +1,84 @@
|
||||
"""Library for the GFX HAT SN3218 backlight."""
|
||||
_sn3218 = None
|
||||
|
||||
_buf = [0 for x in range(18)]
|
||||
|
||||
LED_MAP = [2, 1, 0, 5, 4, 3]
|
||||
|
||||
|
||||
def setup():
|
||||
"""Set up the backlight on GFX HAT."""
|
||||
global _sn3218
|
||||
import sn3218 as _sn3218
|
||||
|
||||
_sn3218.enable()
|
||||
_sn3218.enable_leds(0b111111111111111111)
|
||||
_sn3218.output(_buf)
|
||||
|
||||
|
||||
def set_pixel(x, r, g, b):
|
||||
"""Set a single backlight zone.
|
||||
|
||||
:param x: pixel index (0 = left most, 5 = right most)
|
||||
:param r: amount of red from 0 to 255
|
||||
:param g: amount of green from 0 to 255
|
||||
:param b: amount of blue from 0 to 255
|
||||
|
||||
"""
|
||||
global _buf
|
||||
if x > 5 or x < 0:
|
||||
raise ValueError('x should be in the range 0 to 5')
|
||||
|
||||
x = LED_MAP[x]
|
||||
x *= 3
|
||||
_buf[x:x + 3] = b, g, r
|
||||
|
||||
|
||||
def set_all(r, g, b):
|
||||
"""Set all backlight zones.
|
||||
|
||||
:param r: amount of red from 0 to 255
|
||||
:param g: amount of green from 0 to 255
|
||||
:param b: amount of blue from 0 to 255
|
||||
|
||||
"""
|
||||
for p in range(6):
|
||||
set_pixel(p, r, g, b)
|
||||
|
||||
|
||||
def show():
|
||||
"""Show changes to the backlight."""
|
||||
setup()
|
||||
_sn3218.output(_buf)
|
||||
|
||||
|
||||
if __name__ == '__main__': # pragma: no cover
|
||||
import time
|
||||
import colorsys
|
||||
|
||||
def wipe(r, g, b): # noqa D103
|
||||
for x in range(6):
|
||||
set_pixel(x, r, g, b)
|
||||
show()
|
||||
time.sleep(0.1)
|
||||
set_pixel(x, 0, 0, 0)
|
||||
|
||||
wipe(255, 0, 0)
|
||||
wipe(0, 255, 0)
|
||||
wipe(0, 0, 255)
|
||||
wipe(0, 0, 0)
|
||||
|
||||
try:
|
||||
while True:
|
||||
t = time.time()
|
||||
for x in range(6):
|
||||
offset = (t * 250) + (x * 30)
|
||||
r, g, b = [int(c * 255) for c in colorsys.hsv_to_rgb(offset / 360.0, 1.0, 1.0)]
|
||||
g = int(g * 0.8)
|
||||
b = int(b * 0.8)
|
||||
set_pixel(x, r, g, b)
|
||||
show()
|
||||
time.sleep(1.0 / 60)
|
||||
|
||||
except KeyboardInterrupt:
|
||||
pass
|
669
pwnagotchi/ui/hw/libs/pimoroni/gfxhat/cap1xxx.py
Normal file
669
pwnagotchi/ui/hw/libs/pimoroni/gfxhat/cap1xxx.py
Normal file
@ -0,0 +1,669 @@
|
||||
"""Cap-touch Driver Library for Microchip CAP1xxx ICs
|
||||
Supports communication over i2c only.
|
||||
|
||||
Currently supported ICs:
|
||||
CAP1208 - 8 Inputs
|
||||
CAP1188 - 8 Inputs, 8 LEDs
|
||||
CAP1166 - 6 Inputs, 6 LEDs
|
||||
"""
|
||||
|
||||
import atexit
|
||||
import signal
|
||||
import threading
|
||||
import time
|
||||
from sys import version_info
|
||||
|
||||
try:
|
||||
from smbus import SMBus
|
||||
except ImportError:
|
||||
if version_info[0] < 3:
|
||||
raise ImportError("This library requires python-smbus\nInstall with: sudo apt-get install python-smbus")
|
||||
elif version_info[0] == 3:
|
||||
raise ImportError("This library requires python3-smbus\nInstall with: sudo apt-get install python3-smbus")
|
||||
|
||||
try:
|
||||
import RPi.GPIO as GPIO
|
||||
except ImportError:
|
||||
raise ImportError("This library requires the RPi.GPIO module\nInstall with: sudo pip install RPi.GPIO")
|
||||
|
||||
__version__ = '0.1.4'
|
||||
|
||||
# DEVICE MAP
|
||||
DEFAULT_ADDR = 0x28
|
||||
|
||||
# Supported devices
|
||||
PID_CAP1208 = 0b01101011
|
||||
PID_CAP1188 = 0b01010000
|
||||
PID_CAP1166 = 0b01010001
|
||||
|
||||
# REGISTER MAP
|
||||
|
||||
R_MAIN_CONTROL = 0x00
|
||||
R_GENERAL_STATUS = 0x02
|
||||
R_INPUT_STATUS = 0x03
|
||||
R_LED_STATUS = 0x04
|
||||
R_NOISE_FLAG_STATUS = 0x0A
|
||||
|
||||
# Read-only delta counts for all inputs
|
||||
R_INPUT_1_DELTA = 0x10
|
||||
R_INPUT_2_DELTA = 0x11
|
||||
R_INPUT_3_DELTA = 0x12
|
||||
R_INPUT_4_DELTA = 0x13
|
||||
R_INPUT_5_DELTA = 0x14
|
||||
R_INPUT_6_DELTA = 0x15
|
||||
R_INPUT_7_DELTA = 0x16
|
||||
R_INPUT_8_DELTA = 0x17
|
||||
|
||||
R_SENSITIVITY = 0x1F
|
||||
# B7 = N/A
|
||||
# B6..B4 = Sensitivity
|
||||
# B3..B0 = Base Shift
|
||||
SENSITIVITY = {128: 0b000, 64:0b001, 32:0b010, 16:0b011, 8:0b100, 4:0b100, 2:0b110, 1:0b111}
|
||||
|
||||
R_GENERAL_CONFIG = 0x20
|
||||
# B7 = Timeout
|
||||
# B6 = Wake Config ( 1 = Wake pin asserted )
|
||||
# B5 = Disable Digital Noise ( 1 = Noise threshold disabled )
|
||||
# B4 = Disable Analog Noise ( 1 = Low frequency analog noise blocking disabled )
|
||||
# B3 = Max Duration Recalibration ( 1 = Enable recalibration if touch is held longer than max duration )
|
||||
# B2..B0 = N/A
|
||||
|
||||
R_INPUT_ENABLE = 0x21
|
||||
|
||||
|
||||
R_INPUT_CONFIG = 0x22
|
||||
|
||||
R_INPUT_CONFIG2 = 0x23 # Default 0x00000111
|
||||
|
||||
# Values for bits 3 to 0 of R_INPUT_CONFIG2
|
||||
# Determines minimum amount of time before
|
||||
# a "press and hold" event is detected.
|
||||
|
||||
# Also - Values for bits 3 to 0 of R_INPUT_CONFIG
|
||||
# Determines rate at which interrupt will repeat
|
||||
#
|
||||
# Resolution of 35ms, max = 35 + (35 * 0b1111) = 560ms
|
||||
|
||||
R_SAMPLING_CONFIG = 0x24 # Default 0x00111001
|
||||
R_CALIBRATION = 0x26 # Default 0b00000000
|
||||
R_INTERRUPT_EN = 0x27 # Default 0b11111111
|
||||
R_REPEAT_EN = 0x28 # Default 0b11111111
|
||||
R_MTOUCH_CONFIG = 0x2A # Default 0b11111111
|
||||
R_MTOUCH_PAT_CONF = 0x2B
|
||||
R_MTOUCH_PATTERN = 0x2D
|
||||
R_COUNT_O_LIMIT = 0x2E
|
||||
R_RECALIBRATION = 0x2F
|
||||
|
||||
# R/W Touch detection thresholds for inputs
|
||||
R_INPUT_1_THRESH = 0x30
|
||||
R_INPUT_2_THRESH = 0x31
|
||||
R_INPUT_3_THRESH = 0x32
|
||||
R_INPUT_4_THRESH = 0x33
|
||||
R_INPUT_5_THRESH = 0x34
|
||||
R_INPUT_6_THRESH = 0x35
|
||||
R_INPUT_7_THRESH = 0x36
|
||||
R_INPUT_8_THRESH = 0x37
|
||||
|
||||
# R/W Noise threshold for all inputs
|
||||
R_NOISE_THRESH = 0x38
|
||||
|
||||
# R/W Standby and Config Registers
|
||||
R_STANDBY_CHANNEL = 0x40
|
||||
R_STANDBY_CONFIG = 0x41
|
||||
R_STANDBY_SENS = 0x42
|
||||
R_STANDBY_THRESH = 0x43
|
||||
|
||||
R_CONFIGURATION2 = 0x44
|
||||
# B7 = Linked LED Transition Controls ( 1 = LED trigger is !touch )
|
||||
# B6 = Alert Polarity ( 1 = Active Low Open Drain, 0 = Active High Push Pull )
|
||||
# B5 = Reduce Power ( 1 = Do not power down between poll )
|
||||
# B4 = Link Polarity/Mirror bits ( 0 = Linked, 1 = Unlinked )
|
||||
# B3 = Show RF Noise ( 1 = Noise status registers only show RF, 0 = Both RF and EMI shown )
|
||||
# B2 = Disable RF Noise ( 1 = Disable RF noise filter )
|
||||
# B1..B0 = N/A
|
||||
|
||||
# Read-only reference counts for sensor inputs
|
||||
R_INPUT_1_BCOUNT = 0x50
|
||||
R_INPUT_2_BCOUNT = 0x51
|
||||
R_INPUT_3_BCOUNT = 0x52
|
||||
R_INPUT_4_BCOUNT = 0x53
|
||||
R_INPUT_5_BCOUNT = 0x54
|
||||
R_INPUT_6_BCOUNT = 0x55
|
||||
R_INPUT_7_BCOUNT = 0x56
|
||||
R_INPUT_8_BCOUNT = 0x57
|
||||
|
||||
# LED Controls - For CAP1188 and similar
|
||||
R_LED_OUTPUT_TYPE = 0x71
|
||||
R_LED_LINKING = 0x72
|
||||
R_LED_POLARITY = 0x73
|
||||
R_LED_OUTPUT_CON = 0x74
|
||||
R_LED_LTRANS_CON = 0x77
|
||||
R_LED_MIRROR_CON = 0x79
|
||||
|
||||
# LED Behaviour
|
||||
R_LED_BEHAVIOUR_1 = 0x81 # For LEDs 1-4
|
||||
R_LED_BEHAVIOUR_2 = 0x82 # For LEDs 5-8
|
||||
R_LED_PULSE_1_PER = 0x84
|
||||
R_LED_PULSE_2_PER = 0x85
|
||||
R_LED_BREATHE_PER = 0x86
|
||||
R_LED_CONFIG = 0x88
|
||||
R_LED_PULSE_1_DUT = 0x90
|
||||
R_LED_PULSE_2_DUT = 0x91
|
||||
R_LED_BREATHE_DUT = 0x92
|
||||
R_LED_DIRECT_DUT = 0x93
|
||||
R_LED_DIRECT_RAMP = 0x94
|
||||
R_LED_OFF_DELAY = 0x95
|
||||
|
||||
# R/W Power buttonc ontrol
|
||||
R_POWER_BUTTON = 0x60
|
||||
R_POW_BUTTON_CONF = 0x61
|
||||
|
||||
# Read-only upper 8-bit calibration values for sensors
|
||||
R_INPUT_1_CALIB = 0xB1
|
||||
R_INPUT_2_CALIB = 0xB2
|
||||
R_INPUT_3_CALIB = 0xB3
|
||||
R_INPUT_4_CALIB = 0xB4
|
||||
R_INPUT_5_CALIB = 0xB5
|
||||
R_INPUT_6_CALIB = 0xB6
|
||||
R_INPUT_7_CALIB = 0xB7
|
||||
R_INPUT_8_CALIB = 0xB8
|
||||
|
||||
# Read-only 2 LSBs for each sensor input
|
||||
R_INPUT_CAL_LSB1 = 0xB9
|
||||
R_INPUT_CAL_LSB2 = 0xBA
|
||||
|
||||
# Product ID Registers
|
||||
R_PRODUCT_ID = 0xFD
|
||||
R_MANUFACTURER_ID = 0xFE
|
||||
R_REVISION = 0xFF
|
||||
|
||||
# LED Behaviour settings
|
||||
LED_BEHAVIOUR_DIRECT = 0b00
|
||||
LED_BEHAVIOUR_PULSE1 = 0b01
|
||||
LED_BEHAVIOUR_PULSE2 = 0b10
|
||||
LED_BEHAVIOUR_BREATHE = 0b11
|
||||
|
||||
LED_OPEN_DRAIN = 0 # Default, LED is open-drain output with ext pullup
|
||||
LED_PUSH_PULL = 1 # LED is driven HIGH/LOW with logic 1/0
|
||||
|
||||
LED_RAMP_RATE_2000MS = 7
|
||||
LED_RAMP_RATE_1500MS = 6
|
||||
LED_RAMP_RATE_1250MS = 5
|
||||
LED_RAMP_RATE_1000MS = 4
|
||||
LED_RAMP_RATE_750MS = 3
|
||||
LED_RAMP_RATE_500MS = 2
|
||||
LED_RAMP_RATE_250MS = 1
|
||||
LED_RAMP_RATE_0MS = 0
|
||||
|
||||
## Basic stoppable thread wrapper
|
||||
#
|
||||
# Adds Event for stopping the execution loop
|
||||
# and exiting cleanly.
|
||||
class StoppableThread(threading.Thread):
|
||||
def __init__(self):
|
||||
threading.Thread.__init__(self)
|
||||
self.stop_event = threading.Event()
|
||||
self.daemon = True
|
||||
|
||||
def alive(self):
|
||||
try:
|
||||
return self.isAlive()
|
||||
except AttributeError:
|
||||
# Python >= 3.9
|
||||
return self.is_alive()
|
||||
|
||||
def start(self):
|
||||
if self.alive() == False:
|
||||
self.stop_event.clear()
|
||||
threading.Thread.start(self)
|
||||
|
||||
def stop(self):
|
||||
if self.alive() == True:
|
||||
# set event to signal thread to terminate
|
||||
self.stop_event.set()
|
||||
# block calling thread until thread really has terminated
|
||||
self.join()
|
||||
|
||||
## Basic thread wrapper class for asyncronously running functions
|
||||
#
|
||||
# Basic thread wrapper class for running functions
|
||||
# asyncronously. Return False from your function
|
||||
# to abort looping.
|
||||
class AsyncWorker(StoppableThread):
|
||||
def __init__(self, todo):
|
||||
StoppableThread.__init__(self)
|
||||
self.todo = todo
|
||||
|
||||
def run(self):
|
||||
while self.stop_event.is_set() == False:
|
||||
if self.todo() == False:
|
||||
self.stop_event.set()
|
||||
break
|
||||
|
||||
|
||||
class CapTouchEvent():
|
||||
def __init__(self, channel, event, delta):
|
||||
self.channel = channel
|
||||
self.event = event
|
||||
self.delta = delta
|
||||
|
||||
class Cap1xxx():
|
||||
supported = [PID_CAP1208, PID_CAP1188, PID_CAP1166]
|
||||
number_of_inputs = 8
|
||||
number_of_leds = 8
|
||||
|
||||
def __init__(self, i2c_addr=DEFAULT_ADDR, i2c_bus=1, alert_pin=-1, reset_pin=-1, on_touch=None, skip_init=False):
|
||||
if on_touch == None:
|
||||
on_touch = [None] * self.number_of_inputs
|
||||
|
||||
self.async_poll = None
|
||||
self.i2c_addr = i2c_addr
|
||||
self.i2c = SMBus(i2c_bus)
|
||||
self.alert_pin = alert_pin
|
||||
self.reset_pin = reset_pin
|
||||
self._delta = 50
|
||||
|
||||
GPIO.setmode(GPIO.BCM)
|
||||
if not self.alert_pin == -1:
|
||||
GPIO.setup(self.alert_pin, GPIO.IN, pull_up_down=GPIO.PUD_UP)
|
||||
|
||||
if not self.reset_pin == -1:
|
||||
GPIO.setup(self.reset_pin, GPIO.OUT)
|
||||
GPIO.setup(self.reset_pin, GPIO.LOW)
|
||||
GPIO.output(self.reset_pin, GPIO.HIGH)
|
||||
time.sleep(0.01)
|
||||
GPIO.output(self.reset_pin, GPIO.LOW)
|
||||
|
||||
self.handlers = {
|
||||
'press' : [None] * self.number_of_inputs,
|
||||
'release' : [None] * self.number_of_inputs,
|
||||
'held' : [None] * self.number_of_inputs
|
||||
}
|
||||
|
||||
self.touch_handlers = on_touch
|
||||
self.last_input_status = [False] * self.number_of_inputs
|
||||
self.input_status = ['none'] * self.number_of_inputs
|
||||
self.input_delta = [0] * self.number_of_inputs
|
||||
self.input_pressed = [False] * self.number_of_inputs
|
||||
self.repeat_enabled = 0b00000000
|
||||
self.release_enabled = 0b11111111
|
||||
|
||||
self.product_id = self._get_product_id()
|
||||
|
||||
if not self.product_id in self.supported:
|
||||
raise Exception("Product ID {} not supported!".format(self.product_id))
|
||||
|
||||
if skip_init:
|
||||
return
|
||||
|
||||
# Enable all inputs with interrupt by default
|
||||
self.enable_inputs(0b11111111)
|
||||
self.enable_interrupts(0b11111111)
|
||||
|
||||
# Disable repeat for all channels, but give
|
||||
# it sane defaults anyway
|
||||
self.enable_repeat(0b00000000)
|
||||
self.enable_multitouch(True)
|
||||
|
||||
self.set_hold_delay(210)
|
||||
self.set_repeat_rate(210)
|
||||
|
||||
# Tested sane defaults for various configurations
|
||||
self._write_byte(R_SAMPLING_CONFIG, 0b00001000) # 1sample per measure, 1.28ms time, 35ms cycle
|
||||
self._write_byte(R_SENSITIVITY, 0b01100000) # 2x sensitivity
|
||||
self._write_byte(R_GENERAL_CONFIG, 0b00111000)
|
||||
self._write_byte(R_CONFIGURATION2, 0b01100000)
|
||||
self.set_touch_delta(10)
|
||||
|
||||
atexit.register(self.stop_watching)
|
||||
|
||||
def get_input_status(self):
|
||||
"""Get the status of all inputs.
|
||||
Returns an array of 8 boolean values indicating
|
||||
whether an input has been triggered since the
|
||||
interrupt flag was last cleared."""
|
||||
touched = self._read_byte(R_INPUT_STATUS)
|
||||
threshold = self._read_block(R_INPUT_1_THRESH, self.number_of_inputs)
|
||||
delta = self._read_block(R_INPUT_1_DELTA, self.number_of_inputs)
|
||||
#status = ['none'] * 8
|
||||
for x in range(self.number_of_inputs):
|
||||
if (1 << x) & touched:
|
||||
status = 'none'
|
||||
_delta = self._get_twos_comp(delta[x])
|
||||
#threshold = self._read_byte(R_INPUT_1_THRESH + x)
|
||||
# We only ever want to detect PRESS events
|
||||
# If repeat is disabled, and release detect is enabled
|
||||
if _delta >= threshold[x]: # self._delta:
|
||||
self.input_delta[x] = _delta
|
||||
# Touch down event
|
||||
if self.input_status[x] in ['press','held']:
|
||||
if self.repeat_enabled & (1 << x):
|
||||
status = 'held'
|
||||
if self.input_status[x] in ['none','release']:
|
||||
if self.input_pressed[x]:
|
||||
status = 'none'
|
||||
else:
|
||||
status = 'press'
|
||||
else:
|
||||
# Touch release event
|
||||
if self.release_enabled & (1 << x) and not self.input_status[x] == 'release':
|
||||
status = 'release'
|
||||
else:
|
||||
status = 'none'
|
||||
|
||||
self.input_status[x] = status
|
||||
self.input_pressed[x] = status in ['press','held','none']
|
||||
else:
|
||||
self.input_status[x] = 'none'
|
||||
self.input_pressed[x] = False
|
||||
return self.input_status
|
||||
|
||||
def _get_twos_comp(self,val):
|
||||
if ( val & (1<< (8 - 1))) != 0:
|
||||
val = val - (1 << 8)
|
||||
return val
|
||||
|
||||
def clear_interrupt(self):
|
||||
"""Clear the interrupt flag, bit 0, of the
|
||||
main control register"""
|
||||
main = self._read_byte(R_MAIN_CONTROL)
|
||||
main &= ~0b00000001
|
||||
self._write_byte(R_MAIN_CONTROL, main)
|
||||
|
||||
def _interrupt_status(self):
|
||||
if self.alert_pin == -1:
|
||||
return self._read_byte(R_MAIN_CONTROL) & 1
|
||||
else:
|
||||
return not GPIO.input(self.alert_pin)
|
||||
|
||||
def wait_for_interrupt(self, timeout=100):
|
||||
"""Wait for, interrupt, bit 0 of the main
|
||||
control register to be set, indicating an
|
||||
input has been triggered."""
|
||||
start = self._millis()
|
||||
while True:
|
||||
status = self._interrupt_status() # self._read_byte(R_MAIN_CONTROL)
|
||||
if status:
|
||||
return True
|
||||
if self._millis() > start + timeout:
|
||||
return False
|
||||
time.sleep(0.005)
|
||||
|
||||
def on(self, channel=0, event='press', handler=None):
|
||||
self.handlers[event][channel] = handler
|
||||
self.start_watching()
|
||||
return True
|
||||
|
||||
def start_watching(self):
|
||||
if not self.alert_pin == -1:
|
||||
try:
|
||||
GPIO.add_event_detect(self.alert_pin, GPIO.FALLING, callback=self._handle_alert, bouncetime=1)
|
||||
self.clear_interrupt()
|
||||
except:
|
||||
pass
|
||||
return True
|
||||
|
||||
if self.async_poll == None:
|
||||
self.async_poll = AsyncWorker(self._poll)
|
||||
self.async_poll.start()
|
||||
return True
|
||||
return False
|
||||
|
||||
def stop_watching(self):
|
||||
if not self.alert_pin == -1:
|
||||
GPIO.remove_event_detect(self.alert_pin)
|
||||
|
||||
if not self.async_poll == None:
|
||||
self.async_poll.stop()
|
||||
self.async_poll = None
|
||||
return True
|
||||
return False
|
||||
|
||||
def set_touch_delta(self, delta):
|
||||
self._delta = delta
|
||||
|
||||
def auto_recalibrate(self, value):
|
||||
self._change_bit(R_GENERAL_CONFIG, 3, value)
|
||||
|
||||
def filter_analog_noise(self, value):
|
||||
self._change_bit(R_GENERAL_CONFIG, 4, not value)
|
||||
|
||||
def filter_digital_noise(self, value):
|
||||
self._change_bit(R_GENERAL_CONFIG, 5, not value)
|
||||
|
||||
def set_hold_delay(self, ms):
|
||||
"""Set time before a press and hold is detected,
|
||||
Clamps to multiples of 35 from 35 to 560"""
|
||||
repeat_rate = self._calc_touch_rate(ms)
|
||||
input_config = self._read_byte(R_INPUT_CONFIG2)
|
||||
input_config = (input_config & ~0b1111) | repeat_rate
|
||||
self._write_byte(R_INPUT_CONFIG2, input_config)
|
||||
|
||||
def set_repeat_rate(self, ms):
|
||||
"""Set repeat rate in milliseconds,
|
||||
Clamps to multiples of 35 from 35 to 560"""
|
||||
repeat_rate = self._calc_touch_rate(ms)
|
||||
input_config = self._read_byte(R_INPUT_CONFIG)
|
||||
input_config = (input_config & ~0b1111) | repeat_rate
|
||||
self._write_byte(R_INPUT_CONFIG, input_config)
|
||||
|
||||
def _calc_touch_rate(self, ms):
|
||||
ms = min(max(ms,0),560)
|
||||
scale = int((round(ms / 35.0) * 35) - 35) / 35
|
||||
return int(scale)
|
||||
|
||||
def _handle_alert(self, pin=-1):
|
||||
inputs = self.get_input_status()
|
||||
self.clear_interrupt()
|
||||
for x in range(self.number_of_inputs):
|
||||
self._trigger_handler(x, inputs[x])
|
||||
|
||||
def _poll(self):
|
||||
"""Single polling pass, should be called in
|
||||
a loop, preferably threaded."""
|
||||
if self.wait_for_interrupt():
|
||||
self._handle_alert()
|
||||
|
||||
def _trigger_handler(self, channel, event):
|
||||
if event == 'none':
|
||||
return
|
||||
if callable(self.handlers[event][channel]):
|
||||
try:
|
||||
self.handlers[event][channel](CapTouchEvent(channel, event, self.input_delta[channel]))
|
||||
except TypeError:
|
||||
self.handlers[event][channel](channel, event)
|
||||
|
||||
def _get_product_id(self):
|
||||
return self._read_byte(R_PRODUCT_ID)
|
||||
|
||||
def enable_multitouch(self, en=True):
|
||||
"""Toggles multi-touch by toggling the multi-touch
|
||||
block bit in the config register"""
|
||||
ret_mt = self._read_byte(R_MTOUCH_CONFIG)
|
||||
if en:
|
||||
self._write_byte(R_MTOUCH_CONFIG, ret_mt & ~0x80)
|
||||
else:
|
||||
self._write_byte(R_MTOUCH_CONFIG, ret_mt | 0x80 )
|
||||
|
||||
def enable_repeat(self, inputs):
|
||||
self.repeat_enabled = inputs
|
||||
self._write_byte(R_REPEAT_EN, inputs)
|
||||
|
||||
def enable_interrupts(self, inputs):
|
||||
self._write_byte(R_INTERRUPT_EN, inputs)
|
||||
|
||||
def enable_inputs(self, inputs):
|
||||
self._write_byte(R_INPUT_ENABLE, inputs)
|
||||
|
||||
def _write_byte(self, register, value):
|
||||
self.i2c.write_byte_data(self.i2c_addr, register, value)
|
||||
|
||||
def _read_byte(self, register):
|
||||
return self.i2c.read_byte_data(self.i2c_addr, register)
|
||||
|
||||
def _read_block(self, register, length):
|
||||
return self.i2c.read_i2c_block_data(self.i2c_addr, register, length)
|
||||
|
||||
def _millis(self):
|
||||
return int(round(time.time() * 1000))
|
||||
|
||||
def _set_bit(self, register, bit):
|
||||
self._write_byte( register, self._read_byte(register) | (1 << bit) )
|
||||
|
||||
def _clear_bit(self, register, bit):
|
||||
self._write_byte( register, self._read_byte(register) & ~(1 << bit ) )
|
||||
|
||||
def _change_bit(self, register, bit, state):
|
||||
if state:
|
||||
self._set_bit(register, bit)
|
||||
else:
|
||||
self._clear_bit(register, bit)
|
||||
|
||||
def _change_bits(self, register, offset, size, bits):
|
||||
original_value = self._read_byte(register)
|
||||
for x in range(size):
|
||||
original_value &= ~(1 << (offset+x))
|
||||
original_value |= (bits << offset)
|
||||
self._write_byte(register, original_value)
|
||||
|
||||
def __del__(self):
|
||||
self.stop_watching()
|
||||
|
||||
class Cap1xxxLeds(Cap1xxx):
|
||||
def set_led_linking(self, led_index, state):
|
||||
if led_index >= self.number_of_leds:
|
||||
return False
|
||||
self._change_bit(R_LED_LINKING, led_index, state)
|
||||
|
||||
def set_led_output_type(self, led_index, state):
|
||||
if led_index >= self.number_of_leds:
|
||||
return False
|
||||
self._change_bit(R_LED_OUTPUT_TYPE, led_index, state)
|
||||
|
||||
def set_led_state(self, led_index, state):
|
||||
if led_index >= self.number_of_leds:
|
||||
return False
|
||||
self._change_bit(R_LED_OUTPUT_CON, led_index, state)
|
||||
|
||||
def set_led_polarity(self, led_index, state):
|
||||
if led_index >= self.number_of_leds:
|
||||
return False
|
||||
self._change_bit(R_LED_POLARITY, led_index, state)
|
||||
|
||||
def set_led_behaviour(self, led_index, value):
|
||||
'''Set the behaviour of a LED'''
|
||||
offset = (led_index * 2) % 8
|
||||
register = led_index / 4
|
||||
value &= 0b00000011
|
||||
self._change_bits(R_LED_BEHAVIOUR_1 + register, offset, 2, value)
|
||||
|
||||
def set_led_pulse1_period(self, period_in_seconds):
|
||||
'''Set the overall period of a pulse from 32ms to 4.064 seconds'''
|
||||
period_in_seconds = min(period_in_seconds, 4.064)
|
||||
value = int(period_in_seconds * 1000.0 / 32.0) & 0b01111111
|
||||
self._change_bits(R_LED_PULSE_1_PER, 0, 7, value)
|
||||
|
||||
def set_led_pulse2_period(self, period_in_seconds):
|
||||
'''Set the overall period of a pulse from 32ms to 4.064 seconds'''
|
||||
period_in_seconds = min(period_in_seconds, 4.064)
|
||||
value = int(period_in_seconds * 1000.0 / 32.0) & 0b01111111
|
||||
self._change_bits(R_PULSE_LED_2_PER, 0, 7, value)
|
||||
|
||||
def set_led_breathe_period(self, period_in_seconds):
|
||||
period_in_seconds = min(period_in_seconds, 4.064)
|
||||
value = int(period_in_seconds * 1000.0 / 32.0) & 0b01111111
|
||||
self._change_bits(R_LED_BREATHE_PER, 0, 7, value)
|
||||
|
||||
def set_led_pulse1_count(self, count):
|
||||
count -= 1
|
||||
count &= 0b111
|
||||
self._change_bits(R_LED_CONFIG, 0, 3, count)
|
||||
|
||||
def set_led_pulse2_count(self, count):
|
||||
count -= 1
|
||||
count &= 0b111
|
||||
self._change_bits(R_LED_CONFIG, 3, 3, count)
|
||||
|
||||
def set_led_ramp_alert(self, value):
|
||||
self._change_bit(R_LED_CONFIG, 6, value)
|
||||
|
||||
def set_led_direct_ramp_rate(self, rise_rate=0, fall_rate=0):
|
||||
'''Set the rise/fall rate in ms, max 2000.
|
||||
|
||||
Rounds input to the nearest valid value.
|
||||
|
||||
Valid values are 0, 250, 500, 750, 1000, 1250, 1500, 2000
|
||||
|
||||
'''
|
||||
rise_rate = int(round(rise_rate / 250.0))
|
||||
fall_rate = int(round(fall_rate / 250.0))
|
||||
|
||||
rise_rate = min(7, rise_rate)
|
||||
fall_rate = min(7, fall_rate)
|
||||
|
||||
rate = (rise_rate << 4) | fall_rate
|
||||
self._write_byte(R_LED_DIRECT_RAMP, rate)
|
||||
|
||||
def set_led_direct_duty(self, duty_min, duty_max):
|
||||
value = (duty_max << 4) | duty_min
|
||||
self._write_byte(R_LED_DIRECT_DUT, value)
|
||||
|
||||
def set_led_pulse1_duty(self, duty_min, duty_max):
|
||||
value = (duty_max << 4) | duty_min
|
||||
self._write_byte(R_LED_PULSE_1_DUT, value)
|
||||
|
||||
def set_led_pulse2_duty(self, duty_min, duty_max):
|
||||
value = (duty_max << 4) | duty_min
|
||||
self._write_byte(R_LED_PULSE_2_DUT, value)
|
||||
|
||||
def set_led_breathe_duty(self, duty_min, duty_max):
|
||||
value = (duty_max << 4) | duty_min
|
||||
self._write_byte(R_LED_BREATHE_DUT, value)
|
||||
|
||||
def set_led_direct_min_duty(self, value):
|
||||
self._change_bits(R_LED_DIRECT_DUT, 0, 4, value)
|
||||
|
||||
def set_led_direct_max_duty(self, value):
|
||||
self._change_bits(R_LED_DIRECT_DUT, 4, 4, value)
|
||||
|
||||
def set_led_breathe_min_duty(self, value):
|
||||
self._change_bits(R_LED_BREATHE_DUT, 0, 4, value)
|
||||
|
||||
def set_led_breathe_max_duty(self, value):
|
||||
self._change_bits(R_LED_BREATHE_DUT, 4, 4, value)
|
||||
|
||||
def set_led_pulse1_min_duty(self, value):
|
||||
self._change_bits(R_LED_PULSE_1_DUT, 0, 4, value)
|
||||
|
||||
def set_led_pulse1_max_duty(self, value):
|
||||
self._change_bits(R_LED_PULSE_1_DUT, 4, 4, value)
|
||||
|
||||
def set_led_pulse2_min_duty(self, value):
|
||||
self._change_bits(R_LED_PULSE_2_DUT, 0, 4, value)
|
||||
|
||||
def set_led_pulse2_max_duty(self, value):
|
||||
self._change_bits(R_LED_PULSE_2_DUT, 4, 4, value)
|
||||
|
||||
class Cap1208(Cap1xxx):
|
||||
supported = [PID_CAP1208]
|
||||
|
||||
class Cap1188(Cap1xxxLeds):
|
||||
number_of_leds = 8
|
||||
supported = [PID_CAP1188]
|
||||
|
||||
class Cap1166(Cap1xxxLeds):
|
||||
number_of_inputs = 6
|
||||
number_of_leds = 6
|
||||
supported = [PID_CAP1166]
|
||||
|
||||
def DetectCap(i2c_addr, i2c_bus, product_id):
|
||||
bus = SMBus(i2c_bus)
|
||||
|
||||
try:
|
||||
if bus.read_byte_data(i2c_addr, R_PRODUCT_ID) == product_id:
|
||||
return True
|
||||
else:
|
||||
return False
|
||||
except IOError:
|
||||
return False
|
||||
|
55
pwnagotchi/ui/hw/libs/pimoroni/gfxhat/epd.py
Normal file
55
pwnagotchi/ui/hw/libs/pimoroni/gfxhat/epd.py
Normal file
@ -0,0 +1,55 @@
|
||||
from . import st7567
|
||||
from . import backlight
|
||||
CONTRAST = 40
|
||||
|
||||
# Define RGB colors
|
||||
WHITE = (255, 255, 255)
|
||||
GREY = (255, 255, 255)
|
||||
MAROON = (128, 0, 0)
|
||||
RED = (255, 0, 0)
|
||||
PURPLE = (128, 0, 128)
|
||||
FUCHSIA = (255, 0, 255)
|
||||
GREEN = (0, 128, 0)
|
||||
LIME = (0, 255, 0)
|
||||
OLIVE = (128, 128, 0)
|
||||
YELLOW = (255, 255, 0)
|
||||
NAVY = (0, 0, 128)
|
||||
BLUE = (0, 0, 255)
|
||||
TEAL = (0, 128, 128)
|
||||
AQUA = (0, 255, 255)
|
||||
|
||||
# Map color names to RGB values
|
||||
color_map = {
|
||||
'WHITE': WHITE,
|
||||
'GREY' : GREY,
|
||||
'MAROON': MAROON,
|
||||
'RED': RED,
|
||||
'PURPLE': PURPLE,
|
||||
'FUCHSIA': FUCHSIA,
|
||||
'GREEN' : GREEN,
|
||||
'LIME' : LIME,
|
||||
'OLIVE' : OLIVE,
|
||||
'YELLOW' : YELLOW,
|
||||
'NAVY' : NAVY,
|
||||
'BLUE' : BLUE,
|
||||
'TEAL' : TEAL,
|
||||
'AQUA' : AQUA
|
||||
}
|
||||
|
||||
class EPD(object):
|
||||
|
||||
def __init__(self, contrast=CONTRAST, blcolor=('OLIVE')):
|
||||
self.disp = st7567.ST7567()
|
||||
self.disp.contrast(contrast)
|
||||
|
||||
def Init(self, color_name):
|
||||
self.disp.setup()
|
||||
blcolor = color_map.get(color_name.upper(), OLIVE) # Default to olive if color not found
|
||||
backlight.set_all(*blcolor)
|
||||
backlight.show()
|
||||
|
||||
def Clear(self):
|
||||
self.disp.clear()
|
||||
|
||||
def Display(self, image):
|
||||
self.disp.show(image)
|
57
pwnagotchi/ui/hw/libs/pimoroni/gfxhat/lcd.py
Normal file
57
pwnagotchi/ui/hw/libs/pimoroni/gfxhat/lcd.py
Normal file
@ -0,0 +1,57 @@
|
||||
"""Library for the GFX HAT ST7567 SPI LCD."""
|
||||
from .st7567 import ST7567
|
||||
|
||||
st7567 = ST7567()
|
||||
|
||||
dimensions = st7567.dimensions
|
||||
|
||||
|
||||
def clear():
|
||||
"""Clear GFX HAT's display buffer."""
|
||||
st7567.clear()
|
||||
|
||||
|
||||
def set_pixel(x, y, value):
|
||||
"""Set a single pixel in GTX HAT's display buffer.
|
||||
|
||||
:param x: X position (from 0 to 127)
|
||||
:param y: Y position (from 0 to 63)
|
||||
:param value: pixel state 1 = On, 0 = Off
|
||||
|
||||
"""
|
||||
st7567.set_pixel(x, y, value)
|
||||
|
||||
|
||||
def show():
|
||||
"""Update GFX HAT with the current buffer contents."""
|
||||
st7567.show()
|
||||
|
||||
|
||||
def contrast(value):
|
||||
"""Change GFX HAT LCD contrast."""
|
||||
st7567.contrast(value)
|
||||
|
||||
|
||||
def rotation(r=0):
|
||||
"""Set the display rotation.
|
||||
|
||||
:param r: Specify the rotation in degrees: 0, or 180
|
||||
|
||||
"""
|
||||
if r == 0:
|
||||
st7567.rotated = False
|
||||
|
||||
elif r == 180:
|
||||
st7567.rotated = True
|
||||
|
||||
else:
|
||||
raise ValueError('Rotation must be 0 or 180 degrees')
|
||||
|
||||
|
||||
def get_rotation():
|
||||
"""Get the display rotation value.
|
||||
|
||||
Returns an integer, either 0, or 180
|
||||
|
||||
"""
|
||||
return 180 if st7567.rotated else 0
|
210
pwnagotchi/ui/hw/libs/pimoroni/gfxhat/st7567.py
Normal file
210
pwnagotchi/ui/hw/libs/pimoroni/gfxhat/st7567.py
Normal file
@ -0,0 +1,210 @@
|
||||
"""Library for the ST7567 128x64 SPI LCD."""
|
||||
import RPi.GPIO as GPIO
|
||||
import spidev
|
||||
import time
|
||||
import random
|
||||
|
||||
SPI_SPEED_HZ = 1000000
|
||||
|
||||
WIDTH = 128
|
||||
HEIGHT = 64
|
||||
|
||||
PIN_CS = 8
|
||||
PIN_RST = 5
|
||||
PIN_DC = 6
|
||||
|
||||
ST7567_PAGESIZE = 128
|
||||
|
||||
ST7567_DISPOFF = 0xae # 0xae: Display OFF (sleep mode) */
|
||||
ST7567_DISPON = 0xaf # 0xaf: Display ON in normal mode */
|
||||
|
||||
ST7567_SETSTARTLINE = 0x40 # 0x40-7f: Set display start line */
|
||||
ST7567_STARTLINE_MASK = 0x3f
|
||||
|
||||
ST7567_REG_RATIO = 0x20
|
||||
|
||||
ST7567_SETPAGESTART = 0xb0 # 0xb0-b7: Set page start address */
|
||||
ST7567_PAGESTART_MASK = 0x07
|
||||
|
||||
ST7567_SETCOLL = 0x00 # 0x00-0x0f: Set lower column address */
|
||||
ST7567_COLL_MASK = 0x0f
|
||||
ST7567_SETCOLH = 0x10 # 0x10-0x1f: Set higher column address */
|
||||
ST7567_COLH_MASK = 0x0f
|
||||
|
||||
ST7567_SEG_DIR_NORMAL = 0xa0 # 0xa0: Column address 0 is mapped to SEG0 */
|
||||
ST7567_SEG_DIR_REV = 0xa1 # 0xa1: Column address 128 is mapped to SEG0 */
|
||||
|
||||
ST7567_DISPNORMAL = 0xa6 # 0xa6: Normal display */
|
||||
ST7567_DISPINVERSE = 0xa7 # 0xa7: Inverse display */
|
||||
|
||||
ST7567_DISPRAM = 0xa4 # 0xa4: Resume to RAM content display */
|
||||
ST7567_DISPENTIRE = 0xa5 # 0xa5: Entire display ON */
|
||||
|
||||
ST7567_BIAS_1_9 = 0xa2 # 0xa2: Select BIAS setting 1/9 */
|
||||
ST7567_BIAS_1_7 = 0xa3 # 0xa3: Select BIAS setting 1/7 */
|
||||
|
||||
ST7567_ENTER_RMWMODE = 0xe0 # 0xe0: Enter the Read Modify Write mode */
|
||||
ST7567_EXIT_RMWMODE = 0xee # 0xee: Leave the Read Modify Write mode */
|
||||
ST7567_EXIT_SOFTRST = 0xe2 # 0xe2: Software RESET */
|
||||
|
||||
ST7567_SETCOMNORMAL = 0xc0 # 0xc0: Set COM output direction, normal mode */
|
||||
ST7567_SETCOMREVERSE = 0xc8 # 0xc8: Set COM output direction, reverse mode */
|
||||
|
||||
ST7567_POWERCTRL_VF = 0x29 # 0x29: Control built-in power circuit */
|
||||
ST7567_POWERCTRL_VR = 0x2a # 0x2a: Control built-in power circuit */
|
||||
ST7567_POWERCTRL_VB = 0x2c # 0x2c: Control built-in power circuit */
|
||||
ST7567_POWERCTRL = 0x2f # 0x2c: Control built-in power circuit */
|
||||
|
||||
ST7567_REG_RES_RR0 = 0x21 # 0x21: Regulation Resistior ratio */
|
||||
ST7567_REG_RES_RR1 = 0x22 # 0x22: Regulation Resistior ratio */
|
||||
ST7567_REG_RES_RR2 = 0x24 # 0x24: Regulation Resistior ratio */
|
||||
|
||||
ST7567_SETCONTRAST = 0x81 # 0x81: Set contrast control */
|
||||
|
||||
ST7567_SETBOOSTER = 0xf8 # Set booster level */
|
||||
ST7567_SETBOOSTER4X = 0x00 # Set booster level */
|
||||
ST7567_SETBOOSTER5X = 0x01 # Set booster level */
|
||||
|
||||
ST7567_NOP = 0xe3 # 0xe3: NOP Command for no operation */
|
||||
|
||||
ST7565_STARTBYTES = 0
|
||||
|
||||
|
||||
class ST7567(object):
|
||||
"""Class to drive the ST7567 128x64 SPI LCD."""
|
||||
|
||||
def __init__(self, pin_rst=PIN_RST, pin_dc=PIN_DC, spi_bus=0, spi_cs=0, spi_speed=SPI_SPEED_HZ):
|
||||
"""Initialise the ST7567 class.
|
||||
|
||||
:param pin_rst: BCM GPIO pin number for reset
|
||||
:param pin_dc: BCM GPIO pin number for data/command
|
||||
:param spi_bus: SPI bus ID
|
||||
:param spi_cs: SPI chipselect ID (0/1 not BCM pin number)
|
||||
:param spi_speed: SPI speed (hz)
|
||||
|
||||
"""
|
||||
self._is_setup = False
|
||||
self.pin_rst = pin_rst
|
||||
self.pin_dc = pin_dc
|
||||
self.spi_bus = spi_bus
|
||||
self.spi_cs = spi_cs
|
||||
self.spi_speed = spi_speed
|
||||
|
||||
self.rotated = False
|
||||
|
||||
self.clear()
|
||||
|
||||
def setup(self):
|
||||
"""Set up GPIO and initialise the ST7567 device."""
|
||||
if self._is_setup:
|
||||
return True
|
||||
|
||||
GPIO.setmode(GPIO.BCM)
|
||||
GPIO.setwarnings(False)
|
||||
GPIO.setup(self.pin_rst, GPIO.OUT)
|
||||
GPIO.setup(self.pin_dc, GPIO.OUT)
|
||||
|
||||
self.spi = spidev.SpiDev()
|
||||
self.spi.open(self.spi_bus, self.spi_cs)
|
||||
self.spi.max_speed_hz = self.spi_speed
|
||||
|
||||
self._reset()
|
||||
self._init()
|
||||
|
||||
self._is_setup = True
|
||||
|
||||
def dimensions(self):
|
||||
"""Return the ST7567 display dimensions."""
|
||||
return (WIDTH, HEIGHT)
|
||||
|
||||
def clear(self):
|
||||
"""Clear the python display buffer."""
|
||||
self.buf = [0 for _ in range(128 * 64 // 8)]
|
||||
|
||||
def _command(self, data):
|
||||
GPIO.output(self.pin_dc, 0)
|
||||
self.spi.writebytes(data)
|
||||
|
||||
def _data(self, data):
|
||||
GPIO.output(self.pin_dc, 1)
|
||||
self.spi.writebytes(data)
|
||||
|
||||
def _reset(self):
|
||||
GPIO.output(self.pin_rst, 0)
|
||||
time.sleep(0.01)
|
||||
GPIO.output(self.pin_rst, 1)
|
||||
time.sleep(0.1)
|
||||
|
||||
def _init(self):
|
||||
self._command([
|
||||
ST7567_BIAS_1_7, # Bais 1/7 (0xA2 = Bias 1/9)
|
||||
ST7567_SEG_DIR_NORMAL,
|
||||
ST7567_SETCOMREVERSE, # Reverse COM - vertical flip
|
||||
ST7567_DISPNORMAL, # Inverse display (0xA6 normal)
|
||||
ST7567_SETSTARTLINE | 0, # Start at line 0
|
||||
ST7567_POWERCTRL,
|
||||
ST7567_REG_RATIO | 3,
|
||||
ST7567_DISPON,
|
||||
ST7567_SETCONTRAST, # Set contrast
|
||||
40 # Contrast value
|
||||
])
|
||||
|
||||
def set_pixel(self, x, y, value):
|
||||
"""Set a single pixel in the python display buffer.
|
||||
|
||||
:param x: X position (from 0 to 127)
|
||||
:param y: Y position (from 0 to 63)
|
||||
:param value: pixel state 1 = On, 0 = Off
|
||||
|
||||
"""
|
||||
if self.rotated:
|
||||
x = (WIDTH - 1) - x
|
||||
y = (HEIGHT - 1) - y
|
||||
offset = ((y // 8) * WIDTH) + x
|
||||
bit = y % 8
|
||||
self.buf[offset] &= ~(1 << bit)
|
||||
self.buf[offset] |= (value & 1) << bit
|
||||
|
||||
def show(self, image):
|
||||
"""Update the ST7567 display with the buffer contents."""
|
||||
width, height = self.dimensions()
|
||||
|
||||
for x in range(width):
|
||||
for y in range(height):
|
||||
pixel = image.getpixel((x, y))
|
||||
self.set_pixel(x, y, pixel)
|
||||
self.setup()
|
||||
self._command([ST7567_ENTER_RMWMODE])
|
||||
for page in range(8):
|
||||
offset = page * ST7567_PAGESIZE
|
||||
self._command([ST7567_SETPAGESTART | page, ST7567_SETCOLL, ST7567_SETCOLH])
|
||||
self._data(self.buf[offset:offset + ST7567_PAGESIZE])
|
||||
self._command([ST7567_EXIT_RMWMODE])
|
||||
|
||||
def contrast(self, value):
|
||||
"""Update the ST7568 display contrast."""
|
||||
self.setup()
|
||||
self._command([ST7567_SETCONTRAST, value])
|
||||
|
||||
|
||||
if __name__ == '__main__': # pragma: no cover
|
||||
st7567 = ST7567()
|
||||
st7567.setup()
|
||||
|
||||
for x in range(64):
|
||||
st7567.set_pixel(x, x, 1)
|
||||
st7567.set_pixel(64 - x, x, 1)
|
||||
st7567.set_pixel(x + 2, x, 1)
|
||||
st7567.show()
|
||||
|
||||
time.sleep(2.0)
|
||||
|
||||
try:
|
||||
while True:
|
||||
for x in range(128):
|
||||
for y in range(64):
|
||||
st7567.set_pixel(x, y, random.randint(0, 1))
|
||||
st7567.show()
|
||||
|
||||
except KeyboardInterrupt:
|
||||
pass
|
129
pwnagotchi/ui/hw/libs/pimoroni/gfxhat/touch.py
Normal file
129
pwnagotchi/ui/hw/libs/pimoroni/gfxhat/touch.py
Normal file
@ -0,0 +1,129 @@
|
||||
"""Library for the GFX HAT Cap1166 touch controller."""
|
||||
from . import cap1xxx
|
||||
|
||||
_cap1166 = None
|
||||
is_setup = False
|
||||
|
||||
I2C_ADDR = 0x2c
|
||||
|
||||
UP = 0
|
||||
DOWN = 1
|
||||
BACK = 2
|
||||
MINUS = LEFT = 3
|
||||
SELECT = ENTER = 4
|
||||
PLUS = RIGHT = 5
|
||||
|
||||
LED_MAPPING = [5, 4, 3, 2, 1, 0]
|
||||
|
||||
NAME_MAPPING = ['up', 'down', 'back', 'minus', 'select', 'plus']
|
||||
|
||||
|
||||
def setup():
|
||||
"""Set up the touch input on GFX HAT."""
|
||||
global _cap1166, is_setup
|
||||
|
||||
if is_setup:
|
||||
return
|
||||
|
||||
_cap1166 = cap1xxx.Cap1166(i2c_addr=I2C_ADDR)
|
||||
|
||||
for x in range(6):
|
||||
_cap1166.set_led_linking(x, 0)
|
||||
|
||||
# Force recalibration
|
||||
_cap1166._write_byte(0x26, 0b00111111)
|
||||
_cap1166._write_byte(0x1F, 0b01000000)
|
||||
|
||||
is_setup = True
|
||||
|
||||
|
||||
def get_name(index):
|
||||
"""Get the name of a touch pad from its channel index.
|
||||
|
||||
:param index: Index of touch pad from 0 to 5
|
||||
|
||||
"""
|
||||
return NAME_MAPPING[index]
|
||||
|
||||
|
||||
def set_led(index, state):
|
||||
"""Set LED state.
|
||||
|
||||
:param index: LED index
|
||||
:param state: LED state (1 = on, 0 = off)
|
||||
|
||||
"""
|
||||
setup()
|
||||
|
||||
_cap1166.set_led_state(LED_MAPPING[index], state)
|
||||
|
||||
|
||||
def high_sensitivity():
|
||||
"""Switch to high sensitivity mode.
|
||||
|
||||
This predetermined high sensitivity mode is for using
|
||||
touch through 3mm perspex or similar materials.
|
||||
|
||||
"""
|
||||
setup()
|
||||
|
||||
_cap1166._write_byte(0x00, 0b11000000)
|
||||
_cap1166._write_byte(0x1f, 0b00000000)
|
||||
|
||||
|
||||
def enable_repeat(enable):
|
||||
"""Enable touch hold repeat.
|
||||
|
||||
If enable is true, repeat will be enabled. This will
|
||||
trigger new touch events at the set repeat_rate when
|
||||
a touch input is held.
|
||||
|
||||
:param enable: enable/disable repeat: True/False
|
||||
|
||||
"""
|
||||
setup()
|
||||
|
||||
if enable:
|
||||
_cap1166.enable_repeat(0b11111111)
|
||||
else:
|
||||
_cap1166.enable_repeat(0b00000000)
|
||||
|
||||
|
||||
def set_repeat_rate(rate):
|
||||
"""Set hold repeat rate.
|
||||
|
||||
Repeat rate values are clamped to the nearest 35ms,
|
||||
values from 35 to 560 are valid.
|
||||
|
||||
:param rate: time in ms from 35 to 560
|
||||
|
||||
"""
|
||||
setup()
|
||||
|
||||
_cap1166.set_repeat_rate(rate)
|
||||
|
||||
|
||||
def on(buttons, handler=None):
|
||||
"""Handle a press of one or more buttons.
|
||||
|
||||
Decorator. Use with @captouch.on(UP)
|
||||
|
||||
:param buttons: List, or single instance of cap touch button constant
|
||||
:param bounce: Maintained for compatibility with Dot3k joystick, unused
|
||||
|
||||
"""
|
||||
setup()
|
||||
|
||||
buttons = buttons if isinstance(buttons, list) else [buttons]
|
||||
|
||||
def register(handler):
|
||||
for button in buttons:
|
||||
_cap1166.on(channel=button, event='press', handler=handler)
|
||||
_cap1166.on(channel=button, event='release', handler=handler)
|
||||
_cap1166.on(channel=button, event='held', handler=handler)
|
||||
|
||||
if handler is not None:
|
||||
register(handler)
|
||||
return
|
||||
|
||||
return register
|
49
pwnagotchi/ui/hw/minipitft.py
Normal file
49
pwnagotchi/ui/hw/minipitft.py
Normal file
@ -0,0 +1,49 @@
|
||||
# board GPIO:
|
||||
# A: GPIO22
|
||||
# B: GPIO23
|
||||
#
|
||||
# HW datasheet: https://learn.adafruit.com/adafruit-1-3-color-tft-bonnet-for-raspberry-pi/overview
|
||||
|
||||
import logging
|
||||
|
||||
import pwnagotchi.ui.fonts as fonts
|
||||
from pwnagotchi.ui.hw.base import DisplayImpl
|
||||
|
||||
|
||||
class MiniPitft(DisplayImpl):
|
||||
def __init__(self, config):
|
||||
super(MiniPitft, self).__init__(config, 'minipitft')
|
||||
|
||||
def layout(self):
|
||||
fonts.setup(10, 9, 10, 35, 25, 9)
|
||||
self._layout['width'] = 240
|
||||
self._layout['height'] = 240
|
||||
self._layout['face'] = (0, 40)
|
||||
self._layout['name'] = (5, 20)
|
||||
self._layout['channel'] = (0, 0)
|
||||
self._layout['aps'] = (28, 0)
|
||||
self._layout['uptime'] = (175, 0)
|
||||
self._layout['line1'] = [0, 14, 240, 14]
|
||||
self._layout['line2'] = [0, 108, 240, 108]
|
||||
self._layout['friend_face'] = (0, 92)
|
||||
self._layout['friend_name'] = (40, 94)
|
||||
self._layout['shakes'] = (0, 109)
|
||||
self._layout['mode'] = (215, 109)
|
||||
self._layout['status'] = {
|
||||
'pos': (125, 20),
|
||||
'font': fonts.status_font(fonts.Medium),
|
||||
'max': 20
|
||||
}
|
||||
|
||||
return self._layout
|
||||
|
||||
def initialize(self):
|
||||
logging.info("initializing Adafruit Mini Pi Tft 240x240")
|
||||
from pwnagotchi.ui.hw.libs.adafruit.minipitft.ST7789 import ST7789
|
||||
self._display = ST7789(0,0,25,22)
|
||||
|
||||
def render(self, canvas):
|
||||
self._display.display(canvas)
|
||||
|
||||
def clear(self):
|
||||
self._display.clear()
|
49
pwnagotchi/ui/hw/minipitft2.py
Normal file
49
pwnagotchi/ui/hw/minipitft2.py
Normal file
@ -0,0 +1,49 @@
|
||||
# board GPIO:
|
||||
# A: GPIO22
|
||||
# B: GPIO23
|
||||
#
|
||||
# HW datasheet: https://learn.adafruit.com/adafruit-1-3-color-tft-bonnet-for-raspberry-pi/overview
|
||||
|
||||
import logging
|
||||
|
||||
import pwnagotchi.ui.fonts as fonts
|
||||
from pwnagotchi.ui.hw.base import DisplayImpl
|
||||
|
||||
|
||||
class MiniPitft2(DisplayImpl):
|
||||
def __init__(self, config):
|
||||
super(MiniPitft2, self).__init__(config, 'minipitft2')
|
||||
|
||||
def layout(self):
|
||||
fonts.setup(10, 9, 10, 35, 25, 9)
|
||||
self._layout['width'] = 240
|
||||
self._layout['height'] = 135
|
||||
self._layout['face'] = (0, 40)
|
||||
self._layout['name'] = (5, 20)
|
||||
self._layout['channel'] = (0, 0)
|
||||
self._layout['aps'] = (28, 0)
|
||||
self._layout['uptime'] = (175, 0)
|
||||
self._layout['line1'] = [0, 14, 240, 14]
|
||||
self._layout['line2'] = [0, 108, 240, 108]
|
||||
self._layout['friend_face'] = (0, 92)
|
||||
self._layout['friend_name'] = (40, 94)
|
||||
self._layout['shakes'] = (0, 109)
|
||||
self._layout['mode'] = (215, 109)
|
||||
self._layout['status'] = {
|
||||
'pos': (125, 20),
|
||||
'font': fonts.status_font(fonts.Medium),
|
||||
'max': 20
|
||||
}
|
||||
|
||||
return self._layout
|
||||
|
||||
def initialize(self):
|
||||
logging.info("initializing Adafruit Mini Pi Tft 135x240")
|
||||
from pwnagotchi.ui.hw.libs.adafruit.minipitft2.ST7789 import ST7789
|
||||
self._display = ST7789(0,0,25,22)
|
||||
|
||||
def render(self, canvas):
|
||||
self._display.display(canvas)
|
||||
|
||||
def clear(self):
|
||||
self._display.clear()
|
@ -3,7 +3,6 @@
|
||||
# Key2 (for2,8"): GPIO22
|
||||
# Key3 (for2,8"): GPIO23
|
||||
# Key4 (for2,8"): GPIO27
|
||||
# Key5 (for2,4"): GPIO27
|
||||
#
|
||||
# Key1 (for2,4"): GPIO16
|
||||
# Key2 (for2,4"): GPIO13
|
||||
|
@ -6,6 +6,7 @@ import time
|
||||
from threading import Lock
|
||||
|
||||
from PIL import ImageDraw
|
||||
from PIL import ImageColor as colors
|
||||
|
||||
import pwnagotchi
|
||||
import pwnagotchi.plugins as plugins
|
||||
@ -21,20 +22,98 @@ from pwnagotchi.voice import Voice
|
||||
WHITE = 0x00 # white is actually black on jays image
|
||||
BLACK = 0xFF # black is actually white on jays image
|
||||
|
||||
BACKGROUND_1 = 0
|
||||
FOREGROUND_1 = 1
|
||||
|
||||
BACKGROUND_L = 0
|
||||
FOREGROUND_L = 255
|
||||
|
||||
BACKGROUND_BGR_16 = (0,0,0)
|
||||
FOREGROUND_BGR_16 = (31,63,31)
|
||||
|
||||
BACKGROUND_RGB = (0,0,0)
|
||||
FOREGROUND_RGB = (255,255,255)
|
||||
|
||||
|
||||
ROOT = None
|
||||
|
||||
|
||||
|
||||
|
||||
#1 (1-bit pixels, black and white, stored with one pixel per byte)
|
||||
|
||||
#L (8-bit pixels, grayscale)
|
||||
|
||||
#P (8-bit pixels, mapped to any other mode using a color palette)
|
||||
|
||||
#BGR;16 (5,6,5 bits, for 65k color)
|
||||
|
||||
#RGB (3x8-bit pixels, true color)
|
||||
|
||||
#RGBA (4x8-bit pixels, true color with transparency mask)
|
||||
|
||||
#CMYK (4x8-bit pixels, color separation)
|
||||
|
||||
#YCbCr (3x8-bit pixels, color video format)
|
||||
|
||||
#self.FOREGROUND is the main color
|
||||
#self.BACKGROUNDGROUND is the 2ndary color, used for background
|
||||
|
||||
|
||||
|
||||
class View(object):
|
||||
def __init__(self, config, impl, state=None):
|
||||
global ROOT, BLACK, WHITE
|
||||
|
||||
#values/code for display color mode
|
||||
|
||||
self.mode = '1' # 1 = (1-bit pixels, black and white, stored with one pixel per byte)
|
||||
if hasattr(impl, 'mode'):
|
||||
self.mode = impl.mode
|
||||
|
||||
|
||||
|
||||
match self.mode:
|
||||
case '1':
|
||||
self.BACKGROUND = BACKGROUND_1
|
||||
self.FOREGROUND = FOREGROUND_1
|
||||
# do stuff is color mode is 1 when View object is created.
|
||||
case 'L':
|
||||
self.BACKGROUND = BACKGROUND_L # black 0 to 255
|
||||
self.FOREGROUND = FOREGROUND_L
|
||||
# do stuff is color mode is L when View object is created.
|
||||
case 'P':
|
||||
pass
|
||||
# do stuff is color mode is P when View object is created.
|
||||
case 'BGR;16':
|
||||
self.BACKGROUND = BACKGROUND_BGR_16 #black tuple
|
||||
self.FOREGROUND = FOREGROUND_BGR_16 #white tuple
|
||||
case 'RGB':
|
||||
self.BACKGROUND = BACKGROUND_RGB #black tuple
|
||||
self.FOREGROUND = FOREGROUND_RGB #white tuple
|
||||
# do stuff is color mode is RGB when View object is created.
|
||||
case 'RGBA':
|
||||
# do stuff is color mode is RGBA when View object is created.
|
||||
pass
|
||||
case 'CMYK':
|
||||
# do stuff is color mode is CMYK when View object is created.
|
||||
pass
|
||||
case 'YCbCr':
|
||||
# do stuff is color mode is YCbCr when View object is created.
|
||||
pass
|
||||
case _:
|
||||
# do stuff when color mode doesnt exist for display
|
||||
self.BACKGROUND = BACKGROUND_1
|
||||
self.FOREGROUND = FOREGROUND_1
|
||||
|
||||
|
||||
self.invert = 0
|
||||
self._black = 0xFF
|
||||
self._white = 0x00
|
||||
if 'invert' in config['ui'] and config['ui']['invert'] == True:
|
||||
logging.debug("INVERT BLACK/WHITES:" + str(config['ui']['invert']))
|
||||
logging.debug("INVERT:" + str(config['ui']['invert']))
|
||||
self.invert = 1
|
||||
BLACK = 0x00
|
||||
WHITE = 0xFF
|
||||
self._black = 0x00
|
||||
self._white = 0xFF
|
||||
tmp = self.FOREGROUND
|
||||
self.FOREGROUND = self.FOREGROUND
|
||||
self.FOREGROUND = tmp
|
||||
|
||||
# setup faces from the configuration in case the user customized them
|
||||
faces.load_from_config(config['ui']['faces'])
|
||||
@ -51,40 +130,40 @@ class View(object):
|
||||
self._width = self._layout['width']
|
||||
self._height = self._layout['height']
|
||||
self._state = State(state={
|
||||
'channel': LabeledValue(color=BLACK, label='CH', value='00', position=self._layout['channel'],
|
||||
'channel': LabeledValue(color=self.FOREGROUND, label='CH', value='00', position=self._layout['channel'],
|
||||
label_font=fonts.Bold,
|
||||
text_font=fonts.Medium),
|
||||
'aps': LabeledValue(color=BLACK, label='APS', value='0 (00)', position=self._layout['aps'],
|
||||
'aps': LabeledValue(color=self.FOREGROUND, label='APS', value='0 (00)', position=self._layout['aps'],
|
||||
label_font=fonts.Bold,
|
||||
text_font=fonts.Medium),
|
||||
|
||||
'uptime': LabeledValue(color=BLACK, label='UP', value='00:00:00', position=self._layout['uptime'],
|
||||
'uptime': LabeledValue(color=self.FOREGROUND, label='UP', value='00:00:00', position=self._layout['uptime'],
|
||||
label_font=fonts.Bold,
|
||||
text_font=fonts.Medium),
|
||||
|
||||
'line1': Line(self._layout['line1'], color=BLACK),
|
||||
'line2': Line(self._layout['line2'], color=BLACK),
|
||||
'line1': Line(self._layout['line1'], color=self.FOREGROUND),
|
||||
'line2': Line(self._layout['line2'], color=self.FOREGROUND),
|
||||
|
||||
'face': Text(value=faces.SLEEP, position=(config['ui']['faces']['position_x'], config['ui']['faces']['position_y']), color=BLACK, font=fonts.Huge, png=config['ui']['faces']['png']),
|
||||
'face': Text(value=faces.SLEEP, position=(config['ui']['faces']['position_x'], config['ui']['faces']['position_y']), color=self.FOREGROUND, font=fonts.Huge, png=config['ui']['faces']['png']),
|
||||
|
||||
# 'friend_face': Text(value=None, position=self._layout['friend_face'], font=fonts.Bold, color=BLACK),
|
||||
'friend_name': Text(value=None, position=self._layout['friend_face'], font=fonts.BoldSmall, color=BLACK),
|
||||
# 'friend_face': Text(value=None, position=self._layout['friend_face'], font=fonts.Bold, color=self.FOREGROUND),
|
||||
'friend_name': Text(value=None, position=self._layout['friend_face'], font=fonts.BoldSmall, color=self.FOREGROUND),
|
||||
|
||||
'name': Text(value='%s>' % 'pwnagotchi', position=self._layout['name'], color=BLACK, font=fonts.Bold),
|
||||
'name': Text(value='%s>' % 'pwnagotchi', position=self._layout['name'], color=self.FOREGROUND, font=fonts.Bold),
|
||||
|
||||
'status': Text(value=self._voice.default(),
|
||||
position=self._layout['status']['pos'],
|
||||
color=BLACK,
|
||||
color=self.FOREGROUND,
|
||||
font=self._layout['status']['font'],
|
||||
wrap=True,
|
||||
# the current maximum number of characters per line, assuming each character is 6 pixels wide
|
||||
max_length=self._layout['status']['max']),
|
||||
|
||||
'shakes': LabeledValue(label='PWND ', value='0 (00)', color=BLACK,
|
||||
'shakes': LabeledValue(label='PWND ', value='0 (00)', color=self.FOREGROUND,
|
||||
position=self._layout['shakes'], label_font=fonts.Bold,
|
||||
text_font=fonts.Medium),
|
||||
'mode': Text(value='AUTO', position=self._layout['mode'],
|
||||
font=fonts.Bold, color=BLACK),
|
||||
font=fonts.Bold, color=self.FOREGROUND),
|
||||
})
|
||||
|
||||
if state:
|
||||
@ -110,7 +189,7 @@ class View(object):
|
||||
self._state.has_element(key)
|
||||
|
||||
def add_element(self, key, elem):
|
||||
if self.invert is 1 and elem.color:
|
||||
if self.invert is 1 and hasattr(elem, 'color'):
|
||||
if elem.color == 0xff:
|
||||
elem.color = 0x00
|
||||
elif elem.color == 0x00:
|
||||
@ -389,8 +468,8 @@ class View(object):
|
||||
state = self._state
|
||||
changes = state.changes(ignore=self._ignore_changes)
|
||||
if force or len(changes):
|
||||
self._canvas = Image.new('1', (self._width, self._height), self._white)
|
||||
drawer = ImageDraw.Draw(self._canvas)
|
||||
self._canvas = Image.new(self.mode, (self._width, self._height), self.BACKGROUND)
|
||||
drawer = ImageDraw.Draw(self._canvas, self.mode)
|
||||
|
||||
plugins.on('ui_update', self)
|
||||
|
||||
|
@ -309,9 +309,21 @@ def load_config(args):
|
||||
elif config['ui']['display']['type'] in ('pirateaudio'):
|
||||
config['ui']['display']['type'] = 'pirateaudio'
|
||||
|
||||
elif config['ui']['display']['type'] in ('gfxhat'):
|
||||
config['ui']['display']['type'] = 'gfxhat'
|
||||
|
||||
elif config['ui']['display']['type'] in ('pitft'):
|
||||
config['ui']['display']['type'] = 'pitft'
|
||||
|
||||
elif config['ui']['display']['type'] in ('argonpod'):
|
||||
config['ui']['display']['type'] = 'argonpod'
|
||||
|
||||
elif config['ui']['display']['type'] in ('minipitft'):
|
||||
config['ui']['display']['type'] = 'minipitft'
|
||||
|
||||
elif config['ui']['display']['type'] in ('minipitft2'):
|
||||
config['ui']['display']['type'] = 'minipitft2'
|
||||
|
||||
elif config['ui']['display']['type'] in ('tftbonnet'):
|
||||
config['ui']['display']['type'] = 'tftbonnet'
|
||||
|
||||
|
Reference in New Issue
Block a user