From 54a8fd81a52aab83980a075ad42eb3efa4a0b901 Mon Sep 17 00:00:00 2001 From: Administrator Date: Mon, 14 Oct 2019 16:38:57 +0200 Subject: [PATCH 1/3] Initial commit Waveshare OledHat Add support for Waveshare Oled Hat Change view.py to have more variable to support more types of screen in the future --- pwnagotchi/ui/display.py | 16 +++ pwnagotchi/ui/view.py | 89 +++++++++++-- pwnagotchi/ui/waveshare/oledhat/SH1106.py | 136 ++++++++++++++++++++ pwnagotchi/ui/waveshare/oledhat/__init__.py | 0 pwnagotchi/ui/waveshare/oledhat/config.py | 111 ++++++++++++++++ pwnagotchi/ui/waveshare/oledhat/epd.py | 31 +++++ 6 files changed, 370 insertions(+), 13 deletions(-) create mode 100644 pwnagotchi/ui/waveshare/oledhat/SH1106.py create mode 100644 pwnagotchi/ui/waveshare/oledhat/__init__.py create mode 100644 pwnagotchi/ui/waveshare/oledhat/config.py create mode 100644 pwnagotchi/ui/waveshare/oledhat/epd.py diff --git a/pwnagotchi/ui/display.py b/pwnagotchi/ui/display.py index 065dbd8..22f7f26 100644 --- a/pwnagotchi/ui/display.py +++ b/pwnagotchi/ui/display.py @@ -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) diff --git a/pwnagotchi/ui/view.py b/pwnagotchi/ui/view.py index 03ea0f3..1fd03b8 100644 --- a/pwnagotchi/ui/view.py +++ b/pwnagotchi/ui/view.py @@ -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), }) diff --git a/pwnagotchi/ui/waveshare/oledhat/SH1106.py b/pwnagotchi/ui/waveshare/oledhat/SH1106.py new file mode 100644 index 0000000..68d4f18 --- /dev/null +++ b/pwnagotchi/ui/waveshare/oledhat/SH1106.py @@ -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 Date: Mon, 14 Oct 2019 16:41:09 +0200 Subject: [PATCH 2/3] Add oledhat in the default.yml options for ui --- pwnagotchi/defaults.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pwnagotchi/defaults.yml b/pwnagotchi/defaults.yml index c1d03a5..5034d49 100644 --- a/pwnagotchi/defaults.yml +++ b/pwnagotchi/defaults.yml @@ -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' From 748dbea13e387f79fefb321f8cbba76d467f44eb Mon Sep 17 00:00:00 2001 From: Administrator Date: Mon, 14 Oct 2019 18:19:53 +0200 Subject: [PATCH 3/3] Fix bug. Line misplaced in the code to init the display. --- pwnagotchi/ui/waveshare/oledhat/epd.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/pwnagotchi/ui/waveshare/oledhat/epd.py b/pwnagotchi/ui/waveshare/oledhat/epd.py index d7f5d4c..e6de76a 100644 --- a/pwnagotchi/ui/waveshare/oledhat/epd.py +++ b/pwnagotchi/ui/waveshare/oledhat/epd.py @@ -10,6 +10,8 @@ from PIL import Image,ImageDraw,ImageFont EPD_WIDTH = 64 EPD_HEIGHT = 128 +disp = SH1106.SH1106() + class EPD(object): def __init__(self): @@ -19,7 +21,6 @@ class EPD(object): self.cs_pin = config.CS_PIN self.width = EPD_WIDTH self.height = EPD_HEIGHT - disp = SH1106.SH1106() def init(self): disp.Init()