diff --git a/builder/pwnagotchi.yml b/builder/pwnagotchi.yml index 4387bb01..0b9dad2e 100644 --- a/builder/pwnagotchi.yml +++ b/builder/pwnagotchi.yml @@ -175,10 +175,51 @@ name: "{{ packages.apt.install }}" state: present + - name: clone hannadiamond repository + git: + repo: https://github.com/hannadiamond/pwnagotchi-plugins.git + dest: /usr/local/src/hannadiamond + register: hannadiamondgit + - name: Creates custom plugin directory file: path: /usr/local/share/pwnagotchi/custom-plugins/ state: directory + when: hannadiamondgit.changed + + - name: Copy ups_hat_c.py + copy: + src: /usr/local/src/hannadiamond/plugins/ups_hat_c.py + dest: /usr/local/share/pwnagotchi/custom-plugins/ups_hat_c.py + owner: root + group: root + mode: '644' + + - name: Delete hannadiamond content & directory + file: + state: absent + path: /usr/local/src/hannadiamond + when: hannadiamondgit.changed + + - name: clone pisugar 2 git + git: + repo: https://github.com/PiSugar/pisugar2py.git + dest: /usr/local/lib/python3.7/dist-packages + + - name: clone pisugar2 plugin + git: + repo: https://github.com/PiSugar/pwnagotchi-pisugar2-plugin.git + dest: /usr/local/share/pwnagotchi/custom-plugins/ + + - name: clone pisugar3 plugin + git: + repo: https://github.com/nullm0ose/pwnagotchi-plugin-pisugar3.git + dest: /usr/local/share/pwnagotchi/custom-plugins/ + + - name: clone pwnagotchi plugins repository + git: + repo: https://github.com/evilsocket/pwnagotchi-plugins-contrib.git + dest: /usr/local/share/pwnagotchi/custom-plugins - name: collect python pip package list command: "pip3 list" @@ -205,11 +246,6 @@ path: /usr/local/share/pwnagotchi/ state: directory - - name: clone pwnagotchi plugins repository - git: - repo: https://github.com/evilsocket/pwnagotchi-plugins-contrib.git - dest: /usr/local/share/pwnagotchi/availaible-plugins - - name: fetch pwnagotchi version set_fact: pwnagotchi_version: "{{ lookup('file', '/usr/local/src/pwnagotchi/pwnagotchi/_version.py') | regex_replace('.*__version__.*=.*''([0-9]+\\.[0-9]+\\.[0-9]+[A-Za-z0-9]*)''.*', '\\1') }}" diff --git a/pwnagotchi/_version.py b/pwnagotchi/_version.py index 55fa725b..2b9ccf17 100644 --- a/pwnagotchi/_version.py +++ b/pwnagotchi/_version.py @@ -1 +1 @@ -__version__ = '2.1.1' +__version__ = '2.2' diff --git a/pwnagotchi/ai/gym.py b/pwnagotchi/ai/gym.py index 6c0cac10..7134628b 100644 --- a/pwnagotchi/ai/gym.py +++ b/pwnagotchi/ai/gym.py @@ -1,6 +1,6 @@ import logging -import gymnasium -from gymnasium import spaces +import gym +from gym import spaces import numpy as np import pwnagotchi.ai.featurizer as featurizer @@ -8,7 +8,7 @@ import pwnagotchi.ai.reward as reward from pwnagotchi.ai.parameter import Parameter -class Environment(gymnasium.Env): +class Environment(gym.Env): metadata = {'render.modes': ['human']} params = [ Parameter('min_rssi', min_value=-200, max_value=-50), diff --git a/pwnagotchi/ai/parameter.py b/pwnagotchi/ai/parameter.py index 812e9a4d..79f464c5 100644 --- a/pwnagotchi/ai/parameter.py +++ b/pwnagotchi/ai/parameter.py @@ -1,4 +1,4 @@ -from gymnasium import spaces +from gym import spaces class Parameter(object): diff --git a/pwnagotchi/defaults.toml b/pwnagotchi/defaults.toml index 1590c776..e0bb458d 100644 --- a/pwnagotchi/defaults.toml +++ b/pwnagotchi/defaults.toml @@ -4,6 +4,8 @@ main.confd = "/etc/pwnagotchi/conf.d/" main.custom_plugin_repos = [ "https://github.com/evilsocket/pwnagotchi-plugins-contrib/archive/master.zip", "https://github.com/PwnPeter/pwnagotchi-plugins/archive/master.zip", + "https://github.com/tisboyo/pwnagotchi-pisugar2-plugin/archive/master.zip", + "https://github.com/nullm0ose/pwnagotchi-plugin-pisugar3/archive/master.zip" ] main.custom_plugins = "/usr/local/share/pwnagotchi/custom-plugins/" @@ -22,6 +24,16 @@ main.whitelist = [ ] main.filter = "" +main.plugins.ups_hat_c.enabled = false +main.plugins.ups_hat_c.label_on = true # show BAT label or just percentage +main.plugins.ups_hat_c.shutdown = 5 # battery percent at which the device will turn off +main.plugins.ups_hat_c.bat_x_coord = 140 +main.plugins.ups_hat_c.bat_y_coord = 0 + +main.plugins.pisugar2.enabled = false +main.plugins.pisugar2.shutdown = 5 +main.plugins.pisugar2.sync_rtc_on_boot = false + main.plugins.grid.enabled = true main.plugins.grid.report = true main.plugins.grid.exclude = [ diff --git a/pwnagotchi/ui/components.py b/pwnagotchi/ui/components.py index 1bc7ae9d..5c351195 100644 --- a/pwnagotchi/ui/components.py +++ b/pwnagotchi/ui/components.py @@ -49,12 +49,13 @@ class Text(Widget): self.wrapper = TextWrapper(width=self.max_length, replace_whitespace=False) if wrap else None def draw(self, canvas, drawer): - if self.value is not None: - if self.wrap: - text = '\n'.join(self.wrapper.wrap(self.value)) - else: - text = self.value - drawer.text(self.xy, text, font=self.font, fill=self.color) + if self.label is None: + drawer.text(self.xy, self.value, font=self.label_font, fill=self.color) + else: + pos = self.xy + drawer.text(pos, self.label, font=self.label_font, fill=self.color) + drawer.text((pos[0] + self.label_spacing + self.label_font.getsize(self.label)[0], pos[1]), self.value, + font=self.text_font, fill=self.color) class LabeledValue(Widget): diff --git a/pwnagotchi/ui/display.py b/pwnagotchi/ui/display.py index c23841f5..e1618429 100644 --- a/pwnagotchi/ui/display.py +++ b/pwnagotchi/ui/display.py @@ -84,6 +84,9 @@ class Display(View): def is_waveshare_any(self): return self.is_waveshare_v1() or self.is_waveshare_v2() + def is_waveshare37inch(self): + return self.implementation.name == 'waveshare37inch' + def init_display(self): if self._enabled: self._implementation.initialize() diff --git a/pwnagotchi/ui/hw/__init__.py b/pwnagotchi/ui/hw/__init__.py index 17516da7..5ef2dab3 100644 --- a/pwnagotchi/ui/hw/__init__.py +++ b/pwnagotchi/ui/hw/__init__.py @@ -15,6 +15,7 @@ from pwnagotchi.ui.hw.waveshare213d import Waveshare213d from pwnagotchi.ui.hw.waveshare213bc import Waveshare213bc from pwnagotchi.ui.hw.waveshare35lcd import Waveshare35lcd from pwnagotchi.ui.hw.spotpear24inch import Spotpear24inch +from pwnagotchi.ui.hw.waveshare37inch import Waveshare37inch def display_for(config): @@ -69,3 +70,6 @@ def display_for(config): elif config['ui']['display']['type'] == 'spotpear24inch': return Spotpear24inch(config) + + elif config['ui']['display']['type'] == 'waveshare37inch': + return Waveshare37inch(config) diff --git a/pwnagotchi/ui/hw/libs/waveshare/v37inch/epd3in7.py b/pwnagotchi/ui/hw/libs/waveshare/v37inch/epd3in7.py new file mode 100644 index 00000000..9d243ba3 --- /dev/null +++ b/pwnagotchi/ui/hw/libs/waveshare/v37inch/epd3in7.py @@ -0,0 +1,446 @@ +# ***************************************************************************** +# * | File : epd3in7.py +# * | Author : Waveshare team +# * | Function : Electronic paper driver +# * | Info : +# *---------------- +# * | This version: V1.0 +# * | Date : 2020-07-16 +# # | Info : python demo +# ----------------------------------------------------------------------------- +# 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 logging +from . import epdconfig + +# Display resolution +EPD_WIDTH = 280 +EPD_HEIGHT = 480 + +GRAY1 = 0xff # white +GRAY2 = 0xC0 # Close to white +GRAY3 = 0x80 # Close to black +GRAY4 = 0x00 # black + +logger = logging.getLogger(__name__) + + +class EPD: + def __init__(self): + self.reset_pin = epdconfig.RST_PIN + self.dc_pin = epdconfig.DC_PIN + self.busy_pin = epdconfig.BUSY_PIN + self.cs_pin = epdconfig.CS_PIN + self.width = EPD_WIDTH + self.height = EPD_HEIGHT + self.GRAY1 = GRAY1 # white + self.GRAY2 = GRAY2 + self.GRAY3 = GRAY3 # gray + self.GRAY4 = GRAY4 # Blackest + + lut_4Gray_GC = [ + 0x2A, 0x06, 0x15, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x28, 0x06, 0x14, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x20, 0x06, 0x10, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x14, 0x06, 0x28, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x02, 0x02, 0x0A, 0x00, 0x00, 0x00, 0x08, 0x08, 0x02, + 0x00, 0x02, 0x02, 0x0A, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x22, 0x22, 0x22, 0x22, 0x22 + ] + + lut_1Gray_GC = [ + 0x2A, 0x05, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x05, 0x2A, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x2A, 0x15, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x05, 0x0A, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x02, 0x03, 0x0A, 0x00, 0x02, 0x06, 0x0A, 0x05, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x22, 0x22, 0x22, 0x22, 0x22 + ] + + lut_1Gray_DU = [ + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x01, 0x2A, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x0A, 0x55, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x05, 0x05, 0x00, 0x05, 0x03, 0x05, 0x05, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x22, 0x22, 0x22, 0x22, 0x22 + ] + + lut_1Gray_A2 = [ + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x0A, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x05, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x03, 0x05, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x22, 0x22, 0x22, 0x22, 0x22 + ] + + # Hardware reset + def reset(self): + epdconfig.digital_write(self.reset_pin, 1) + epdconfig.delay_ms(200) + epdconfig.digital_write(self.reset_pin, 0) + epdconfig.delay_ms(5) + epdconfig.digital_write(self.reset_pin, 1) + epdconfig.delay_ms(200) + + def send_command(self, command): + epdconfig.digital_write(self.dc_pin, 0) + epdconfig.digital_write(self.cs_pin, 0) + epdconfig.spi_writebyte([command]) + epdconfig.digital_write(self.cs_pin, 1) + + def send_data(self, data): + epdconfig.digital_write(self.dc_pin, 1) + epdconfig.digital_write(self.cs_pin, 0) + epdconfig.spi_writebyte([data]) + epdconfig.digital_write(self.cs_pin, 1) + + def ReadBusy(self): + logger.debug("e-Paper busy") + while (epdconfig.digital_read(self.busy_pin) == 1): # 0: idle, 1: busy + epdconfig.delay_ms(10) + logger.debug("e-Paper busy release") + + def init(self, mode): + if (epdconfig.module_init() != 0): + return -1 + # EPD hardware init start + self.reset() + + self.send_command(0x12) + epdconfig.delay_ms(300) + + self.send_command(0x46) + self.send_data(0xF7) + self.ReadBusy() + self.send_command(0x47) + self.send_data(0xF7) + self.ReadBusy() + + self.send_command(0x01) # setting gaet number + self.send_data(0xDF) + self.send_data(0x01) + self.send_data(0x00) + + self.send_command(0x03) # set gate voltage + self.send_data(0x00) + + self.send_command(0x04) # set source voltage + self.send_data(0x41) + self.send_data(0xA8) + self.send_data(0x32) + + self.send_command(0x11) # set data entry sequence + self.send_data(0x03) + + self.send_command(0x3C) # set border + self.send_data(0x03) + + self.send_command(0x0C) # set booster strength + self.send_data(0xAE) + self.send_data(0xC7) + self.send_data(0xC3) + self.send_data(0xC0) + self.send_data(0xC0) + + self.send_command(0x18) # set internal sensor on + self.send_data(0x80) + + self.send_command(0x2C) # set vcom value + self.send_data(0x44) + + if (mode == 0): # 4Gray + self.send_command(0x37) # set display option, these setting turn on previous function + self.send_data(0x00) + self.send_data(0x00) + self.send_data(0x00) + self.send_data(0x00) + self.send_data(0x00) + self.send_data(0x00) + self.send_data(0x00) + self.send_data(0x00) + self.send_data(0x00) + self.send_data(0x00) + elif (mode == 1): # 1Gray + self.send_command(0x37) # set display option, these setting turn on previous function + self.send_data(0x00) # can switch 1 gray or 4 gray + self.send_data(0xFF) + self.send_data(0xFF) + self.send_data(0xFF) + self.send_data(0xFF) + self.send_data(0x4F) + self.send_data(0xFF) + self.send_data(0xFF) + self.send_data(0xFF) + self.send_data(0xFF) + else: + logger.debug("There is no such mode") + + self.send_command(0x44) # setting X direction start/end position of RAM + self.send_data(0x00) + self.send_data(0x00) + self.send_data(0x17) + self.send_data(0x01) + + self.send_command(0x45) # setting Y direction start/end position of RAM + self.send_data(0x00) + self.send_data(0x00) + self.send_data(0xDF) + self.send_data(0x01) + + self.send_command(0x22) # Display Update Control 2 + self.send_data(0xCF) + return 0 + + def load_lut(self, lut): + self.send_command(0x32) + for i in range(0, 105): + self.send_data(lut[i]) + + def getbuffer(self, image): + # logger.debug("bufsiz = ",int(self.width/8) * self.height) + buf = [0xFF] * (int(self.width / 8) * self.height) + image_monocolor = image.convert('1') + imwidth, imheight = image_monocolor.size + pixels = image_monocolor.load() + # logger.debug("imwidth = %d, imheight = %d",imwidth,imheight) + if (imwidth == self.width and imheight == self.height): + logger.debug("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[int((x + y * self.width) / 8)] &= ~(0x80 >> (x % 8)) + elif (imwidth == self.height and imheight == self.width): + logger.debug("Horizontal") + for y in range(imheight): + for x in range(imwidth): + newx = y + newy = self.height - x - 1 + if pixels[x, y] == 0: + buf[int((newx + newy * self.width) / 8)] &= ~(0x80 >> (y % 8)) + return buf + + def getbuffer_4Gray(self, image): + # logger.debug("bufsiz = ",int(self.width/8) * self.height) + buf = [0xFF] * (int(self.width / 4) * self.height) + image_monocolor = image.convert('L') + imwidth, imheight = image_monocolor.size + pixels = image_monocolor.load() + i = 0 + # logger.debug("imwidth = %d, imheight = %d",imwidth,imheight) + if (imwidth == self.width and imheight == self.height): + logger.debug("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] == 0xC0): + pixels[x, y] = 0x80 + elif (pixels[x, y] == 0x80): + pixels[x, y] = 0x40 + i = i + 1 + if (i % 4 == 0): + buf[int((x + (y * self.width)) / 4)] = ( + (pixels[x - 3, y] & 0xc0) | (pixels[x - 2, y] & 0xc0) >> 2 | ( + pixels[x - 1, y] & 0xc0) >> 4 | (pixels[x, y] & 0xc0) >> 6) + + elif (imwidth == self.height and imheight == self.width): + logger.debug("Horizontal") + for x in range(imwidth): + for y in range(imheight): + newx = y + newy = imwidth - x - 1 + if (pixels[x, y] == 0xC0): + pixels[x, y] = 0x80 + elif (pixels[x, y] == 0x80): + pixels[x, y] = 0x40 + i = i + 1 + if (i % 4 == 0): + buf[int((newx + (newy * self.width)) / 4)] = ( + (pixels[x, y - 3] & 0xc0) | (pixels[x, y - 2] & 0xc0) >> 2 | ( + pixels[x, y - 1] & 0xc0) >> 4 | (pixels[x, y] & 0xc0) >> 6) + return buf + + def display_4Gray(self, image): + if (image == None): + return + + self.send_command(0x4E) + self.send_data(0x00) + self.send_data(0x00) + self.send_command(0x4F) + self.send_data(0x00) + self.send_data(0x00) + + self.send_command(0x24) + for i in range(0, (int)(self.height * (self.width / 8))): + temp3 = 0 + for j in range(0, 2): + temp1 = image[i * 2 + j] + for k in range(0, 2): + temp2 = temp1 & 0xC0 + if (temp2 == 0xC0): + temp3 |= 0x01 # white + elif (temp2 == 0x00): + temp3 |= 0x00 # black + elif (temp2 == 0x80): + temp3 |= 0x00 # gray1 + else: # 0x40 + temp3 |= 0x01 # gray2 + temp3 <<= 1 + temp1 <<= 2 + temp2 = temp1 & 0xC0 + if (temp2 == 0xC0): # white + temp3 |= 0x01 + elif (temp2 == 0x00): # black + temp3 |= 0x00 + elif (temp2 == 0x80): + temp3 |= 0x00 # gray1 + else: # 0x40 + temp3 |= 0x01 # gray2 + if (j != 1 or k != 1): + temp3 <<= 1 + temp1 <<= 2 + self.send_data(temp3) + + self.send_command(0x4E) + self.send_data(0x00) + self.send_data(0x00) + self.send_command(0x4F) + self.send_data(0x00) + self.send_data(0x00) + + self.send_command(0x26) + for i in range(0, (int)(self.height * (self.width / 8))): + temp3 = 0 + for j in range(0, 2): + temp1 = image[i * 2 + j] + for k in range(0, 2): + temp2 = temp1 & 0xC0 + if (temp2 == 0xC0): + temp3 |= 0x01 # white + elif (temp2 == 0x00): + temp3 |= 0x00 # black + elif (temp2 == 0x80): + temp3 |= 0x01 # gray1 + else: # 0x40 + temp3 |= 0x00 # gray2 + temp3 <<= 1 + temp1 <<= 2 + temp2 = temp1 & 0xC0 + if (temp2 == 0xC0): # white + temp3 |= 0x01 + elif (temp2 == 0x00): # black + temp3 |= 0x00 + elif (temp2 == 0x80): + temp3 |= 0x01 # gray1 + else: # 0x40 + temp3 |= 0x00 # gray2 + if (j != 1 or k != 1): + temp3 <<= 1 + temp1 <<= 2 + self.send_data(temp3) + + self.load_lut(self.lut_4Gray_GC) + self.send_command(0x22) + self.send_data(0xC7) + self.send_command(0x20) + self.ReadBusy() + + def display_1Gray(self, image): + if (image == None): + return + + self.send_command(0x4E) + self.send_data(0x00) + self.send_data(0x00) + self.send_command(0x4F) + self.send_data(0x00) + self.send_data(0x00) + + self.send_command(0x24) + for j in range(0, self.height): + for i in range(0, int(self.width / 8)): + self.send_data(image[i + j * int(self.width / 8)]) + + self.load_lut(self.lut_1Gray_A2) + self.send_command(0x20) + self.ReadBusy() + + def Clear(self, color, mode): + self.send_command(0x4E) + self.send_data(0x00) + self.send_data(0x00) + self.send_command(0x4F) + self.send_data(0x00) + self.send_data(0x00) + + self.send_command(0x24) + for j in range(0, self.height): + for i in range(0, int(self.width / 8)): + self.send_data(0xff) + + if (mode == 0): # 4Gray + self.send_command(0x26) + for j in range(0, self.height): + for i in range(0, int(self.width / 8)): + self.send_data(0xff) + self.load_lut(self.lut_4Gray_GC) + self.send_command(0x22) + self.send_data(0xC7) + elif (mode == 1): # 1Gray + self.load_lut(self.lut_1Gray_DU) + else: + logger.debug("There is no such mode") + + self.send_command(0x20) + self.ReadBusy() + + def sleep(self): + self.send_command(0X50) # DEEP_SLEEP_MODE + self.send_data(0xf7) + self.send_command(0X02) # power off + self.send_command(0X07) # deep sleep + self.send_data(0xA5) + + epdconfig.delay_ms(2000) + epdconfig.module_exit() + +### END OF FILE ### \ No newline at end of file diff --git a/pwnagotchi/ui/hw/libs/waveshare/v37inch/epdconfig.py b/pwnagotchi/ui/hw/libs/waveshare/v37inch/epdconfig.py new file mode 100644 index 00000000..78cf9c8f --- /dev/null +++ b/pwnagotchi/ui/hw/libs/waveshare/v37inch/epdconfig.py @@ -0,0 +1,160 @@ +# /***************************************************************************** +# * | File : epdconfig.py +# * | Author : Waveshare team +# * | Function : Hardware underlying interface +# * | Info : +# *---------------- +# * | This version: V1.0 +# * | Date : 2019-06-21 +# * | 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 os +import logging +import sys +import time + +logger = logging.getLogger(__name__) + + +class RaspberryPi: + # Pin definition + RST_PIN = 17 + DC_PIN = 25 + CS_PIN = 8 + BUSY_PIN = 24 + + def __init__(self): + import spidev + import RPi.GPIO + + self.GPIO = RPi.GPIO + self.SPI = spidev.SpiDev() + + def digital_write(self, pin, value): + self.GPIO.output(pin, value) + + def digital_read(self, pin): + return self.GPIO.input(pin) + + def delay_ms(self, delaytime): + time.sleep(delaytime / 1000.0) + + def spi_writebyte(self, data): + self.SPI.writebytes(data) + + def spi_writebyte2(self, data): + self.SPI.writebytes2(data) + + def module_init(self): + self.GPIO.setmode(self.GPIO.BCM) + self.GPIO.setwarnings(False) + self.GPIO.setup(self.RST_PIN, self.GPIO.OUT) + self.GPIO.setup(self.DC_PIN, self.GPIO.OUT) + self.GPIO.setup(self.CS_PIN, self.GPIO.OUT) + self.GPIO.setup(self.BUSY_PIN, self.GPIO.IN) + + # SPI device, bus = 0, device = 0 + self.SPI.open(0, 0) + self.SPI.max_speed_hz = 4000000 + self.SPI.mode = 0b00 + return 0 + + def module_exit(self): + logger.debug("spi end") + self.SPI.close() + + logger.debug("close 5V, Module enters 0 power consumption ...") + self.GPIO.output(self.RST_PIN, 0) + self.GPIO.output(self.DC_PIN, 0) + + self.GPIO.cleanup([self.RST_PIN, self.DC_PIN, self.CS_PIN, self.BUSY_PIN]) + + +class JetsonNano: + # Pin definition + RST_PIN = 17 + DC_PIN = 25 + CS_PIN = 8 + BUSY_PIN = 24 + + def __init__(self): + import ctypes + find_dirs = [ + os.path.dirname(os.path.realpath(__file__)), + '/usr/local/lib', + '/usr/lib', + ] + self.SPI = None + for find_dir in find_dirs: + so_filename = os.path.join(find_dir, 'sysfs_software_spi.so') + if os.path.exists(so_filename): + self.SPI = ctypes.cdll.LoadLibrary(so_filename) + break + if self.SPI is None: + raise RuntimeError('Cannot find sysfs_software_spi.so') + + import Jetson.GPIO + self.GPIO = Jetson.GPIO + + def digital_write(self, pin, value): + self.GPIO.output(pin, value) + + def digital_read(self, pin): + return self.GPIO.input(self.BUSY_PIN) + + def delay_ms(self, delaytime): + time.sleep(delaytime / 1000.0) + + def spi_writebyte(self, data): + self.SPI.SYSFS_software_spi_transfer(data[0]) + + def module_init(self): + self.GPIO.setmode(self.GPIO.BCM) + self.GPIO.setwarnings(False) + self.GPIO.setup(self.RST_PIN, self.GPIO.OUT) + self.GPIO.setup(self.DC_PIN, self.GPIO.OUT) + self.GPIO.setup(self.CS_PIN, self.GPIO.OUT) + self.GPIO.setup(self.BUSY_PIN, self.GPIO.IN) + self.SPI.SYSFS_software_spi_begin() + return 0 + + def module_exit(self): + logger.debug("spi end") + self.SPI.SYSFS_software_spi_end() + + logger.debug("close 5V, Module enters 0 power consumption ...") + self.GPIO.output(self.RST_PIN, 0) + self.GPIO.output(self.DC_PIN, 0) + + self.GPIO.cleanup([self.RST_PIN, self.DC_PIN, self.CS_PIN, self.BUSY_PIN]) + + +if os.path.exists('/sys/bus/platform/drivers/gpiomem-bcm2835'): + implementation = RaspberryPi() +else: + implementation = JetsonNano() + +for func in [x for x in dir(implementation) if not x.startswith('_')]: + setattr(sys.modules[__name__], func, getattr(implementation, func)) + + +### END OF FILE ### \ No newline at end of file diff --git a/pwnagotchi/ui/hw/waveshare37inch.py b/pwnagotchi/ui/hw/waveshare37inch.py new file mode 100644 index 00000000..12849721 --- /dev/null +++ b/pwnagotchi/ui/hw/waveshare37inch.py @@ -0,0 +1,47 @@ +import logging + +import pwnagotchi.ui.fonts as fonts +from pwnagotchi.ui.hw.base import DisplayImpl + + +class Waveshare37inch(DisplayImpl): + def __init__(self, config): + super(Waveshare37inch, self).__init__(config, 'waveshare37inch') + self._display = None + + def layout(self): + fonts.setup(20, 19, 20, 45, 35, 19) + self._layout['width'] = 480 + self._layout['height'] = 280 + self._layout['face'] = (0, 75) + self._layout['name'] = (5, 35) + self._layout['channel'] = (0, 0) + self._layout['aps'] = (65, 0) + self._layout['uptime'] = (355, 0) + self._layout['line1'] = [0, 25, 480, 25] + self._layout['line2'] = [0, 255, 480, 255] + self._layout['friend_face'] = (0, 146) + self._layout['friend_name'] = (40, 146) + self._layout['shakes'] = (0, 258) + self._layout['mode'] = (430, 258) + self._layout['status'] = { + 'pos': (225, 35), + 'font': fonts.status_font(fonts.Medium), + 'max': 21 + } + return self._layout + + def initialize(self): + logging.info("initializing waveshare v1 3.7 inch display") + from pwnagotchi.ui.hw.libs.waveshare.v37inch.epd3in7 import EPD + self._display = EPD() + self._display.init(0) + self._display.Clear(0xFF, 0) + self._display.init(1) # 1Gray mode + + def render(self, canvas): + buf = self._display.getbuffer(canvas) + self._display.display_1Gray(buf) + + def clear(self): + self._display.Clear(0xFF, 1) \ No newline at end of file diff --git a/pwnagotchi/utils.py b/pwnagotchi/utils.py index 16f022e2..36559a8b 100644 --- a/pwnagotchi/utils.py +++ b/pwnagotchi/utils.py @@ -283,6 +283,9 @@ def load_config(args): elif config['ui']['display']['type'] in ('spotpear24inch'): config['ui']['display']['type'] = 'spotpear24inch' + + elif config['ui']['display']['type'] in ('ws_37inch', 'ws37inch', 'waveshare_37inch', 'waveshare37inch'): + config['ui']['display']['type'] = 'waveshare37inch' else: print("unsupported display type %s" % config['ui']['display']['type']) diff --git a/requirements.txt b/requirements.txt index f1c2f445..decdc195 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,4 +1,4 @@ -Gymnasium +gym shimmy pycryptodome requests