Merge pull request #292 from systemik/master

Add support for waveshare oledhat display.
This commit is contained in:
evilsocket 2019-10-14 18:46:28 +02:00 committed by GitHub
commit d814de75ab
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
7 changed files with 372 additions and 14 deletions

View File

@ -168,7 +168,7 @@ ui:
display:
enabled: true
rotation: 180
# Possible options inkyphat/inky, papirus/papi, waveshare_1/ws_1 or waveshare_2/ws_2
# Possible options inkyphat/inky, papirus/papi, waveshare_1/ws_1 or waveshare_2/ws_2, oledhat
type: 'waveshare_2'
# Possible options red/yellow/black (black used for monocromatic displays)
color: 'black'

View File

@ -130,6 +130,9 @@ class Display(View):
def is_waveshare_v2(self):
return self._display_type in ('waveshare_2', 'ws_2', 'waveshare2', 'ws2')
def is_oledhat(self):
return self._display_type in ('oledhat')
def is_waveshare_any(self):
return self.is_waveshare_v1() or self.is_waveshare_v2()
@ -176,6 +179,14 @@ class Display(View):
self._display.init(self._display.PART_UPDATE)
self._render_cb = self._waveshare_render
elif self.is_oledhat():
logging.info("initializing oledhat display")
from pwnagotchi.ui.waveshare.oledhat.epd import EPD
self._display = EPD()
self._display.init()
self._display.Clear()
self._render_cb = self._oledhat_render
else:
logging.critical("unknown display type %s" % self._display_type)
@ -192,6 +203,8 @@ class Display(View):
self._display.clear()
elif self.is_waveshare_any():
self._display.Clear(WHITE)
elif self.is_oledhat():
self._display.clear()
else:
logging.critical("unknown display type %s" % self._display_type)
@ -237,6 +250,9 @@ class Display(View):
elif self.is_waveshare_v2():
self._display.displayPartial(buf)
def _oledhat_render(self):
self._display.display(self._canvas)
def _waveshare_bc_render(self):
buf_black = self._display.getbuffer(self._canvas)
# emptyImage = Image.new('1', (self._display.height, self._display.width), 255)

View File

