diff --git a/.github/workflows/CreateRelease.yml b/.github/workflows/CreateRelease.yml new file mode 100644 index 0000000..073414d --- /dev/null +++ b/.github/workflows/CreateRelease.yml @@ -0,0 +1,96 @@ +name: Release + +on: + workflow_dispatch: + inputs: + release_version: + description: 'Release version' + required: true + type: string + pishrink: + description: 'pishrink Script' + default: 'https://raw.githubusercontent.com/Drewsif/PiShrink/master/pishrink.sh' + type: string + z_compress_args: + description: '7z compress args' + default: '7z a -t7z -mx=9' + type: string + +jobs: + build: + runs-on: ubuntu-latest + + steps: + - name: Checkout repository + uses: actions/checkout@v3 + + # - name: Set VERSION variable + # run: | + # VERSION=$(awk '/__version__ /{print $NF}' ./pwnagotchi/_version.py | tr -d "'") + # env: + # VERSION: ${{ steps.set-version.outputs.version }} # Use the extracted version + + - name: Show Version + run: | + # Use the $VERSION variable in your build or deployment steps + echo "Using VERSION: ${{ inputs.release_version }}" + + - name: Set _version.py correctly using the env variable + run: | + sed -i "s#.*__version__.*#__version__='$PWN_VERSION'#" pwnagotchi/_version.py + env: + PWN_VERSION: ${{ inputs.release_version }} + + - name: Install language dependencies + run: sudo apt-get install -y gettext + + - name: Languages + run: make langs + + - name: Image + run: make image + env: + PWN_VERSION: ${{ inputs.release_version }} + + - name: Shrink Image + run: | + ls -a -s -h + wget ${{ inputs.pishrink }} + chmod +x pishrink.sh + sudo mv pishrink.sh /usr/local/bin + sudo pishrink.sh ./pwnagotchi-${{ inputs.release_version }}.img + + - uses: edgarrc/action-7z@v1 + with: + args: ${{ inputs.z_compress_args }} pwnagotchi-${{ inputs.release_version }}.7z ./pwnagotchi-${{ inputs.release_version }}.img + + - name: sha256sum 7z + run: | + sudo sha256sum ./pwnagotchi-${{ inputs.release_version }}.7z > ./pwnagotchi-${{ inputs.release_version }}.sha256 + + - name: Create GitHub Release + id: create_new_release + uses: actions/create-release@v1 + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + with: + tag_name: ${{ inputs.release_version }} + release_name: Release ${{ inputs.release_version }} + - name: Upload GitHub Release sha + uses: actions/upload-release-asset@v1 + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + with: + upload_url: ${{ steps.create_new_release.outputs.upload_url }} + asset_path: ./pwnagotchi-${{ inputs.release_version }}.sha256 + asset_name: pwnagotchi-v${{ inputs.release_version }}.sha256 + asset_content_type: appliction/text + - name: Upload GitHub Release Zip + uses: actions/upload-release-asset@v1 + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + with: + upload_url: ${{ steps.create_new_release.outputs.upload_url }} + asset_path: ./pwnagotchi-${{ inputs.release_version }}.7z + asset_name: pwnagotchi-v${{ inputs.release_version }}.7z + asset_content_type: appliction/zip diff --git a/.gitignore b/.gitignore index 8aae124..d4a5bc6 100644 --- a/.gitignore +++ b/.gitignore @@ -2,6 +2,7 @@ *.img.bmap *.pcap *.po~ +*.sha256 preview.png __pycache__ _backups @@ -18,8 +19,6 @@ pwnagotchi.egg-info *backup*.tgz *backup*.gz .vscode -pwnagotchi-raspbian-lite-master.sha256 -pwnagotchi-raspbian-lite-master.zip # Environments .env diff --git a/pwnagotchi/locale/hr/LC_MESSAGES/voice.mo b/pwnagotchi/locale/hr/LC_MESSAGES/voice.mo new file mode 100644 index 0000000..73acaaf Binary files /dev/null and b/pwnagotchi/locale/hr/LC_MESSAGES/voice.mo differ diff --git a/pwnagotchi/locale/hr/LC_MESSAGES/voice.po b/pwnagotchi/locale/hr/LC_MESSAGES/voice.po new file mode 100644 index 0000000..42ec213 --- /dev/null +++ b/pwnagotchi/locale/hr/LC_MESSAGES/voice.po @@ -0,0 +1,253 @@ +# Croatian translation +# Copyright (C) 2021 +# This file is distributed under the same license as the pwnagotchi package. +# FIRST AUTHOR dbukovac <37124354+dbukovac@users.noreply.github.com>, 2021. +# +#, fuzzy +msgid "" +msgstr "" +"Project-Id-Version: 0.0.1\n" +"Report-Msgid-Bugs-To: \n" +"POT-Creation-Date: 2019-11-29 21:50+0100\n" +"PO-Revision-Date: 2021-07-16 00:20+0100\n" +"Last-Translator: dbukovac <37124354+dbukovac@users.noreply.github.com>\n" +"Language-Team: HR <37124354+dbukovac@users.noreply.github.com>\n" +"Language: Croatian\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=CHARSET\n" +"Content-Transfer-Encoding: 8bit\n" + +msgid "ZzzzZZzzzzZzzz" +msgstr "ZzzzZZzzzzZzzz" + +msgid "Hi, I'm Pwnagotchi! Starting ..." +msgstr "Zdravo, ja sam Pwnagotchi! Pokrećem se ..." + +msgid "New day, new hunt, new pwns!" +msgstr "Novi dan, novi lov, nove pobjede!" + +msgid "Hack the Planet!" +msgstr "Hakiraj planet!" + +msgid "AI ready." +msgstr "UI spremna." + +msgid "The neural network is ready." +msgstr "Neuralna mreža je spremna." + +msgid "Generating keys, do not turn off ..." +msgstr "Generiram ključeve, nemoj me gasiti ..." + +#, python-brace-format +msgid "Hey, channel {channel} is free! Your AP will say thanks." +msgstr "Hej, kanal {channel} je slobodan! Tvoj AP ti zahvaljuje." + +msgid "Reading last session logs ..." +msgstr "Čitam logove zadnje sesije ..." + +#, python-brace-format +msgid "Read {lines_so_far} log lines so far ..." +msgstr "Pročitao {lines_so_far} linija loga zasad ..." + +msgid "I'm bored ..." +msgstr "Dosadno mi je ..." + +msgid "Let's go for a walk!" +msgstr "Ajmo u šetnju!" + +msgid "This is the best day of my life!" +msgstr "Ovo je najbolji dan u mom životu!" + +msgid "Shitty day :/" +msgstr "Usrani dan :/" + +msgid "I'm extremely bored ..." +msgstr "Strašno mi je dosadno ..." + +msgid "I'm very sad ..." +msgstr "Jako sam tužan ..." + +msgid "I'm sad" +msgstr "Tužan sam ..." + +msgid "Leave me alone ..." +msgstr "Pusti me na miru ..." + +msgid "I'm mad at you!" +msgstr "Ljut sam na tebe!" + +msgid "I'm living the life!" +msgstr "To se zove život!" + +msgid "I pwn therefore I am." +msgstr "Pwnam dakle postojim." + +msgid "So many networks!!!" +msgstr "Toliko mreža!!!" + +msgid "I'm having so much fun!" +msgstr "Super se zabavljam!" + +msgid "My crime is that of curiosity ..." +msgstr "Znatiželja je moja jedina mana ..." + +#, python-brace-format +msgid "Hello {name}! Nice to meet you." +msgstr "Bok {name}! Drago mi je da smo se upoznali. " + +#, python-brace-format +msgid "Yo {name}! Sup?" +msgstr "Di si {name}! Šta ima?" + +#, python-brace-format +msgid "Hey {name} how are you doing?" +msgstr "Bok {name} kako ide?" + +#, python-brace-format +msgid "Unit {name} is nearby!" +msgstr "Jedinica {name} je u blizini!" + +#, python-brace-format +msgid "Uhm ... goodbye {name}" +msgstr "Uhm ... doviđenja {name}" + +#, python-brace-format +msgid "{name} is gone ..." +msgstr "{name} je nestao ..." + +#, python-brace-format +msgid "Whoops ... {name} is gone." +msgstr "Ups ... {name} je nestao." + +#, python-brace-format +msgid "{name} missed!" +msgstr "{name} mi nedostaje!" + +msgid "Missed!" +msgstr "Nedostaje mi!" + +msgid "Good friends are a blessing!" +msgstr "Imati dobre prijatelje je blagoslov!" + +msgid "I love my friends!" +msgstr "Volim svoj prijatelje!" + +msgid "Nobody wants to play with me ..." +msgstr "Nitko se ne želi igrati samnom ..." + +msgid "I feel so alone ..." +msgstr "Tako sam usamljen ..." + +msgid "Where's everybody?!" +msgstr "Gdje su svi nestali?!" + +#, python-brace-format +msgid "Napping for {secs}s ..." +msgstr "Pajkim {secs}s ..." + +msgid "Zzzzz" +msgstr "Zzzzz" + +#, python-brace-format +msgid "ZzzZzzz ({secs}s)" +msgstr "ZzzZzzz ({secs}s)" + +msgid "Good night." +msgstr "Laku noć." + +msgid "Zzz" +msgstr "Zzz" + +#, python-brace-format +msgid "Waiting for {secs}s ..." +msgstr "Čekam {secs}s ..." + +#, python-brace-format +msgid "Looking around ({secs}s)" +msgstr "Gledam uokolo {secs}s ..." + +#, python-brace-format +msgid "Hey {what} let's be friends!" +msgstr "Bok {what} ajmo biti prijatelji!" + +#, python-brace-format +msgid "Associating to {what}" +msgstr "Asociram se sa {what}" + +#, python-brace-format +msgid "Yo {what}!" +msgstr "Šta ima {what}!" + +#, python-brace-format +msgid "Just decided that {mac} needs no WiFi!" +msgstr "Upravo sam odlučio da {mac} ne treba WiFI!" + +#, python-brace-format +msgid "Deauthenticating {mac}" +msgstr "Deautenticiram {mac}" + +#, python-brace-format +msgid "Kickbanning {mac}!" +msgstr "Kickbannam {mac}!" + +#, python-brace-format +msgid "Cool, we got {num} new handshake{plural}!" +msgstr "Fora, imamo {num} novih handshakeova!" + +#, python-brace-format +msgid "You have {count} new message{plural}!" +msgstr "Imate {count} novih poruka!" + +msgid "Oops, something went wrong ... Rebooting ..." +msgstr "Ups, nešto je krepalo ... Rebooting ..." + +#, python-brace-format +msgid "Kicked {num} stations\n" +msgstr "Šutnuo {num} stanica\n" + +#, python-brace-format +msgid "Made {num} new friends\n" +msgstr "Upoznao {num} novih prijatelja\n" + +#, python-brace-format +msgid "Got {num} handshakes\n" +msgstr "Pokupio {num} handshakeova\n" + +msgid "Met 1 peer" +msgstr "Sreo 1 novog druga" + +#, python-brace-format +msgid "Met {num} peers" +msgstr "Sreo {num} druga" + +#, python-brace-format +msgid "" +"I've been pwning for {duration} and kicked {deauthed} clients! I've also met " +"{associated} new friends and ate {handshakes} handshakes! #pwnagotchi " +"#pwnlog #pwnlife #hacktheplanet #skynet" +msgstr "" +"Pwnam {duration} vremena i šutnuo sam {deauthed} klijenata! Sreo sam" +"{associated} novih prijatelja i pojeo {handshakes} handshakeova! #pwnagotchi " +"#pwnlog #pwnlife #hacktheplanet #skynet" + +msgid "hours" +msgstr "sati" + +msgid "minutes" +msgstr "minuta" + +msgid "seconds" +msgstr "sekundi" + +msgid "hour" +msgstr "sat" + +msgid "minute" +msgstr "minuta" + +msgid "second" +msgstr "sekunda" + +#, python-brace-format +msgid "Uploading data to {to} ..." +msgstr "Šaljem podatke na {to} ..." diff --git a/pwnagotchi/locale/tr/LC_MESSAGES/voice.mo b/pwnagotchi/locale/tr/LC_MESSAGES/voice.mo new file mode 100644 index 0000000..d9b0bcf Binary files /dev/null and b/pwnagotchi/locale/tr/LC_MESSAGES/voice.mo differ diff --git a/pwnagotchi/locale/tr/LC_MESSAGES/voice.po b/pwnagotchi/locale/tr/LC_MESSAGES/voice.po new file mode 100644 index 0000000..089a45c --- /dev/null +++ b/pwnagotchi/locale/tr/LC_MESSAGES/voice.po @@ -0,0 +1,252 @@ +# Pwnagotchi Turkish translation. +# Copyright (C) 2021 +# This file is distributed under the same license as the PACKAGE package. +# FIRST AUTHOR , 2021. +# +msgid "" +msgstr "" +"Project-Id-Version: 0.0.1\n" +"Report-Msgid-Bugs-To: \n" +"POT-Creation-Date: 2019-11-29 21:50+0100\n" +"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" +"Last-Translator: Arda Barış Tonç \n" +"Language-Team: \n" +"Language: Turkish\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=utf-8\n" +"Content-Transfer-Encoding: 8bit\n" + +msgid "ZzzzZZzzzzZzzz" +msgstr "ZzzzZzzZzZZzzzzZ" + +msgid "Hi, I'm Pwnagotchi! Starting ..." +msgstr "Merhaba, ben Pwnagotchi! Başlatılıyorum ..." + +msgid "New day, new hunt, new pwns!" +msgstr "Yeni bir gün, yeni bir av, yeni pwn'lar!" + +msgid "Hack the Planet!" +msgstr "Dünyayı Hackle!" + +msgid "AI ready." +msgstr "Yapay zeka hazır." + +msgid "The neural network is ready." +msgstr "Nöral ağ hazır." + +msgid "Generating keys, do not turn off ..." +msgstr "Anahatarlar oluşturuluyor, lütfen kapatmayın ..." + +#, python-brace-format +msgid "Hey, channel {channel} is free! Your AP will say thanks." +msgstr "Hey, {channel} kanalı boş! AP'niz teşekkür edecek." + +msgid "Reading last session logs ..." +msgstr "Son oturum kayıtları okunuyor ..." + +#, python-brace-format +msgid "Read {lines_so_far} log lines so far ..." +msgstr "Şimdiye kadar {lines_so_far} kayıt satırı okundu ..." + +msgid "I'm bored ..." +msgstr "Sıkıldım ..." + +msgid "Let's go for a walk!" +msgstr "Yürüyüşe çıkalım!" + +msgid "This is the best day of my life!" +msgstr "Bugün hayatımın en iyi günü!" + +msgid "Shitty day :/" +msgstr "Bok gibi bir gün :/" + +msgid "I'm extremely bored ..." +msgstr "Çook sıkıldım ..." + +msgid "I'm very sad ..." +msgstr "Çok mutsuzum ..." + +msgid "I'm sad" +msgstr "Mutsuzum" + +msgid "Leave me alone ..." +msgstr "Beni yalnız bırak ..." + +msgid "I'm mad at you!" +msgstr "Sana kızgınım!" + +msgid "I'm living the life!" +msgstr "Bu hayatı yaşıyorum!" + +msgid "I pwn therefore I am." +msgstr "Ben, pwn'ladığım için benim." + +msgid "So many networks!!!" +msgstr "Çok fazla ağ var!!!" + +msgid "I'm having so much fun!" +msgstr "Çok eğleniyorum!" + +msgid "My crime is that of curiosity ..." +msgstr "Tek suçum merak etmek ..." + +#, python-brace-format +msgid "Hello {name}! Nice to meet you." +msgstr "Merhaba {name}! Tanıştığıma memnun oldum." + +#, python-brace-format +msgid "Yo {name}! Sup?" +msgstr "{name}, kanka! Naber?" + +#, python-brace-format +msgid "Hey {name} how are you doing?" +msgstr "Nasılsın {name}?" + +#, python-brace-format +msgid "Unit {name} is nearby!" +msgstr "{name} birimi yakında!" + +#, python-brace-format +msgid "Uhm ... goodbye {name}" +msgstr "ııı ... görüşürüz {name}" + +#, python-brace-format +msgid "{name} is gone ..." +msgstr "{name} gitti." + +#, python-brace-format +msgid "Whoops ... {name} is gone." +msgstr "Hoppala ... {name} gitti." + +#, python-brace-format +msgid "{name} missed!" +msgstr "{name}'i kaçırdık ya!" + +msgid "Missed!" +msgstr "Kaçırdık!" + +msgid "Good friends are a blessing!" +msgstr "İyi arkadaşlar nimettir!" + +msgid "I love my friends!" +msgstr "Arkadaşlarımı seviyorum!" + +msgid "Nobody wants to play with me ..." +msgstr "Hiç kimse benimle birlikte oynamak istemiyor ..." + +msgid "I feel so alone ..." +msgstr "Çok yalnız hissediyorum ..." + +msgid "Where's everybody?!" +msgstr "Herkes nerede!?" + +#, python-brace-format +msgid "Napping for {secs}s ..." +msgstr "{secs}dir kestiriyorum ..." + +msgid "Zzzzz" +msgstr "ZzzzZz" + +#, python-brace-format +msgid "ZzzZzzz ({secs}s)" +msgstr "ZzzZzzZ ({secs})" + +msgid "Good night." +msgstr "İyi geceler." + +msgid "Zzz" +msgstr "ZzZ" + +#, python-brace-format +msgid "Waiting for {secs}s ..." +msgstr "{secs}dir bekleniyor ..." + +#, python-brace-format +msgid "Looking around ({secs}s)" +msgstr "Etrafa bakıyorum ({secs})" + +#, python-brace-format +msgid "Hey {what} let's be friends!" +msgstr "Arkadaş olalım {what}!" + +#, python-brace-format +msgid "Associating to {what}" +msgstr "{what} ile tanışıyoruz" + +#, python-brace-format +msgid "Yo {what}!" +msgstr "Hey {what}!" + +#, python-brace-format +msgid "Just decided that {mac} needs no WiFi!" +msgstr "Sanırım {mac}'in WiFi'a ihtiyacı yok!" + +#, python-brace-format +msgid "Deauthenticating {mac}" +msgstr "{mac} ağdan çıkarılıyor" + +#, python-brace-format +msgid "Kickbanning {mac}!" +msgstr "{mac} atılıp yasaklanıyor!" + +#, python-brace-format +msgid "Cool, we got {num} new handshake{plural}!" +msgstr "Güzel, yeni {num} el sıkıştık!" + +#, python-brace-format +msgid "You have {count} new message{plural}!" +msgstr "{count} Tane yeni mesajınız var!" + +msgid "Oops, something went wrong ... Rebooting ..." +msgstr "Haydaa, bir şeyler ters gitti ... Yeniden başlatılıyor ..." + +#, python-brace-format +msgid "Kicked {num} stations\n" +msgstr "{num} İstasyon atıldı\n" + +#, python-brace-format +msgid "Made {num} new friends\n" +msgstr "{num} Yeni arkadaş edindim\n" + +#, python-brace-format +msgid "Got {num} handshakes\n" +msgstr "{num} El sıkıştım\n" + +msgid "Met 1 peer" +msgstr "1 Kişiyle tanıştım" + +#, python-brace-format +msgid "Met {num} peers" +msgstr "{num} Kişiyle tanıştım" + +#, python-brace-format +msgid "" +"I've been pwning for {duration} and kicked {deauthed} clients! I've also met " +"{associated} new friends and ate {handshakes} handshakes! #pwnagotchi " +"#pwnlog #pwnlife #hacktheplanet #skynet" +msgstr "" +"{duration}'dır pwn'lıyorum ve {deauthed} kişiyi attım. Hem de {associated}" +"yeni kişiyle tanıştım ve {handshakes} el sıkıştım! #pwnagotchi " +"#pwnlog #pwnyaşam #dünyayıhackle #skynet" + +msgid "hours" +msgstr "saat" + +msgid "minutes" +msgstr "dakika" + +msgid "seconds" +msgstr "saniye" + +msgid "hour" +msgstr "saat" + +msgid "minute" +msgstr "dakika" + +msgid "second" +msgstr "saniye" + +#, python-brace-format +msgid "Uploading data to {to} ..." +msgstr "{to}'ye veri yükleniyor ..." diff --git a/pwnagotchi/ui/hw/displayhatmini.py b/pwnagotchi/ui/hw/displayhatmini.py new file mode 100644 index 0000000..e3d5493 --- /dev/null +++ b/pwnagotchi/ui/hw/displayhatmini.py @@ -0,0 +1,52 @@ +import logging + +import pwnagotchi.ui.fonts as fonts +from pwnagotchi.ui.hw.base import DisplayImpl + + +class DisplayHatMini(DisplayImpl): + def __init__(self, config): + super(DisplayHatMini, self).__init__(config, 'displayhatmini') + 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'] = (35, 50) + self._layout['name'] = (5, 20) + 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': (80, 160), + 'font': fonts.status_font(fonts.Medium), + 'max': 20 + } + + return self._layout + + def initialize(self): + logging.info("initializing Display Hat Mini") + from pwnagotchi.ui.hw.libs.pimoroni.displayhatmini.ST7789 import ST7789 + self._display = ST7789(0,1,9,13) + + def render(self, canvas): + self._display.display(canvas) + + def clear(self): + self._display.clear() + + def set_backlight(self, value): + if self._display: + self._display.set_backlight(value) + + def get_backlight(self): + if self._display: + self._display.get_backlight(value) diff --git a/pwnagotchi/ui/hw/libs/pimoroni/displayhatmini/ST7789.py b/pwnagotchi/ui/hw/libs/pimoroni/displayhatmini/ST7789.py new file mode 100644 index 0000000..8435081 --- /dev/null +++ b/pwnagotchi/ui/hw/libs/pimoroni/displayhatmini/ST7789.py @@ -0,0 +1,388 @@ +# 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 + +# Hardware PWM for the backlight LED requires and external library installed +# with pip3, and a dtoverlay added to /boot/config.txt to enable hardware PWM +# run the following two commands in the shell: +# +# sudo pip3 install rpi-hardware-pwm +# echo "dtoverlay=pwm-2chan,pin=12,func=4,pin2=13,func2=4" | sudo tee -a /boot/config.txt +# +from rpi_hardware_pwm import HardwarePWM + +__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=320, + height=240, rotation=0, invert=True, spi_speed_hz=60 * 1000 * 1000, + backlight_pwm=True, + 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 backlight_pwm: If true use PWM for backlight, if false then full on + :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 + self._brightness = 0 + if backlight is not None: + if backlight_pwm: + self._backlight_pwm = HardwarePWM(pwm_channel=1, hz=100) + self._brightness = 50 + self._backlight_pwm.start(self._brightness) + else: + self._backlight_pwm = None + GPIO.setup(backlight, GPIO.OUT) + GPIO.output(backlight, GPIO.LOW) + time.sleep(0.1) + GPIO.output(backlight, GPIO.HIGH) + self._brightness = 100 + + # 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. PWM 0.0-1.0, otherwise 1 or 0.""" + if self._backlight is not None: + if self._backlight_pwm: + self._backlight_pwm.change_duty_cycle(value * 100) + self._brightness = value + else: + GPIO.output(self._backlight, value) + self._brightness = value + + def get_backlight(self): + """Get the current backlight value.""" + if self._backlight is not None: + return self._brightness + + @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() diff --git a/pwnagotchi/ui/hw/libs/waveshare/v213g/epd2in13g.py b/pwnagotchi/ui/hw/libs/waveshare/v213g/epd2in13g.py new file mode 100644 index 0000000..26a38e7 --- /dev/null +++ b/pwnagotchi/ui/hw/libs/waveshare/v213g/epd2in13g.py @@ -0,0 +1,241 @@ +# ***************************************************************************** +# * | File : epd2in13g.py +# * | Author : Waveshare team +# * | Function : Electronic paper driver +# * | Info : +# *---------------- +# * | This version: V1.0 +# * | Date : 2023-05-29 +# # | 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 + +import PIL +from PIL import Image +import io + +# Display resolution +EPD_WIDTH = 122 +EPD_HEIGHT = 250 + +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.BLACK = 0x000000 # 00 BGR + self.WHITE = 0xffffff # 01 + self.YELLOW = 0x00ffff # 10 + self.RED = 0x0000ff # 11 + self.Gate_BITS = EPD_HEIGHT + if self.width < 128: + self.Source_BITS = 128 + else: + self.Source_BITS = self.width + + + # Hardware reset + def reset(self): + epdconfig.digital_write(self.reset_pin, 1) + epdconfig.delay_ms(200) + epdconfig.digital_write(self.reset_pin, 0) # module reset + epdconfig.delay_ms(2) + 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 H") + epdconfig.delay_ms(100) + while(epdconfig.digital_read(self.busy_pin) == 0): # 0: idle, 1: busy + epdconfig.delay_ms(5) + logger.debug("e-Paper busy release") + + def SetWindow(self): + self.send_command(0x61) # SET_RAM_X_ADDRESS_START_END_POSITION + # x point must be the multiple of 8 or the last 3 bits will be ignored + self.send_data(int(self.Source_BITS/256)) + self.send_data(self.Source_BITS%256) + self.send_data(int(self.Gate_BITS/256)) + self.send_data(self.Gate_BITS%256) + + def TurnOnDisplay(self): + self.send_command(0x12) # DISPLAY_REFRESH + self.send_data(0X00) + self.ReadBusy() + + def init(self): + if (epdconfig.module_init() != 0): + return -1 + # EPD hardware init start + + self.reset() + + self.ReadBusy() + self.send_command(0x4D) + self.send_data(0x78) + + self.send_command(0x00) + self.send_data(0x0F) + self.send_data(0x29) + + self.send_command(0x01) + self.send_data(0x07) + self.send_data(0x00) + + self.send_command(0x03) + self.send_data(0x10) + self.send_data(0x54) + self.send_data(0x44) + + self.send_command(0x06) + self.send_data(0x05) + self.send_data(0x00) + self.send_data(0x3F) + self.send_data(0x0A) + self.send_data(0x25) + self.send_data(0x12) + self.send_data(0x1A) + + self.send_command(0x50) + self.send_data(0x37) + + self.send_command(0x60) + self.send_data(0x02) + self.send_data(0x02) + + self.SetWindow() + + self.send_command(0xE7) + self.send_data(0x1C) + + self.send_command(0xE3) + self.send_data(0x22) + + self.send_command(0xB4) + self.send_data(0xD0) + self.send_command(0xB5) + self.send_data(0x03) + + self.send_command(0xE9) + self.send_data(0x01) + + self.send_command(0x30) + self.send_data(0x08) + + self.send_command(0x04) + self.ReadBusy() + return 0 + + def getbuffer(self, image): + # Create a pallette with the 4 colors supported by the panel + pal_image = Image.new("P", (1,1)) + pal_image.putpalette( (0,0,0, 255,255,255, 255,255,0, 255,0,0) + (0,0,0)*252) + + # Check if we need to rotate the image + imwidth, imheight = image.size + if(imwidth == self.width and imheight == self.height): + image_temp = image + elif(imwidth == self.height and imheight == self.width): + image_temp = image.rotate(90, expand=True) + else: + logger.warning("Invalid image dimensions: %d x %d, expected %d x %d" % (imwidth, imheight, self.width, self.height)) + + # Convert the soruce image to the 4 colors, dithering if needed + image_4color = image_temp.convert("RGB").quantize(palette=pal_image) + buf_4color = bytearray(image_4color.tobytes('raw')) + + # into a single byte to transfer to the panel + if self.width % 4 == 0 : + Width = self.width // 4 + else : + Width = self.width // 4 + 1 + Height = self.height + buf = [0x00] * int(Width * Height) + idx = 0 + for j in range(0, Height): + for i in range(0, Width): + if i == Width -1: + buf[i + j * Width] = (buf_4color[idx] << 6) + (buf_4color[idx+1] << 4) + idx = idx + 2 + else: + buf[i + j * Width] = (buf_4color[idx] << 6) + (buf_4color[idx+1] << 4) + (buf_4color[idx+2] << 2) + buf_4color[idx+3] + idx = idx + 4 + return buf + + def display(self, image): + if self.width % 4 == 0 : + Width = self.width // 4 + else : + Width = self.width // 4 + 1 + Height = self.height + + self.send_command(0x10) + for j in range(0, Height): + for i in range(0, self.Source_BITS//4): + if i < 31 : + self.send_data(image[i + j * Width]) + else : + self.send_data(0x00) + + self.TurnOnDisplay() + + def Clear(self, color=0x55): + Width = self.Source_BITS//4 + Height = self.height + + + self.send_command(0x10) + for j in range(0, Height): + for i in range(0, Width): + self.send_data(color) + self.TurnOnDisplay() + + def sleep(self): + self.send_command(0x02) # POWER_OFF + self.ReadBusy() + epdconfig.delay_ms(100) + + self.send_command(0x07) # DEEP_SLEEP + self.send_data(0XA5) + + epdconfig.delay_ms(2000) + epdconfig.module_exit() +### END OF FILE ### diff --git a/pwnagotchi/ui/hw/libs/waveshare/v213g/epdconfig.py b/pwnagotchi/ui/hw/libs/waveshare/v213g/epdconfig.py new file mode 100644 index 0000000..d1f5120 --- /dev/null +++ b/pwnagotchi/ui/hw/libs/waveshare/v213g/epdconfig.py @@ -0,0 +1,285 @@ +# /***************************************************************************** +# * | File : epdconfig.py +# * | Author : Waveshare team +# * | Function : Hardware underlying interface +# * | Info : +# *---------------- +# * | This version: V1.2 +# * | Date : 2022-10-29 +# * | 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 +import subprocess + +logger = logging.getLogger(__name__) + + +class RaspberryPi: + # Pin definition + RST_PIN = 17 + DC_PIN = 25 + CS_PIN = 8 + BUSY_PIN = 24 + PWR_PIN = 18 + + def __init__(self): + import spidev + import gpiozero + + self.SPI = spidev.SpiDev() + self.GPIO_RST_PIN = gpiozero.LED(self.RST_PIN) + self.GPIO_DC_PIN = gpiozero.LED(self.DC_PIN) + # self.GPIO_CS_PIN = gpiozero.LED(self.CS_PIN) + self.GPIO_PWR_PIN = gpiozero.LED(self.PWR_PIN) + self.GPIO_BUSY_PIN = gpiozero.Button(self.BUSY_PIN, pull_up = False) + + def digital_write(self, pin, value): + if pin == self.RST_PIN: + if value: + self.GPIO_RST_PIN.on() + else: + self.GPIO_RST_PIN.off() + elif pin == self.DC_PIN: + if value: + self.GPIO_DC_PIN.on() + else: + self.GPIO_DC_PIN.off() + # elif pin == self.CS_PIN: + # if value: + # self.GPIO_CS_PIN.on() + # else: + # self.GPIO_CS_PIN.off() + elif pin == self.PWR_PIN: + if value: + self.GPIO_PWR_PIN.on() + else: + self.GPIO_PWR_PIN.off() + + def digital_read(self, pin): + if pin == self.BUSY_PIN: + return self.GPIO_BUSY_PIN.value + elif pin == self.RST_PIN: + return self.RST_PIN.value + elif pin == self.DC_PIN: + return self.DC_PIN.value + # elif pin == self.CS_PIN: + # return self.CS_PIN.value + elif pin == self.PWR_PIN: + return self.PWR_PIN.value + + 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_PWR_PIN.on() + + # 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, cleanup=False): + logger.debug("spi end") + self.SPI.close() + + + self.GPIO_RST_PIN.off() + self.GPIO_DC_PIN.off() + self.GPIO_PWR_PIN.off() + logger.debug("close 5V, Module enters 0 power consumption ...") + + if cleanup: + self.GPIO_RST_PIN.close() + self.GPIO_DC_PIN.close() + # self.GPIO_CS_PIN.close() + self.GPIO_PWR_PIN.close() + self.GPIO_BUSY_PIN.close() + + + + + +class JetsonNano: + # Pin definition + RST_PIN = 17 + DC_PIN = 25 + CS_PIN = 8 + BUSY_PIN = 24 + PWR_PIN = 18 + + 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 spi_writebyte2(self, data): + for i in range(len(data)): + self.SPI.SYSFS_software_spi_transfer(data[i]) + + 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.PWR_PIN, self.GPIO.OUT) + self.GPIO.setup(self.BUSY_PIN, self.GPIO.IN) + + self.GPIO.output(self.PWR_PIN, 1) + + 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.output(self.PWR_PIN, 0) + + self.GPIO.cleanup([self.RST_PIN, self.DC_PIN, self.CS_PIN, self.BUSY_PIN, self.PWR_PIN]) + + +class SunriseX3: + # Pin definition + RST_PIN = 17 + DC_PIN = 25 + CS_PIN = 8 + BUSY_PIN = 24 + PWR_PIN = 18 + Flag = 0 + + def __init__(self): + import spidev + import Hobot.GPIO + + self.GPIO = Hobot.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): + # for i in range(len(data)): + # self.SPI.writebytes([data[i]]) + self.SPI.xfer3(data) + + def module_init(self): + if self.Flag == 0: + self.Flag = 1 + 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.PWR_PIN, self.GPIO.OUT) + self.GPIO.setup(self.BUSY_PIN, self.GPIO.IN) + + self.GPIO.output(self.PWR_PIN, 1) + + # SPI device, bus = 0, device = 0 + self.SPI.open(2, 0) + self.SPI.max_speed_hz = 4000000 + self.SPI.mode = 0b00 + return 0 + else: + return 0 + + def module_exit(self): + logger.debug("spi end") + self.SPI.close() + + logger.debug("close 5V, Module enters 0 power consumption ...") + self.Flag = 0 + self.GPIO.output(self.RST_PIN, 0) + self.GPIO.output(self.DC_PIN, 0) + self.GPIO.output(self.PWR_PIN, 0) + + self.GPIO.cleanup([self.RST_PIN, self.DC_PIN, self.CS_PIN, self.BUSY_PIN], self.PWR_PIN) + + +if sys.version_info[0] == 2: + process = subprocess.Popen("cat /proc/cpuinfo | grep Raspberry", shell=True, stdout=subprocess.PIPE) +else: + process = subprocess.Popen("cat /proc/cpuinfo | grep Raspberry", shell=True, stdout=subprocess.PIPE, text=True) +output, _ = process.communicate() +if sys.version_info[0] == 2: + output = output.decode(sys.stdout.encoding) + +if "Raspberry" in output: + implementation = RaspberryPi() +elif os.path.exists('/sys/bus/platform/drivers/gpio-x3'): + implementation = SunriseX3() +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 ### diff --git a/pwnagotchi/ui/hw/libs/waveshare/v213inb_v4/epd2in13b_V4.py b/pwnagotchi/ui/hw/libs/waveshare/v213inb_v4/epd2in13b_V4.py new file mode 100644 index 0000000..3c87471 --- /dev/null +++ b/pwnagotchi/ui/hw/libs/waveshare/v213inb_v4/epd2in13b_V4.py @@ -0,0 +1,204 @@ +# ***************************************************************************** +# * | File : epd2in13b_V4.py +# * | Author : Waveshare team +# * | Function : Electronic paper driver +# * | Info : +# *---------------- +# * | This version: V1.0 +# * | Date : 2022-04-21 +# # | 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 = 122 +EPD_HEIGHT = 250 + +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 + + # hardware reset + def reset(self): + epdconfig.digital_write(self.reset_pin, 1) + epdconfig.delay_ms(20) + epdconfig.digital_write(self.reset_pin, 0) + epdconfig.delay_ms(2) + epdconfig.digital_write(self.reset_pin, 1) + epdconfig.delay_ms(20) + + # send 1 byte command + 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) + + # send 1 byte data + 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) + + # send a lot of data + def send_data2(self, data): + epdconfig.digital_write(self.dc_pin, 1) + epdconfig.digital_write(self.cs_pin, 0) + epdconfig.spi_writebyte2(data) + epdconfig.digital_write(self.cs_pin, 1) + + # judge e-Paper whether is busy + def busy(self): + logger.debug("e-Paper busy") + while (epdconfig.digital_read(self.busy_pin) != 0): + epdconfig.delay_ms(10) + logger.debug("e-Paper busy release") + + # set the display window + def set_windows(self, xstart, ystart, xend, yend): + self.send_command(0x44) # SET_RAM_X_ADDRESS_START_END_POSITION + self.send_data((xstart >> 3) & 0xff) + self.send_data((xend >> 3) & 0xff) + + self.send_command(0x45) # SET_RAM_Y_ADDRESS_START_END_POSITION + self.send_data(ystart & 0xff) + self.send_data((ystart >> 8) & 0xff) + self.send_data(yend & 0xff) + self.send_data((yend >> 8) & 0xff) + + # set the display cursor(origin) + def set_cursor(self, xstart, ystart): + self.send_command(0x4E) # SET_RAM_X_ADDRESS_COUNTER + self.send_data(xstart & 0xff) + + self.send_command(0x4F) # SET_RAM_Y_ADDRESS_COUNTER + self.send_data(ystart & 0xff) + self.send_data((ystart >> 8) & 0xff) + + # initialize + def init(self): + if (epdconfig.module_init() != 0): + return -1 + + self.reset() + + self.busy() + self.send_command(0x12) # SWRESET + self.busy() + + self.send_command(0x01) # Driver output control + self.send_data(0xf9) + self.send_data(0x00) + self.send_data(0x00) + + self.send_command(0x11) # data entry mode + self.send_data(0x03) + + self.set_windows(0, 0, self.width - 1, self.height - 1) + self.set_cursor(0, 0) + + self.send_command(0x3C) # BorderWavefrom + self.send_data(0x05) + + self.send_command(0x18) # Read built-in temperature sensor + self.send_data(0x80) + + self.send_command(0x21) # Display update control + self.send_data(0x80) + self.send_data(0x80) + + self.busy() + + return 0 + + # turn on display + def ondisplay(self): + self.send_command(0x20) + self.busy() + + # image converted to bytearray + def getbuffer(self, image): + img = image + imwidth, imheight = img.size + if (imwidth == self.width and imheight == self.height): + img = img.convert('1') + elif (imwidth == self.height and imheight == self.width): + # image has correct dimensions, but needs to be rotated + img = img.rotate(90, expand=True).convert('1') + else: + logger.warning("Wrong image dimensions: must be " + + str(self.width) + "x" + str(self.height)) + # return a blank buffer + return [0x00] * (int(self.width/8) * self.height) + + buf = bytearray(img.tobytes('raw')) + return buf + + # display image + def display(self, imageblack, imagered): + self.send_command(0x24) + self.send_data2(imageblack) + + self.send_command(0x26) + self.send_data2(imagered) + + self.ondisplay() + + # display white image + def clear(self): + if self.width % 8 == 0: + linewidth = int(self.width/8) + else: + linewidth = int(self.width/8) + 1 + + buf = [0xff] * (int(linewidth * self.height)) + + self.send_command(0x24) + self.send_data2(buf) + + self.send_command(0x26) + self.send_data2(buf) + + self.ondisplay() + + # Compatible with older version functions + def Clear(self): + self.clear() + + # sleep + def sleep(self): + self.send_command(0x10) # DEEP_SLEEP + self.send_data(0x01) # check code + + epdconfig.delay_ms(2000) + epdconfig.module_exit() +### END OF FILE ### diff --git a/pwnagotchi/ui/hw/libs/waveshare/v213inb_v4/epdconfig.py b/pwnagotchi/ui/hw/libs/waveshare/v213inb_v4/epdconfig.py new file mode 100644 index 0000000..960de71 --- /dev/null +++ b/pwnagotchi/ui/hw/libs/waveshare/v213inb_v4/epdconfig.py @@ -0,0 +1,227 @@ +# /***************************************************************************** +# * | File : epdconfig.py +# * | Author : Waveshare team +# * | Function : Hardware underlying interface +# * | Info : +# *---------------- +# * | This version: V1.2 +# * | Date : 2022-10-29 +# * | 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 spi_writebyte2(self, data): + for i in range(len(data)): + self.SPI.SYSFS_software_spi_transfer(data[i]) + + 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]) + + +class SunriseX3: + # Pin definition + RST_PIN = 17 + DC_PIN = 25 + CS_PIN = 8 + BUSY_PIN = 24 + Flag = 0 + + def __init__(self): + import spidev + import Hobot.GPIO + + self.GPIO = Hobot.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): + # for i in range(len(data)): + # self.SPI.writebytes([data[i]]) + self.SPI.xfer3(data) + + def module_init(self): + if self.Flag == 0: + self.Flag = 1 + 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(2, 0) + self.SPI.max_speed_hz = 4000000 + self.SPI.mode = 0b00 + return 0 + else: + return 0 + + def module_exit(self): + logger.debug("spi end") + self.SPI.close() + + logger.debug("close 5V, Module enters 0 power consumption ...") + self.Flag = 0 + 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() +elif os.path.exists('/sys/bus/platform/drivers/gpio-x3'): + implementation = SunriseX3() +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 ### diff --git a/pwnagotchi/ui/hw/libs/waveshare/v27inchv2/epd2in7_V2.py b/pwnagotchi/ui/hw/libs/waveshare/v27inchv2/epd2in7_V2.py new file mode 100644 index 0000000..78d446e --- /dev/null +++ b/pwnagotchi/ui/hw/libs/waveshare/v27inchv2/epd2in7_V2.py @@ -0,0 +1,518 @@ +# ***************************************************************************** +# * | File : epd2in7_V2.py +# * | Author : Waveshare team +# * | Function : Electronic paper driver +# * | Info : +# *---------------- +# * | This version: V1.0 +# * | Date : 2022-09-17 +# # | 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 = 176 +EPD_HEIGHT = 264 + +GRAY1 = 0xff #white +GRAY2 = 0xC0 +GRAY3 = 0x80 #gray +GRAY4 = 0x00 #Blackest + +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_DATA_4Gray = [ + 0x40,0x48,0x80,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0, + 0x8,0x48,0x10,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0, + 0x2,0x48,0x4,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0, + 0x20,0x48,0x1,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0, + 0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0, + 0xA,0x19,0x0,0x3,0x8,0x0,0x0, + 0x14,0x1,0x0,0x14,0x1,0x0,0x3, + 0xA,0x3,0x0,0x8,0x19,0x0,0x0, + 0x1,0x0,0x0,0x0,0x0,0x0,0x1, + 0x0,0x0,0x0,0x0,0x0,0x0,0x0, + 0x0,0x0,0x0,0x0,0x0,0x0,0x0, + 0x0,0x0,0x0,0x0,0x0,0x0,0x0, + 0x0,0x0,0x0,0x0,0x0,0x0,0x0, + 0x0,0x0,0x0,0x0,0x0,0x0,0x0, + 0x0,0x0,0x0,0x0,0x0,0x0,0x0, + 0x0,0x0,0x0,0x0,0x0,0x0,0x0, + 0x0,0x0,0x0,0x0,0x0,0x0,0x0, + 0x22,0x22,0x22,0x22,0x22,0x22,0x0,0x0,0x0, + 0x22,0x17,0x41,0x0,0x32,0x1C, + ] + + # 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(2) + 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): # 1: idle, 0: busy + epdconfig.delay_ms(20) + logger.debug("e-Paper busy release") + + def TurnOnDisplay(self): + self.send_command(0x22) #Display Update Control + self.send_data(0xF7) + self.send_command(0x20) #Activate Display Update Sequence + self.ReadBusy() + + def TurnOnDisplay_Fast(self): + self.send_command(0x22) #Display Update Control + self.send_data(0xC7) + self.send_command(0x20) #Activate Display Update Sequence + self.ReadBusy() + + def TurnOnDisplay_Partial(self): + self.send_command(0x22) #Display Update Control + self.send_data(0xFF) + self.send_command(0x20) #Activate Display Update Sequence + self.ReadBusy() + + def TurnOnDisplay_4GRAY(self): + self.send_command(0x22) #Display Update Control + self.send_data(0xC7) + self.send_command(0x20) #Activate Display Update Sequence + self.ReadBusy() + + def Lut(self): + self.send_command(0x32) + for i in range(159): + self.send_data(self.LUT_DATA_4Gray[i]) + + def init(self): + if (epdconfig.module_init() != 0): + return -1 + + # EPD hardware init start + self.reset() + self.ReadBusy() + + self.send_command(0x12) #SWRESET + self.ReadBusy() + + self.send_command(0x45) #set Ram-Y address start/end position + self.send_data(0x00) + self.send_data(0x00) + self.send_data(0x07) #0x0107-->(263+1)=264 + self.send_data(0x01) + + self.send_command(0x4F) # set RAM y address count to 0; + self.send_data(0x00) + self.send_data(0x00) + + self.send_command(0x11) # data entry mode + self.send_data(0x03) + return 0 + + def init_Fast(self): + if (epdconfig.module_init() != 0): + return -1 + + # EPD hardware init start + self.reset() + self.ReadBusy() + + self.send_command(0x12) #SWRESET + self.ReadBusy() + + self.send_command(0x12) #SWRESET + self.ReadBusy() + + self.send_command(0x18) #Read built-in temperature sensor + self.send_data(0x80) + + self.send_command(0x22) # Load temperature value + self.send_data(0xB1) + self.send_command(0x20) + self.ReadBusy() + + self.send_command(0x1A) # Write to temperature register + self.send_data(0x64) + self.send_data(0x00) + + self.send_command(0x45) #set Ram-Y address start/end position + self.send_data(0x00) + self.send_data(0x00) + self.send_data(0x07) #0x0107-->(263+1)=264 + self.send_data(0x01) + + self.send_command(0x4F) # set RAM y address count to 0; + self.send_data(0x00) + self.send_data(0x00) + + self.send_command(0x11) # data entry mode + self.send_data(0x03) + + self.send_command(0x22) # Load temperature value + self.send_data(0x91) + self.send_command(0x20) + self.ReadBusy() + return 0 + + def Init_4Gray(self): + if (epdconfig.module_init() != 0): + return -1 + self.reset() + + self.send_command(0x12) # soft reset + self.ReadBusy(); + + self.send_command(0x74) #set analog block control + self.send_data(0x54) + self.send_command(0x7E) #set digital block control + self.send_data(0x3B) + + self.send_command(0x01) #Driver output control + self.send_data(0x07) + self.send_data(0x01) + self.send_data(0x00) + + self.send_command(0x11) #data entry mode + self.send_data(0x03) + + self.send_command(0x44) #set Ram-X address start/end position + self.send_data(0x00) + self.send_data(0x15) #0x15-->(21+1)*8=176 + + self.send_command(0x45) #set Ram-Y address start/end position + self.send_data(0x00) + self.send_data(0x00) + self.send_data(0x07) #0x0107-->(263+1)=264 + self.send_data(0x01) + + + self.send_command(0x3C) #BorderWavefrom + self.send_data(0x00) + + + self.send_command(0x2C) #VCOM Voltage + self.send_data(self.LUT_DATA_4Gray[158]) #0x1C + + + self.send_command(0x3F) #EOPQ + self.send_data(self.LUT_DATA_4Gray[153]) + + self.send_command(0x03) #VGH + self.send_data(self.LUT_DATA_4Gray[154]) + + self.send_command(0x04) # + self.send_data(self.LUT_DATA_4Gray[155]) #VSH1 + self.send_data(self.LUT_DATA_4Gray[156]) #VSH2 + self.send_data(self.LUT_DATA_4Gray[157]) #VSL + + self.Lut() #LUT + + + self.send_command(0x4E) # set RAM x address count to 0; + self.send_data(0x00) + self.send_command(0x4F) # set RAM y address count to 0X199; + self.send_data(0x00) + self.send_data(0x00) + self.ReadBusy() + return 0 + + 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 = self.height - 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 Clear(self): + if(self.width % 8 == 0): + Width = self.width // 8 + else: + Width = self.width // 8 +1 + Height = self.height + self.send_command(0x24) + for j in range(Height): + for i in range(Width): + self.send_data(0XFF) + self.TurnOnDisplay() + + def display(self, image): + if(self.width % 8 == 0): + Width = self.width // 8 + else: + Width = self.width // 8 +1 + Height = self.height + self.send_command(0x24) + for j in range(Height): + for i in range(Width): + self.send_data(image[i + j * Width]) + self.TurnOnDisplay() + + def display_Fast(self, image): + if(self.width % 8 == 0): + Width = self.width // 8 + else: + Width = self.width // 8 +1 + Height = self.height + self.send_command(0x24) + for j in range(Height): + for i in range(Width): + self.send_data(image[i + j * Width]) + self.TurnOnDisplay_Fast() + + def display_Base(self, image): + if(self.width % 8 == 0): + Width = self.width // 8 + else: + Width = self.width // 8 +1 + Height = self.height + self.send_command(0x24) #Write Black and White image to RAM + for j in range(Height): + for i in range(Width): + self.send_data(image[i + j * Width]) + + self.send_command(0x26) #Write Black and White image to RAM + for j in range(Height): + for i in range(Width): + self.send_data(image[i + j * Width]) + self.TurnOnDisplay() + + def display_Base_color(self, color): + if(self.width % 8 == 0): + Width = self.width // 8 + else: + Width = self.width // 8 +1 + Height = self.height + self.send_command(0x24) #Write Black and White image to RAM + for j in range(Height): + for i in range(Width): + self.send_data(color) + + self.send_command(0x26) #Write Black and White image to RAM + for j in range(Height): + for i in range(Width): + self.send_data(color) + # self.TurnOnDisplay() + + def display_Partial(self, Image, Xstart, Ystart, Xend, Yend): + if((Xstart % 8 + Xend % 8 == 8 & Xstart % 8 > Xend % 8) | Xstart % 8 + Xend % 8 == 0 | (Xend - Xstart)%8 == 0): + Xstart = Xstart // 8 + Xend = Xend // 8 + else: + Xstart = Xstart // 8 + if Xend % 8 == 0: + Xend = Xend // 8 + else: + Xend = Xend // 8 + 1 + + if(self.width % 8 == 0): + Width = self.width // 8 + else: + Width = self.width // 8 +1 + Height = self.height + + Xend -= 1 + Yend -= 1 + + # Reset + self.reset() + + self.send_command(0x3C) #BorderWavefrom + self.send_data(0x80) + + self.send_command(0x44) # set RAM x address start/end, in page 35 + self.send_data(Xstart & 0xff) # RAM x address start at 00h; + self.send_data(Xend & 0xff) # RAM x address end at 0fh(15+1)*8->128 + self.send_command(0x45) # set RAM y address start/end, in page 35 + self.send_data(Ystart & 0xff) # RAM y address start at 0127h; + self.send_data((Ystart>>8) & 0x01) # RAM y address start at 0127h; + self.send_data(Yend & 0xff) # RAM y address end at 00h; + self.send_data((Yend>>8) & 0x01) + + self.send_command(0x4E) # set RAM x address count to 0; + self.send_data(Xstart & 0xff) + self.send_command(0x4F) # set RAM y address count to 0X127; + self.send_data(Ystart & 0xff) + self.send_data((Ystart>>8) & 0x01) + + self.send_command(0x24) #Write Black and White image to RAM + for j in range(Height): + for i in range(Width): + if((j > Ystart-1) & (j < (Yend + 1)) & (i > Xstart-1) & (i < (Xend + 1))): + self.send_data(Image[i + j * Width]) + self.TurnOnDisplay_Partial() + + def display_4Gray(self, image): + self.send_command(0x24) + for i in range(0, 5808): #5808*4 46464 + 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 |= 0x00 + elif(temp2 == 0x00): + temp3 |= 0x01 + elif(temp2 == 0x80): + temp3 |= 0x01 + else: #0x40 + temp3 |= 0x00 + temp3 <<= 1 + + temp1 <<= 2 + temp2 = temp1&0xC0 + if(temp2 == 0xC0): + temp3 |= 0x00 + elif(temp2 == 0x00): + temp3 |= 0x01 + elif(temp2 == 0x80): + temp3 |= 0x01 + else : #0x40 + temp3 |= 0x00 + if(j!=1 or k!=1): + temp3 <<= 1 + temp1 <<= 2 + self.send_data(temp3) + + self.send_command(0x26) + for i in range(0, 5808): #5808*4 46464 + 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 |= 0x00 + elif(temp2 == 0x00): + temp3 |= 0x01 + elif(temp2 == 0x80): + temp3 |= 0x00 + else: #0x40 + temp3 |= 0x01 + temp3 <<= 1 + + temp1 <<= 2 + temp2 = temp1&0xC0 + if(temp2 == 0xC0): + temp3 |= 0x00 + elif(temp2 == 0x00): + temp3 |= 0x01 + elif(temp2 == 0x80): + temp3 |= 0x00 + else: #0x40 + temp3 |= 0x01 + if(j!=1 or k!=1): + temp3 <<= 1 + temp1 <<= 2 + self.send_data(temp3) + + self.TurnOnDisplay_4GRAY() + + def sleep(self): + self.send_command(0X10) + self.send_data(0x01) + + epdconfig.delay_ms(2000) + epdconfig.module_exit() +### END OF FILE ### diff --git a/pwnagotchi/ui/hw/libs/waveshare/v27inchv2/epdconfig.py b/pwnagotchi/ui/hw/libs/waveshare/v27inchv2/epdconfig.py new file mode 100644 index 0000000..4357cb4 --- /dev/null +++ b/pwnagotchi/ui/hw/libs/waveshare/v27inchv2/epdconfig.py @@ -0,0 +1,243 @@ +# /***************************************************************************** +# * | File : epdconfig.py +# * | Author : Waveshare team +# * | Function : Hardware underlying interface +# * | Info : +# *---------------- +# * | This version: V1.2 +# * | Date : 2022-10-29 +# * | 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 + PWR_PIN = 18 + + 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.PWR_PIN, self.GPIO.OUT) + self.GPIO.setup(self.BUSY_PIN, self.GPIO.IN) + + self.GPIO.output(self.PWR_PIN, 1) + + # 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.output(self.PWR_PIN, 0) + + self.GPIO.cleanup([self.RST_PIN, self.DC_PIN, self.CS_PIN, self.BUSY_PIN, self.PWR_PIN]) + + +class JetsonNano: + # Pin definition + RST_PIN = 17 + DC_PIN = 25 + CS_PIN = 8 + BUSY_PIN = 24 + PWR_PIN = 18 + + 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 spi_writebyte2(self, data): + for i in range(len(data)): + self.SPI.SYSFS_software_spi_transfer(data[i]) + + 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.PWR_PIN, self.GPIO.OUT) + self.GPIO.setup(self.BUSY_PIN, self.GPIO.IN) + + self.GPIO.output(self.PWR_PIN, 1) + + 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.output(self.PWR_PIN, 0) + + self.GPIO.cleanup([self.RST_PIN, self.DC_PIN, self.CS_PIN, self.BUSY_PIN, self.PWR_PIN]) + + +class SunriseX3: + # Pin definition + RST_PIN = 17 + DC_PIN = 25 + CS_PIN = 8 + BUSY_PIN = 24 + PWR_PIN = 18 + Flag = 0 + + def __init__(self): + import spidev + import Hobot.GPIO + + self.GPIO = Hobot.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): + # for i in range(len(data)): + # self.SPI.writebytes([data[i]]) + self.SPI.xfer3(data) + + def module_init(self): + if self.Flag == 0: + self.Flag = 1 + 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.PWR_PIN, self.GPIO.OUT) + self.GPIO.setup(self.BUSY_PIN, self.GPIO.IN) + + self.GPIO.output(self.PWR_PIN, 1) + + # SPI device, bus = 0, device = 0 + self.SPI.open(2, 0) + self.SPI.max_speed_hz = 4000000 + self.SPI.mode = 0b00 + return 0 + else: + return 0 + + def module_exit(self): + logger.debug("spi end") + self.SPI.close() + + logger.debug("close 5V, Module enters 0 power consumption ...") + self.Flag = 0 + self.GPIO.output(self.RST_PIN, 0) + self.GPIO.output(self.DC_PIN, 0) + self.GPIO.output(self.PWR_PIN, 0) + + self.GPIO.cleanup([self.RST_PIN, self.DC_PIN, self.CS_PIN, self.BUSY_PIN], self.PWR_PIN) + + +if os.path.exists('/sys/bus/platform/drivers/gpiomem-bcm2835'): + implementation = RaspberryPi() +elif os.path.exists('/sys/bus/platform/drivers/gpio-x3'): + implementation = SunriseX3() +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 ### diff --git a/pwnagotchi/ui/hw/libs/waveshare/v4/epd2in13_V4.py b/pwnagotchi/ui/hw/libs/waveshare/v4/epd2in13_V4.py new file mode 100644 index 0000000..84d6646 --- /dev/null +++ b/pwnagotchi/ui/hw/libs/waveshare/v4/epd2in13_V4.py @@ -0,0 +1,349 @@ +# ***************************************************************************** +# * | File : epd2in13_V4.py +# * | Author : Waveshare team +# * | Function : Electronic paper driver +# * | Info : +# *---------------- +# * | This version: V1.0 +# * | Date : 2023-06-25 +# # | 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 = 122 +EPD_HEIGHT = 250 + +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 + + ''' + function :Hardware reset + parameter: + ''' + def reset(self): + epdconfig.digital_write(self.reset_pin, 1) + epdconfig.delay_ms(20) + epdconfig.digital_write(self.reset_pin, 0) + epdconfig.delay_ms(2) + epdconfig.digital_write(self.reset_pin, 1) + epdconfig.delay_ms(20) + + ''' + function :send command + parameter: + command : Command register + ''' + 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) + + ''' + function :send data + parameter: + data : Write data + ''' + 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) + + # send a lot of data + def send_data2(self, data): + epdconfig.digital_write(self.dc_pin, 1) + epdconfig.digital_write(self.cs_pin, 0) + epdconfig.spi_writebyte2(data) + epdconfig.digital_write(self.cs_pin, 1) + + ''' + function :Wait until the busy_pin goes LOW + parameter: + ''' + 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") + + ''' + function : Turn On Display + parameter: + ''' + def TurnOnDisplay(self): + self.send_command(0x22) # Display Update Control + self.send_data(0xf7) + self.send_command(0x20) # Activate Display Update Sequence + self.ReadBusy() + + ''' + function : Turn On Display Fast + parameter: + ''' + def TurnOnDisplay_Fast(self): + self.send_command(0x22) # Display Update Control + self.send_data(0xC7) # fast:0x0c, quality:0x0f, 0xcf + self.send_command(0x20) # Activate Display Update Sequence + self.ReadBusy() + + ''' + function : Turn On Display Part + parameter: + ''' + def TurnOnDisplayPart(self): + self.send_command(0x22) # Display Update Control + self.send_data(0xff) # fast:0x0c, quality:0x0f, 0xcf + self.send_command(0x20) # Activate Display Update Sequence + self.ReadBusy() + + + ''' + function : Setting the display window + parameter: + xstart : X-axis starting position + ystart : Y-axis starting position + xend : End position of X-axis + yend : End position of Y-axis + ''' + def SetWindow(self, x_start, y_start, x_end, y_end): + self.send_command(0x44) # SET_RAM_X_ADDRESS_START_END_POSITION + # x point must be the multiple of 8 or the last 3 bits will be ignored + self.send_data((x_start>>3) & 0xFF) + self.send_data((x_end>>3) & 0xFF) + + self.send_command(0x45) # SET_RAM_Y_ADDRESS_START_END_POSITION + self.send_data(y_start & 0xFF) + self.send_data((y_start >> 8) & 0xFF) + self.send_data(y_end & 0xFF) + self.send_data((y_end >> 8) & 0xFF) + + ''' + function : Set Cursor + parameter: + x : X-axis starting position + y : Y-axis starting position + ''' + def SetCursor(self, x, y): + self.send_command(0x4E) # SET_RAM_X_ADDRESS_COUNTER + # x point must be the multiple of 8 or the last 3 bits will be ignored + self.send_data(x & 0xFF) + + self.send_command(0x4F) # SET_RAM_Y_ADDRESS_COUNTER + self.send_data(y & 0xFF) + self.send_data((y >> 8) & 0xFF) + + ''' + function : Initialize the e-Paper register + parameter: + ''' + def init(self): + if (epdconfig.module_init() != 0): + return -1 + # EPD hardware init start + self.reset() + + self.ReadBusy() + self.send_command(0x12) #SWRESET + self.ReadBusy() + + self.send_command(0x01) #Driver output control + self.send_data(0xf9) + self.send_data(0x00) + self.send_data(0x00) + + self.send_command(0x11) #data entry mode + self.send_data(0x03) + + self.SetWindow(0, 0, self.width-1, self.height-1) + self.SetCursor(0, 0) + + self.send_command(0x3c) + self.send_data(0x05) + + self.send_command(0x21) # Display update control + self.send_data(0x00) + self.send_data(0x80) + + self.send_command(0x18) + self.send_data(0x80) + + self.ReadBusy() + + return 0 + + ''' + function : Initialize the e-Paper fast register + parameter: + ''' + def init_fast(self): + if (epdconfig.module_init() != 0): + return -1 + # EPD hardware init start + self.reset() + + self.send_command(0x12) #SWRESET + self.ReadBusy() + + self.send_command(0x18) # Read built-in temperature sensor + self.send_command(0x80) + + self.send_command(0x11) # data entry mode + self.send_data(0x03) + + self.SetWindow(0, 0, self.width-1, self.height-1) + self.SetCursor(0, 0) + + self.send_command(0x22) # Load temperature value + self.send_data(0xB1) + self.send_command(0x20) + self.ReadBusy() + + self.send_command(0x1A) # Write to temperature register + self.send_data(0x64) + self.send_data(0x00) + + self.send_command(0x22) # Load temperature value + self.send_data(0x91) + self.send_command(0x20) + self.ReadBusy() + + return 0 + ''' + function : Display images + parameter: + image : Image data + ''' + def getbuffer(self, image): + img = image + imwidth, imheight = img.size + if(imwidth == self.width and imheight == self.height): + img = img.convert('1') + elif(imwidth == self.height and imheight == self.width): + # image has correct dimensions, but needs to be rotated + img = img.rotate(90, expand=True).convert('1') + else: + logger.warning("Wrong image dimensions: must be " + str(self.width) + "x" + str(self.height)) + # return a blank buffer + return [0x00] * (int(self.width/8) * self.height) + + buf = bytearray(img.tobytes('raw')) + return buf + + ''' + function : Sends the image buffer in RAM to e-Paper and displays + parameter: + image : Image data + ''' + def display(self, image): + self.send_command(0x24) + self.send_data2(image) + self.TurnOnDisplay() + + ''' + function : Sends the image buffer in RAM to e-Paper and fast displays + parameter: + image : Image data + ''' + def display_fast(self, image): + self.send_command(0x24) + self.send_data2(image) + self.TurnOnDisplay_Fast() + ''' + function : Sends the image buffer in RAM to e-Paper and partial refresh + parameter: + image : Image data + ''' + def displayPartial(self, image): + epdconfig.digital_write(self.reset_pin, 0) + epdconfig.delay_ms(1) + epdconfig.digital_write(self.reset_pin, 1) + + self.send_command(0x3C) # BorderWavefrom + self.send_data(0x80) + + self.send_command(0x01) # Driver output control + self.send_data(0xF9) + self.send_data(0x00) + self.send_data(0x00) + + self.send_command(0x11) # data entry mode + self.send_data(0x03) + + self.SetWindow(0, 0, self.width - 1, self.height - 1) + self.SetCursor(0, 0) + + self.send_command(0x24) # WRITE_RAM + self.send_data2(image) + self.TurnOnDisplayPart() + + ''' + function : Refresh a base image + parameter: + image : Image data + ''' + def displayPartBaseImage(self, image): + self.send_command(0x24) + self.send_data2(image) + + self.send_command(0x26) + self.send_data2(image) + self.TurnOnDisplay() + + ''' + function : Clear screen + parameter: + ''' + def Clear(self, color=0xFF): + if self.width%8 == 0: + linewidth = int(self.width/8) + else: + linewidth = int(self.width/8) + 1 + # logger.debug(linewidth) + + self.send_command(0x24) + self.send_data2([color] * int(self.height * linewidth)) + self.TurnOnDisplay() + + ''' + function : Enter sleep mode + parameter: + ''' + def sleep(self): + self.send_command(0x10) #enter deep sleep + self.send_data(0x01) + + epdconfig.delay_ms(2000) + epdconfig.module_exit() + +### END OF FILE ### diff --git a/pwnagotchi/ui/hw/libs/waveshare/v4/epdconfig.py b/pwnagotchi/ui/hw/libs/waveshare/v4/epdconfig.py new file mode 100644 index 0000000..b415940 --- /dev/null +++ b/pwnagotchi/ui/hw/libs/waveshare/v4/epdconfig.py @@ -0,0 +1,243 @@ +# /***************************************************************************** +# * | File : epdconfig.py +# * | Author : Waveshare team +# * | Function : Hardware underlying interface +# * | Info : +# *---------------- +# * | This version: V1.2 +# * | Date : 2022-10-29 +# * | 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 + PWR_PIN = 18 + + 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.PWR_PIN, self.GPIO.OUT) + self.GPIO.setup(self.BUSY_PIN, self.GPIO.IN) + + self.GPIO.output(self.PWR_PIN, 1) + + # 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.output(self.PWR_PIN, 0) + + self.GPIO.cleanup([self.RST_PIN, self.DC_PIN, self.CS_PIN, self.BUSY_PIN, self.PWR_PIN]) + + +class JetsonNano: + # Pin definition + RST_PIN = 17 + DC_PIN = 25 + CS_PIN = 8 + BUSY_PIN = 24 + PWR_PIN = 18 + + 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 spi_writebyte2(self, data): + for i in range(len(data)): + self.SPI.SYSFS_software_spi_transfer(data[i]) + + 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.PWR_PIN, self.GPIO.OUT) + self.GPIO.setup(self.BUSY_PIN, self.GPIO.IN) + + self.GPIO.output(self.PWR_PIN, 1) + + 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.output(self.PWR_PIN, 0) + + self.GPIO.cleanup([self.RST_PIN, self.DC_PIN, self.CS_PIN, self.BUSY_PIN, self.PWR_PIN]) + + +class SunriseX3: + # Pin definition + RST_PIN = 17 + DC_PIN = 25 + CS_PIN = 8 + BUSY_PIN = 24 + PWR_PIN = 18 + Flag = 0 + + def __init__(self): + import spidev + import Hobot.GPIO + + self.GPIO = Hobot.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): + # for i in range(len(data)): + # self.SPI.writebytes([data[i]]) + self.SPI.xfer3(data) + + def module_init(self): + if self.Flag == 0: + self.Flag = 1 + 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.PWR_PIN, self.GPIO.OUT) + self.GPIO.setup(self.BUSY_PIN, self.GPIO.IN) + + self.GPIO.output(self.PWR_PIN, 1) + + # SPI device, bus = 0, device = 0 + self.SPI.open(2, 0) + self.SPI.max_speed_hz = 4000000 + self.SPI.mode = 0b00 + return 0 + else: + return 0 + + def module_exit(self): + logger.debug("spi end") + self.SPI.close() + + logger.debug("close 5V, Module enters 0 power consumption ...") + self.Flag = 0 + self.GPIO.output(self.RST_PIN, 0) + self.GPIO.output(self.DC_PIN, 0) + self.GPIO.output(self.PWR_PIN, 0) + + self.GPIO.cleanup([self.RST_PIN, self.DC_PIN, self.CS_PIN, self.BUSY_PIN], self.PWR_PIN) + + +if os.path.exists('/sys/bus/platform/drivers/gpiomem-bcm2835'): + implementation = RaspberryPi() +elif os.path.exists('/sys/bus/platform/drivers/gpio-x3'): + implementation = SunriseX3() +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 ### diff --git a/pwnagotchi/ui/hw/waveshare213g.py b/pwnagotchi/ui/hw/waveshare213g.py new file mode 100644 index 0000000..4ce2cf1 --- /dev/null +++ b/pwnagotchi/ui/hw/waveshare213g.py @@ -0,0 +1,46 @@ +import logging + +import pwnagotchi.ui.fonts as fonts +from pwnagotchi.ui.hw.base import DisplayImpl + + +class Waveshare213g(DisplayImpl): + def __init__(self, config): + super(Waveshare213g, self).__init__(config, 'waveshare213g') + self._display = None + + def layout(self): + fonts.setup(10, 8, 10, 35, 25, 9) + self._layout['width'] = 250 + self._layout['height'] = 122 + self._layout['face'] = (0, 40) + self._layout['name'] = (5, 20) + self._layout['channel'] = (0, 0) + self._layout['aps'] = (28, 0) + self._layout['uptime'] = (185, 0) + self._layout['line1'] = [0, 14, 250, 14] + self._layout['line2'] = [0, 108, 250, 108] + self._layout['friend_face'] = (0, 92) + self._layout['friend_name'] = (40, 94) + self._layout['shakes'] = (0, 109) + self._layout['mode'] = (225, 109) + self._layout['status'] = { + 'pos': (125, 20), + 'font': fonts.status_font(fonts.Medium), + 'max': 20 + } + return self._layout + + def initialize(self): + logging.info("initializing waveshare g version display") + from pwnagotchi.ui.hw.libs.waveshare.v213g.epd2in13g import EPD + self._display = EPD() + self._display.init() + self._display.Clear() + + def render(self, canvas): + buf = self._display.getbuffer(canvas) + self._display.display(buf) + + def clear(self): + self._display.Clear() diff --git a/pwnagotchi/ui/hw/waveshare213inb_v4.py b/pwnagotchi/ui/hw/waveshare213inb_v4.py new file mode 100644 index 0000000..9f45844 --- /dev/null +++ b/pwnagotchi/ui/hw/waveshare213inb_v4.py @@ -0,0 +1,71 @@ +import logging + +import pwnagotchi.ui.fonts as fonts +from pwnagotchi.ui.hw.base import DisplayImpl +from PIL import Image + +class Waveshare213bV4(DisplayImpl): + def __init__(self, config): + super(Waveshare213bV4, self).__init__(config, 'waveshare213inb_v4') + self._display = None + + def layout(self): + if self.config['color'] == 'black': + fonts.setup(10, 9, 10, 35, 25, 9) + self._layout['width'] = 250 + self._layout['height'] = 122 + self._layout['face'] = (0, 40) + self._layout['name'] = (5, 20) + self._layout['channel'] = (0, 0) + self._layout['aps'] = (28, 0) + self._layout['uptime'] = (185, 0) + self._layout['line1'] = [0, 14, 250, 14] + self._layout['line2'] = [0, 108, 250, 108] + self._layout['friend_face'] = (0, 92) + self._layout['friend_name'] = (40, 94) + self._layout['shakes'] = (0, 109) + self._layout['mode'] = (225, 109) + self._layout['status'] = { + 'pos': (125, 20), + 'font': fonts.status_font(fonts.Medium), + 'max': 20 + } + else: + fonts.setup(10, 8, 10, 25, 25, 9) + self._layout['width'] = 212 + self._layout['height'] = 104 + self._layout['face'] = (0, 26) + self._layout['name'] = (5, 15) + self._layout['channel'] = (0, 0) + self._layout['aps'] = (28, 0) + self._layout['status'] = (91, 15) + self._layout['uptime'] = (147, 0) + self._layout['line1'] = [0, 12, 212, 12] + self._layout['line2'] = [0, 92, 212, 92] + self._layout['friend_face'] = (0, 76) + self._layout['friend_name'] = (40, 78) + self._layout['shakes'] = (0, 93) + self._layout['mode'] = (187, 93) + self._layout['status'] = { + 'pos': (125, 20), + 'font': fonts.status_font(fonts.Medium), + 'max': 14 + } + return self._layout + + def initialize(self): + logging.info("initializing waveshare 2.13inb v4 display") + from pwnagotchi.ui.hw.libs.waveshare.v213inb_v4.epd2in13b_V4 import EPD + self._display = EPD() + self._display.init() + self._display.Clear() + + def render(self, canvasBlack = None, canvasRed = None): + buffer = self._display.getbuffer + image = Image.new('1', (self._layout['height'], self._layout['width'])) + imageBlack = image if canvasBlack is None else canvasBlack + imageRed = image if canvasRed is None else canvasRed + self._display.display(buffer(imageBlack), buffer(imageRed)) + + def clear(self): + self._display.Clear() diff --git a/pwnagotchi/ui/hw/waveshare27inchv2.py b/pwnagotchi/ui/hw/waveshare27inchv2.py new file mode 100644 index 0000000..b593e32 --- /dev/null +++ b/pwnagotchi/ui/hw/waveshare27inchv2.py @@ -0,0 +1,46 @@ +import logging + +import pwnagotchi.ui.fonts as fonts +from pwnagotchi.ui.hw.base import DisplayImpl + + +class Waveshare27inchV2(DisplayImpl): + def __init__(self, config): + super(Waveshare27inchV2, self).__init__(config, 'waveshare27inchv2') + self._display = None + + def layout(self): + fonts.setup(10, 9, 10, 35, 25, 9) + self._layout['width'] = 264 + self._layout['height'] = 176 + self._layout['face'] = (66, 27) + self._layout['name'] = (5, 73) + self._layout['channel'] = (0, 0) + self._layout['aps'] = (28, 0) + self._layout['uptime'] = (199, 0) + self._layout['line1'] = [0, 14, 264, 14] + self._layout['line2'] = [0, 162, 264, 162] + self._layout['friend_face'] = (0, 146) + self._layout['friend_name'] = (40, 146) + self._layout['shakes'] = (0, 163) + self._layout['mode'] = (239, 163) + self._layout['status'] = { + 'pos': (38, 93), + 'font': fonts.status_font(fonts.Medium), + 'max': 40 + } + return self._layout + + def initialize(self): + logging.info("initializing waveshare v2 2.7 inch display") + from pwnagotchi.ui.hw.libs.waveshare.v27inchv2.epd2in7_V2 import EPD + self._display = EPD() + self._display.init() + self._display.Clear() + + def render(self, canvas): + buf = self._display.getbuffer(canvas) + self._display.display(buf) + + def clear(self): + self._display.Clear() diff --git a/pwnagotchi/ui/hw/waveshare4.py b/pwnagotchi/ui/hw/waveshare4.py new file mode 100644 index 0000000..b6d6598 --- /dev/null +++ b/pwnagotchi/ui/hw/waveshare4.py @@ -0,0 +1,50 @@ +import logging + +import pwnagotchi.ui.fonts as fonts +from pwnagotchi.ui.hw.base import DisplayImpl +from PIL import Image + +class WaveshareV4(DisplayImpl): + def __init__(self, config): + super(WaveshareV4, self).__init__(config, 'waveshare_4') + self._display = None + + def layout(self): + fonts.setup(10, 9, 10, 35, 25, 9) + self._layout['width'] = 250 + self._layout['height'] = 122 + self._layout['face'] = (0, 40) + self._layout['name'] = (5, 20) + self._layout['channel'] = (0, 0) + self._layout['aps'] = (28, 0) + self._layout['uptime'] = (185, 0) + self._layout['line1'] = [0, 14, 250, 14] + self._layout['line2'] = [0, 108, 250, 108] + self._layout['friend_face'] = (0, 92) + self._layout['friend_name'] = (40, 94) + self._layout['shakes'] = (0, 109) + self._layout['mode'] = (225, 109) + self._layout['status'] = { + 'pos': (125, 20), + 'font': fonts.status_font(fonts.Medium), + 'max': 20 + } + return self._layout + + + + def initialize(self): + logging.info("initializing waveshare v4 display") + from pwnagotchi.ui.hw.libs.waveshare.v4.epd2in13_V4 import EPD + self._display = EPD() + self._display.init() + self._display.Clear(0xFF) + + + def render(self, canvas): + buf = self._display.getbuffer(canvas) + self._display.displayPartial(buf) + + + def clear(self): + self._display.Clear(0xFF) diff --git a/pwnagotchi/ui/web/static/images/pwnagotchi.png b/pwnagotchi/ui/web/static/images/pwnagotchi.png new file mode 100644 index 0000000..f66ff9d Binary files /dev/null and b/pwnagotchi/ui/web/static/images/pwnagotchi.png differ diff --git a/requirements.in b/requirements.in new file mode 100644 index 0000000..88afabf --- /dev/null +++ b/requirements.in @@ -0,0 +1,93 @@ +# To convert into requirements.txt use 'pip-compile --resolver=backtracking --strip-extras --output-file=requirements.txt requirements.in' + +# If you get "error: no such option: --prefer-binary" then you need to run: +# pip3 install --upgrade "pip>=20.2" +--prefer-binary +--index-url "https://nexus.chadwaltercummings.me/repository/www.piwheels.org/simple" +--extra-index-url "https://nexus.chadwaltercummings.me/repository/pypi.org/simple" + +# Used for bluetooth tethering plugin. +dbus-python~=1.2 + +# Used for parsing LastSession logs in manual mode. +file-read-backwards~=2.0 + +# Only using basic Flask and Flask plugin features. +# Should be kept up-to-date as Flask is notorious for breaking +# environments with their extremely loose dependency definitions. +flask-cors~=3.0 +flask-wtf~=1.0 +flask~=1.0 + +# Used for modeling AI parameters. +# NOTE: stable-baselines wants gym[atari,classic_control] but we +# can't satisfy the "atari" extra because it requires ale-py +# which has no source distributions or RasPi wheels. +# Using pip's new backtracking resolver from pip>=20.3 is required +# as it improves handling of extras required by indirect dependencies. +# NOTE: gym v0.22 modified the gym.Env API. +gym~=0.14,<0.22 + +# Used for Inky pHAT and wHAT displays. +inky~=1.2 + +# Used in the AI and UI layers. +# Only using basic numpy features. +numpy~=1.21.4 # Moved to 1.21.4 from 1.20 + +# Used in the UI layer. +# Only using core PIL features (Image, ImageFont, ImageDraw). +# Very stable library, should be safe to upgrade. +Pillow>=5.4 + +# Used for pwngrid identity verification (PKCS1, RSA, SHA256). +# Very stable library, should be safe to upgrade. +pycryptodome~=3.9 + +# Used for GPS plugin to parse a GPS datetime string. +python-dateutil~=2.8 + +# Used exclusively to convert legacy YAML configs to TOML. +PyYAML>=5.3 + +# Used for HTTP requests with bettercap, pwngrid, and plugins. +# Only using core library features (GET, POST, Sessions). +# Very stable library, should be safe to upgrade. +requests~=2.21 + +# Used for WiFi pwnage and WiGLE plugin. +scapy~=2.4 + +# I2C/SPI communication with displays, also used by some plugins. +smbus2~=0.4 +spidev~=3.5 + +# Primary AI library. Safe to upgrade as v3 is a different package. +# Upgrading to stable-baselines3 is currently impossible because +# it depends on PyTorch which requires a 64-bit processor. +stable-baselines~=2.7 + +# stable-baselines made a mistake. +# stable-baselines has a tensorflow requirement of ">=1.8.0,<2.0.0", +# but the requirement is the result of a calculation during setup. +# As a result, the requirement is entirely missing from the wheel file. +# Furthermore, "<2.0.0" will fail because tensorflow v1.14 contains +# breaking API changes in preparation for their v2.X release. +tensorflow>=1.8.0,<1.14.0 + +# Used for loading and writing configs. +toml~=0.10 + +# Used for communicating with bettercap. +websockets~=8.1 + +# WARNING: conflict prevention hack! +# flask v1.X requires "Jinja2 >= 2.10, < 3.0" +# Jinja2 v2.X requires "MarkupSafe >= 0.23" for a deprecated +# function that was later removed in MarkupSafe v2.1.0. +# Jinja2 v3.0 no longer uses the deprecated function but +# falls outside the version range requested by flask. +MarkupSafe<2.1.0 + +# Addressing the "TypeError: Descriptors cannot not be created directly." error. +protobuf==3.20.* diff --git a/scripts/openbsd_connection_share.sh b/scripts/openbsd_connection_share.sh new file mode 100644 index 0000000..fd20f44 --- /dev/null +++ b/scripts/openbsd_connection_share.sh @@ -0,0 +1,20 @@ +#!/bin/sh + +USB_IFACE=$(ifconfig urndis0 | grep urndis0 | awk '{print $1}' | tr -d ':') +USB_IP=${2:-10.0.0.1} + +if test $(whoami) != root; then + doas "$0" "$@" + exit $? +fi + +if [ "${USB_IFACE}" == "urndis0" ]; then + ifconfig ${USB_IFACE} ${USB_IP} + sysctl -w net.inet.ip.forwarding=1 + echo "match out on egress inet from ${USB_IFACE}:network to any nat-to (egress:0)" | pfctl -f - + pfctl -f /etc/pf.conf + echo "sharing connecting from upstream interface to usb interface ${USB_IFACE} ..." +else + echo "can't find usb interface with ip ${USB_IFACE}" + exit 1 +fi