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):
|
def is_pirateaudio(self):
|
||||||
return self._implementation.name == 'pirateaudio'
|
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):
|
def is_pitft(self):
|
||||||
return self._implementation.name == 'pitft'
|
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):
|
def is_tftbonnet(self):
|
||||||
return self._implementation.name == 'tftbonnet'
|
return self._implementation.name == 'tftbonnet'
|
||||||
|
|
||||||
|
@ -96,10 +96,26 @@ def display_for(config):
|
|||||||
from pwnagotchi.ui.hw.pirateaudio import PirateAudio
|
from pwnagotchi.ui.hw.pirateaudio import PirateAudio
|
||||||
return PirateAudio(config)
|
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':
|
elif config['ui']['display']['type'] == 'pitft':
|
||||||
from pwnagotchi.ui.hw.pitft import Pitft
|
from pwnagotchi.ui.hw.pitft import Pitft
|
||||||
return Pitft(config)
|
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':
|
elif config['ui']['display']['type'] == 'tftbonnet':
|
||||||
from pwnagotchi.ui.hw.tftbonnet import TftBonnet
|
from pwnagotchi.ui.hw.tftbonnet import TftBonnet
|
||||||
return TftBonnet(config)
|
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):
|
class DisplayHatMini(DisplayImpl):
|
||||||
def __init__(self, config):
|
def __init__(self, config):
|
||||||
super(DisplayHatMini, self).__init__(config, 'displayhatmini')
|
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):
|
def layout(self):
|
||||||
fonts.setup(12, 10, 12, 70, 25, 9)
|
fonts.setup(12, 10, 12, 70, 25, 9)
|
||||||
@ -35,10 +35,10 @@ class DisplayHatMini(DisplayImpl):
|
|||||||
def initialize(self):
|
def initialize(self):
|
||||||
logging.info("initializing Display Hat Mini")
|
logging.info("initializing Display Hat Mini")
|
||||||
from pwnagotchi.ui.hw.libs.pimoroni.displayhatmini.ST7789 import ST7789
|
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):
|
def render(self, canvas):
|
||||||
self._display.display(canvas)
|
self._display.display(canvas)
|
||||||
|
|
||||||
def clear(self):
|
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):
|
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.width = width
|
||||||
self.height = height
|
self.height = height
|
||||||
|
|
||||||
|
@ -90,8 +90,8 @@ ST7789_PWCTR6 = 0xFC
|
|||||||
class ST7789(object):
|
class ST7789(object):
|
||||||
"""Representation of an ST7789 TFT LCD."""
|
"""Representation of an ST7789 TFT LCD."""
|
||||||
|
|
||||||
def __init__(self, port, cs, dc, backlight=None, rst=None, width=240,
|
def __init__(self, port, cs, dc, backlight, rst=None, width=320,
|
||||||
height=240, rotation=90, invert=True, spi_speed_hz=4000000,
|
height=240, rotation=0, invert=True, spi_speed_hz=60 * 1000 * 1000,
|
||||||
offset_left=0,
|
offset_left=0,
|
||||||
offset_top=0):
|
offset_top=0):
|
||||||
"""Create an instance of the display using SPI communication.
|
"""Create an instance of the display using SPI communication.
|
||||||
@ -357,4 +357,4 @@ class ST7789(object):
|
|||||||
result = red | green | blue
|
result = red | green | blue
|
||||||
|
|
||||||
# Output the raw bytes
|
# Output the raw bytes
|
||||||
return result.byteswap().tobytes()
|
return result.byteswap().tobytes()
|
||||||
|
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
|
# Key2 (for2,8"): GPIO22
|
||||||
# Key3 (for2,8"): GPIO23
|
# Key3 (for2,8"): GPIO23
|
||||||
# Key4 (for2,8"): GPIO27
|
# Key4 (for2,8"): GPIO27
|
||||||
# Key5 (for2,4"): GPIO27
|
|
||||||
#
|
#
|
||||||
# Key1 (for2,4"): GPIO16
|
# Key1 (for2,4"): GPIO16
|
||||||
# Key2 (for2,4"): GPIO13
|
# Key2 (for2,4"): GPIO13
|
||||||
|
@ -6,6 +6,7 @@ import time
|
|||||||
from threading import Lock
|
from threading import Lock
|
||||||
|
|
||||||
from PIL import ImageDraw
|
from PIL import ImageDraw
|
||||||
|
from PIL import ImageColor as colors
|
||||||
|
|
||||||
import pwnagotchi
|
import pwnagotchi
|
||||||
import pwnagotchi.plugins as plugins
|
import pwnagotchi.plugins as plugins
|
||||||
@ -21,20 +22,98 @@ from pwnagotchi.voice import Voice
|
|||||||
WHITE = 0x00 # white is actually black on jays image
|
WHITE = 0x00 # white is actually black on jays image
|
||||||
BLACK = 0xFF # black is actually white 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):
|
class View(object):
|
||||||
def __init__(self, config, impl, state=None):
|
def __init__(self, config, impl, state=None):
|
||||||
global ROOT, BLACK, WHITE
|
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.invert = 0
|
||||||
self._black = 0xFF
|
|
||||||
self._white = 0x00
|
|
||||||
if 'invert' in config['ui'] and config['ui']['invert'] == True:
|
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
|
self.invert = 1
|
||||||
BLACK = 0x00
|
tmp = self.FOREGROUND
|
||||||
WHITE = 0xFF
|
self.FOREGROUND = self.FOREGROUND
|
||||||
self._black = 0x00
|
self.FOREGROUND = tmp
|
||||||
self._white = 0xFF
|
|
||||||
|
|
||||||
# setup faces from the configuration in case the user customized them
|
# setup faces from the configuration in case the user customized them
|
||||||
faces.load_from_config(config['ui']['faces'])
|
faces.load_from_config(config['ui']['faces'])
|
||||||
@ -51,40 +130,40 @@ class View(object):
|
|||||||
self._width = self._layout['width']
|
self._width = self._layout['width']
|
||||||
self._height = self._layout['height']
|
self._height = self._layout['height']
|
||||||
self._state = State(state={
|
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,
|
label_font=fonts.Bold,
|
||||||
text_font=fonts.Medium),
|
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,
|
label_font=fonts.Bold,
|
||||||
text_font=fonts.Medium),
|
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,
|
label_font=fonts.Bold,
|
||||||
text_font=fonts.Medium),
|
text_font=fonts.Medium),
|
||||||
|
|
||||||
'line1': Line(self._layout['line1'], color=BLACK),
|
'line1': Line(self._layout['line1'], color=self.FOREGROUND),
|
||||||
'line2': Line(self._layout['line2'], color=BLACK),
|
'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_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=BLACK),
|
'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(),
|
'status': Text(value=self._voice.default(),
|
||||||
position=self._layout['status']['pos'],
|
position=self._layout['status']['pos'],
|
||||||
color=BLACK,
|
color=self.FOREGROUND,
|
||||||
font=self._layout['status']['font'],
|
font=self._layout['status']['font'],
|
||||||
wrap=True,
|
wrap=True,
|
||||||
# the current maximum number of characters per line, assuming each character is 6 pixels wide
|
# the current maximum number of characters per line, assuming each character is 6 pixels wide
|
||||||
max_length=self._layout['status']['max']),
|
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,
|
position=self._layout['shakes'], label_font=fonts.Bold,
|
||||||
text_font=fonts.Medium),
|
text_font=fonts.Medium),
|
||||||
'mode': Text(value='AUTO', position=self._layout['mode'],
|
'mode': Text(value='AUTO', position=self._layout['mode'],
|
||||||
font=fonts.Bold, color=BLACK),
|
font=fonts.Bold, color=self.FOREGROUND),
|
||||||
})
|
})
|
||||||
|
|
||||||
if state:
|
if state:
|
||||||
@ -110,7 +189,7 @@ class View(object):
|
|||||||
self._state.has_element(key)
|
self._state.has_element(key)
|
||||||
|
|
||||||
def add_element(self, key, elem):
|
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:
|
if elem.color == 0xff:
|
||||||
elem.color = 0x00
|
elem.color = 0x00
|
||||||
elif elem.color == 0x00:
|
elif elem.color == 0x00:
|
||||||
@ -389,8 +468,8 @@ class View(object):
|
|||||||
state = self._state
|
state = self._state
|
||||||
changes = state.changes(ignore=self._ignore_changes)
|
changes = state.changes(ignore=self._ignore_changes)
|
||||||
if force or len(changes):
|
if force or len(changes):
|
||||||
self._canvas = Image.new('1', (self._width, self._height), self._white)
|
self._canvas = Image.new(self.mode, (self._width, self._height), self.BACKGROUND)
|
||||||
drawer = ImageDraw.Draw(self._canvas)
|
drawer = ImageDraw.Draw(self._canvas, self.mode)
|
||||||
|
|
||||||
plugins.on('ui_update', self)
|
plugins.on('ui_update', self)
|
||||||
|
|
||||||
|
@ -309,9 +309,21 @@ def load_config(args):
|
|||||||
elif config['ui']['display']['type'] in ('pirateaudio'):
|
elif config['ui']['display']['type'] in ('pirateaudio'):
|
||||||
config['ui']['display']['type'] = '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'):
|
elif config['ui']['display']['type'] in ('pitft'):
|
||||||
config['ui']['display']['type'] = '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'):
|
elif config['ui']['display']['type'] in ('tftbonnet'):
|
||||||
config['ui']['display']['type'] = 'tftbonnet'
|
config['ui']['display']['type'] = 'tftbonnet'
|
||||||
|
|
||||||
|
Reference in New Issue
Block a user