@ -24,6 +24,15 @@ def setup_display_specifics(config):
face_pos = (0, 0)
name_pos = (0, 0)
status_pos = (0, 0)
channel_pos = (0, 0)
aps_pos = (0, 0)
uptime_pos = (0, 0)
line1_pos = [(0, 0),(0, 0)]
line2_pos = (0, 0)
friend_face = (0, 0)
friend_name = (0, 0)
shakes_pos = (0, 0)
mode_pos = (0, 0)
status_font = fonts.Medium
status_max_length = None
@ -34,7 +43,16 @@ def setup_display_specifics(config):
height = 104
face_pos = (0, 37)
name_pos = (5, 18)
channel_pos = (0, 0)
aps_pos = (25, 0)
status_pos = (102, 18)
uptime_pos = (width - 65, 0)
line1_pos = [0, int(height * .12), width, int(height * .12)]
line2_pos = [0, height - int(height * .12), width, height - int(height * .12)]
friend_face_pos = (0, (height * 0.88) - 15)
friend_name_pos = (40, (height * 0.88) - 13)
shakes_pos = (0, height - int(height * .12) + 1)
mode_pos = (width - 25, height - int(height * .12) + 1)
status_font = fonts.Small
status_max_length = 20
@ -45,10 +63,39 @@ def setup_display_specifics(config):
height = 96
face_pos = (0, int(height / 4))
name_pos = (5, int(height * .15))
channel_pos = (0, 0)
aps_pos = (25, 0)
status_pos = (int(width / 2) - 15, int(height * .15))
uptime_pos = (width - 65, 0)
line1_pos = [0, int(height * .12), width, int(height * .12)]
line2_pos = [0, height - int(height * .12), width, height - int(height * .12)]
friend_face_pos = (0, (height * 0.88) - 15)
friend_name_pos = (40, (height * 0.88) - 13)
shakes_pos = (0, height - int(height * .12) + 1)
mode_pos = mode_pos = (width - 25, height - int(height * .12) + 1)
status_font = fonts.Medium
status_max_length = (width - status_pos[0]) // 6
if config['ui']['display']['type'] in ('oledhat'):
fonts.setup(8, 8, 8, 8)
width = 128
height = 64
face_pos = (0, 32)
name_pos = (0, 10)
channel_pos = (0, 0)
aps_pos = (25, 0)
status_pos = (30, 18)
uptime_pos = (width - 58, 0)
line1_pos = [0, 9, width, 9]
line2_pos = [0, 53, width, 53]
friend_face_pos = (0, (height * 0.88) - 15)
friend_name_pos = (40, (height * 0.88) - 13)
shakes_pos = (0, 53)
mode_pos = (width - 25, 10 )
status_font = fonts.Small
status_max_length = 20
elif config['ui']['display']['type'] in ('ws_1', 'ws1', 'waveshare_1', 'waveshare1',
'ws_2', 'ws2', 'waveshare_2', 'waveshare2'):
if config['ui']['display']['color'] == 'black':
@ -58,7 +105,16 @@ def setup_display_specifics(config):
height = 122
face_pos = (0, 40)
name_pos = (5, 20)
channel_pos = (0, 0)
aps_pos = (25, 0)
status_pos = (125, 20)
uptime_pos = (width - 65, 0)
line1_pos = [0, int(height * .12), width, int(height * .12)]
line2_pos = [0, height - int(height * .12), width, height - int(height * .12)]
friend_face_pos = (0, (height * 0.88) - 15)
friend_name_pos = (40, (height * 0.88) - 13)
shakes_pos = (0, height - int(height * .12) + 1)
mode_pos = mode_pos = (width - 25, height - int(height * .12) + 1)
status_font = fonts.Medium
else:
fonts.setup(10, 8, 10, 25)
@ -67,11 +123,20 @@ def setup_display_specifics(config):
height = 104
face_pos = (0, int(height / 4))
name_pos = (5, int(height * .15))
channel_pos = (0, 0)
aps_pos = (25, 0)
status_pos = (int(width / 2) - 15, int(height * .15))
uptime_pos = (width - 65, 0)
line1_pos = [0, int(height * .12), width, int(height * .12)]
line2_pos = [0, height - int(height * .12), width, height - int(height * .12)]
friend_face_pos = (0, (height * 0.88) - 15)
friend_name_pos = (40, (height * 0.88) - 13)
shakes_pos = (0, height - int(height * .12) + 1)
mode_pos = mode_pos = (width - 25, height - int(height * .12) + 1)
status_font = fonts.Medium
status_max_length = (width - status_pos[0]) // 6
return width, height, face_pos, name_pos, status_pos, status_font, status_max_length
return width, height, face_pos, name_pos, channel_pos, aps_pos, status_pos, uptime_pos, line1_pos, line2_pos, friend_face_pos, friend_name_pos, shakes_pos, mode_pos, status_font, status_max_length
class View(object):
@ -86,30 +151,28 @@ class View(object):
self._voice = Voice(lang=config['main']['lang'])
self._width, self._height, \
face_pos, name_pos, status_pos, status_font, status_max_length = setup_display_specifics(config)
face_pos, name_pos, channel_pos, aps_pos, status_pos, uptime_pos, line1_pos, line2_pos, friend_face_pos, friend_name_pos, shakes_pos, mode_pos, status_font, status_max_length = setup_display_specifics(config)
self._state = State(state={
'channel': LabeledValue(color=BLACK, label='CH', value='00', position=(0, 0), label_font=fonts.Bold,
'channel': LabeledValue(color=BLACK, label='CH', value='00', position=channel_pos, label_font=fonts.Bold,
text_font=fonts.Medium),
'aps': LabeledValue(color=BLACK, label='APS', value='0 (00)', position=(30, 0), label_font=fonts.Bold,
'aps': LabeledValue(color=BLACK, label='APS', value='0 (00)', position=aps_pos, label_font=fonts.Bold,
text_font=fonts.Medium),
# 'epoch': LabeledValue(color=BLACK, label='E', value='0000', position=(145, 0), label_font=fonts.Bold,
# text_font=fonts.Medium),
'uptime': LabeledValue(color=BLACK, label='UP', value='00:00:00', position=(self._width - 65, 0),
'uptime': LabeledValue(color=BLACK, label='UP', value='00:00:00', position=uptime_pos,
label_font=fonts.Bold,
text_font=fonts.Medium),
'line1': Line([0, int(self._height * .12), self._width, int(self._height * .12)], color=BLACK),
'line2': Line(
[0, self._height - int(self._height * .12), self._width, self._height - int(self._height * .12)],
color=BLACK),
'line1': Line(line1_pos,color=BLACK),
'line2': Line(line2_pos,color=BLACK),
'face': Text(value=faces.SLEEP, position=face_pos, color=BLACK, font=fonts.Huge),
'friend_face': Text(value=None, position=(0, (self._height * 0.88) - 15), font=fonts.Bold, color=BLACK),
'friend_name': Text(value=None, position=(40, (self._height * 0.88) - 13), font=fonts.BoldSmall,
'friend_face': Text(value=None, position=friend_face_pos, font=fonts.Bold, color=BLACK),
'friend_name': Text(value=None, position=friend_name_pos, font=fonts.BoldSmall,
color=BLACK),
'name': Text(value='%s>' % 'pwnagotchi', position=name_pos, color=BLACK, font=fonts.Bold),
@ -123,9 +186,9 @@ class View(object):
max_length=status_max_length),
'shakes': LabeledValue(label='PWND ', value='0 (00)', color=BLACK,
position=(0, self._height - int(self._height * .12) + 1), label_font=fonts.Bold,
position=shakes_pos, label_font=fonts.Bold,
text_font=fonts.Medium),
'mode': Text(value='AUTO', position=(self._width - 25, self._height - int(self._height * .12) + 1),
'mode': Text(value='AUTO', position=mode_pos,
font=fonts.Bold, color=BLACK),
})

View File

@ -0,0 +1,136 @@
from . import config
import RPi.GPIO as GPIO
import time
import numpy as np
Device_SPI = config.Device_SPI
Device_I2C = config.Device_I2C
LCD_WIDTH = 128 #LCD width
LCD_HEIGHT = 64 #LCD height
class SH1106(object):
def __init__(self):
self.width = LCD_WIDTH
self.height = LCD_HEIGHT
#Initialize DC RST pin
self._dc = config.DC_PIN
self._rst = config.RST_PIN
self._bl = config.BL_PIN
self.Device = config.Device
""" Write register address and data """
def command(self, cmd):
if(self.Device == Device_SPI):
GPIO.output(self._dc, GPIO.LOW)
config.spi_writebyte([cmd])
else:
config.i2c_writebyte(0x00, cmd)
# def data(self, val):
# GPIO.output(self._dc, GPIO.HIGH)
# config.spi_writebyte([val])
def Init(self):
if (config.module_init() != 0):
return -1
"""Initialize dispaly"""
self.reset()
self.command(0xAE);#--turn off oled panel
self.command(0x02);#---set low column address
self.command(0x10);#---set high column address
self.command(0x40);#--set start line address Set Mapping RAM Display Start Line (0x00~0x3F)
self.command(0x81);#--set contrast control register
self.command(0xA0);#--Set SEG/Column Mapping
self.command(0xC0);#Set COM/Row Scan Direction
self.command(0xA6);#--set normal display
self.command(0xA8);#--set multiplex ratio(1 to 64)
self.command(0x3F);#--1/64 duty
self.command(0xD3);#-set display offset Shift Mapping RAM Counter (0x00~0x3F)
self.command(0x00);#-not offset
self.command(0xd5);#--set display clock divide ratio/oscillator frequency
self.command(0x80);#--set divide ratio, Set Clock as 100 Frames/Sec
self.command(0xD9);#--set pre-charge period
self.command(0xF1);#Set Pre-Charge as 15 Clocks & Discharge as 1 Clock
self.command(0xDA);#--set com pins hardware configuration
self.command(0x12);
self.command(0xDB);#--set vcomh
self.command(0x40);#Set VCOM Deselect Level
self.command(0x20);#-Set Page Addressing Mode (0x00/0x01/0x02)
self.command(0x02);#
self.command(0xA4);# Disable Entire Display On (0xa4/0xa5)
self.command(0xA6);# Disable Inverse Display On (0xa6/a7)
time.sleep(0.1)
self.command(0xAF);#--turn on oled panel
def reset(self):
"""Reset the display"""
GPIO.output(self._rst,GPIO.HIGH)
time.sleep(0.1)
GPIO.output(self._rst,GPIO.LOW)
time.sleep(0.1)
GPIO.output(self._rst,GPIO.HIGH)
time.sleep(0.1)
def getbuffer(self, image):
# print "bufsiz = ",(self.width/8) * self.height
buf = [0xFF] * ((self.width//8) * self.height)
image_monocolor = image.convert('1')
imwidth, imheight = image_monocolor.size
pixels = image_monocolor.load()
# print "imwidth = %d, imheight = %d",imwidth,imheight
if(imwidth == self.width and imheight == self.height):
#print ("Vertical")
for y in range(imheight):
for x in range(imwidth):
# Set the bits for the column of pixels at the current position.
if pixels[x, y] == 0:
buf[x + (y // 8) * self.width] &= ~(1 << (y % 8))
# print x,y,x + (y * self.width)/8,buf[(x + y * self.width) / 8]
elif(imwidth == self.height and imheight == self.width):
#print ("Vertical")
for y in range(imheight):
for x in range(imwidth):
newx = y
newy = self.height - x - 1
if pixels[x, y] == 0:
buf[(newx + (newy // 8 )*self.width) ] &= ~(1 << (y % 8))
return buf
# def ShowImage(self,Image):
# self.SetWindows()
# GPIO.output(self._dc, GPIO.HIGH);
# for i in range(0,self.width * self.height/8):
# config.spi_writebyte([~Image[i]])
def ShowImage(self, pBuf):
for page in range(0,8):
# set page address #
self.command(0xB0 + page);
# set low column address #
self.command(0x02);
# set high column address #
self.command(0x10);
# write data #
time.sleep(0.01)
if(self.Device == Device_SPI):
GPIO.output(self._dc, GPIO.HIGH);
for i in range(0,self.width):#for(int i=0;i<self.width; i++)
if(self.Device == Device_SPI):
config.spi_writebyte([~pBuf[i+self.width*page]]);
else :
config.i2c_writebyte(0x40, ~pBuf[i+self.width*page])
def clear(self):
"""Clear contents of image buffer"""
_buffer = [0xff]*(self.width * self.height//8)
self.ShowImage(_buffer)
#print "%d",_buffer[i:i+4096]

View File

@ -0,0 +1,111 @@
# /*****************************************************************************
# * | File : config.py
# * | Author : Waveshare team
# * | Function : Hardware underlying interface,for Jetson nano
# * | Info :
# *----------------
# * | This version: V1.0
# * | Date : 2019-06-06
# * | Info :
# ******************************************************************************/
# Permission is hereby granted, free of charge, to any person obtaining a copy
# of this software and associated documnetation 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
# furished 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 OR 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 RPi.GPIO as GPIO
import time
from smbus import SMBus
import spidev
import ctypes
# import spidev
# Pin definition
RST_PIN = 25
DC_PIN = 24
CS_PIN = 8
BL_PIN = 18
BUSY_PIN = 18
Device_SPI = 1
Device_I2C = 0
if(Device_SPI == 1):
Device = Device_SPI
spi = spidev.SpiDev(0, 0)
else :
Device = Device_I2C
address = 0x3C
bus = SMBus(1)
def digital_write(pin, value):
GPIO.output(pin, value)
def digital_read(pin):
return GPIO.input(BUSY_PIN)
def delay_ms(delaytime):
time.sleep(delaytime / 1000.0)
def spi_writebyte(data):
# SPI.writebytes(data)
spi.writebytes([data[0]])
def i2c_writebyte(reg, value):
bus.write_byte_data(address, reg, value)
# time.sleep(0.01)
def module_init():
# print("module_init")
GPIO.setmode(GPIO.BCM)
GPIO.setwarnings(False)
GPIO.setup(RST_PIN, GPIO.OUT)
GPIO.setup(DC_PIN, GPIO.OUT)
GPIO.setup(CS_PIN, GPIO.OUT)
GPIO.setup(BL_PIN, GPIO.OUT)
# SPI.max_speed_hz = 2000000
# SPI.mode = 0b00
# i2c_writebyte(0xff,0xff)
if(Device == Device_SPI):
# spi.SYSFS_software_spi_begin()
# spi.SYSFS_software_spi_setDataMode(0);
# spi.SYSFS_software_spi_setClockDivider(1);
spi.max_speed_hz = 2000000
spi.mode = 0b00
GPIO.output(CS_PIN, 0)
GPIO.output(BL_PIN, 1)
GPIO.output(DC_PIN, 0)
return 0
def module_exit():
if(Device == Device_SPI):
spi.SYSFS_software_spi_end()
else :
bus.close()
GPIO.output(RST_PIN, 0)
GPIO.output(DC_PIN, 0)
### END OF FILE ###

View File

@ -0,0 +1,32 @@
from . import SH1106
import time
from . import config
import traceback
from PIL import Image,ImageDraw,ImageFont
# Display resolution
EPD_WIDTH = 64
EPD_HEIGHT = 128
disp = SH1106.SH1106()
class EPD(object):
def __init__(self):
self.reset_pin = config.RST_PIN
self.dc_pin = config.DC_PIN
self.busy_pin = config.BUSY_PIN
self.cs_pin = config.CS_PIN
self.width = EPD_WIDTH
self.height = EPD_HEIGHT
def init(self):
disp.Init()
def Clear(self):
disp.clear()
def display(self, image):
disp.ShowImage(disp.getbuffer(image))