diff --git a/.github/workflows/publish.yml b/.github/workflows/publish.yml index fec25bfd..fbb3c19c 100644 --- a/.github/workflows/publish.yml +++ b/.github/workflows/publish.yml @@ -8,205 +8,65 @@ on: required: true jobs: - - publish: + build: runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 - - name: Remove unnecessary directories - run: | - sudo rm -rf /usr/share/dotnet - sudo rm -rf /opt/ghc - sudo rm -rf /usr/local/share/boost - sudo rm -rf "$AGENT_TOOLSDIRECTORY" + - name: Extract version from file + id: get_version + run: | + VERSION=$(cut -d "'" -f2 < pwnagotchi/_version.py) + echo "VERSION=$VERSION" >> $GITHUB_ENV - - name: Check disk space - run: df -BG + - name: Get latest tag + uses: actions-ecosystem/action-get-latest-tag@v1 + id: get-latest-tag - - name: Checkout code - uses: actions/checkout@v4 + - name: Set LAST_VERSION as an environment variable + run: echo "LAST_VERSION=${{ steps.get-latest-tag.outputs.tag }}" >> $GITHUB_ENV - - name: Validate tag - id: tag-setter - run: | - TAG=${{ github.event.inputs.version }} - if [[ $TAG =~ ^v[0-9]+\.[0-9]+\.[0-9]+$ ]]; then - echo "Tag $TAG is valid." - echo "TAG=$TAG" >> $GITHUB_OUTPUT - else - echo "Tag $TAG is not a valid semantic version. Aborting." - exit 1 - fi + - name: Generate release notes + id: generate_release_notes + run: | + COMMITS=$(git log --merges --pretty=format:"* %s" $LAST_VERSION--$VERSION | sed 's/$/\\n/g') + CONTRIBUTORS=$(git shortlog -sn $LAST_VERSION--$VERSION | awk '{print "* @" $2}' | sed 's/$/\\n/g') + RELEASE_BODY="**Full Changelog**: https://github.com/jayofelony/pwnagotchi/compare/$LAST_VERSION...$VERSION" + echo "RELEASE_BODY=$RELEASE_BODY" >> $GITHUB_ENV - - name: Set up Python - uses: actions/setup-python@v5 - with: - python-version: 3.9 + - name: Install qemu dependencies + run: sudo apt update && sudo apt install qemu-user-static qemu-utils xz-utils -y - - name: Install dependencies - run: | - sudo apt-get update && sudo apt-get install -y libdbus-1-dev curl unzip gettext qemu-utils qemu qemu-user-static binfmt-support - pip install -r requirements.txt + - name: Build img file + run: ls -la .; pwd; make all - - name: Update QEMU - run: | - sudo update-binfmts --enable qemu-aarch64 - echo $(ls /usr/bin/qemu-aarch64-static) + - name: Transfer 32bit.img to docker and give permissions + run: sudo chown runner:docker "pwnagotchi-32bit.img" - - name: Restart binfmt-support - run: sudo service binfmt-support restart + - name: Transfer 64bit.img to docker and give permissions + run: sudo chown runner:docker "pwnagotchi-64bit.img" - - name: Mount binfmt_misc - run: | - if ! grep -qs '/proc/sys/fs/binfmt_misc ' /proc/mounts; then - echo "Mounting binfmt_misc" - sudo mount binfmt_misc -t binfmt_misc /proc/sys/fs/binfmt_misc - fi + - name: PiShrink + run: | + wget https://raw.githubusercontent.com/Drewsif/PiShrink/master/pishrink.sh + chmod +x pishrink.sh + sudo mv pishrink.sh /usr/local/bin + find /home/runner/work/ -type f -name "*.img" -exec sudo pishrink.sh -aZ {} \; - - name: Restart binfmt-support - run: sudo service binfmt-support restart - - - name: Update Languages - run: make update_langs - - - name: Compile Languages - run: make compile_langs + - name: Change name of 32.img.xz to add version + run: mv "pwnagotchi-32bit.img.xz" "pwnagotchi-$VERSION-32bit.img.xz" - - name: Check disk space - run: df -BG + - name: Change name of 64.img.xz to add version + run: mv "pwnagotchi-64bit.img.xz" "pwnagotchi-$VERSION-64bit.img.xz" - - name: Check qemu-user-static package - run: | - echo "Checking qemu-user-static package..." - dpkg -s qemu-user-static && echo "qemu-user-static is installed." || echo "qemu-user-static is NOT installed." - - - name: Check binfmt-support service - run: | - echo "Checking binfmt-support service..." - service binfmt-support status && echo "binfmt-support service is running." || echo "binfmt-support service is NOT running." - - - name: Check binfmt_misc filesystem - run: | - echo "Checking binfmt_misc filesystem..." - mount | grep binfmt_misc && echo "binfmt_misc is mounted." || echo "binfmt_misc is NOT mounted." - echo $(ls /proc/sys/fs/binfmt_misc | grep qemu-aarch64) - - - name: Run Makefile - run: make - env: - PWN_VERSION: ${{ steps.tag-setter.outputs.TAG }} - - - name: PiShrink - run: | - wget https://raw.githubusercontent.com/Drewsif/PiShrink/master/pishrink.sh - chmod +x pishrink.sh - sudo mv pishrink.sh /usr/local/bin - find /home/runner/work/ -type f -name "*.img" -exec sudo pishrink.sh {} \; - - - name: Compress .img files - run: | - find /home/runner/work/ -type f -name "*.img" -exec xz --no-warn {} \; - - - name: Create tag - uses: actions/github-script@v7 - with: - script: | - const version = "${{ steps.tag-setter.outputs.TAG }}" - console.log(`Creating tag ${version}`) - await github.rest.git.createRef({ - owner: context.repo.owner, - repo: context.repo.repo, - ref: `refs/tags/${version}`, - sha: context.sha - }) - - - name: Create Release - id: create_release - uses: actions/github-script@v7 - with: - script: | - const tag = "${{ steps.tag-setter.outputs.TAG }}" - console.log(`Creating release with tag: ${tag}`) - const release = await github.rest.repos.createRelease({ - owner: context.repo.owner, - repo: context.repo.repo, - tag_name: tag, - name: tag, - draft: false, - prerelease: true, - generate_release_notes: true - }) - console.log(`Created release with id: ${release.data.id}`) - return release.data.id - - - name: Upload Release Asset - id: upload-release-asset - uses: actions/github-script@v7 - env: - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - with: - script: | - const fs = require('fs'); - const path = require('path'); - const release_id = "${{ steps.create_release.outputs.result }}"; - const asset_content_type = 'application/octet-stream'; - const distDir = '/home/runner/work/'; - - const uploadFile = async (filePath) => { - if (fs.lstatSync(filePath).isDirectory()) { - const files = fs.readdirSync(filePath); - for (const file of files) { - await uploadFile(path.join(filePath, file)); - } - } else { - // Check if the file has a .xz extension - if (path.extname(filePath) === '.xz') { - console.log(`Uploading ${filePath}...`); - - const asset_name = path.basename(filePath); - const asset_size = fs.statSync(filePath).size; - const asset = fs.createReadStream(filePath); - - const response = await github.rest.repos.uploadReleaseAsset({ - owner: context.repo.owner, - repo: context.repo.repo, - release_id: release_id, - name: asset_name, - data: asset, - headers: { - 'content-type': asset_content_type, - 'content-length': asset_size - } - }); - - console.log(`Uploaded ${filePath}: ${response.data.browser_download_url}`); - } - } - } - - await uploadFile(distDir); - - - name: Update Release - uses: actions/github-script@v7 - with: - script: | - const release_id = "${{ steps.create_release.outputs.result }}" - console.log(`Updating release with id: ${release_id}`) - github.rest.repos.updateRelease({ - owner: context.repo.owner, - repo: context.repo.repo, - release_id: release_id, - tag_name: "${{ steps.tag-setter.outputs.TAG }}", - name: "${{ steps.tag-setter.outputs.TAG }}", - draft: false, - prerelease: false - }) - - - name: Save environment variable - run: echo "${{ steps.tag-setter.outputs.TAG }}" > env_var.txt - - - name: Upload artifact - uses: actions/upload-artifact@v4 - with: - name: env-var - path: env_var.txt \ No newline at end of file + - name: Release + uses: softprops/action-gh-release@v1 + with: + prerelease: true + tag_name: v${{ env.VERSION }} + name: Pwnagotchi v${{ env.VERSION }} + files: | + pwnagotchi-${{ env.VERSION }}-32bit.img.xz + pwnagotchi-${{ env.VERSION }}-64bit.img.xz + body: ${{ env.RELEASE_BODY }} \ No newline at end of file diff --git a/Makefile b/Makefile index 898da0ea..668b4fa4 100644 --- a/Makefile +++ b/Makefile @@ -1,6 +1,6 @@ -PACKER_VERSION := 1.10.0 +PACKER_VERSION := 1.10.1 PWN_HOSTNAME := pwnagotchi -PWN_VERSION := ${PWN_VERSION} +PWN_VERSION := $(shell cut -d"'" -f2 < pwnagotchi/_version.py) MACHINE_TYPE := $(shell uname -m) ifneq (,$(filter x86_64,$(MACHINE_TYPE))) @@ -25,7 +25,8 @@ ifneq (,$(UNSHARE)) UNSHARE := $(UNSHARE) --uts endif -all: clean image clean +# sudo apt-get install qemu-user-static qemu-utils +all: clean packer image update_langs: @for lang in pwnagotchi/locale/*/; do\ @@ -39,30 +40,24 @@ compile_langs: ./scripts/language.sh compile $$(basename $$lang); \ done -PACKER := ~/pwnagotchi/packer -PACKER_URL := https://releases.hashicorp.com/packer/$(PACKER_VERSION)/packer_$(PACKER_VERSION)_linux_$(GOARCH).zip -$(PACKER): - mkdir -p $(@D) - curl -L "$(PACKER_URL)" -o $(PACKER).zip - unzip $(PACKER).zip -d $(@D) - rm $(PACKER).zip - chmod +x $@ +packer: clean + curl https://releases.hashicorp.com/packer/$(PACKER_VERSION)/packer_$(PACKER_VERSION)_linux_amd64.zip -o /tmp/packer.zip + unzip /tmp/packer.zip -d /tmp + sudo mv /tmp/packer /usr/bin/packer -SDIST := dist/pwnagotchi-$(PWN_VERSION).tar.gz -$(SDIST): setup.py pwnagotchi - python3 setup.py sdist +image: clean packer + export LC_ALL=en_GB.UTF-8 + cd builder && sudo /usr/bin/packer init combined.json.pkr.hcl && sudo $(UNSHARE) /usr/bin/packer build -var "pwn_hostname=$(PWN_HOSTNAME)" -var "pwn_version=$(PWN_VERSION)" combined.json.pkr.hcl -# Building the image requires packer, but don't rebuild the image just because packer updated. -pwnagotchi: | $(PACKER) +bullseye: clean packer + export LC_ALL=en_GB.UTF-8 + cd builder && sudo /usr/bin/packer init data/32bit/raspberrypi32.json.pkr.hcl && sudo $(UNSHARE) /usr/bin/packer build -var "pwn_hostname=$(PWN_HOSTNAME)" -var "pwn_version=$(PWN_VERSION)" data/32bit/raspberrypi32.json.pkr.hcl + sudo pishrink -vaZ pwnagotchi-32bit.img -# If the packer or ansible files are updated, rebuild the image. -pwnagotchi: $(SDIST) builder/pwnagotchi.json.pkr.hcl builder/raspberrypi64.yml $(shell find builder/data -type f) - - cd builder && $(PACKER) init pwnagotchi.json.pkr.hcl && sudo $(UNSHARE) $(PACKER) build -var "pwn_hostname=$(PWN_HOSTNAME)" -var "pwn_version=$(PWN_VERSION)" pwnagotchi.json.pkr.hcl - -.PHONY: image -image: pwnagotchi +bookworm: clean packer + export LC_ALL=en_GB.UTF-8 + cd builder && sudo /usr/bin/packer init data/64bit/raspberrypi64.json.pkr.hcl && sudo $(UNSHARE) /usr/bin/packer build -var "pwn_hostname=$(PWN_HOSTNAME)" -var "pwn_version=$(PWN_VERSION)" data/64bit/raspberrypi64.json.pkr.hcl + sudo pishrink -vaZ pwnagotchi-64bit.img clean: - - rm -rf dist pwnagotchi.egg-info - - rm -f $(PACKER) \ No newline at end of file + - rm -rf /tmp/packer* diff --git a/README-google.md b/README-google.md index 7a942f3b..e9b75b94 100644 --- a/README-google.md +++ b/README-google.md @@ -11,7 +11,7 @@ Select ‘Credentials’ from the left menu, click ‘Create Credentials’, sel Now, the product name and consent screen need to be set -> click ‘Configure consent screen’ and follow the instructions. Once finished: -Select ‘Application type’ to be Web application. +Select ‘Application type’ to be Desktop application. Enter an appropriate name. @@ -19,7 +19,7 @@ Input http://localhost/ for ‘Authorized redirect URIs’. Select the correct oauth scope: - - drive.file + - drive - drive.install Click ‘Create’. diff --git a/README.md b/README.md index aa005160..ceaa1a67 100644 --- a/README.md +++ b/README.md @@ -1,30 +1,7 @@ -# Pwnagotchi-Torch -Release
-**This fork of [Pwnagotchi](https://www.pwnagotchi.ai) is only for 64-bit Raspberry Pi's. Such as the 02W, 3(b+) and 4(b) and the new Raspberry Pi 5!!.** - -The RPi5 can only be used headless currently. (without display.) - - -If you are using an older 32-bit version Raspberry Pi, ZeroWH, use this [fork](https://github.com/jayofelony/pwnagotchi-torch/releases/tag/v2.6.4) and make sure you download the `armhf` version. - ---- -Download the latest image file [here](https://github.com/jayofelony/pwnagotchi-bookworm/releases/tag/v2.7.9), and let it auto-update from here on out. - -**Use RPi imager to flash, please don't flash a new user as this will mess with logs created.** - - Select `Use Custom Image` - - Browse for the downloaded image file - - Select No under `Use OS Customization` - -SSH credentials are `pi/raspberry`. - -# Donations: -I would like to thank -- [findingmoist](https://github.com/findingmoist) -- [kr4k0n](https://github.com/kr4k0n) - -for donating! - -[Pwnagotchi-Torch](https://www.patreon.com/pwnagotchi_torch) +# Pwnagotchi +This is the main source for all forks: +- RPiZeroW (32bit) +- RPiZero2W, RPi3, RPi4, RPi5 (64bit) [GH Sponsor](https://github.com/sponsors/jayofelony) diff --git a/bin/pwnagotchi b/bin/pwnagotchi index 81f2f0f2..df5a8724 100755 --- a/bin/pwnagotchi +++ b/bin/pwnagotchi @@ -7,6 +7,7 @@ import sys import toml import requests import os +import re import pwnagotchi from pwnagotchi import utils @@ -50,7 +51,6 @@ def pwnagotchi_cli(): agent.mode = 'auto' agent.start() - config = agent.config() while True: try: @@ -60,7 +60,7 @@ def pwnagotchi_cli(): channels = agent.get_access_points_by_channel() # for each channel for ch, aps in channels: - time.sleep(0.2) # This is to make sure it doesn't error (https://github.com/seemoo-lab/nexmon/issues/596) + time.sleep(0.2) agent.set_channel(ch) if not agent.is_stale() and agent.any_activity(): @@ -68,9 +68,6 @@ def pwnagotchi_cli(): # for each ap on this channel for ap in aps: - if ap['mac'][:13].lower in config['main']['whitelist'] or ap['hostname'] in config['main']['whitelist']: - logging.info(f"Found your MAC address {ap['mac']} - {config['main']['whitelist']}") - continue # send an association frame in order to get for a PMKID agent.associate(ap) # deauth all client stations in order to get a full handshake @@ -92,10 +89,8 @@ def pwnagotchi_cli(): except Exception as e: if str(e).find("wifi.interface not set") > 0: - logging.exception( - "main loop exception due to unavailable wifi device, likely programmatically disabled (%s)", e) - logging.info( - "sleeping 60 seconds then advancing to next epoch to allow for cleanup code to trigger") + logging.exception("main loop exception due to unavailable wifi device, likely programmatically disabled (%s)", e) + logging.info("sleeping 60 seconds then advancing to next epoch to allow for cleanup code to trigger") time.sleep(60) agent.next_epoch() else: @@ -137,6 +132,8 @@ def pwnagotchi_cli(): help="Print the configuration.") # Jayofelony added these + parser.add_argument('--wizard', dest="wizard", action="store_true", default=False, + help="Interactive installation of your personal configuration.") parser.add_argument('--check-update', dest="check_update", action="store_true", default=False, help="Check for updates on Pwnagotchi. And tells current version.") parser.add_argument('--donate', dest="donate", action="store_true", default=False, @@ -161,30 +158,137 @@ def pwnagotchi_cli(): print(pwnagotchi.__version__) sys.exit(0) + if args.wizard: + def is_valid_hostname(hostname): + if len(hostname) > 255: + return False + if hostname[-1] == ".": + hostname = hostname[:-1] # strip exactly one dot from the right, if present + allowed = re.compile("(?!-)[A-Z\d-]{1,63}(? 0: + f.write("main.whitelist = [\n") + for x in range(int(pwn_whitelist)): + ssid = input("SSID (Name): ") + bssid = input("BSSID (MAC): ") + f.write(f"\t\"{ssid}\",\n") + f.write(f"\t\"{bssid}\",\n") + f.write("]\n") + # set bluetooth tether + pwn_bluetooth = input("Do you want to enable BT-Tether?\n\n" + "[Y/N] ") + if pwn_bluetooth.lower() in ('y', 'yes'): + f.write("main.plugins.bt-tether.enabled = true\n\n") + pwn_bluetooth_device = input("What device do you use? Android or iOS?\n\n" + "Device: ") + if pwn_bluetooth_device.lower() == "android": + f.write("main.plugins.bt-tether.devices.android-phone.enabled = true\n") + pwn_bluetooth_mac = input("What is the bluetooth MAC of your device?\n\n" + "MAC: ") + if pwn_bluetooth_mac != "": + f.write(f"main.plugins.bt-tether.devices.android-phone.mac = \"{pwn_bluetooth_mac}\"\n") + elif pwn_bluetooth_device.lower() == "ios": + f.write("main.plugins.bt-tether.devices.ios-phone.enabled = true\n") + pwn_bluetooth_mac = input("What is the bluetooth MAC of your device?\n\n" + "MAC: ") + if pwn_bluetooth_mac != "": + f.write(f"main.plugins.bt-tether.devices.ios-phone.mac = \"{pwn_bluetooth_mac}\"\n") + # set up display settings + pwn_display_enabled = input("Do you want to enable a display?\n\n" + "[Y/N]: ") + if pwn_display_enabled.lower() in ('y', 'yes'): + f.write("ui.display.enabled = true\n") + pwn_display_type = input("What display do you use?\n\n" + "Be sure to check for the correct display type @ \n" + "https://github.com/jayofelony/pwnagotchi/blob/master/pwnagotchi/utils.py#L240-L431\n\n" + "Display type: ") + if pwn_display_type != "": + f.write(f"ui.display.type = \"{pwn_display_type}\"\n") + pwn_display_invert = input("Do you want to invert the display colors?\n" + "N = Black background\n" + "Y = White background\n\n" + "[Y/N]: ") + if pwn_display_invert.lower() in ('y', 'yes'): + f.write("ui.invert = true\n") + f.close() + if pwn_bluetooth.lower() in ('y', 'yes'): + if pwn_bluetooth_device.lower == "android": + print("To visit the webui when connected with your phone, visit: http://192.168.44.44:8080\n" + "Your configuration is done, and I will restart in 5 seconds.") + elif pwn_bluetooth_device.lower == "ios": + print("To visit the webui when connected with your phone, visit: http://172.20.10.6:8080\n" + "Your configuration is done, and I will restart in 5 seconds.") + else: + print("Your configuration is done, and I will restart in 5 seconds.") + time.sleep(5) + os.system("service pwnagotchi restart") + else: + print("Ok, doing nothing.") + sys.exit(0) + if args.donate: - print("Donations can made @ https://github.com/sponsors/jayofelony \n\nBut only if you really want to!") + print("Donations can made @ \n " + "https://www.patreon.com/pwnagotchi_torch \n " + "https://github.com/sponsors/jayofelony \n\n" + "But only if you really want to!") sys.exit(0) if args.check_update: - resp = requests.get("https://api.github.com/repos/jayofelony/pwnagotchi-bookworm/releases/latest") + resp = requests.get("https://api.github.com/repos/jayofelony/pwnagotchi/releases/latest") latest = resp.json() latest_ver = latest['tag_name'].replace('v', '') local = version_to_tuple(pwnagotchi.__version__) remote = version_to_tuple(latest_ver) if remote > local: - user_input = input("There is a new version available! Update from v%s to v%s?\n[y(es)/n(o)]" + user_input = input("There is a new version available! Update from v%s to v%s?\n[Y/N] " % (pwnagotchi.__version__, latest_ver)) # input validation if user_input.lower() in ('y', 'yes'): if os.path.exists('/root/.auto-update'): os.system("rm /root/.auto-update && systemctl restart pwnagotchi") - os.system("systemctl restart pwnagotchi") + else: + logging.error("You should make sure auto-update is enabled!") print("Okay, give me a couple minutes. Just watch pwnlog while you wait.") - elif user_input.lower() in ('n', 'no'): + elif user_input.lower() in ('n', 'no'): # using this elif for readability print("Okay, guess not!") - else: - print("Invalid input.") else: print("You are currently on the latest release, v%s." % pwnagotchi.__version__) sys.exit(0) @@ -230,6 +334,5 @@ def pwnagotchi_cli(): else: do_auto_mode(agent) - if __name__ == '__main__': - pwnagotchi_cli() + pwnagotchi_cli() \ No newline at end of file diff --git a/builder/combined.json.pkr.hcl b/builder/combined.json.pkr.hcl new file mode 100644 index 00000000..c69f0bc9 --- /dev/null +++ b/builder/combined.json.pkr.hcl @@ -0,0 +1,173 @@ +packer { + required_plugins { + arm = { + version = "1.0.0" + source = "github.com/cdecoux/builder-arm" + } + ansible = { + source = "github.com/hashicorp/ansible" + version = ">= 1.1.1" + } + } +} + +variable "pwn_hostname" { + type = string +} + +variable "pwn_version" { + type = string +} + +source "arm" "rpi64-pwnagotchi" { + file_checksum_url = "https://downloads.raspberrypi.org/raspios_lite_arm64/images/raspios_lite_arm64-2023-12-11/2023-12-11-raspios-bookworm-arm64-lite.img.xz.sha256" + file_urls = ["https://downloads.raspberrypi.org/raspios_lite_arm64/images/raspios_lite_arm64-2023-12-11/2023-12-11-raspios-bookworm-arm64-lite.img.xz"] + file_checksum_type = "sha256" + file_target_extension = "xz" + file_unarchive_cmd = ["unxz", "$ARCHIVE_PATH"] + image_path = "../pwnagotchi-64bit.img" + qemu_binary_source_path = "/usr/libexec/qemu-binfmt/aarch64-binfmt-P" + qemu_binary_destination_path = "/usr/libexec/qemu-binfmt/aarch64-binfmt-P" + image_build_method = "resize" + image_size = "9G" + image_type = "dos" + image_partitions { + name = "boot" + type = "c" + start_sector = "8192" + filesystem = "fat" + size = "256M" + mountpoint = "/boot/firmware" + } + image_partitions { + name = "root" + type = "83" + start_sector = "532480" + filesystem = "ext4" + size = "0" + mountpoint = "/" + } +} + +source "arm" "rpi32-pwnagotchi" { + file_checksum_url = "https://downloads.raspberrypi.com/raspios_oldstable_lite_armhf/images/raspios_oldstable_lite_armhf-2023-12-06/2023-12-05-raspios-bullseye-armhf-lite.img.xz.sha256" + file_urls = ["https://downloads.raspberrypi.com/raspios_oldstable_lite_armhf/images/raspios_oldstable_lite_armhf-2023-12-06/2023-12-05-raspios-bullseye-armhf-lite.img.xz"] + file_checksum_type = "sha256" + file_target_extension = "xz" + file_unarchive_cmd = ["unxz", "$ARCHIVE_PATH"] + image_path = "../pwnagotchi-32bit.img" + qemu_binary_source_path = "/usr/libexec/qemu-binfmt/arm-binfmt-P" + qemu_binary_destination_path = "/usr/libexec/qemu-binfmt/arm-binfmt-P" + image_build_method = "resize" + image_size = "9G" + image_type = "dos" + image_partitions { + name = "boot" + type = "c" + start_sector = "8192" + filesystem = "fat" + size = "256M" + mountpoint = "/boot" + } + image_partitions { + name = "root" + type = "83" + start_sector = "532480" + filesystem = "ext4" + size = "0" + mountpoint = "/" + } +} + +# a build block invokes sources and runs provisioning steps on them. The +# documentation for build blocks can be found here: +# https://www.packer.io/docs/from-1.5/blocks/build +build { + name = "Raspberry Pi 64 Pwnagotchi" + sources = ["source.arm.rpi64-pwnagotchi"] + + provisioner "file" { + destination = "/usr/bin/" + sources = [ + "data/64bit/usr/bin/bettercap-launcher", + "data/64bit/usr/bin/hdmioff", + "data/64bit/usr/bin/hdmion", + "data/64bit/usr/bin/monstart", + "data/64bit/usr/bin/monstop", + "data/64bit/usr/bin/pwnagotchi-launcher", + "data/64bit/usr/bin/pwnlib", + ] + } + provisioner "shell" { + inline = ["chmod +x /usr/bin/*"] + } + + provisioner "file" { + destination = "/etc/systemd/system/" + sources = [ + "data/64bit/etc/systemd/system/bettercap.service", + "data/64bit/etc/systemd/system/pwnagotchi.service", + "data/64bit/etc/systemd/system/pwngrid-peer.service", + ] + } + provisioner "file" { + destination = "/etc/update-motd.d/01-motd" + source = "data/64bit/etc/update-motd.d/01-motd" + } + provisioner "shell" { + inline = ["chmod +x /etc/update-motd.d/*"] + } + provisioner "shell" { + inline = ["apt-get -y --allow-releaseinfo-change update", "apt-get -y dist-upgrade", "apt-get install -y --no-install-recommends ansible"] + } + provisioner "ansible-local" { + command = "ANSIBLE_FORCE_COLOR=1 PYTHONUNBUFFERED=1 PWN_VERSION=${var.pwn_version} PWN_HOSTNAME=${var.pwn_hostname} ansible-playbook" + extra_arguments = ["--extra-vars \"ansible_python_interpreter=/usr/bin/python3\""] + playbook_file = "data/64bit/raspberrypi64.yml" + } +} + +build { + name = "Raspberry Pi 32 Pwnagotchi" + sources = ["source.arm.rpi32-pwnagotchi"] + provisioner "file" { + destination = "/usr/bin/" + sources = [ + "data/32bit/usr/bin/bettercap-launcher", + "data/32bit/usr/bin/hdmioff", + "data/32bit/usr/bin/hdmion", + "data/32bit/usr/bin/monstart", + "data/32bit/usr/bin/monstop", + "data/32bit/usr/bin/pwnagotchi-launcher", + "data/32bit/usr/bin/pwnlib", + ] + } + provisioner "shell" { + inline = ["chmod +x /usr/bin/*"] + } + + provisioner "file" { + destination = "/etc/systemd/system/" + sources = [ + "data/32bit/etc/systemd/system/bettercap.service", + "data/32bit/etc/systemd/system/pwnagotchi.service", + "data/32bit/etc/systemd/system/pwngrid-peer.service", + ] + } + provisioner "file" { + destination = "/etc/update-motd.d/01-motd" + source = "data/32bit/etc/update-motd.d/01-motd" + } + provisioner "shell" { + inline = ["chmod +x /etc/update-motd.d/*"] + } + provisioner "shell" { + inline = ["apt-get -y --allow-releaseinfo-change update", "apt-get -y dist-upgrade", "apt-get install -y --no-install-recommends ansible"] + } + provisioner "ansible-local" { + command = "ANSIBLE_FORCE_COLOR=1 PYTHONUNBUFFERED=1 PWN_VERSION=${var.pwn_version} PWN_HOSTNAME=${var.pwn_hostname} ansible-playbook" + extra_arguments = ["--extra-vars \"ansible_python_interpreter=/usr/bin/python3\""] + playbook_dir = "data/32bit/extras/" + playbook_file = "data/32bit/raspberrypi32.yml" + } +} \ No newline at end of file diff --git a/builder/data/etc/bash_completion.d/pwnagotchi_completion.sh b/builder/data/32bit/etc/bash_completion.d/pwnagotchi_completion.sh similarity index 100% rename from builder/data/etc/bash_completion.d/pwnagotchi_completion.sh rename to builder/data/32bit/etc/bash_completion.d/pwnagotchi_completion.sh diff --git a/builder/data/32bit/etc/dhcpcd.conf b/builder/data/32bit/etc/dhcpcd.conf new file mode 100644 index 00000000..90444ad0 --- /dev/null +++ b/builder/data/32bit/etc/dhcpcd.conf @@ -0,0 +1,62 @@ +# A sample configuration for dhcpcd. +# See dhcpcd.conf(5) for details. + +# Allow users of this group to interact with dhcpcd via the control socket. +#controlgroup wheel + +# Inform the DHCP server of our hostname for DDNS. +hostname + +# Use the hardware address of the interface for the Client ID. +clientid +# or +# Use the same DUID + IAID as set in DHCPv6 for DHCPv4 ClientID as per RFC4361. +# Some non-RFC compliant DHCP servers do not reply with this set. +# In this case, comment out duid and enable clientid above. +#duid + +# Persist interface configuration when dhcpcd exits. +persistent + +# Rapid commit support. +# Safe to enable by default because it requires the equivalent option set +# on the server to actually work. +option rapid_commit + +# A list of options to request from the DHCP server. +option domain_name_servers, domain_name, domain_search, host_name +option classless_static_routes +# Respect the network MTU. This is applied to DHCP routes. +option interface_mtu + +# Most distributions have NTP support. +#option ntp_servers + +# A ServerID is required by RFC2131. +require dhcp_server_identifier + +# Generate SLAAC address using the Hardware Address of the interface +#slaac hwaddr +# OR generate Stable Private IPv6 Addresses based from the DUID +slaac private + +# !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! +# !! DO NOT EDIT THESE LINES BELOW PLEASE !! +# !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! + +# static IP configuration: +denyinterfaces wlan0 + +interface eth0 +static domain_name_servers=8.8.8.8 1.1.1.1 +metric 201 + +interface usb0 +static ip_address=10.0.0.2/24 +static routers=10.0.0.1 +static domain_name_servers=10.0.0.1 8.8.8.8 1.1.1.1 +metric 202 + +interface bnep0 +static domain_name_servers=8.8.8.8 1.1.1.1 +metric 203 \ No newline at end of file diff --git a/builder/data/32bit/etc/dphys-swapfile b/builder/data/32bit/etc/dphys-swapfile new file mode 100644 index 00000000..1c908a10 --- /dev/null +++ b/builder/data/32bit/etc/dphys-swapfile @@ -0,0 +1,26 @@ +# /etc/dphys-swapfile - user settings for dphys-swapfile package +# author Neil Franklin, last modification 2010.05.05 +# copyright ETH Zuerich Physics Departement +# use under either modified/non-advertising BSD or GPL license + +# this file is sourced with . so full normal sh syntax applies + +# the default settings are added as commented out CONF_*=* lines + + +# where we want the swapfile to be, this is the default +#CONF_SWAPFILE=/var/swap + +# set size to absolute value, leaving empty (default) then uses computed value +# you most likely don't want this, unless you have an special disk situation +CONF_SWAPSIZE=2048 + +# set size to computed value, this times RAM size, dynamically adapts, +# guarantees that there is enough swap without wasting disk space on excess +#CONF_SWAPFACTOR=2 + +# restrict size (computed and absolute!) to maximally this limit +# can be set to empty for no limit, but beware of filled partitions! +# this is/was a (outdated?) 32bit kernel limit (in MBytes), do not overrun it +# but is also sensible on 64bit to prevent filling /var or even / partition +#CONF_MAXSWAP=2048 \ No newline at end of file diff --git a/builder/data/etc/systemd/system/bettercap.service b/builder/data/32bit/etc/systemd/system/bettercap.service similarity index 100% rename from builder/data/etc/systemd/system/bettercap.service rename to builder/data/32bit/etc/systemd/system/bettercap.service diff --git a/builder/data/32bit/etc/systemd/system/bluetooth.service b/builder/data/32bit/etc/systemd/system/bluetooth.service new file mode 100644 index 00000000..6780e4b8 --- /dev/null +++ b/builder/data/32bit/etc/systemd/system/bluetooth.service @@ -0,0 +1,20 @@ +[Unit] +Description=Bluetooth service +Documentation=man:bluetoothd(8) +ConditionPathIsDirectory=/sys/class/bluetooth + +[Service] +Type=dbus +BusName=org.bluez +ExecStart=/usr/libexec/bluetooth/bluetoothd --noplugin=sap,a2dp +NotifyAccess=main +#WatchdogSec=10 +#Restart=on-failure +CapabilityBoundingSet=CAP_NET_ADMIN CAP_NET_BIND_SERVICE +LimitNPROC=1 +ProtectHome=true +ProtectSystem=full + +[Install] +WantedBy=bluetooth.target +Alias=dbus-org.bluez.service diff --git a/builder/data/32bit/etc/systemd/system/bthelper@.service.d/override.conf b/builder/data/32bit/etc/systemd/system/bthelper@.service.d/override.conf new file mode 100644 index 00000000..e15c3c4b --- /dev/null +++ b/builder/data/32bit/etc/systemd/system/bthelper@.service.d/override.conf @@ -0,0 +1,6 @@ +[Unit] +After=hciuart.service bluetooth.service +Before= + +[Service] +ExecStartPre=/bin/sleep 5 \ No newline at end of file diff --git a/builder/data/etc/systemd/system/pwnagotchi.service b/builder/data/32bit/etc/systemd/system/pwnagotchi.service similarity index 100% rename from builder/data/etc/systemd/system/pwnagotchi.service rename to builder/data/32bit/etc/systemd/system/pwnagotchi.service diff --git a/builder/data/etc/systemd/system/pwngrid-peer.service b/builder/data/32bit/etc/systemd/system/pwngrid-peer.service similarity index 85% rename from builder/data/etc/systemd/system/pwngrid-peer.service rename to builder/data/32bit/etc/systemd/system/pwngrid-peer.service index 0ea07f92..9a55eb3f 100644 --- a/builder/data/etc/systemd/system/pwngrid-peer.service +++ b/builder/data/32bit/etc/systemd/system/pwngrid-peer.service @@ -8,7 +8,7 @@ After=bettercap.service Environment=LD_PRELOAD=/usr/local/lib/libpcap.so.1 Environment=LD_LIBRARY_PATH=/usr/local/lib Type=simple -ExecStart=/usr/local/bin/pwngrid -keys /etc/pwnagotchi -peers /root/peers -address 127.0.0.1:8666 -client-token /root/.api-enrollment.json -wait -log /home/pi/logs/pwngrid-peer.log -iface wlan0mon +ExecStart=/usr/local/bin/pwngrid -keys /etc/pwnagotchi -peers /root/peers -address 127.0.0.1:8666 -client-token /root/.api-enrollment.json -wait -log /etc/pwnagotchi/log/pwngrid-peer.log -iface wlan0mon Restart=always RestartSec=30 diff --git a/builder/data/32bit/etc/update-motd.d/01-motd b/builder/data/32bit/etc/update-motd.d/01-motd new file mode 100755 index 00000000..8c13ae9f --- /dev/null +++ b/builder/data/32bit/etc/update-motd.d/01-motd @@ -0,0 +1,33 @@ +#!/bin/sh +_hostname=$(hostname) +_version=$(cut -d"'" -f2 < /usr/local/lib/python3.9/dist-packages/pwnagotchi/_version.py) +echo +echo "(◕‿‿◕) $_hostname" +echo +echo " Hi! I'm a pwnagotchi $_version, please take good care of me!" +echo " Here are some basic things you need to know to raise me properly!" +echo +echo " If you want to change my configuration, use /etc/pwnagotchi/config.toml" +echo " All plugin config files are located in /etc/pwnagotchi/conf.d/" +echo " Read the readme if you want to use gdrivesync plugin!!" +echo +echo " All the configuration options can be found on /etc/pwnagotchi/default.toml," +echo " but don't change this file because I will recreate it every time I'm restarted!" +echo +echo " I use oPwnGrid as my main API, you can check stats at https://opwngrid.xyz" +echo +echo " I'm managed by systemd. Here are some basic commands." +echo +echo " If you want to know what I'm doing, you can check my logs with the command" +echo " - pwnlog" +echo " - sudo pwnagotchi --version, to check the current version" +echo " - sudo pwnagotchi --donate, to see how you can donate to this project" +echo " - sudo pwnagotchi --check-update, to see if there is a new version available" +echo +echo " If you want to know if I'm running, you can use" +echo " sudo systemctl status pwnagotchi" +echo +echo " You can restart me using" +echo " pwnkill" +echo +echo " You learn more about me at https://pwnagotchi.ai/" \ No newline at end of file diff --git a/builder/data/32bit/extras/nexmon.yml b/builder/data/32bit/extras/nexmon.yml new file mode 100644 index 00000000..6cdaafac --- /dev/null +++ b/builder/data/32bit/extras/nexmon.yml @@ -0,0 +1,40 @@ +# Install nexmon to fix wireless scanning (takes 2.5G of space) +- name: clone nexmon repository + git: + repo: https://github.com/DrSchottky/nexmon.git + dest: /usr/local/src/nexmon + +- name: make firmware + shell: "source ./setup_env.sh && make" + args: + executable: /bin/bash + chdir: /usr/local/src/nexmon/ + +- name: "make firmware patch ({{ item.name }})" + shell: "source ./setup_env.sh && cd /usr/local/src/nexmon/patches/{{ item.patch }}/nexmon/ && make" + args: + executable: /bin/bash + chdir: /usr/local/src/nexmon/ + environment: + QEMU_UNAME: "{{ item.kernel }}" + ARCHFLAGS: "{{ item.arch_flags }}" + +- name: "install new firmware ({{ item.name }})" + copy: + src: "/usr/local/src/nexmon/patches/{{ item.patch }}/nexmon/{{ item.firmware }}" + dest: "/usr/lib/firmware/brcm/{{ item.firmware }}" + follow: true + environment: + QEMU_UNAME: "{{ item.kernel }}" + ARCHFLAGS: "{{ item.arch_flags }}" + +- name: backup original driver + command: "mv /usr/lib/modules/{{ item.kernel }}/kernel/drivers/net/wireless/broadcom/brcm80211/brcmfmac/brcmfmac.ko.xz /usr/lib/modules/{{ item.kernel }}/kernel/drivers/net/wireless/broadcom/brcm80211/brcmfmac/brcmfmac.ko.xz.orig" + +- name: copy modified driver + copy: + src: "/usr/local/src/nexmon/patches/driver/brcmfmac_6.1.y-nexmon/brcmfmac.ko" + dest: "/usr/lib/modules/{{ item.kernel }}/kernel/drivers/net/wireless/broadcom/brcm80211/brcmfmac/brcmfmac.ko" + +- name : load brcmfmac drivers + command: "/sbin/depmod -a {{ item.kernel }}" diff --git a/builder/data/32bit/raspberrypi32.json.pkr.hcl b/builder/data/32bit/raspberrypi32.json.pkr.hcl new file mode 100644 index 00000000..a20e747f --- /dev/null +++ b/builder/data/32bit/raspberrypi32.json.pkr.hcl @@ -0,0 +1,94 @@ +packer { + required_plugins { + arm = { + version = "1.0.0" + source = "github.com/cdecoux/builder-arm" + } + ansible = { + source = "github.com/hashicorp/ansible" + version = ">= 1.1.1" + } + } +} + +variable "pwn_hostname" { + type = string +} + +variable "pwn_version" { + type = string +} + +source "arm" "rpi32-pwnagotchi" { + file_checksum_url = "https://downloads.raspberrypi.com/raspios_oldstable_lite_armhf/images/raspios_oldstable_lite_armhf-2023-12-06/2023-12-05-raspios-bullseye-armhf-lite.img.xz.sha256" + file_urls = ["https://downloads.raspberrypi.com/raspios_oldstable_lite_armhf/images/raspios_oldstable_lite_armhf-2023-12-06/2023-12-05-raspios-bullseye-armhf-lite.img.xz"] + file_checksum_type = "sha256" + file_target_extension = "xz" + file_unarchive_cmd = ["unxz", "$ARCHIVE_PATH"] + image_path = "../../pwnagotchi-32bit.img" + qemu_binary_source_path = "/usr/libexec/qemu-binfmt/arm-binfmt-P" + qemu_binary_destination_path = "/usr/libexec/qemu-binfmt/arm-binfmt-P" + image_build_method = "resize" + image_size = "9G" + image_type = "dos" + image_partitions { + name = "boot" + type = "c" + start_sector = "8192" + filesystem = "fat" + size = "256M" + mountpoint = "/boot" + } + image_partitions { + name = "root" + type = "83" + start_sector = "532480" + filesystem = "ext4" + size = "0" + mountpoint = "/" + } +} +build { + name = "Raspberry Pi 32 Pwnagotchi" + sources = ["source.arm.rpi32-pwnagotchi"] + provisioner "file" { + destination = "/usr/bin/" + sources = [ + "data/32bit/usr/bin/bettercap-launcher", + "data/32bit/usr/bin/hdmioff", + "data/32bit/usr/bin/hdmion", + "data/32bit/usr/bin/monstart", + "data/32bit/usr/bin/monstop", + "data/32bit/usr/bin/pwnagotchi-launcher", + "data/32bit/usr/bin/pwnlib", + ] + } + provisioner "shell" { + inline = ["chmod +x /usr/bin/*"] + } + + provisioner "file" { + destination = "/etc/systemd/system/" + sources = [ + "data/32bit/etc/systemd/system/bettercap.service", + "data/32bit/etc/systemd/system/pwnagotchi.service", + "data/32bit/etc/systemd/system/pwngrid-peer.service", + ] + } + provisioner "file" { + destination = "/etc/update-motd.d/01-motd" + source = "data/32bit/etc/update-motd.d/01-motd" + } + provisioner "shell" { + inline = ["chmod +x /etc/update-motd.d/*"] + } + provisioner "shell" { + inline = ["apt-get -y --allow-releaseinfo-change update", "apt-get -y dist-upgrade", "apt-get install -y --no-install-recommends ansible"] + } + provisioner "ansible-local" { + command = "ANSIBLE_FORCE_COLOR=1 PYTHONUNBUFFERED=1 PWN_VERSION=${var.pwn_version} PWN_HOSTNAME=${var.pwn_hostname} ansible-playbook" + extra_arguments = ["--extra-vars \"ansible_python_interpreter=/usr/bin/python3\""] + playbook_dir = "data/32bit/extras/" + playbook_file = "data/32bit/raspberrypi32.yml" + } +} \ No newline at end of file diff --git a/builder/data/32bit/raspberrypi32.yml b/builder/data/32bit/raspberrypi32.yml new file mode 100644 index 00000000..453de361 --- /dev/null +++ b/builder/data/32bit/raspberrypi32.yml @@ -0,0 +1,594 @@ +--- +- hosts: + - 127.0.0.1 + gather_facts: true + become: true + vars: + boards: + - { + kernel: "6.1.21+", + name: "PiZeroW", + firmware: "brcmfmac43430-sdio.bin", + patch: "bcm43430a1/7_45_41_46", + cpu: arm1176, + arch_flags: "-arch armv6l" + } + - { + kernel: "6.1.21-v7+", + name: "PiZero2W", + firmware: "brcmfmac43436-sdio.bin", + patch: "bcm43436b0/9_88_4_65", + cpu: any, #cortex-a53 + arch_flags: "-arch armv7l" + } + - { + kernel: "6.1.21-v7l+", + name: "Pi4b_32", + firmware: "brcmfmac43455-sdio.bin", + patch: "bcm43455c0/7_45_206", + cpu: any, #cortex-a72 + arch_flags: "-arch armv7l" + } + kernel: + min: "6.1" + full: "6.1.21+" + full_2w: "6.1.21-v7+" + full_4b: "6.1.21-v7l+" + arch: "v6l" + pwnagotchi: + hostname: "{{ lookup('env', 'PWN_HOSTNAME') | default('pwnagotchi', true) }}" + version: "{{ lookup('env', 'PWN_VERSION') | default('pwnagotchi-torch', true) }}" + custom_plugin_dir: "/usr/local/share/pwnagotchi/custom-plugins" + system: + boot_options: + - "#### pwnagotchi additions" + - "# this pwnagotchi image is 32-bit only no v8+ headers to build nexmon for 64 bit" + - "arm_64bit=0" + - "# dwc2 for RNDIS. comment out, and remove dwc2 and g_ether from cmdline.txt for X306 usb battery hat" + - "dtoverlay=dwc2" + - "dtoverlay=spi1-3cs" + - "dtparam=i2c1=on" + - "dtparam=i2c_arm=on" + - "dtparam=spi=on" + - "gpu_mem=16" + - "#### audio out on pins 18 and 19" + - "#dtoverlay=audremap,pins_18_19" + - "#### touchscreen on waveshare touch e-paper" + - "#dtoverlay=goodix,interrupt=27,reset=22" + - "#### for PWM backlighting on pimoroni displayhatmini" + - "dtoverlay=pwm-2chan,pin=12,func=4,pin2=13,func2=4" + modules: + - "i2c-dev" + services: + enable: + - bettercap.service + - bluetooth.service + - dphys-swapfile.service + - fstrim.timer + - pwnagotchi.service + - pwngrid-peer.service + disable: + - apt-daily-upgrade.service + - apt-daily-upgrade.timer + - apt-daily.service + - apt-daily.timer + - ifup@wlan0.service + - triggerhappy.service + - wpa_supplicant.service + packages: + caplets: + source: "https://github.com/jayofelony/caplets.git" + bettercap: + source: "https://github.com/jayofelony/bettercap.git" + url: "https://github.com/jayofelony/bettercap/releases/download/2.32.2/bettercap-2.32.2-armhf.zip" + ui: "https://github.com/bettercap/ui/releases/download/v1.3.0/ui.zip" + opwngrid: + source: "https://github.com/jayofelony/pwngrid.git" + url: "https://github.com/jayofelony/pwngrid/releases/download/v1.10.7/pwngrid-1.10.7-armhf.zip" + torch: + wheel: "torch-2.1.0a0+gitunknown-cp39-cp39-linux_armv6l.whl" + url: "https://github.com/Sniffleupagus/Torch4Pizero/releases/download/v1.0.0/torch-2.1.0a0+gitunknown-cp39-cp39-linux_armv6l.whl" + torchvision: + wheel: "torchvision-0.16.0a0-cp39-cp39-linux_armv6l.whl" + url: "https://github.com/Sniffleupagus/Torch4Pizero/releases/download/v1.0.0/torchvision-0.16.0a0-cp39-cp39-linux_armv6l.whl" + apt: + downgrade: + - libpcap-dev_1.9.1-4_armhf.deb + - libpcap0.8-dbg_1.9.1-4_armhf.deb + - libpcap0.8-dev_1.9.1-4_armhf.deb + - libpcap0.8_1.9.1-4_armhf.deb + hold: + - firmware-atheros + - firmware-brcm80211 + - firmware-libertas + - firmware-misc-nonfree + - firmware-realtek + - libpcap-dev + - libpcap0.8 + - libpcap0.8-dev + - libpcap0.8-dbg + remove: + - avahi-daemon + - nfs-common + - triggerhappy + - wpasupplicant + install: + - autoconf + - bc + - bison + - bluez + - build-essential + - curl + - dkms + - dphys-swapfile + - espeak-ng + - evtest + - fbi + - flex + - fonts-dejavu + - fonts-dejavu-core + - fonts-dejavu-extra + - fonts-freefont-ttf + - g++ + - gawk + - gcc-arm-none-eabi + - git + - libatlas-base-dev + - libavcodec58 + - libavformat58 + - libblas-dev + - libbluetooth-dev + - libbz2-dev + - libc-ares-dev + - libc6-dev + - libcpuinfo-dev + - libdbus-1-dev + - libdbus-glib-1-dev + - libeigen3-dev + - libelf-dev + - libffi-dev + - libfl-dev + - libfuse-dev + - libgdbm-dev + - libgl1-mesa-glx + - libgmp3-dev + - libgstreamer1.0-0 + - libhdf5-dev + - liblapack-dev + - libncursesw5-dev + - libnetfilter-queue-dev + - libopenblas-dev + - libopenjp2-7 + - libopenmpi-dev + - libopenmpi3 + - libpcap-dev + - libprotobuf-dev + - libraspberrypi-bin + - libraspberrypi-dev + - libraspberrypi-doc + - libraspberrypi0 + - libsleef-dev + - libsqlite3-dev + - libssl-dev + - libswscale5 + - libtiff5 + - libtool + - libts-bin + - libusb-1.0-0-dev + - lsof + - make + - ntp + - python3-flask + - python3-flask-cors + - python3-flaskext.wtf + - python3-pil + - python3-pip + - python3-protobuf + - python3-smbus + - qpdf + - raspberrypi-kernel-headers + - rsync + - screen + - tcpdump + - texinfo + - time + - tk-dev + - unzip + - vim + - wget + - wl + - xxd + - zlib1g-dev + + tasks: + - name: Create pi user + copy: + dest: /boot/userconf + content: | + pi:$6$3jNr0GA9KIyt4hmM$efeVIopdMQ8DGgEPCWWlbx3mJJNAYci1lEXGdlky0xPyjqwKNbwTL5SrCcpb4144C4IvzWjn7Iv.QjqmU7iyT/ + + - name: change hostname + lineinfile: + dest: /etc/hostname + regexp: '^raspberrypi' + line: "{{pwnagotchi.hostname}}" + state: present + when: lookup('file', '/etc/hostname') == "raspberrypi" + register: hostname + + - name: add hostname to /etc/hosts + lineinfile: + dest: /etc/hosts + regexp: '^127\.0\.1\.1[ \t]+raspberrypi' + line: "127.0.1.1\t{{pwnagotchi.hostname}}" + state: present + when: hostname.changed + + - name: Create custom plugin directory + file: + path: '{{ pwnagotchi.custom_plugin_dir }}' + state: directory + + - name: update apt package cache + apt: + update_cache: yes + + - name: install packages + apt: + name: "{{ packages.apt.install }}" + state: present + + - name: update pip3, setuptools, wheel + shell: "python3 -m pip install --upgrade pip setuptools wheel" + args: + executable: /bin/bash + chdir: /usr/local/src + + ########################################### + # + # libpcap v1.9 - build from source + # + ########################################### + + # check for presence, then it can re-run in later parts if needed + # use the "make" built in + + # install libpcap before bettercap and pwngrid, so they use it + - name: clone libpcap v1.9 from github + git: + repo: 'https://github.com/the-tcpdump-group/libpcap.git' + dest: /usr/local/src/libpcap + version: libpcap-1.9 + + - name: build and install libpcap into /usr/local/lib + shell: "./configure && make && make install" + args: + executable: /bin/bash + chdir: /usr/local/src/libpcap + + - name: remove libpcap build folder + file: + state: absent + path: /usr/local/src/libpcap + + - name: create symlink /usr/local/lib/libpcap.so.1.9.1 + file: + src: /usr/local/lib/libpcap.so.1.9.1 + dest: /usr/local/lib/libpcap.so.0.8 + state: link + + ############################################################### + # Install nexmon to fix wireless scanning (takes 2.5G of space) + ############################################################### + + # Install nexmon for all boards + - name: build and install nexmon as needed + include_tasks: nexmon.yml + loop: "{{ boards }}" + + # some pizero2w have the pizeroW wifi chip + # could this be a link instead of a copy? and force, only if not a link? + - name: copy 43430-sdio as 43436s-sdio for the special 43430/1 /2 + copy: + src: /usr/lib/firmware/brcm/brcmfmac43430-sdio.bin + dest: /usr/lib/firmware/brcm/brcmfmac43436s-sdio.bin + follow: true + + # delete blob files that make nexmon sad + - name: Delete the firmware blob files to avoid some nexmon crashing + file: + state: absent + path: '{{ item }}' + loop: + - /usr/lib/firmware/brcm/brcmfmac43430-sdio.clm_blob + - /usr/lib/firmware/brcm/brcmfmac43430-sdio.raspberrypi,model-zero-w.clm_blob + - /usr/lib/firmware/brcm/brcmfmac43430b0-sdio.raspberrypi,model-zero-2-w.clm_blob + - /usr/lib/firmware/brcm/brcmfmac43436-sdio.raspberrypi,model-zero-2-w.clm_blob + - /usr/lib/firmware/brcm/brcmfmac43430-sdio.raspberrypi,3-model-b.clm_blob + + # To shrink the final image, remove the nexmon directory (takes 2.5G of space) post build and installation + - name: Delete nexmon content & directory + file: + state: absent + path: /usr/local/src/nexmon/ + + - name: clone pwnagotchi repository + git: + repo: https://github.com/jayofelony/pwnagotchi.git + dest: /usr/local/src/pwnagotchi + register: pwnagotchigit + + # is this even necessary? Can't we just link from /home/pi/pwnagotchi to /usr/local/{bin,lib,etc} + # then just git update in the home dir and encourage hacking? + # make owned by pi.pi, and custom plugins. + - name: build pwnagotchi wheel + command: "python3 setup.py sdist bdist_wheel" + args: + chdir: /usr/local/src/pwnagotchi + when: (pwnagotchigit.changed) or (pip_packages['pwnagotchi'] is undefined) or (pip_packages['pwnagotchi'] != pwnagotchi_version) + + - name: download torch whl + get_url: + url: "{{ packages.torch.url }}" + dest: /usr/local/src/ + + - name: download torchvision whl + get_url: + url: "{{ packages.torchvision.url }}" + dest: /usr/local/src/ + + - name: install 32-bit pwnagotchi wheel and dependencies with 32-bit torch wheels + pip: + name: + - "{{ lookup('fileglob', '/usr/local/src/pwnagotchi/dist/pwnagotchi*.whl') }}" + - "{{ packages.torch.url }}" + - "{{ packages.torchvision.url }}" + extra_args: "--no-cache-dir" + environment: + QEMU_CPU: arm1176 + QEMU_UNAME: "{{ kernel.full }}" + when: (pwnagotchigit.changed) or (pip_packages['pwnagotchi'] is undefined) or (pip_packages['pwnagotchi'] != pwnagotchi_version) + + - name: create /usr/local/share/pwnagotchi/ folder + file: + path: /usr/local/share/pwnagotchi/ + state: directory + + - name: remove pwnagotchi folder + file: + state: absent + path: /usr/local/src/pwnagotchi + + - name: remove torch whl + file: + state: absent + path: "{{ lookup('fileglob', '/usr/local/src/torch*.whl') }}" + + ########################################## + # + # pwngrid, bettercap + # + ########################################## + + - name: Install go-1.21 + unarchive: + src: https://go.dev/dl/go1.21.6.linux-armv6l.tar.gz + dest: /usr/local + remote_src: yes + register: golang + + - name: Update .bashrc for go-1.21 + blockinfile: + dest: /home/pi/.bashrc + state: present + block: | + export GOPATH=$HOME/go + export PATH=/usr/local/go/bin:$PATH:$GOPATH/bin + alias custom='cd /usr/local/share/pwnagotchi/custom-plugins/' + alias config='sudo nano /etc/pwnagotchi/config.toml' + alias pwnlog='tail -f -n300 /etc/pwnagotchi/log/pwn*.log | sed --unbuffered "s/,[[:digit:]]\\{3\\}\\]//g" | cut -d " " -f 2-' + alias pwnver='python3 -c "import pwnagotchi as p; print(p.__version__)"' + alias pwnkill='sudo killall -USR1 pwnagotchi' + when: golang.changed + + - name: download pwngrid + unarchive: + remote_src: yes + src: "{{ packages.opwngrid.url }}" + dest: /usr/local/bin/ + mode: 0755 + + - name: download and install bettercap + unarchive: + src: "{{ packages.bettercap.url }}" + dest: /usr/local/bin + remote_src: yes + exclude: + - README.md + - LICENSE.md + mode: 0755 + + - name: clone bettercap caplets + git: + repo: "{{ packages.caplets.source }}" + dest: /tmp/caplets + register: capletsgit + + - name: install bettercap caplets + make: + chdir: /tmp/caplets + target: install + when: capletsgit.changed + + - name: download and install bettercap ui + unarchive: + src: "{{ packages.bettercap.ui }}" + dest: /usr/local/share/bettercap/ + remote_src: yes + mode: 0755 + + # to always have the bettercap webui available (because why not?) + - name: copy pwnagotchi-manual over pwnagotchi-auto caplet + ansible.builtin.copy: + src: /usr/local/share/bettercap/caplets/pwnagotchi-manual.cap + dest: /usr/local/share/bettercap/caplets/pwnagotchi-auto.cap + force: true + ignore_errors: true + + - name: create /etc/pwnagotchi folder + file: + path: /etc/pwnagotchi + state: directory + + - name: create log folder + file: + path: /home/pi/logs + state: directory + + - name: check if user configuration exists + stat: + path: /etc/pwnagotchi/config.toml + register: user_config + + - name: create /etc/pwnagotchi/config.toml + copy: + dest: /etc/pwnagotchi/config.toml + content: | + # Add your configuration overrides on this file any configuration changes done to default.toml will be lost! + # Example: + # ui.display.enabled = true + # ui.display.type = "waveshare_4" + when: not user_config.stat.exists + + - name: Delete motd 10-uname + file: + state: absent + path: /etc/update-motd.d/10-uname + + - name: enable ssh on boot + file: + path: /boot/ssh + state: touch + + - name: adjust /boot/config.txt + lineinfile: + dest: /boot/config.txt + insertafter: EOF + line: '{{ item }}' + with_items: "{{system.boot_options}}" + + - name: adjust /etc/modules + lineinfile: + dest: /etc/modules + insertafter: EOF + line: '{{ item }}' + with_items: "{{system.modules}}" + + - name: change root partition + replace: + dest: /boot/cmdline.txt + backup: no + regexp: "root=PARTUUID=[a-zA-Z0-9\\-]+" + replace: "root=/dev/mmcblk0p2" + + - name: configure /boot/cmdline.txt + lineinfile: + path: /boot/cmdline.txt + backrefs: True + state: present + backup: no + regexp: '(.*)$' + line: '\1 modules-load=dwc2,g_ether' + + - name: add firmware packages to hold + dpkg_selections: + name: "{{ item }}" + selection: hold + with_items: "{{ packages.apt.hold }}" + + - name: disable unnecessary services + systemd: + name: "{{ item }}" + state: stopped + enabled: no + with_items: "{{ services.disable }}" + + - name: enable services + systemd: + name: "{{ item }}" + enabled: true + state: stopped + with_items: "{{ services.enable }}" + + #- name: remove golang build libraries + # file: + # state: absent + # path: /root/go + + #- name: remove golang + # file: + # state: absent + # path: /usr/local/go + + - name: make /root readable, becauase that's where all the files are + file: + path: /root + mode: '755' + + - name: fix permissions on /home/pi + file: + path: /home/pi + owner: pi + group: pi + recurse: true + + - name: remove unnecessary apt packages + apt: + name: "{{ packages.apt.remove }}" + state: absent + purge: yes + + - name: remove dependencies that are no longer required + apt: + autoremove: yes + + - name: clean apt cache + apt: + autoclean: true + + - name: remove golang build libraries + file: + state: absent + path: /root/go + + - name: remove pre-collected packages zip + file: + path: /root/go_pkgs.tgz + state: absent + + - name: remove golang + file: + state: absent + path: /usr/local/go + + - name: remove /root/.cache (pip cache) + file: + state: absent + path: /root/.cache + + - name: remove ssh keys + file: + state: absent + path: "{{ item }}" + with_fileglob: + - "/etc/ssh/ssh_host*_key*" + + - name: regenerate ssh keys + shell: "dpkg-reconfigure openssh-server" + args: + executable: /bin/bash + + handlers: + - name: reload systemd services + systemd: + daemon_reload: yes \ No newline at end of file diff --git a/builder/data/root/client_secrets.json b/builder/data/32bit/root/client_secrets.json similarity index 100% rename from builder/data/root/client_secrets.json rename to builder/data/32bit/root/client_secrets.json diff --git a/builder/data/root/settings.yaml b/builder/data/32bit/root/settings.yaml similarity index 100% rename from builder/data/root/settings.yaml rename to builder/data/32bit/root/settings.yaml diff --git a/builder/data/32bit/usr/bin/bettercap-launcher b/builder/data/32bit/usr/bin/bettercap-launcher new file mode 100755 index 00000000..31b30621 --- /dev/null +++ b/builder/data/32bit/usr/bin/bettercap-launcher @@ -0,0 +1,28 @@ +#!/usr/bin/env bash +source /usr/bin/pwnlib + +# we need to decrypt something +if is_crypted_mode; then + while ! is_decrypted; do + echo "Waiting for decryption..." + sleep 1 + done +fi + +# check if wifi driver is bugged +if ! check_brcm; then + if ! reload_brcm; then + echo "Could not reload wifi driver. Reboot" + reboot + fi + sleep 10 +fi + +# start wlan0mon +start_monitor_interface + +if is_auto_mode_no_delete; then + /usr/local/bin/bettercap -no-colors -caplet pwnagotchi-auto -iface wlan0mon +else + /usr/local/bin/bettercap -no-colors -caplet pwnagotchi-manual -iface wlan0mon +fi diff --git a/builder/data/32bit/usr/bin/decryption-webserver b/builder/data/32bit/usr/bin/decryption-webserver new file mode 100755 index 00000000..cbd4633c --- /dev/null +++ b/builder/data/32bit/usr/bin/decryption-webserver @@ -0,0 +1,149 @@ +#!/usr/bin/env python3 + +from http.server import HTTPServer, BaseHTTPRequestHandler +from urllib.parse import parse_qsl + + +_HTML_FORM_TEMPLATE = """ + + + + Decryption + + + +
+

Decryption

+

Some of your files are encrypted.

+

Please provide the decryption password.

+
+
+ {password_fields} + +
+
+
+ + +""" + +POST_RESPONSE = """ + + + + + + + + + +
+ + + +""" + +HTML_FORM = None + + +class SimpleHTTPRequestHandler(BaseHTTPRequestHandler): + + def do_GET(self): + self.send_response(200) + self.end_headers() + self.wfile.write(HTML_FORM.encode()) + + def do_POST(self): + content_length = int(self.headers['Content-Length']) + body = self.rfile.read(content_length) + for mapping, password in parse_qsl(body.decode('UTF-8')): + with open('/tmp/.pwnagotchi-secret-{}'.format(mapping), 'wt') as pwfile: + pwfile.write(password) + self.send_response(200) + self.end_headers() + self.wfile.write(POST_RESPONSE.encode()) + + +with open('/root/.pwnagotchi-crypted') as crypted_file: + mappings = [line.split()[0] for line in crypted_file.readlines()] + fields = ''.join(['\n
'.format(m=m) + for m in mappings]) + HTML_FORM = _HTML_FORM_TEMPLATE.format(password_fields=fields) + +httpd = HTTPServer(('0.0.0.0', 80), SimpleHTTPRequestHandler) +httpd.serve_forever() diff --git a/builder/data/usr/bin/hdmioff b/builder/data/32bit/usr/bin/hdmioff similarity index 100% rename from builder/data/usr/bin/hdmioff rename to builder/data/32bit/usr/bin/hdmioff diff --git a/builder/data/usr/bin/hdmion b/builder/data/32bit/usr/bin/hdmion similarity index 100% rename from builder/data/usr/bin/hdmion rename to builder/data/32bit/usr/bin/hdmion diff --git a/builder/data/usr/bin/monstart b/builder/data/32bit/usr/bin/monstart similarity index 100% rename from builder/data/usr/bin/monstart rename to builder/data/32bit/usr/bin/monstart diff --git a/builder/data/usr/bin/monstop b/builder/data/32bit/usr/bin/monstop similarity index 100% rename from builder/data/usr/bin/monstop rename to builder/data/32bit/usr/bin/monstop diff --git a/builder/data/usr/bin/pwnagotchi-launcher b/builder/data/32bit/usr/bin/pwnagotchi-launcher similarity index 100% rename from builder/data/usr/bin/pwnagotchi-launcher rename to builder/data/32bit/usr/bin/pwnagotchi-launcher diff --git a/builder/data/32bit/usr/bin/pwnlib b/builder/data/32bit/usr/bin/pwnlib new file mode 100755 index 00000000..45b875c6 --- /dev/null +++ b/builder/data/32bit/usr/bin/pwnlib @@ -0,0 +1,184 @@ +#!/usr/bin/env bash + +# check if brcm is stuck +check_brcm() { + if [[ "$(journalctl -n10 -k --since -5m | grep -c 'brcmf_cfg80211_nexmon_set_channel.*Set Channel failed')" -ge 5 ]]; then + return 1 + fi + return 0 +} + +# reload mod +reload_brcm() { + if ! modprobe -r brcmfmac; then + return 1 + fi + sleep 1 + if ! modprobe brcmfmac; then + return 1 + fi + sleep 2 + iw dev wlan0 set power_save off + return 0 +} + +# starts mon0 +start_monitor_interface() { + rfkill unblock all + ifconfig wlan0 up + iw dev wlan0 set power_save off + iw phy "$(iw phy | head -1 | cut -d" " -f2)" interface add wlan0mon type monitor + rfkill unblock all + ifconfig wlan0 down + ifconfig wlan0mon up + iw dev wlan0mon set power_save off +} + +# stops mon0 +stop_monitor_interface() { + ifconfig wlan0mon down && iw dev wlan0mon del + reload_brcm + ifconfig wlan0 up +} + +# returns 0 if the specificed network interface is up +is_interface_up() { + if grep -qi 'up' /sys/class/net/"$1"/operstate; then + return 0 + fi + return 1 +} + +# returns 0 if conditions for AUTO mode are met +is_auto_mode() { + # check override file first + if [ -f /root/.pwnagotchi-manual ]; then + # remove the override file if found + rm -rf /root/.pwnagotchi-manual + return 1 + fi + + # check override file first + if [ -f /root/.pwnagotchi-auto ]; then + # remove the override file if found + rm -rf /root/.pwnagotchi-auto + return 0 + fi + + # if usb0 is up, we're in MANU + if is_interface_up usb0; then + return 1 + fi + + # if eth0 is up (for other boards), we're in MANU + if is_interface_up eth0; then + return 0 + fi + + # no override, but none of the interfaces is up -> AUTO + return 0 +} + +# returns 0 if conditions for AUTO mode are met +is_auto_mode_no_delete() { + # check override file first + if [ -f /root/.pwnagotchi-manual ]; then + return 1 + fi + + # check override file first + if [ -f /root/.pwnagotchi-auto ]; then + return 0 + fi + + # if usb0 is up, we're in MANU + if is_interface_up usb0; then + return 1 + fi + + # if eth0 is up (for other boards), we're in MANU + if is_interface_up eth0; then + return 0 + fi + + # no override, but none of the interfaces is up -> AUTO + return 0 +} + +# check if we need to decrypt something +is_crypted_mode() { + if [ -f /root/.pwnagotchi-crypted ]; then + return 0 + fi + return 1 +} + +# decryption loop +is_decrypted() { + while read -r mapping container mount; do + # mapping = name the device or file will be mapped to + # container = the luks encrypted device or file + # mount = the mountpoint + + # fail if not mounted + if ! mountpoint -q "$mount" >/dev/null 2>&1; then + if [ -f /tmp/.pwnagotchi-secret-"$mapping" ]; then + /dev/null 2>&1; then + echo "Container decrypted!" + fi + fi + + if mount /dev/mapper/"$mapping" "$mount" >/dev/null 2>&1; then + echo "Mounted /dev/mapper/$mapping to $mount" + continue + fi + fi + + if ! ip -4 addr show wlan0 | grep inet >/dev/null 2>&1; then + >/dev/null 2>&1 ip addr add 192.168.0.10/24 dev wlan0 + fi + + if ! pgrep -f decryption-webserver >/dev/null 2>&1; then + >/dev/null 2>&1 decryption-webserver & + fi + + if ! pgrep wpa_supplicant >/dev/null 2>&1; then + >/tmp/wpa_supplicant.conf cat </dev/null 2>&1 wpa_supplicant -u -s -O -D nl80211 -i wlan0 -c /tmp/wpa_supplicant.conf & + fi + + if ! pgrep dnsmasq >/dev/null 2>&1; then + >/dev/null 2>&1 dnsmasq -k -p 53 -h -O "6,192.168.0.10" -A "/#/192.168.0.10" -i wlan0 -K -F 192.168.0.50,192.168.0.60,255.255.255.0,24h & + fi + + return 1 + fi + done /dev/null + # delete + rm /tmp/.pwnagotchi-secret-* + sync # flush + + pkill wpa_supplicant + pkill dnsmasq + pid="$(pgrep -f "decryption-webserver")" + [[ -n "$pid" ]] && kill "$pid" + + return 0 +} diff --git a/builder/data/etc/NetworkManager/NetworkManager.conf b/builder/data/64bit/etc/NetworkManager/NetworkManager.conf similarity index 100% rename from builder/data/etc/NetworkManager/NetworkManager.conf rename to builder/data/64bit/etc/NetworkManager/NetworkManager.conf diff --git a/builder/data/64bit/etc/bash_completion.d/pwnagotchi_completion.sh b/builder/data/64bit/etc/bash_completion.d/pwnagotchi_completion.sh new file mode 100644 index 00000000..68edc4e9 --- /dev/null +++ b/builder/data/64bit/etc/bash_completion.d/pwnagotchi_completion.sh @@ -0,0 +1,36 @@ +_show_complete() +{ + local cur opts node_names all_options opt_line + all_options=" +pwnagotchi -h --help -C --config -U --user-config --manual --skip-session --clear --debug --version --print-config --wizard --check-update --donate {plugins,google} +pwnagotchi plugins -h --help {list,install,enable,disable,uninstall,update,upgrade} +pwnagotchi plugins list -i --installed -h --help +pwnagotchi plugins install -h --help +pwnagotchi plugins uninstall -h --help +pwnagotchi plugins enable -h --help +pwnagotchi plugins disable -h --help +pwnagotchi plugins update -h --help +pwnagotchi plugins upgrade -h --help +pwnagotchi google -h --help {login,refresh} +pwnagotchi google login -h --help +pwnagotchi google refresh -h --help +" + COMPREPLY=() + cur="${COMP_WORDS[COMP_CWORD]}" + # shellcheck disable=SC2124 + cmd="${COMP_WORDS[@]:0:${#COMP_WORDS[@]}-1}" + opt_line="$(grep -m1 "$cmd" <<<"$all_options")" + if [[ ${cur} == -* ]] ; then + opts="$(echo "$opt_line" | tr ' ' '\n' | awk '/^ *-/{gsub("[^a-zA-Z0-9-]","",$1);print $1}')" + # shellcheck disable=SC2207 + COMPREPLY=( $(compgen -W "${opts}" -- "${cur}") ) + return 0 + fi + + # shellcheck disable=SC2086 + opts="$(echo $opt_line | grep -Po '{\K[^}]+' | tr ',' '\n')" + # shellcheck disable=SC2207 + COMPREPLY=( $(compgen -W "${opts}" -- "${cur}") ) +} + +complete -F _show_complete pwnagotchi diff --git a/builder/data/64bit/etc/dphys-swapfile b/builder/data/64bit/etc/dphys-swapfile new file mode 100644 index 00000000..1c908a10 --- /dev/null +++ b/builder/data/64bit/etc/dphys-swapfile @@ -0,0 +1,26 @@ +# /etc/dphys-swapfile - user settings for dphys-swapfile package +# author Neil Franklin, last modification 2010.05.05 +# copyright ETH Zuerich Physics Departement +# use under either modified/non-advertising BSD or GPL license + +# this file is sourced with . so full normal sh syntax applies + +# the default settings are added as commented out CONF_*=* lines + + +# where we want the swapfile to be, this is the default +#CONF_SWAPFILE=/var/swap + +# set size to absolute value, leaving empty (default) then uses computed value +# you most likely don't want this, unless you have an special disk situation +CONF_SWAPSIZE=2048 + +# set size to computed value, this times RAM size, dynamically adapts, +# guarantees that there is enough swap without wasting disk space on excess +#CONF_SWAPFACTOR=2 + +# restrict size (computed and absolute!) to maximally this limit +# can be set to empty for no limit, but beware of filled partitions! +# this is/was a (outdated?) 32bit kernel limit (in MBytes), do not overrun it +# but is also sensible on 64bit to prevent filling /var or even / partition +#CONF_MAXSWAP=2048 \ No newline at end of file diff --git a/builder/data/etc/network/interfaces.d/eth0-cfg b/builder/data/64bit/etc/network/interfaces.d/eth0-cfg similarity index 100% rename from builder/data/etc/network/interfaces.d/eth0-cfg rename to builder/data/64bit/etc/network/interfaces.d/eth0-cfg diff --git a/builder/data/etc/network/interfaces.d/lo-cfg b/builder/data/64bit/etc/network/interfaces.d/lo-cfg similarity index 100% rename from builder/data/etc/network/interfaces.d/lo-cfg rename to builder/data/64bit/etc/network/interfaces.d/lo-cfg diff --git a/builder/data/etc/network/interfaces.d/usb0-cfg b/builder/data/64bit/etc/network/interfaces.d/usb0-cfg similarity index 91% rename from builder/data/etc/network/interfaces.d/usb0-cfg rename to builder/data/64bit/etc/network/interfaces.d/usb0-cfg index a5ba8508..3521780a 100644 --- a/builder/data/etc/network/interfaces.d/usb0-cfg +++ b/builder/data/64bit/etc/network/interfaces.d/usb0-cfg @@ -5,4 +5,4 @@ iface usb0 inet static network 10.0.0.0 broadcast 10.0.0.255 gateway 10.0.0.1 - metric 101 \ No newline at end of file + metric 101 diff --git a/builder/data/etc/network/interfaces.d/wlan0-cfg b/builder/data/64bit/etc/network/interfaces.d/wlan0-cfg similarity index 100% rename from builder/data/etc/network/interfaces.d/wlan0-cfg rename to builder/data/64bit/etc/network/interfaces.d/wlan0-cfg diff --git a/builder/data/64bit/etc/systemd/system/bettercap.service b/builder/data/64bit/etc/systemd/system/bettercap.service new file mode 100644 index 00000000..ce8e8290 --- /dev/null +++ b/builder/data/64bit/etc/systemd/system/bettercap.service @@ -0,0 +1,13 @@ +[Unit] +Description=bettercap api.rest service. +Documentation=https://bettercap.org +Wants=network.target + +[Service] +Type=simple +ExecStart=/usr/bin/bettercap-launcher +Restart=always +RestartSec=30 + +[Install] +WantedBy=multi-user.target diff --git a/builder/data/64bit/etc/systemd/system/bluetooth.service b/builder/data/64bit/etc/systemd/system/bluetooth.service new file mode 100644 index 00000000..6780e4b8 --- /dev/null +++ b/builder/data/64bit/etc/systemd/system/bluetooth.service @@ -0,0 +1,20 @@ +[Unit] +Description=Bluetooth service +Documentation=man:bluetoothd(8) +ConditionPathIsDirectory=/sys/class/bluetooth + +[Service] +Type=dbus +BusName=org.bluez +ExecStart=/usr/libexec/bluetooth/bluetoothd --noplugin=sap,a2dp +NotifyAccess=main +#WatchdogSec=10 +#Restart=on-failure +CapabilityBoundingSet=CAP_NET_ADMIN CAP_NET_BIND_SERVICE +LimitNPROC=1 +ProtectHome=true +ProtectSystem=full + +[Install] +WantedBy=bluetooth.target +Alias=dbus-org.bluez.service diff --git a/builder/data/64bit/etc/systemd/system/pwnagotchi.service b/builder/data/64bit/etc/systemd/system/pwnagotchi.service new file mode 100644 index 00000000..2810e3f5 --- /dev/null +++ b/builder/data/64bit/etc/systemd/system/pwnagotchi.service @@ -0,0 +1,20 @@ +[Unit] +Description=pwnagotchi Deep Reinforcement Learning instrumenting bettercap for WiFI pwning. +Documentation=https://pwnagotchi.org +Wants=network.target +After=pwngrid-peer.service + +[Service] +Type=simple +WorkingDirectory=~ +ExecStart=/usr/bin/pwnagotchi-launcher +ExecStopPost=/usr/bin/bash -c "if egrep -qi 'personality.clear_on_exit[ =]*true' /etc/pwnagotchi/config.toml ; then /usr/local/bin/pwnagotchi --clear; fi" +Restart=always +RestartSec=30 +TasksMax=infinity +LimitNPROC=infinity +StandardOutput=null +StandardError=null + +[Install] +WantedBy=multi-user.target diff --git a/builder/data/64bit/etc/systemd/system/pwngrid-peer.service b/builder/data/64bit/etc/systemd/system/pwngrid-peer.service new file mode 100644 index 00000000..9a55eb3f --- /dev/null +++ b/builder/data/64bit/etc/systemd/system/pwngrid-peer.service @@ -0,0 +1,16 @@ +[Unit] +Description=pwngrid peer service. +Documentation=https://pwnagotchi.ai +Wants=network.target +After=bettercap.service + +[Service] +Environment=LD_PRELOAD=/usr/local/lib/libpcap.so.1 +Environment=LD_LIBRARY_PATH=/usr/local/lib +Type=simple +ExecStart=/usr/local/bin/pwngrid -keys /etc/pwnagotchi -peers /root/peers -address 127.0.0.1:8666 -client-token /root/.api-enrollment.json -wait -log /etc/pwnagotchi/log/pwngrid-peer.log -iface wlan0mon +Restart=always +RestartSec=30 + +[Install] +WantedBy=multi-user.target diff --git a/builder/data/etc/update-motd.d/01-motd b/builder/data/64bit/etc/update-motd.d/01-motd similarity index 95% rename from builder/data/etc/update-motd.d/01-motd rename to builder/data/64bit/etc/update-motd.d/01-motd index 31f6c6bd..1f9d52d2 100755 --- a/builder/data/etc/update-motd.d/01-motd +++ b/builder/data/64bit/etc/update-motd.d/01-motd @@ -20,6 +20,7 @@ echo " I'm managed by systemd. Here are some basic commands." echo echo " If you want to know what I'm doing, you can check my logs with the command" echo " - pwnlog" +echo " - sudo pwnagotchi --wizard, to help set up a config.toml" echo " - sudo pwnagotchi --version, to check the current version" echo " - sudo pwnagotchi --donate, to see how you can donate to this project" echo " - sudo pwnagotchi --check-update, to see if there is a new version available" diff --git a/builder/pwnagotchi.json.pkr.hcl b/builder/data/64bit/raspberrypi64.json.pkr.hcl similarity index 79% rename from builder/pwnagotchi.json.pkr.hcl rename to builder/data/64bit/raspberrypi64.json.pkr.hcl index 363e7f0c..2dbe0cb5 100644 --- a/builder/pwnagotchi.json.pkr.hcl +++ b/builder/data/64bit/raspberrypi64.json.pkr.hcl @@ -6,7 +6,7 @@ packer { } ansible = { source = "github.com/hashicorp/ansible" - version = "~> 1" + version = ">= 1.1.1" } } } @@ -25,7 +25,7 @@ source "arm" "rpi64-pwnagotchi" { file_checksum_type = "sha256" file_target_extension = "xz" file_unarchive_cmd = ["unxz", "$ARCHIVE_PATH"] - image_path = "../../../pwnagotchi-rpi-bookworm-${var.pwn_version}-arm64.img" + image_path = "../../../pwnagotchi-64bit.img" qemu_binary_source_path = "/usr/libexec/qemu-binfmt/aarch64-binfmt-P" qemu_binary_destination_path = "/usr/libexec/qemu-binfmt/aarch64-binfmt-P" image_build_method = "resize" @@ -61,34 +61,30 @@ build { provisioner "file" { destination = "/usr/bin/" sources = [ - "data/usr/bin/bettercap-launcher", - "data/usr/bin/hdmioff", - "data/usr/bin/hdmion", - "data/usr/bin/monstart", - "data/usr/bin/monstop", - "data/usr/bin/pwnagotchi-launcher", - "data/usr/bin/pwnlib", + "data/64bit/usr/bin/bettercap-launcher", + "data/64bit/usr/bin/hdmioff", + "data/64bit/usr/bin/hdmion", + "data/64bit/usr/bin/monstart", + "data/64bit/usr/bin/monstop", + "data/64bit/usr/bin/pwnagotchi-launcher", + "data/64bit/usr/bin/pwnlib", ] } provisioner "shell" { inline = ["chmod +x /usr/bin/*"] } - provisioner "shell" { - inline = ["dpkg --add-architecture armhf"] - } - provisioner "file" { destination = "/etc/systemd/system/" sources = [ - "data/etc/systemd/system/bettercap.service", - "data/etc/systemd/system/pwnagotchi.service", - "data/etc/systemd/system/pwngrid-peer.service", + "data/64bit/etc/systemd/system/bettercap.service", + "data/64bit/etc/systemd/system/pwnagotchi.service", + "data/64bit/etc/systemd/system/pwngrid-peer.service", ] } provisioner "file" { destination = "/etc/update-motd.d/01-motd" - source = "data/etc/update-motd.d/01-motd" + source = "data/64bit/etc/update-motd.d/01-motd" } provisioner "shell" { inline = ["chmod +x /etc/update-motd.d/*"] @@ -99,6 +95,6 @@ build { provisioner "ansible-local" { command = "ANSIBLE_FORCE_COLOR=1 PYTHONUNBUFFERED=1 PWN_VERSION=${var.pwn_version} PWN_HOSTNAME=${var.pwn_hostname} ansible-playbook" extra_arguments = ["--extra-vars \"ansible_python_interpreter=/usr/bin/python3\""] - playbook_file = "raspberrypi64.yml" + playbook_file = "data/64bit/raspberrypi64.yml" } } \ No newline at end of file diff --git a/builder/raspberrypi64.yml b/builder/data/64bit/raspberrypi64.yml similarity index 91% rename from builder/raspberrypi64.yml rename to builder/data/64bit/raspberrypi64.yml index 6b7e8973..b26b1f7b 100644 --- a/builder/raspberrypi64.yml +++ b/builder/data/64bit/raspberrypi64.yml @@ -10,7 +10,7 @@ full_pi5: "6.1.0-rpi8-rpi-2712" pwnagotchi: hostname: "{{ lookup('env', 'PWN_HOSTNAME') | default('pwnagotchi', true) }}" - version: "{{ lookup('env', 'PWN_VERSION') | default('pwnagotchi-torch', true) }}" + version: "{{ lookup('env', 'PWN_VERSION') | default('pwnagotchi', true) }}" system: boot_options: - "dtoverlay=dwc2" @@ -27,7 +27,6 @@ - fstrim.timer - pwnagotchi.service - pwngrid-peer.service - - zramswap.service disable: - apt-daily-upgrade.service - apt-daily-upgrade.timer @@ -40,11 +39,11 @@ source: "https://github.com/jayofelony/caplets.git" bettercap: source: "https://github.com/jayofelony/bettercap.git" - url: "https://github.com/jayofelony/bettercap/releases/download/v2.32.4/bettercap-2.32.4-aarch64.zip" + url: "https://github.com/jayofelony/bettercap/releases/download/2.32.4/bettercap-2.32.4.zip" ui: "https://github.com/bettercap/ui/releases/download/v1.3.0/ui.zip" pwngrid: source: "https://github.com/jayofelony/pwngrid.git" - url: "https://github.com/jayofelony/pwngrid/releases/download/v1.11.0/pwngrid-1.11.0-aarch64.zip" + url: "https://github.com/jayofelony/pwngrid/releases/download/v1.10.5/pwngrid-1.10.5-aarch64.zip" apt: downgrade: - libpcap-dev_1.9.1-4_arm64.deb @@ -78,6 +77,7 @@ - build-essential - curl - dkms + - dphys-swapfile - fbi - firmware-atheros - firmware-brcm80211 @@ -102,7 +102,6 @@ - libbz2-dev - libc-ares-dev - libc6-dev - - libc6:armhf - libcap-dev - libcurl-ocaml-dev - libdbus-1-dev @@ -117,10 +116,7 @@ - libgmp3-dev - libgstreamer1.0-0 - libhdf5-dev - - libisl23:armhf - liblapack-dev - - libmpc3:armhf - - libmpfr6:armhf - libncursesw5-dev - libnetfilter-queue-dev - libopenblas-dev @@ -135,13 +131,13 @@ - libsqlite3-dev - libssl-dev - libssl-ocaml-dev - - libstdc++6:armhf - libswscale5 - libtiff6 - libtool - libusb-1.0-0-dev - lsof - make + - ntp - python3-dbus - python3-flask - python3-flask-cors @@ -173,10 +169,8 @@ - wl - xxd - zlib1g-dev - - zram-tools environment: ARCHFLAGS: "-arch aarch64" - QEMU_UNAME: "{{ kernel.full }}" tasks: # First we install packages @@ -187,6 +181,12 @@ update_cache: yes install_recommends: false + - name: update pip3, setuptools, wheel + shell: "python3 -m pip install --upgrade pip setuptools wheel --break-system-packages" + args: + executable: /bin/bash + chdir: /usr/local/src + # Now we set up /boot/firmware - name: Create pi user copy: @@ -298,22 +298,9 @@ state: absent path: /usr/local/src/hcxtools - # Install nexmon to fix wireless scanning (takes 2.5G of space) - - name: symlink 1 - file: - src: "/usr/lib/arm-linux-gnueabihf/libisl.so.23.2.0" - dest: "/usr/lib/arm-linux-gnueabihf/libisl.so.10" - state: link - - - name: symlink 2 - file: - src: "/usr/lib/arm-linux-gnueabihf/libmpfr.so.6.2.0" - dest: "/usr/lib/arm-linux-gnueabihf/libmpfr.so.4" - state: link - - name: clone nexmon repository git: - repo: https://github.com/seemoo-lab/nexmon.git + repo: https://github.com/DrSchottky/nexmon.git dest: /usr/local/src/nexmon # FIRST WE BUILD DRIVER FOR RPi5 @@ -366,7 +353,7 @@ - name: clone nexmon repository git: - repo: https://github.com/seemoo-lab/nexmon.git + repo: https://github.com/DrSchottky/nexmon.git dest: /usr/local/src/nexmon - name: make firmware, RPi4 @@ -479,7 +466,7 @@ - name: clone pwnagotchi repository git: - repo: https://github.com/jayofelony/pwnagotchi-bookworm.git + repo: https://github.com/jayofelony/pwnagotchi.git dest: /usr/local/src/pwnagotchi - name: build pwnagotchi wheel @@ -511,6 +498,11 @@ block: | export GOPATH=$HOME/go export PATH=/usr/local/go/bin:$PATH:$GOPATH/bin + alias custom='cd /usr/local/share/pwnagotchi/custom-plugins/' + alias config='sudo nano /etc/pwnagotchi/config.toml' + alias pwnlog='tail -f -n300 /etc/pwnagotchi/log/pwn*.log | sed --unbuffered "s/,[[:digit:]]\\{3\\}\\]//g" | cut -d " " -f 2-' + alias pwnver='python3 -c "import pwnagotchi as p; print(p.__version__)"' + alias pwnkill='sudo killall -USR1 pwnagotchi' when: golang.changed - name: download pwngrid @@ -534,7 +526,7 @@ repo: "{{ packages.bettercap.source }}" dest: /usr/local/src/bettercap - - name: install bettercap 2.32.2 + - name: install bettercap 2.32.4 shell: "export GOPATH=$HOME/go && export PATH=/usr/local/go/bin:$PATH:$GOPATH/bin && go mod tidy && make && make install" args: executable: /bin/bash @@ -587,11 +579,6 @@ path: /etc/pwnagotchi state: directory - - name: create log folder - file: - path: /home/pi/logs - state: directory - - name: check if user configuration exists stat: path: /etc/pwnagotchi/config.toml @@ -604,7 +591,7 @@ # Add your configuration overrides on this file any configuration changes done to default.toml will be lost! # Example: # ui.display.enabled = true - # ui.display.type = "waveshare_2" + # ui.display.type = "waveshare_4" when: not user_config.stat.exists - name: Delete motd @@ -617,24 +604,6 @@ state: absent path: /etc/update-motd.d/10-uname - - name: Add pwnlog alias - lineinfile: - dest: /home/pi/.bashrc - line: "\nalias pwnlog='tail -f -n300 /home/pi/logs/pwn*.log | sed --unbuffered \"s/,[[:digit:]]\\{3\\}\\]//g\" | cut -d \" \" -f 2-'" - insertafter: EOF - - - name: Add pwnver alias - lineinfile: - dest: /home/pi/.bashrc - line: "\nalias pwnver='python3 -c \"import pwnagotchi as p; print(p.__version__)\"'" - insertafter: EOF - - - name: Add pwnkill alias to restart pwnagotchi with a signal - lineinfile: - dest: /home/pi/.bashrc - line: "\nalias pwnkill='sudo killall -USR1 pwnagotchi'" - insertafter: EOF - - name: add firmware packages to hold dpkg_selections: name: "{{ item }}" diff --git a/builder/data/64bit/root/client_secrets.json b/builder/data/64bit/root/client_secrets.json new file mode 100644 index 00000000..e69de29b diff --git a/builder/data/64bit/root/settings.yaml b/builder/data/64bit/root/settings.yaml new file mode 100644 index 00000000..5f198bde --- /dev/null +++ b/builder/data/64bit/root/settings.yaml @@ -0,0 +1,15 @@ +client_config_backend: file +client_config_file: /root/client_secrets.json +client_config: + client_id: + client_secret: + +save_credentials: True +save_credentials_backend: file +save_credentials_file: /root/credentials.json + +get_refresh_token: True + +oauth_scope: + - https://www.googleapis.com/auth/drive + - https://www.googleapis.com/auth/drive.install \ No newline at end of file diff --git a/builder/data/usr/bin/bettercap-launcher b/builder/data/64bit/usr/bin/bettercap-launcher similarity index 100% rename from builder/data/usr/bin/bettercap-launcher rename to builder/data/64bit/usr/bin/bettercap-launcher diff --git a/builder/data/usr/bin/decryption-webserver b/builder/data/64bit/usr/bin/decryption-webserver similarity index 100% rename from builder/data/usr/bin/decryption-webserver rename to builder/data/64bit/usr/bin/decryption-webserver diff --git a/builder/data/64bit/usr/bin/hdmioff b/builder/data/64bit/usr/bin/hdmioff new file mode 100755 index 00000000..5c32d62c --- /dev/null +++ b/builder/data/64bit/usr/bin/hdmioff @@ -0,0 +1,2 @@ +#!/usr/bin/env bash +sudo /usr/bin/tvservice -o \ No newline at end of file diff --git a/builder/data/64bit/usr/bin/hdmion b/builder/data/64bit/usr/bin/hdmion new file mode 100755 index 00000000..eec440fc --- /dev/null +++ b/builder/data/64bit/usr/bin/hdmion @@ -0,0 +1,2 @@ +#!/usr/bin/env bash +sudo /usr/bin/tvservice -p \ No newline at end of file diff --git a/builder/data/64bit/usr/bin/monstart b/builder/data/64bit/usr/bin/monstart new file mode 100755 index 00000000..4c00a5c5 --- /dev/null +++ b/builder/data/64bit/usr/bin/monstart @@ -0,0 +1,3 @@ +#!/usr/bin/env bash +source /usr/bin/pwnlib +start_monitor_interface diff --git a/builder/data/64bit/usr/bin/monstop b/builder/data/64bit/usr/bin/monstop new file mode 100755 index 00000000..c90a3aef --- /dev/null +++ b/builder/data/64bit/usr/bin/monstop @@ -0,0 +1,3 @@ +#!/usr/bin/env bash +source /usr/bin/pwnlib +stop_monitor_interface \ No newline at end of file diff --git a/builder/data/64bit/usr/bin/pwnagotchi-launcher b/builder/data/64bit/usr/bin/pwnagotchi-launcher new file mode 100755 index 00000000..9728c0cd --- /dev/null +++ b/builder/data/64bit/usr/bin/pwnagotchi-launcher @@ -0,0 +1,16 @@ +#!/usr/bin/env bash +source /usr/bin/pwnlib + +# we need to decrypt something +if is_crypted_mode; then + while ! is_decrypted; do + echo "Waiting for decryption..." + sleep 1 + done +fi + +if is_auto_mode; then + /usr/local/bin/pwnagotchi +else + /usr/local/bin/pwnagotchi --manual +fi diff --git a/builder/data/usr/bin/pwnlib b/builder/data/64bit/usr/bin/pwnlib similarity index 100% rename from builder/data/usr/bin/pwnlib rename to builder/data/64bit/usr/bin/pwnlib diff --git a/builder/data/etc/default/zramswap b/builder/data/etc/default/zramswap deleted file mode 100644 index 98f9b722..00000000 --- a/builder/data/etc/default/zramswap +++ /dev/null @@ -1,15 +0,0 @@ -# Specifies amount of zram devices to create. -# By default, zramswap-start will use all available cores. -#CORES=1 - -# Specifies the amount of RAM that should be used for zram -# based on a percentage the total amount of available memory -PERCENTAGE=60 - -# Specifies a static amount of RAM that should be used for -# the ZRAM devices, this is in MiB -#ALLOCATION=256 - -# Specifies the priority for the swap devices, see swapon(2) -# for more details. -#PRIORITY=100 \ No newline at end of file diff --git a/pwnagotchi/_version.py b/pwnagotchi/_version.py index 964a32ab..af815f23 100644 --- a/pwnagotchi/_version.py +++ b/pwnagotchi/_version.py @@ -1 +1 @@ -__version__ = '2.8.2' +__version__ = '2.8.7' diff --git a/pwnagotchi/ai/__init__.py b/pwnagotchi/ai/__init__.py index dd1c549a..24d798a5 100644 --- a/pwnagotchi/ai/__init__.py +++ b/pwnagotchi/ai/__init__.py @@ -23,6 +23,12 @@ def load(config, agent, epoch, from_disk=True): from stable_baselines3 import A2C logging.debug("[AI] A2C imported in %.2fs" % (time.time() - start)) + # remove invalid ai.parameters leftover from tensor_flow, if present + for key in [ 'alpha', 'epsilon', 'lr_schedule' ]: + if key in config['params']: + logging.info("Removing legacy ai parameter %s" % key); + del config['params'][key] + start = time.time() from stable_baselines3.a2c import MlpPolicy logging.debug("[AI] MlpPolicy imported in %.2fs" % (time.time() - start)) diff --git a/pwnagotchi/defaults.toml b/pwnagotchi/defaults.toml index b175fff1..5fdc276e 100644 --- a/pwnagotchi/defaults.toml +++ b/pwnagotchi/defaults.toml @@ -18,10 +18,6 @@ main.custom_plugin_repos = [ main.custom_plugins = "/usr/local/share/pwnagotchi/custom-plugins/" -main.plugins.aircrackonly.enabled = true - -main.plugins.hashie.enabled = true - main.plugins.auto-update.enabled = true main.plugins.auto-update.install = true main.plugins.auto-update.interval = 1 @@ -36,7 +32,7 @@ main.plugins.bt-tether.devices.android-phone.netmask = 24 main.plugins.bt-tether.devices.android-phone.interval = 1 main.plugins.bt-tether.devices.android-phone.scantime = 10 main.plugins.bt-tether.devices.android-phone.max_tries = 10 -main.plugins.bt-tether.devices.android-phone.share_internet = false +main.plugins.bt-tether.devices.android-phone.share_internet = true main.plugins.bt-tether.devices.android-phone.priority = 1 main.plugins.bt-tether.devices.ios-phone.enabled = false @@ -47,7 +43,7 @@ main.plugins.bt-tether.devices.ios-phone.netmask = 24 main.plugins.bt-tether.devices.ios-phone.interval = 5 main.plugins.bt-tether.devices.ios-phone.scantime = 20 main.plugins.bt-tether.devices.ios-phone.max_tries = 0 -main.plugins.bt-tether.devices.ios-phone.share_internet = false +main.plugins.bt-tether.devices.ios-phone.share_internet = true main.plugins.bt-tether.devices.ios-phone.priority = 999 main.plugins.fix_services.enabled = true @@ -118,7 +114,7 @@ main.no_restart = false main.filter = "" -main.log.path = "/home/pi/logs/pwnagotchi.log" +main.log.path = "/etc/pwnagotchi/log/pwnagotchi.log" main.log.rotation.enabled = true main.log.rotation.size = "10M" @@ -156,6 +152,10 @@ personality.bond_encounters_factor = 20000 personality.throttle_a = 0.4 personality.throttle_d = 0.9 +personality.clear_on_exit = true # clear display when shutting down cleanly + +ui.invert = false # false = black background, true = white background + ui.fps = 0.0 ui.font.name = "DejaVuSansMono" # for japanese: fonts-japanese-gothic ui.font.size_offset = 0 # will be added to the font size @@ -185,6 +185,9 @@ ui.faces.debug = "(#__#)" ui.faces.upload = "(1__0)" ui.faces.upload1 = "(1__1)" ui.faces.upload2 = "(0__1)" +ui.faces.png = false +ui.faces.position_x = 0 +ui.faces.position_y = 34 ui.web.enabled = true ui.web.address = "::" # listening on both ipv4 and ipv6 - switch to 0.0.0.0 to listen on just ipv4 @@ -216,7 +219,7 @@ bettercap.silence = [ fs.memory.enabled = true fs.memory.mounts.log.enabled = true -fs.memory.mounts.log.mount = "/home/pi/logs" +fs.memory.mounts.log.mount = "/etc/pwnagotchi/log/" fs.memory.mounts.log.size = "50M" fs.memory.mounts.log.sync = 60 fs.memory.mounts.log.zram = true diff --git a/pwnagotchi/grid.py b/pwnagotchi/grid.py index 6c908dbd..ee1aac8f 100644 --- a/pwnagotchi/grid.py +++ b/pwnagotchi/grid.py @@ -89,7 +89,7 @@ def update_data(last_session): 'uname': subprocess.getoutput("uname -a"), 'brain': brain, 'version': pwnagotchi.__version__, - 'build': "Pwnagotchi-Torch by Jayofelony", + 'build': "Pwnagotchi by Jayofelony", 'plugins': enabled, 'language': language, 'bettercap': subprocess.getoutput("bettercap -version").split(".\n\n")[1], diff --git a/pwnagotchi/locale/es/LC_MESSAGES/voice.mo b/pwnagotchi/locale/es/LC_MESSAGES/voice.mo index 3d6639e5..12ce7f24 100644 Binary files a/pwnagotchi/locale/es/LC_MESSAGES/voice.mo and b/pwnagotchi/locale/es/LC_MESSAGES/voice.mo differ diff --git a/pwnagotchi/locale/es/LC_MESSAGES/voice.po b/pwnagotchi/locale/es/LC_MESSAGES/voice.po index b2f77686..fc5f8091 100644 --- a/pwnagotchi/locale/es/LC_MESSAGES/voice.po +++ b/pwnagotchi/locale/es/LC_MESSAGES/voice.po @@ -36,18 +36,18 @@ msgid "The neural network is ready." msgstr "La red neuronal está lista." msgid "Generating keys, do not turn off ..." -msgstr "" +msgstr "Generando llaves, no me apagues ..." #, python-brace-format msgid "Hey, channel {channel} is free! Your AP will say thanks." msgstr "¡Oye, el canal {channel} está libre! Tu AP lo agradecerá." msgid "Reading last session logs ..." -msgstr "" +msgstr "Leyendo los logs de la última sesión ..." #, python-brace-format msgid "Read {lines_so_far} log lines so far ..." -msgstr "" +msgstr "Leyendo {lines_so_far} líneas de log hasta ahora ..." msgid "I'm bored ..." msgstr "Estoy aburrido ..." @@ -74,7 +74,7 @@ msgid "Leave me alone ..." msgstr "Me siento tan solo ..." msgid "I'm mad at you!" -msgstr "" +msgstr "Toy re enojado con vos!" msgid "I'm living the life!" msgstr "¡Estoy viviendo la vida!" @@ -97,11 +97,11 @@ msgstr "¡Hola {name}! Encantado de conocerte." #, python-brace-format msgid "Yo {name}! Sup?" -msgstr "" +msgstr "Que onda {name}!?" #, python-brace-format msgid "Hey {name} how are you doing?" -msgstr "" +msgstr "Eh!, ¿Que haces {name}?" #, python-brace-format msgid "Unit {name} is nearby!" @@ -127,10 +127,10 @@ msgid "Missed!" msgstr "¡Perdido!" msgid "Good friends are a blessing!" -msgstr "" +msgstr "Lxs buenxs amigxs son una masa!" msgid "I love my friends!" -msgstr "" +msgstr "¡Amo a mis amigxs!" msgid "Nobody wants to play with me ..." msgstr "Nadie quiere jugar conmigo ..." @@ -143,7 +143,7 @@ msgstr "¡¿Dónde está todo el mundo?!" #, python-brace-format msgid "Napping for {secs}s ..." -msgstr "Descansando durante {secs}s ..." +msgstr "Descansando por {secs}s ..." msgid "Zzzzz" msgstr "Zzzzz" @@ -203,7 +203,7 @@ msgstr "Oops, algo salió mal ... Reiniciando ..." #, python-brace-format msgid "Uploading data to {to} ..." -msgstr "" +msgstr "Subiendo data a {to} ..." #, python-brace-format msgid "Downloading from {name} ..." diff --git a/pwnagotchi/locale/voice.pot b/pwnagotchi/locale/voice.pot index 6bf175cd..a8a46eea 100644 --- a/pwnagotchi/locale/voice.pot +++ b/pwnagotchi/locale/voice.pot @@ -8,7 +8,7 @@ msgid "" msgstr "" "Project-Id-Version: PACKAGE VERSION\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2024-01-25 23:40+0100\n" +"POT-Creation-Date: 2024-02-16 15:26-0300\n" "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" "Last-Translator: FULL NAME \n" "Language-Team: LANGUAGE \n" diff --git a/pwnagotchi/plugins/cmd.py b/pwnagotchi/plugins/cmd.py index 30e9fb18..9433c338 100644 --- a/pwnagotchi/plugins/cmd.py +++ b/pwnagotchi/plugins/cmd.py @@ -199,6 +199,9 @@ def list_plugins(args, config, pattern='*'): available_not_installed = set(available.keys()) - set(installed.keys()) max_len_list = available_and_installed if args.installed else available_not_installed + if not max_len_list: + print('Maybe try: sudo pwnagotchi plugins update') + return 1 max_len = max(map(len, max_len_list)) header = line.format(name='Plugin', width=max_len, version='Version', enabled='Active', status='Status') line_length = max(max_len, len('Plugin')) + len(header) - len('Plugin') - 12 # lol @@ -239,7 +242,7 @@ def list_plugins(args, config, pattern='*'): print('-' * line_length) if not found: - logging.info('Maybe try: pwnagotchi plugins update') + print('Maybe try: sudo pwnagotchi plugins update') return 1 return 0 diff --git a/pwnagotchi/plugins/default/aircrackonly.py b/pwnagotchi/plugins/default/aircrackonly.py deleted file mode 100644 index 98b1db52..00000000 --- a/pwnagotchi/plugins/default/aircrackonly.py +++ /dev/null @@ -1,73 +0,0 @@ -import pwnagotchi.plugins as plugins - -import logging -import subprocess -import string -import os - -''' -Aircrack-ng needed, to install: -> apt-get install aircrack-ng -''' - - -class AircrackOnly(plugins.Plugin): - __author__ = 'pwnagotchi [at] rossmarks [dot] uk' - __version__ = '1.0.1' - __license__ = 'GPL3' - __description__ = 'confirm pcap contains handshake/PMKID or delete it' - - def __init__(self): - self.text_to_set = "" - self.options = dict() - - def on_ready(self): - return - - def on_loaded(self): - logging.info("aircrackonly plugin loaded") - - if 'face' not in self.options: - self.options['face'] = '(>.<)' - - check = subprocess.run( - '/usr/bin/dpkg -l aircrack-ng | grep aircrack-ng | awk \'{print $2, $3}\'', shell=True, - stdout=subprocess.PIPE) - check = check.stdout.decode('utf-8').strip() - if check != "aircrack-ng ": - logging.info("aircrackonly: Found " + check) - else: - logging.warning("aircrack-ng is not installed!") - - def on_handshake(self, agent, filename, access_point, client_station): - display = agent.view() - to_delete = 0 - handshake_found = 0 - - result = subprocess.run(('/usr/bin/aircrack-ng ' + filename + ' | grep "1 handshake" | awk \'{print $2}\''), - shell=True, stdout=subprocess.PIPE) - result = result.stdout.decode('utf-8').translate({ord(c): None for c in string.whitespace}) - if result: - handshake_found = 1 - logging.info("[AircrackOnly] contains handshake") - - if handshake_found == 0: - result = subprocess.run(('/usr/bin/aircrack-ng ' + filename + ' | grep "PMKID" | awk \'{print $2}\''), - shell=True, stdout=subprocess.PIPE) - result = result.stdout.decode('utf-8').translate({ord(c): None for c in string.whitespace}) - if result: - logging.info("[AircrackOnly] contains PMKID") - else: - to_delete = 1 - - if to_delete == 1: - os.remove(filename) - self.text_to_set = "Removed an uncrackable pcap" - logging.warning("Removed uncrackable pcap " + filename) - display.update(force=True) - - def on_ui_update(self, ui): - if self.text_to_set: - ui.set('face', self.options['face']) - ui.set('status', self.text_to_set) - self.text_to_set = "" diff --git a/pwnagotchi/plugins/default/auto-update.py b/pwnagotchi/plugins/default/auto-update.py index bd279f15..9cd918f8 100644 --- a/pwnagotchi/plugins/default/auto-update.py +++ b/pwnagotchi/plugins/default/auto-update.py @@ -28,7 +28,8 @@ def check(version, repo, native=True): resp = requests.get("https://api.github.com/repos/%s/releases/latest" % repo) latest = resp.json() info['available'] = latest_ver = latest['tag_name'].replace('v', '') - is_arm64 = info['arch'].startswith('aarch') + is_armhf = info['arch'].startswith('arm') + is_aarch = info['arch'].startswith('aarch') local = version_to_tuple(info['current']) remote = version_to_tuple(latest_ver) @@ -36,12 +37,20 @@ def check(version, repo, native=True): if not native: info['url'] = "https://github.com/%s/archive/%s.zip" % (repo, latest['tag_name']) else: - if is_arm64: - # check if this release is compatible with aarch64 + if is_armhf: + # check if this release is compatible with armhf for asset in latest['assets']: download_url = asset['browser_download_url'] if (download_url.endswith('.zip') and - (info['arch'] in download_url or (is_arm64 and 'aarch64' in download_url))): + (info['arch'] in download_url or (is_armhf and 'armhf' in download_url))): + info['url'] = download_url + break + elif is_aarch: + # check if this release is compatible with arm64/aarch64 + for asset in latest['assets']: + download_url = asset['browser_download_url'] + if (download_url.endswith('.zip') and + (info['arch'] in download_url or (is_aarch and 'aarch' in download_url))): info['url'] = download_url break @@ -189,7 +198,7 @@ class AutoUpdate(plugins.Plugin): to_check = [ ('jayofelony/bettercap', parse_version('bettercap -version'), True, 'bettercap'), ('jayofelony/pwngrid', parse_version('pwngrid -version'), True, 'pwngrid-peer'), - ('jayofelony/pwnagotchi-bookworm', pwnagotchi.__version__, False, 'pwnagotchi') + ('jayofelony/pwnagotchi', pwnagotchi.__version__, False, 'pwnagotchi') ] for repo, local_version, is_native, svc_name in to_check: diff --git a/pwnagotchi/plugins/default/fix_services.py b/pwnagotchi/plugins/default/fix_services.py index af265cf4..51f2398e 100644 --- a/pwnagotchi/plugins/default/fix_services.py +++ b/pwnagotchi/plugins/default/fix_services.py @@ -11,6 +11,10 @@ from pwnagotchi import plugins import pwnagotchi.ui.faces as faces from pwnagotchi.bettercap import Client +from pwnagotchi.ui.components import Text +from pwnagotchi.ui.view import BLACK +import pwnagotchi.ui.fonts as fonts + class FixServices(plugins.Plugin): __author__ = 'jayofelony' @@ -21,19 +25,16 @@ class FixServices(plugins.Plugin): __help__ = """ Reload brcmfmac module when blindbug is detected, instead of rebooting. Adapted from WATCHDOG. """ - __defaults__ = { - 'enabled': True, - } def __init__(self): self.options = dict() - self.pattern1 = re.compile(r'wifi error while hopping to channel') - self.pattern2 = re.compile(r'Firmware has halted or crashed') - self.pattern3 = re.compile(r'error 400: could not find interface wlan0mon') + self.pattern = re.compile(r'brcmf_cfg80211_nexmon_set_channel.*?Set Channel failed') + self.pattern2 = re.compile(r'wifi error while hopping to channel') + self.pattern3 = re.compile(r'Firmware has halted or crashed') + self.pattern4 = re.compile(r'error 400: could not find interface wlan0mon') self.isReloadingMon = False self.connection = None self.LASTTRY = 0 - self._count = 0 def on_loaded(self): """ @@ -42,14 +43,27 @@ class FixServices(plugins.Plugin): logging.info("[Fix_Services] plugin loaded.") def on_ready(self, agent): - last_lines = self.get_last_lines('journalctl', ['-n10', '-k'], 10) + last_lines = ''.join(list(TextIOWrapper(subprocess.Popen(['journalctl', '-n10', '-k'], + stdout=subprocess.PIPE).stdout))[-10:]) try: cmd_output = subprocess.check_output("ip link show wlan0mon", shell=True) logging.debug("[Fix_Services ip link show wlan0mon]: %s" % repr(cmd_output)) if ",UP," in str(cmd_output): - logging.info("wlan0mon is up.") + logging.debug("wlan0mon is up.") - logging.info("[Fix_Services] Logs look good!") + if len(self.pattern.findall(last_lines)) >= 3: + if hasattr(agent, 'view'): + display = agent.view() + display.set('status', 'Blind-Bug detected. Restarting.') + display.update(force=True) + logging.debug('[Fix_Services] Blind-Bug detected. Restarting.') + try: + self._tryTurningItOffAndOnAgain(agent) + except Exception as err: + logging.warning("[Fix_Services turnOffAndOn] %s" % repr(err)) + + else: + logging.debug("[Fix_Services] Logs look good!") except Exception as err: logging.error("[Fix_Services ip link show wlan0mon]: %s" % repr(err)) @@ -63,12 +77,12 @@ class FixServices(plugins.Plugin): # apparently this only gets messages from bettercap going to syslog, not from syslog def on_bcap_sys_log(self, agent, event): if re.search('wifi error while hopping to channel', event['data']['Message']): - logging.info("[Fix_Services]SYSLOG MATCH: %s" % event['data']['Message']) - logging.info("[Fix_Services]**** restarting wifi.recon") + logging.debug("[Fix_Services]SYSLOG MATCH: %s" % event['data']['Message']) + logging.debug("[Fix_Services]**** restarting wifi.recon") try: result = agent.run("wifi.recon off; wifi.recon on") if result["success"]: - logging.info("[Fix_Services] wifi.recon flip: success!") + logging.debug("[Fix_Services] wifi.recon flip: success!") if hasattr(agent, 'view'): display = agent.view() if display: @@ -82,23 +96,14 @@ class FixServices(plugins.Plugin): logging.error("[Fix_Services]SYSLOG wifi.recon flip fail: %s" % err) self._tryTurningItOffAndOnAgain(agent) - def get_last_lines(self, command, args, n): - try: - process = subprocess.Popen([command] + args, stdout=subprocess.PIPE) - output = TextIOWrapper(process.stdout) - lines = output.readlines() - last_n_lines = ''.join(lines[-n:]) - return last_n_lines - except Exception as e: - print(f"Error occurred: {e}") - return None - def on_epoch(self, agent, epoch, epoch_data): - - last_lines = self.get_last_lines('journalctl', ['-n10', '-k'], 10) - other_last_lines = self.get_last_lines('journalctl', ['-n10'], 10) - other_other_last_lines = self.get_last_lines('tail', ['-n10', '/home/pi/logs/pwnagotchi.log'], 10) - + last_lines = ''.join(list(TextIOWrapper(subprocess.Popen(['journalctl', '-n10', '-k'], + stdout=subprocess.PIPE).stdout))[-10:]) + other_last_lines = ''.join(list(TextIOWrapper(subprocess.Popen(['journalctl', '-n10'], + stdout=subprocess.PIPE).stdout))[-10:]) + other_other_last_lines = ''.join( + list(TextIOWrapper(subprocess.Popen(['tail', '-n10', '/var/log/pwnagotchi.log'], + stdout=subprocess.PIPE).stdout))[-10:]) # don't check if we ran a reset recently logging.debug("[Fix_Services]**** epoch") if time.time() - self.LASTTRY > 180: @@ -108,18 +113,31 @@ class FixServices(plugins.Plugin): logging.debug("[Fix_Services]**** checking") # Look for pattern 1 - if len(self.pattern1.findall(other_last_lines)) >= 5: + if len(self.pattern.findall(last_lines)) >= 3: + logging.debug("[Fix_Services]**** Should trigger a reload of the wlan0mon device:\n%s" % last_lines) + if hasattr(agent, 'view'): + display = agent.view() + display.set('status', 'Blind-Bug detected. Restarting.') + display.update(force=True) + logging.debug('[Fix_Services] Blind-Bug detected. Restarting.') + try: + self._tryTurningItOffAndOnAgain(agent) + except Exception as err: + logging.warning("[Fix_Services] TTOAOA: %s" % repr(err)) + + # Look for pattern 2 + elif len(self.pattern2.findall(other_last_lines)) >= 5: logging.debug("[Fix_Services]**** Should trigger a reload of the wlan0mon device:\n%s" % last_lines) if hasattr(agent, 'view'): display = agent.view() display.set('status', 'Wifi channel stuck. Restarting recon.') display.update(force=True) - logging.info('[Fix_Services] Wifi channel stuck. Restarting recon.') + logging.debug('[Fix_Services] Wifi channel stuck. Restarting recon.') try: result = agent.run("wifi.recon off; wifi.recon on") if result["success"]: - logging.info("[Fix_Services] wifi.recon flip: success!") + logging.debug("[Fix_Services] wifi.recon flip: success!") if display: display.update(force=True, new_data={"status": "Wifi recon flipped!", "face": faces.COOL}) @@ -131,9 +149,9 @@ class FixServices(plugins.Plugin): except Exception as err: logging.error("[Fix_Services wifi.recon flip] %s" % repr(err)) - # Look for pattern 2 - elif len(self.pattern2.findall(other_last_lines)) >= 1: - logging.info("[Fix_Services] Firmware has halted or crashed. Restarting wlan0mon.") + # Look for pattern 3 + elif len(self.pattern3.findall(other_last_lines)) >= 1: + logging.debug("[Fix_Services] Firmware has halted or crashed. Restarting wlan0mon.") if hasattr(agent, 'view'): display = agent.view() display.set('status', 'Firmware has halted or crashed. Restarting wlan0mon.') @@ -145,9 +163,9 @@ class FixServices(plugins.Plugin): except Exception as err: logging.error("[Fix_Services monstart]: %s" % repr(err)) - # Look for pattern 3 - elif len(self.pattern3.findall(other_other_last_lines)) >= 3: - logging.info("[Fix_Services] wlan0 is down!") + # Look for pattern 4 + elif len(self.pattern4.findall(other_other_last_lines)) >= 3: + logging.debug("[Fix_Services] wlan0 is down!") if hasattr(agent, 'view'): display = agent.view() display.set('status', 'Restarting wlan0 now!') @@ -171,7 +189,7 @@ class FixServices(plugins.Plugin): elif level == "debug": logging.debug(message) else: - logging.info(message) + logging.debug(message) if ui: ui.update(force=force, new_data=displayData) @@ -186,7 +204,7 @@ class FixServices(plugins.Plugin): # avoid overlapping restarts, but allow it if it's been a while # (in case the last attempt failed before resetting "isReloadingMon") if self.isReloadingMon and (time.time() - self.LASTTRY) < 180: - logging.info("[Fix_Services] Duplicate attempt ignored") + logging.debug("[Fix_Services] Duplicate attempt ignored") else: self.isReloadingMon = True self.LASTTRY = time.time() @@ -213,7 +231,7 @@ class FixServices(plugins.Plugin): cmd_output = subprocess.check_output("ip link show wlan0mon", shell=True) logging.debug("[Fix_Services ip link show wlan0mon]: %s" % repr(cmd_output)) if ",UP," in str(cmd_output): - logging.info("wlan0mon is up. Skip reset?") + logging.debug("wlan0mon is up. Skip reset?") # not reliable, so don't skip just yet # print("wlan0mon is up. Skipping reset.") # self.isReloadingMon = False @@ -234,7 +252,7 @@ class FixServices(plugins.Plugin): except Exception as err: logging.error("[Fix_Services wifi.recon off] error %s" % (repr(err))) - logging.info("[Fix_Services] recon paused. Now trying wlan0mon reload") + logging.debug("[Fix_Services] recon paused. Now trying wlan0mon reload") try: cmd_output = subprocess.check_output("monstop", shell=True) @@ -250,14 +268,13 @@ class FixServices(plugins.Plugin): # # Future: while "not fixed yet": blah blah blah. if "max_attemts", then reboot like the old days # - tries = 0 + tries = 1 while tries < 3: try: # unload the module cmd_output = subprocess.check_output("sudo modprobe -r brcmfmac", shell=True) self.logPrintView("info", "[Fix_Services] unloaded brcmfmac", display, {"status": "Turning it off #%s" % tries, "face": faces.SMART}) - time.sleep(1 + tries) # reload the module try: @@ -265,27 +282,25 @@ class FixServices(plugins.Plugin): cmd_output = subprocess.check_output("sudo modprobe brcmfmac", shell=True) self.logPrintView("info", "[Fix_Services] reloaded brcmfmac") - time.sleep(10 + 4 * tries) # give it some time for wlan device to stabilize, or whatever # success! now make the mon0 try: cmd_output = subprocess.check_output("monstart", shell=True) - self.logPrintView("info", "[Fix_Services interface add wlan0mon] worked #%s: %s" + self.logPrintView("info", "[Fix_Services interface add wlan0mon worked #%s: %s" % (tries, cmd_output)) - time.sleep(tries + 5) try: # try accessing mon0 in bettercap result = connection.run("set wifi.interface wlan0mon") if "success" in result: - logging.info("[Fix_Services set wifi.interface wlan0mon] worked!") - self._count = self._count + 1 - time.sleep(1) + logging.debug("[Fix_Services set wifi.interface wlan0mon worked!") # stop looping and get back to recon break else: - logging.debug("[Fix_Services set wifi.interfaceface wlan0mon] failed? %s" % repr(result)) + logging.debug( + "[Fix_Services set wifi.interfaceface wlan0mon] failed? %s" % repr(result)) except Exception as err: - logging.debug("[Fix_Services set wifi.interface wlan0mon] except: %s" % repr(err)) + logging.debug( + "[Fix_Services set wifi.interface wlan0mon] except: %s" % repr(err)) except Exception as cerr: # if not display: print("failed loading wlan0mon attempt #%s: %s" % (tries, repr(cerr))) @@ -303,11 +318,11 @@ class FixServices(plugins.Plugin): tries = tries + 1 if tries < 3: - logging.info("[Fix_Services] wlan0mon didn't make it. trying again") + logging.debug("[Fix_Services] wlan0mon didn't make it. trying again") if not display: print(" wlan0mon didn't make it. trying again") else: - logging.info("[Fix_Services] wlan0mon loading failed, no choice but to reboot ..") + logging.debug("[Fix_Services] wlan0mon loading failed, no choice but to reboot ..") pwnagotchi.reboot() # exited the loop, so hopefully it loaded @@ -317,14 +332,14 @@ class FixServices(plugins.Plugin): "face": faces.INTENSE}) else: print("And back on again...") - logging.info("[Fix_Services] wlan0mon back up") + logging.debug("[Fix_Services] wlan0mon back up") else: self.LASTTRY = time.time() time.sleep(8 + tries * 2) # give it a bit before restarting recon in bettercap self.isReloadingMon = False - logging.info("[Fix_Services] re-enable recon") + logging.debug("[Fix_Services] re-enable recon") try: result = connection.run("wifi.clear; wifi.recon on") @@ -345,13 +360,24 @@ class FixServices(plugins.Plugin): logging.error("[Fix_Services wifi.recon on] %s" % repr(err)) pwnagotchi.reboot() - def on_unload(self, ui): + # called to setup the ui elements + def on_ui_setup(self, ui): with ui._lock: - try: - logging.info("[Fix_Services] unloaded") - except Exception as err: - logging.error("[Fix_Services] unload err %s " % repr(err)) - pass + # add custom UI elements + if "position" in self.options: + pos = self.options['position'].split(',') + pos = [int(x.strip()) for x in pos] + else: + pos = (ui.width() / 2 + 35, ui.height() - 11) + + logging.debug("Got here") + + # called when the ui is updated + def on_ui_update(self, ui): + return + + def on_unload(self, ui): + return # run from command line to brute force a reload diff --git a/pwnagotchi/plugins/default/gps.py b/pwnagotchi/plugins/default/gps.py index 0e5f958d..7bf72f8e 100644 --- a/pwnagotchi/plugins/default/gps.py +++ b/pwnagotchi/plugins/default/gps.py @@ -98,7 +98,7 @@ class GPS(plugins.Plugin): lat_pos = (127, 74) lon_pos = (122, 84) alt_pos = (127, 94) - elif ui.is_waveshare27inch(): + elif ui.is_waveshare2in7(): lat_pos = (6, 120) lon_pos = (1, 135) alt_pos = (6, 150) diff --git a/pwnagotchi/plugins/default/hashie.py b/pwnagotchi/plugins/default/hashie.py deleted file mode 100644 index b5c8e18a..00000000 --- a/pwnagotchi/plugins/default/hashie.py +++ /dev/null @@ -1,179 +0,0 @@ -import logging -import subprocess -import os -import json -import pwnagotchi.plugins as plugins -from threading import Lock - -''' -hcxpcapngtool needed, to install: -> git clone https://github.com/ZerBea/hcxtools.git -> cd hcxtools -> apt-get install libcurl4-openssl-dev libssl-dev zlib1g-dev -> make -> sudo make install -''' - - -class Hashie(plugins.Plugin): - __author__ = 'Jayofelony' - __version__ = '1.0.4' - __license__ = 'GPL3' - __description__ = ''' - Attempt to automatically convert pcaps to a crackable format. - If successful, the files containing the hashes will be saved - in the same folder as the handshakes. - The files are saved in their respective Hashcat format: - - EAPOL hashes are saved as *.22000 - - PMKID hashes are saved as *.16800 - All PCAP files without enough information to create a hash are - stored in a file that can be read by the webgpsmap plugin. - - Why use it?: - - Automatically convert handshakes to crackable formats! - We dont all upload our hashes online ;) - - Repair PMKID handshakes that hcxpcapngtool misses - - If running at time of handshake capture, on_handshake can - be used to improve the chance of the repair succeeding - - Be a completionist! Not enough packets captured to crack a network? - This generates an output file for the webgpsmap plugin, use the - location data to revisit networks you need more packets for! - - Additional information: - - Currently requires hcxpcapngtool compiled and installed - - Attempts to repair PMKID hashes when hcxpcapngtool cant find the SSID - - hcxpcapngtool sometimes has trouble extracting the SSID, so we - use the raw 16800 output and attempt to retrieve the SSID via tcpdump - - When access_point data is available (on_handshake), we leverage - the reported AP name and MAC to complete the hash - - The repair is very basic and could certainly be improved! - Todo: - Make it so users dont need hcxpcapngtool (unless it gets added to the base image) - Phase 1: Extract/construct 22000/16800 hashes through tcpdump commands - Phase 2: Extract/construct 22000/16800 hashes entirely in python - Improve the code, a lot - ''' - - def __init__(self): - self.lock = Lock() - self.options = dict() - - def on_loaded(self): - logging.info("[Hashie] Plugin loaded") - - def on_unloaded(self): - logging.info("[Hashie] Plugin unloaded") - - # called when everything is ready and the main loop is about to start - def on_ready(self, agent): - config = agent.config() - handshake_dir = config['bettercap']['handshakes'] - - logging.info('[Hashie] Starting batch conversion of pcap files') - with self.lock: - self._process_stale_pcaps(handshake_dir) - - def on_handshake(self, agent, filename, access_point, client_station): - with self.lock: - handshake_status = [] - fullpathNoExt = filename.split('.')[0] - name = filename.split('/')[-1:][0].split('.')[0] - - if os.path.isfile(fullpathNoExt + '.22000'): - handshake_status.append('Already have {}.22000 (EAPOL)'.format(name)) - elif self._writeEAPOL(filename): - handshake_status.append('Created {}.22000 (EAPOL) from pcapng'.format(name)) - - if os.path.isfile(fullpathNoExt + '.16800'): - handshake_status.append('Already have {}.16800 (PMKID)'.format(name)) - elif self._writePMKID(filename): - handshake_status.append('Created {}.16800 (PMKID) from pcapng'.format(name)) - - if handshake_status: - logging.info('[Hashie] Good news:\n\t' + '\n\t'.join(handshake_status)) - - def _writeEAPOL(self, fullpath): - fullpathNoExt = fullpath.split('.')[0] - filename = fullpath.split('/')[-1:][0].split('.')[0] - subprocess.getoutput('hcxpcapngtool -o {}.22000 {} >/dev/null 2>&1'.format(fullpathNoExt, fullpath)) - if os.path.isfile(fullpathNoExt + '.22000'): - logging.debug('[Hashie] [+] EAPOL Success: {}.22000 created'.format(filename)) - return True - return False - - def _writePMKID(self, fullpath): - fullpathNoExt = fullpath.split('.')[0] - filename = fullpath.split('/')[-1:][0].split('.')[0] - subprocess.getoutput('hcxpcapngtool -o {}.16800 {} >/dev/null 2>&1'.format(fullpathNoExt, fullpath)) - if os.path.isfile(fullpathNoExt + '.16800'): - logging.debug('[Hashie] [+] PMKID Success: {}.16800 created'.format(filename)) - return True - return False - - def _process_stale_pcaps(self, handshake_dir): - handshakes_list = [os.path.join(handshake_dir, filename) for filename in os.listdir(handshake_dir) if filename.endswith('.pcapng')] - failed_jobs = [] - successful_jobs = [] - lonely_pcaps = [] - for num, handshake in enumerate(handshakes_list): - fullpathNoExt = handshake.split('.')[0] - pcapFileName = handshake.split('/')[-1:][0] - if not os.path.isfile(fullpathNoExt + '.22000'): # if no 22000, try - if self._writeEAPOL(handshake): - successful_jobs.append('22000: ' + pcapFileName) - else: - failed_jobs.append('22000: ' + pcapFileName) - if not os.path.isfile(fullpathNoExt + '.16800'): # if no 16800, try - if self._writePMKID(handshake): - successful_jobs.append('16800: ' + pcapFileName) - else: - failed_jobs.append('16800: ' + pcapFileName) - if not os.path.isfile(fullpathNoExt + '.22000'): # if no 16800 AND no 22000 - lonely_pcaps.append(handshake) - logging.debug('[hashie] Batch job: added {} to lonely list'.format(pcapFileName)) - if ((num + 1) % 50 == 0) or (num + 1 == len(handshakes_list)): # report progress every 50, or when done - logging.info('[Hashie] Batch job: {}/{} done ({} fails)'.format(num + 1, len(handshakes_list), len(lonely_pcaps))) - if successful_jobs: - logging.info('[Hashie] Batch job: {} new handshake files created'.format(len(successful_jobs))) - if lonely_pcaps: - logging.info('[Hashie] Batch job: {} networks without enough packets to create a hash'.format(len(lonely_pcaps))) - self._getLocations(lonely_pcaps) - - def _getLocations(self, lonely_pcaps): - # export a file for webgpsmap to load - with open('/root/.incompletePcaps', 'w') as isIncomplete: - count = 0 - for pcapFile in lonely_pcaps: - filename = pcapFile.split('/')[-1:][0] # keep extension - fullpathNoExt = pcapFile.split('.')[0] - isIncomplete.write(filename + '\n') - if os.path.isfile(fullpathNoExt + '.gps.json') or os.path.isfile(fullpathNoExt + '.geo.json'): - count += 1 - if count != 0: - logging.info('[Hashie] Used {} GPS/GEO files to find lonely networks, ' - 'go check webgpsmap! ;)'.format(str(count))) - else: - logging.info('[Hashie] Could not find any GPS/GEO files ' - 'for the lonely networks'.format(str(count))) - - def _getLocationsCSV(self, lonely_pcaps): - # in case we need this later, export locations manually to CSV file, needs try/catch format/etc. - locations = [] - for pcapFile in lonely_pcaps: - filename = pcapFile.split('/')[-1:][0].split('.')[0] - fullpathNoExt = pcapFile.split('.')[0] - if os.path.isfile(fullpathNoExt + '.gps.json'): - with open(fullpathNoExt + '.gps.json', 'r') as tempFileA: - data = json.load(tempFileA) - locations.append(filename + ',' + str(data['Latitude']) + ',' + str(data['Longitude']) + ',50') - elif os.path.isfile(fullpathNoExt + '.geo.json'): - with open(fullpathNoExt + '.geo.json', 'r') as tempFileB: - data = json.load(tempFileB) - locations.append( - filename + ',' + str(data['location']['lat']) + ',' + str(data['location']['lng']) + ',' + str(data['accuracy'])) - if locations: - with open('/root/locations.csv', 'w') as tempFileD: - for loc in locations: - tempFileD.write(loc + '\n') - logging.info('[Hashie] Used {} GPS/GEO files to find lonely networks, ' - 'load /root/locations.csv into a mapping app and go say hi!'.format(len(locations))) diff --git a/pwnagotchi/plugins/default/memtemp.py b/pwnagotchi/plugins/default/memtemp.py index b9003be1..6298eca9 100644 --- a/pwnagotchi/plugins/default/memtemp.py +++ b/pwnagotchi/plugins/default/memtemp.py @@ -144,6 +144,9 @@ class MemTemp(plugins.Plugin): elif ui.is_waveshare2in7(): h_pos = (192, 138) v_pos = (211, 122) + elif ui.is_waveshare1in54V2(): + h_pos = (53, 77) + v_pos = (154, 65) else: h_pos = (155, 76) v_pos = (175, 61) diff --git a/pwnagotchi/ui/components.py b/pwnagotchi/ui/components.py index 1bc7ae9d..b6d4db49 100644 --- a/pwnagotchi/ui/components.py +++ b/pwnagotchi/ui/components.py @@ -1,4 +1,4 @@ -from PIL import Image +from PIL import Image, ImageOps from textwrap import TextWrapper @@ -40,21 +40,37 @@ class FilledRect(Widget): class Text(Widget): - def __init__(self, value="", position=(0, 0), font=None, color=0, wrap=False, max_length=0): + def __init__(self, value="", position=(0, 0), font=None, color=0, wrap=False, max_length=0, png=False): super().__init__(position, color) self.value = value self.font = font self.wrap = wrap self.max_length = max_length self.wrapper = TextWrapper(width=self.max_length, replace_whitespace=False) if wrap else None + self.png = png def draw(self, canvas, drawer): if self.value is not None: - if self.wrap: - text = '\n'.join(self.wrapper.wrap(self.value)) + if not self.png: + if self.wrap: + text = '\n'.join(self.wrapper.wrap(self.value)) + else: + text = self.value + drawer.text(self.xy, text, font=self.font, fill=self.color) else: - text = self.value - drawer.text(self.xy, text, font=self.font, fill=self.color) + self.image = Image.open(self.value) + self.image = self.image.convert('RGBA') + self.pixels = self.image.load() + for y in range(self.image.size[1]): + for x in range(self.image.size[0]): + if self.pixels[x,y][3] < 255: # check alpha + self.pixels[x,y] = (255, 255, 255, 255) + if self.color == 255: + self._image = ImageOps.colorize(self.image.convert('L'), black = "white", white = "black") + else: + self._image = self.image + self.image = self._image.convert('1') + canvas.paste(self.image, self.xy) class LabeledValue(Widget): diff --git a/pwnagotchi/ui/display.py b/pwnagotchi/ui/display.py index 1f08c71b..6087c539 100644 --- a/pwnagotchi/ui/display.py +++ b/pwnagotchi/ui/display.py @@ -118,6 +118,9 @@ class Display(View): def is_waveshare2in66g(self): return self._implementation.name == 'waveshare2in66g' + def is_weact2in9(self): + return self._implementation.name == 'weact2in9' + def is_waveshare3in0g(self): return self._implementation.name == 'waveshare3in0g' diff --git a/pwnagotchi/ui/faces.py b/pwnagotchi/ui/faces.py index 963e6967..63bd30f0 100644 --- a/pwnagotchi/ui/faces.py +++ b/pwnagotchi/ui/faces.py @@ -23,7 +23,9 @@ DEBUG = '(#__#)' UPLOAD = '(1__0)' UPLOAD1 = '(1__1)' UPLOAD2 = '(0__1)' - +PNG = False +POSITION_X = 0 +POSITION_Y = 40 def load_from_config(config): for face_name, face_value in config.items(): diff --git a/pwnagotchi/ui/hw/libs/dfrobot/v2/dfrobot_display/__init__.pyc b/pwnagotchi/ui/hw/libs/dfrobot/v2/dfrobot_display/__init__.pyc deleted file mode 100644 index d739fd19..00000000 Binary files a/pwnagotchi/ui/hw/libs/dfrobot/v2/dfrobot_display/__init__.pyc and /dev/null differ diff --git a/pwnagotchi/ui/hw/libs/dfrobot/v2/dfrobot_display/dfrobot_display.pyc b/pwnagotchi/ui/hw/libs/dfrobot/v2/dfrobot_display/dfrobot_display.pyc deleted file mode 100644 index d9bcc266..00000000 Binary files a/pwnagotchi/ui/hw/libs/dfrobot/v2/dfrobot_display/dfrobot_display.pyc and /dev/null differ diff --git a/pwnagotchi/ui/hw/libs/dfrobot/v2/dfrobot_display/dfrobot_fonts.pyc b/pwnagotchi/ui/hw/libs/dfrobot/v2/dfrobot_display/dfrobot_fonts.pyc deleted file mode 100644 index 274bbf43..00000000 Binary files a/pwnagotchi/ui/hw/libs/dfrobot/v2/dfrobot_display/dfrobot_fonts.pyc and /dev/null differ diff --git a/pwnagotchi/ui/hw/libs/dfrobot/v2/dfrobot_display/dfrobot_printString.pyc b/pwnagotchi/ui/hw/libs/dfrobot/v2/dfrobot_display/dfrobot_printString.pyc deleted file mode 100644 index 51dc169e..00000000 Binary files a/pwnagotchi/ui/hw/libs/dfrobot/v2/dfrobot_display/dfrobot_printString.pyc and /dev/null differ diff --git a/pwnagotchi/ui/hw/libs/dfrobot/v2/display_extension/__init__.pyc b/pwnagotchi/ui/hw/libs/dfrobot/v2/display_extension/__init__.pyc deleted file mode 100644 index 38130c00..00000000 Binary files a/pwnagotchi/ui/hw/libs/dfrobot/v2/display_extension/__init__.pyc and /dev/null differ diff --git a/pwnagotchi/ui/hw/libs/dfrobot/v2/display_extension/fonts_8_16.pyc b/pwnagotchi/ui/hw/libs/dfrobot/v2/display_extension/fonts_8_16.pyc deleted file mode 100644 index 96d1b66e..00000000 Binary files a/pwnagotchi/ui/hw/libs/dfrobot/v2/display_extension/fonts_8_16.pyc and /dev/null differ diff --git a/pwnagotchi/ui/hw/libs/dfrobot/v2/display_extension/freetype_helper.pyc b/pwnagotchi/ui/hw/libs/dfrobot/v2/display_extension/freetype_helper.pyc deleted file mode 100644 index b45f122f..00000000 Binary files a/pwnagotchi/ui/hw/libs/dfrobot/v2/display_extension/freetype_helper.pyc and /dev/null differ diff --git a/pwnagotchi/ui/hw/libs/waveshare/lcdhat/ST7789.pyc b/pwnagotchi/ui/hw/libs/waveshare/lcdhat/ST7789.pyc deleted file mode 100644 index 1837ca2a..00000000 Binary files a/pwnagotchi/ui/hw/libs/waveshare/lcdhat/ST7789.pyc and /dev/null differ diff --git a/pwnagotchi/ui/hw/libs/waveshare/lcdhat144/epd.py b/pwnagotchi/ui/hw/libs/waveshare/lcdhat144/epd.py index 2f6fbfd9..75615187 100644 --- a/pwnagotchi/ui/hw/libs/waveshare/lcdhat144/epd.py +++ b/pwnagotchi/ui/hw/libs/waveshare/lcdhat144/epd.py @@ -12,6 +12,7 @@ from . import config from . import LCD_1in44 from PIL import ImageOps + class EPD(object): def __init__(self): self.width = 128 @@ -24,9 +25,8 @@ class EPD(object): pass def clear(self): - #self.LCD.LCD_Clear() - pass + self.LCD.LCD_Clear() def display(self, image): - rgb_im = ImageOps.colorize(image.convert("L"), black ="green", white ="black") + rgb_im = ImageOps.colorize(image.convert("L"), black="green", white="black") self.LCD.LCD_ShowImage(rgb_im, 0, 0) diff --git a/pwnagotchi/ui/hw/libs/waveshare/v2in23g/epd2in13g.py b/pwnagotchi/ui/hw/libs/waveshare/v2in23g/epd2in13g.py new file mode 100644 index 00000000..d767af73 --- /dev/null +++ b/pwnagotchi/ui/hw/libs/waveshare/v2in23g/epd2in13g.py @@ -0,0 +1,242 @@ +# ***************************************************************************** +# * | 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(self.Source_BITS / 256) + self.send_data(self.Source_BITS % 256) + self.send_data(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/weact/epdconfig.py b/pwnagotchi/ui/hw/libs/weact/epdconfig.py new file mode 100644 index 00000000..72dfd076 --- /dev/null +++ b/pwnagotchi/ui/hw/libs/weact/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 = RaspberryPi() + +for func in [x for x in dir(implementation) if not x.startswith('_')]: + setattr(sys.modules[__name__], func, getattr(implementation, func)) + +### END OF FILE ### \ No newline at end of file diff --git a/pwnagotchi/ui/hw/libs/weact/v2in9/epd2in9.py b/pwnagotchi/ui/hw/libs/weact/v2in9/epd2in9.py new file mode 100644 index 00000000..e5987bc7 --- /dev/null +++ b/pwnagotchi/ui/hw/libs/weact/v2in9/epd2in9.py @@ -0,0 +1,374 @@ +import logging +from .. import epdconfig + +# Display resolution +EPD_WIDTH = 296 +EPD_HEIGHT = 128 + +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 + + lut_partial_update = [ + 0x0, 0x40, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x80, 0x80, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x40, 0x40, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x80, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x14, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x1, 0x0, 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, 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, 0x00, 0x32, 0x36, + ] + + lut_full_update = [ + 0x80, 0x4A, 0x40, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x40, 0x4A, 0x80, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x80, 0x4A, 0x40, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x40, 0x4A, 0x80, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0xF, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0xF, 0x0, 0x0, 0xF, 0x0, 0x0, 0x2, + 0xF, 0x0, 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, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x0, 0x0, 0x0, + 0x22, 0x17, 0x41, 0x0, 0x32, 0x36, + ] + + ''' + 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(0xC7) + 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(0x0f) # fast:0x0c, quality:0x0f, 0xcf + self.send_command(0x20) # Activate Display Update Sequence + self.ReadBusy() + + ''' + function : Set lut + parameter: + lut : lut data + ''' + + def Lut(self, lut): + self.send_command(0x32) + for i in range(0, 153): + self.send_data(lut[i]) + self.ReadBusy() + + ''' + function : Send lut data and configuration + parameter: + lut : lut data + ''' + + def SetLut(self, lut): + self.Lut(lut) + self.send_command(0x3f) + self.send_data(lut[153]) + self.send_command(0x03) # gate voltage + self.send_data(lut[154]) + self.send_command(0x04) # source voltage + self.send_data(lut[155]) # VSH + self.send_data(lut[156]) # VSH2 + self.send_data(lut[157]) # VSL + self.send_command(0x2c) # VCOM + self.send_data(lut[158]) + + ''' + 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() + + self.SetLut(self.lut_full_update) + 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): + if self.width % 8 == 0: + linewidth = int(self.width / 8) + else: + linewidth = int(self.width / 8) + 1 + + self.send_command(0x24) + for j in range(0, self.height): + for i in range(0, linewidth): + self.send_data(image[i + j * linewidth]) + self.TurnOnDisplay() + + ''' + 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.SetLut(self.lut_partial_update) + self.send_command(0x37) + self.send_data(0x00) + self.send_data(0x00) + self.send_data(0x00) + self.send_data(0x00) + self.send_data(0x00) + self.send_data(0x40) + self.send_data(0x00) + self.send_data(0x00) + self.send_data(0x00) + self.send_data(0x00) + + self.send_command(0x3C) # BorderWavefrom + self.send_data(0x80) + + self.send_command(0x22) + self.send_data(0xC0) + self.send_command(0x20) + self.ReadBusy() + + self.SetWindow(0, 0, self.width - 1, self.height - 1) + self.SetCursor(0, 0) + + self.send_command(0x24) # WRITE_RAM + # for j in range(0, self.height): + # for i in range(0, linewidth): + # self.send_data(image[i + j * linewidth]) + 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/waveshare13in3k.py b/pwnagotchi/ui/hw/waveshare13in3k.py index b57f8f29..5b464057 100644 --- a/pwnagotchi/ui/hw/waveshare13in3k.py +++ b/pwnagotchi/ui/hw/waveshare13in3k.py @@ -38,7 +38,8 @@ class Waveshare13in3k(DisplayImpl): self._display.Clear() def render(self, canvas): - self._display.display(canvas) + buf = self._display.getbuffer(canvas) + self._display.display(buf) def clear(self): self._display.Clear() diff --git a/pwnagotchi/ui/hw/waveshare1in02.py b/pwnagotchi/ui/hw/waveshare1in02.py index 3cfef7a3..e5d5cee1 100644 --- a/pwnagotchi/ui/hw/waveshare1in02.py +++ b/pwnagotchi/ui/hw/waveshare1in02.py @@ -38,7 +38,8 @@ class Waveshare1in02(DisplayImpl): self._display.Clear() def render(self, canvas): - self._display.display(canvas) + buf = self._display.getbuffer(canvas) + self._display.display(buf) def clear(self): self._display.Clear() diff --git a/pwnagotchi/ui/hw/waveshare1in54.py b/pwnagotchi/ui/hw/waveshare1in54.py index 7c04a2db..02a9e576 100644 --- a/pwnagotchi/ui/hw/waveshare1in54.py +++ b/pwnagotchi/ui/hw/waveshare1in54.py @@ -34,12 +34,12 @@ class Waveshare154(DisplayImpl): logging.info("initializing waveshare v1in54 display") from pwnagotchi.ui.hw.libs.waveshare.v1in54.epd1in54 import EPD self._display = EPD() - self._display.init(0x00) + self._display.init(self._display.lut_partial_update) self._display.Clear() def render(self, canvas): buf = self._display.getbuffer(canvas) - self._display.display(buf, None) + self._display.display(buf) def clear(self): # pass diff --git a/pwnagotchi/ui/hw/waveshare1in54_V2.py b/pwnagotchi/ui/hw/waveshare1in54_V2.py index e50cf500..02cd85a3 100644 --- a/pwnagotchi/ui/hw/waveshare1in54_V2.py +++ b/pwnagotchi/ui/hw/waveshare1in54_V2.py @@ -31,15 +31,27 @@ class Waveshare154V2(DisplayImpl): return self._layout def initialize(self): - logging.info("initializing waveshare v1in54_v2 display") + logging.info("initializing waveshare1in54_v2 display") from pwnagotchi.ui.hw.libs.waveshare.v1in54_v2.epd1in54_V2 import EPD - self._display = EPD() - self._display.init(False) - self._display.Clear() + try: + # Double initialization is a workaround for the display not working after a reboot, or mirrored/flipped screen + self._display = EPD() + self._display.init(0) + self.clear() + self._display.init(1) + self.clear() + except Exception as e: + logging.error(f"failed to initialize waveshare1in54_v2 display: {e}") def render(self, canvas): - buf = self._display.getbuffer(canvas) - self._display.display(buf, None) + try: + buf = self._display.getbuffer(canvas) + self._display.displayPart(buf) + except Exception as e: + logging.error(f"failed to render to waveshare1in54_v2 display: {e}") def clear(self): - self._display.Clear() + try: + self._display.Clear(0xFF) + except Exception as e: + logging.error(f"failed to clear waveshare1in54_v2 display: {e}") diff --git a/pwnagotchi/ui/hw/waveshare1in54c.py b/pwnagotchi/ui/hw/waveshare1in54c.py index e9fd2c27..8b206f7a 100644 --- a/pwnagotchi/ui/hw/waveshare1in54c.py +++ b/pwnagotchi/ui/hw/waveshare1in54c.py @@ -38,7 +38,8 @@ class Waveshare1in54c(DisplayImpl): self._display.Clear() def render(self, canvas): - self._display.display(canvas) + buf = self._display.getbuffer(canvas) + self._display.display(buf, None) def clear(self): self._display.Clear() diff --git a/pwnagotchi/ui/hw/waveshare1in64g.py b/pwnagotchi/ui/hw/waveshare1in64g.py index 5f994682..c84cac6e 100644 --- a/pwnagotchi/ui/hw/waveshare1in64g.py +++ b/pwnagotchi/ui/hw/waveshare1in64g.py @@ -38,7 +38,8 @@ class Waveshare1in64g(DisplayImpl): self._display.Clear() def render(self, canvas): - self._display.display(canvas) + buf = self._display.getbuffer(canvas) + self._display.display(buf) def clear(self): self._display.Clear() diff --git a/pwnagotchi/ui/hw/waveshare2in13.py b/pwnagotchi/ui/hw/waveshare2in13.py index de6c29b7..b720b555 100644 --- a/pwnagotchi/ui/hw/waveshare2in13.py +++ b/pwnagotchi/ui/hw/waveshare2in13.py @@ -9,81 +9,38 @@ class WaveshareV1(DisplayImpl): super(WaveshareV1, self).__init__(config, 'waveshare_1') 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['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': (91, 15), - 'font': fonts.status_font(fonts.Medium), - 'max': 20 - } + 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): - if self.config['color'] == 'black': - logging.info("initializing waveshare v2in13_V1 display in monochromatic mode") - from pwnagotchi.ui.hw.libs.waveshare.v2in13_V1.epd2in13 import EPD - self._display = EPD() - self._display.init(self._display.lut_full_update) - self._display.Clear(0xFF) - self._display.init(self._display.lut_partial_update) - elif self.config['color'] == 'fastAndFurious': - logging.info("initializing waveshare v2in13_V1 3-color display in FAST MODE") - logging.info("THIS MAY BE POTENTIALLY DANGEROUS. NO WARRANTY IS PROVIDED") - logging.info("USE THIS DISPLAY IN THIS MODE AT YOUR OWN RISK") - from pwnagotchi.ui.hw.libs.waveshare.v2in13_V1.epd2in13bcFAST import EPD - self._display = EPD() - self._display.init() - self._display.Clear() - else: - logging.info("initializing waveshare v2in13_V1 display 3-color mode") - from pwnagotchi.ui.hw.libs.waveshare.v2in13_V1.epd2in13bc import EPD - self._display = EPD() - self._display.init() - self._display.Clear() + logging.info("initializing waveshare v2in13_V1 display in monochromatic mode") + from pwnagotchi.ui.hw.libs.waveshare.v2in13_V1.epd2in13 import EPD + self._display = EPD() + self._display.init(self._display.lut_full_update) + self._display.Clear(0xFF) + self._display.init(self._display.lut_partial_update) def render(self, canvas): - if self.config['color'] == 'black': - buf = self._display.getbuffer(canvas) - self._display.display(buf) - elif self.config['color'] == 'fastAndFurious': - buf_black = self._display.getbuffer(canvas) - self._display.DisplayPartial(buf_black) - else: - buf_black = self._display.getbuffer(canvas) - self._display.displayBlack(buf_black) + buf = self._display.getbuffer(canvas) + self._display.display(buf) def clear(self): self._display.Clear(0xff) diff --git a/pwnagotchi/ui/hw/waveshare2in13_V2.py b/pwnagotchi/ui/hw/waveshare2in13_V2.py index 76c48ad0..48042746 100644 --- a/pwnagotchi/ui/hw/waveshare2in13_V2.py +++ b/pwnagotchi/ui/hw/waveshare2in13_V2.py @@ -9,47 +9,25 @@ class WaveshareV2(DisplayImpl): super(WaveshareV2, self).__init__(config, 'waveshare_2') 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 - } + 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): diff --git a/pwnagotchi/ui/hw/waveshare2in13b_V3.py b/pwnagotchi/ui/hw/waveshare2in13b_V3.py index 9c13436d..c5c96453 100644 --- a/pwnagotchi/ui/hw/waveshare2in13b_V3.py +++ b/pwnagotchi/ui/hw/waveshare2in13b_V3.py @@ -10,47 +10,25 @@ class Waveshare2in13bV3(DisplayImpl): super(Waveshare2in13bV3, self).__init__(config, 'waveshare2in13b_v3') 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 - } + 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): diff --git a/pwnagotchi/ui/hw/waveshare2in13b_V4.py b/pwnagotchi/ui/hw/waveshare2in13b_V4.py index 1b190d14..7cc5a543 100644 --- a/pwnagotchi/ui/hw/waveshare2in13b_V4.py +++ b/pwnagotchi/ui/hw/waveshare2in13b_V4.py @@ -10,47 +10,25 @@ class Waveshare213bV4(DisplayImpl): super(Waveshare213bV4, self).__init__(config, 'waveshare2in13b_v4') 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 - } + 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): diff --git a/pwnagotchi/ui/hw/waveshare2in23g.py b/pwnagotchi/ui/hw/waveshare2in23g.py new file mode 100644 index 00000000..e69de29b diff --git a/pwnagotchi/ui/hw/waveshare2in36g.py b/pwnagotchi/ui/hw/waveshare2in36g.py index 8865368a..2157d1f8 100644 --- a/pwnagotchi/ui/hw/waveshare2in36g.py +++ b/pwnagotchi/ui/hw/waveshare2in36g.py @@ -38,7 +38,8 @@ class Waveshare2in36g(DisplayImpl): self._display.Clear() def render(self, canvas): - self._display.display(canvas) + buf = self._display.getbuffer(canvas) + self._display.display(buf) def clear(self): self._display.Clear() diff --git a/pwnagotchi/ui/hw/waveshare2in66.py b/pwnagotchi/ui/hw/waveshare2in66.py index bf3afd35..0d58667b 100644 --- a/pwnagotchi/ui/hw/waveshare2in66.py +++ b/pwnagotchi/ui/hw/waveshare2in66.py @@ -34,11 +34,12 @@ class Waveshare2in66(DisplayImpl): logging.info("initializing waveshare 2.66 inch lcd display") from pwnagotchi.ui.hw.libs.waveshare.v2in66.epd2in66 import EPD self._display = EPD() - self._display.init(0) + self._display.init(1) self._display.Clear() def render(self, canvas): - self._display.display(canvas) + buf = self._display.getbuffer(canvas) + self._display.display(buf) def clear(self): self._display.Clear() diff --git a/pwnagotchi/ui/hw/waveshare2in66g.py b/pwnagotchi/ui/hw/waveshare2in66g.py index c5089ed5..6279487e 100644 --- a/pwnagotchi/ui/hw/waveshare2in66g.py +++ b/pwnagotchi/ui/hw/waveshare2in66g.py @@ -38,7 +38,8 @@ class Waveshare2in66g(DisplayImpl): self._display.Clear() def render(self, canvas): - self._display.display(canvas) + buf = self._display.getbuffer(canvas) + self._display.display(buf) def clear(self): self._display.Clear() diff --git a/pwnagotchi/ui/hw/waveshare2in7_V2.py b/pwnagotchi/ui/hw/waveshare2in7_V2.py index 42272fb6..60679835 100644 --- a/pwnagotchi/ui/hw/waveshare2in7_V2.py +++ b/pwnagotchi/ui/hw/waveshare2in7_V2.py @@ -35,8 +35,6 @@ class Waveshare27inchV2(DisplayImpl): from pwnagotchi.ui.hw.libs.waveshare.v2in7_v2.epd2in7_V2 import EPD self._display = EPD() self._display.init() - # this must have changed by waveshare - # remove the 0xFF(Clear(0xFF)) other wise it errors. can't pass oxff and self self._display.Clear() def render(self, canvas): diff --git a/pwnagotchi/ui/hw/waveshare2in7b.py b/pwnagotchi/ui/hw/waveshare2in7b.py index 4d559b6e..1dc96dbd 100644 --- a/pwnagotchi/ui/hw/waveshare2in7b.py +++ b/pwnagotchi/ui/hw/waveshare2in7b.py @@ -39,7 +39,7 @@ class Waveshare27b(DisplayImpl): def render(self, canvas): buf = self._display.getbuffer(canvas) - self._display.display(buf) + self._display.display(buf, None) def clear(self): self._display.Clear(0xff) diff --git a/pwnagotchi/ui/hw/waveshare2in7b_V2.py b/pwnagotchi/ui/hw/waveshare2in7b_V2.py index d734ec38..8d676ce3 100644 --- a/pwnagotchi/ui/hw/waveshare2in7b_V2.py +++ b/pwnagotchi/ui/hw/waveshare2in7b_V2.py @@ -39,7 +39,7 @@ class Waveshare27bV2(DisplayImpl): def render(self, canvas): buf = self._display.getbuffer(canvas) - self._display.display(buf) + self._display.display(buf, None) def clear(self): self._display.Clear(0xff) diff --git a/pwnagotchi/ui/hw/waveshare2in9b_V3.py b/pwnagotchi/ui/hw/waveshare2in9b_V3.py index 93e14381..7505e502 100644 --- a/pwnagotchi/ui/hw/waveshare2in9b_V3.py +++ b/pwnagotchi/ui/hw/waveshare2in9b_V3.py @@ -35,12 +35,12 @@ class Waveshare29bV3(DisplayImpl): from pwnagotchi.ui.hw.libs.waveshare.v2in9b_v3.epd2in9b_V3 import EPD self._display = EPD() self._display.init() - self._display.Clear(0xFF) + self._display.Clear() self._display.init() def render(self, canvas): buf = self._display.getbuffer(canvas) - self._display.display(buf) + self._display.display(buf, None) def clear(self): - self._display.Clear(0xFF) + self._display.Clear() diff --git a/pwnagotchi/ui/hw/waveshare2in9b_V4.py b/pwnagotchi/ui/hw/waveshare2in9b_V4.py index 0a7e2ee4..ab79a369 100644 --- a/pwnagotchi/ui/hw/waveshare2in9b_V4.py +++ b/pwnagotchi/ui/hw/waveshare2in9b_V4.py @@ -35,12 +35,12 @@ class Waveshare29bV4(DisplayImpl): from pwnagotchi.ui.hw.libs.waveshare.v2in9b_v4.epd2in9b_V4 import EPD self._display = EPD() self._display.init() - self._display.Clear(0xFF) + self._display.Clear() self._display.init() def render(self, canvas): buf = self._display.getbuffer(canvas) - self._display.display(buf) + self._display.display(buf, None) def clear(self): - self._display.Clear(0xFF) + self._display.Clear() diff --git a/pwnagotchi/ui/hw/waveshare2in9bc.py b/pwnagotchi/ui/hw/waveshare2in9bc.py index b9d6004d..804000b0 100644 --- a/pwnagotchi/ui/hw/waveshare2in9bc.py +++ b/pwnagotchi/ui/hw/waveshare2in9bc.py @@ -38,7 +38,8 @@ class Waveshare2in9bc(DisplayImpl): self._display.Clear() def render(self, canvas): - self._display.display(canvas) + buf = self._display.getbuffer(canvas) + self._display.display(buf, None) def clear(self): self._display.Clear() diff --git a/pwnagotchi/ui/hw/waveshare2in9d.py b/pwnagotchi/ui/hw/waveshare2in9d.py index 1b52ab17..47742db9 100644 --- a/pwnagotchi/ui/hw/waveshare2in9d.py +++ b/pwnagotchi/ui/hw/waveshare2in9d.py @@ -38,7 +38,8 @@ class Waveshare2in9d(DisplayImpl): self._display.Clear() def render(self, canvas): - self._display.display(canvas) + buf = self._display.getbuffer(canvas) + self._display.display(buf) def clear(self): self._display.Clear() diff --git a/pwnagotchi/ui/hw/waveshare3in0g.py b/pwnagotchi/ui/hw/waveshare3in0g.py index 46c40f3b..d8a79e28 100644 --- a/pwnagotchi/ui/hw/waveshare3in0g.py +++ b/pwnagotchi/ui/hw/waveshare3in0g.py @@ -38,7 +38,8 @@ class Waveshare3in0g(DisplayImpl): self._display.Clear() def render(self, canvas): - self._display.display(canvas) + buf = self._display.getbuffer(canvas) + self._display.display(buf) def clear(self): self._display.Clear() diff --git a/pwnagotchi/ui/hw/waveshare3in52.py b/pwnagotchi/ui/hw/waveshare3in52.py index 3489728a..a980e40e 100644 --- a/pwnagotchi/ui/hw/waveshare3in52.py +++ b/pwnagotchi/ui/hw/waveshare3in52.py @@ -9,22 +9,22 @@ class Waveshare3in52(DisplayImpl): super(Waveshare3in52, self).__init__(config, 'waveshare3in52') def layout(self): - fonts.setup(16, 14, 16, 100, 31, 15) + fonts.setup(16, 14, 16, 50, 31, 15) self._layout['width'] = 360 self._layout['height'] = 240 - self._layout['face'] = (0, 40) - self._layout['name'] = (0, 0) - self._layout['channel'] = (300, 0) - self._layout['aps'] = (0, 220) - self._layout['uptime'] = (120, 0) + self._layout['face'] = (0, 85) + self._layout['name'] = (10, 30) + self._layout['channel'] = (1, 4) + self._layout['aps'] = (45, 4) + self._layout['uptime'] = (250, 4) self._layout['line1'] = [0, 24, 360, 24] self._layout['line2'] = [0, 220, 360, 220] - self._layout['friend_face'] = (0, 195) - self._layout['friend_name'] = (0, 185) - self._layout['shakes'] = (100, 220) - self._layout['mode'] = (0,200) + self._layout['friend_face'] = (0, 180) + self._layout['friend_name'] = (0, 170) + self._layout['shakes'] = (1, 223) + self._layout['mode'] = (320, 222) self._layout['status'] = { - 'pos': (3, 170), + 'pos': (185, 50), 'font': fonts.status_font(fonts.Small), 'max': 100 } diff --git a/pwnagotchi/ui/hw/waveshare3in7.py b/pwnagotchi/ui/hw/waveshare3in7.py index c901eb52..32523c16 100644 --- a/pwnagotchi/ui/hw/waveshare3in7.py +++ b/pwnagotchi/ui/hw/waveshare3in7.py @@ -36,9 +36,11 @@ class Waveshare3in7(DisplayImpl): self._display = EPD() self._display.init(0) self._display.Clear(0) + self._display.init(1) # 1Gray mode def render(self, canvas): - self._display.display_4Gray(canvas) + buf = self._display.getbuffer(canvas) + self._display.display_1Gray(buf) def clear(self): self._display.Clear(0) diff --git a/pwnagotchi/ui/hw/waveshare4in01f.py b/pwnagotchi/ui/hw/waveshare4in01f.py index e5167efe..6951567c 100644 --- a/pwnagotchi/ui/hw/waveshare4in01f.py +++ b/pwnagotchi/ui/hw/waveshare4in01f.py @@ -38,7 +38,8 @@ class Waveshare4in01f(DisplayImpl): self._display.Clear() def render(self, canvas): - self._display.display(canvas) + buf = self._display.getbuffer(canvas) + self._display.display(buf) def clear(self): self._display.Clear() diff --git a/pwnagotchi/ui/hw/waveshare4in2.py b/pwnagotchi/ui/hw/waveshare4in2.py index 857dcc4c..c5dc8b71 100644 --- a/pwnagotchi/ui/hw/waveshare4in2.py +++ b/pwnagotchi/ui/hw/waveshare4in2.py @@ -38,7 +38,8 @@ class Waveshare4in2(DisplayImpl): self._display.Clear() def render(self, canvas): - self._display.display(canvas) + buf = self._display.getbuffer(canvas) + self._display.display(buf) def clear(self): self._display.Clear() diff --git a/pwnagotchi/ui/hw/waveshare4in26.py b/pwnagotchi/ui/hw/waveshare4in26.py index 41def32e..1abc92fc 100644 --- a/pwnagotchi/ui/hw/waveshare4in26.py +++ b/pwnagotchi/ui/hw/waveshare4in26.py @@ -38,7 +38,8 @@ class Waveshare4in26(DisplayImpl): self._display.Clear() def render(self, canvas): - self._display.display(canvas) + buf = self._display.getbuffer(canvas) + self._display.display(buf) def clear(self): self._display.Clear() diff --git a/pwnagotchi/ui/hw/waveshare4in2_V2.py b/pwnagotchi/ui/hw/waveshare4in2_V2.py index 96e8d162..27ac21ec 100644 --- a/pwnagotchi/ui/hw/waveshare4in2_V2.py +++ b/pwnagotchi/ui/hw/waveshare4in2_V2.py @@ -38,7 +38,8 @@ class Waveshare4in2V2(DisplayImpl): self._display.Clear() def render(self, canvas): - self._display.display(canvas) + buf = self._display.getbuffer(canvas) + self._display.display(buf) def clear(self): self._display.Clear() diff --git a/pwnagotchi/ui/hw/waveshare4in2b_V2.py b/pwnagotchi/ui/hw/waveshare4in2b_V2.py index a6dd942d..9569cfce 100644 --- a/pwnagotchi/ui/hw/waveshare4in2b_V2.py +++ b/pwnagotchi/ui/hw/waveshare4in2b_V2.py @@ -38,7 +38,8 @@ class Waveshare4in2bV2(DisplayImpl): self._display.Clear() def render(self, canvas): - self._display.display(canvas) + buf = self._display.getbuffer(canvas) + self._display.display(buf, None) def clear(self): self._display.Clear() diff --git a/pwnagotchi/ui/hw/waveshare4in2bc.py b/pwnagotchi/ui/hw/waveshare4in2bc.py index 7dbf967c..623a603a 100644 --- a/pwnagotchi/ui/hw/waveshare4in2bc.py +++ b/pwnagotchi/ui/hw/waveshare4in2bc.py @@ -38,7 +38,8 @@ class Waveshare4in2bc(DisplayImpl): self._display.Clear() def render(self, canvas): - self._display.display(canvas) + buf = self._display.getbuffer(canvas) + self._display.display(buf, None) def clear(self): self._display.Clear() diff --git a/pwnagotchi/ui/hw/waveshare4in37g.py b/pwnagotchi/ui/hw/waveshare4in37g.py index 2aac16e5..3bc58503 100644 --- a/pwnagotchi/ui/hw/waveshare4in37g.py +++ b/pwnagotchi/ui/hw/waveshare4in37g.py @@ -38,7 +38,8 @@ class Waveshare4in37g(DisplayImpl): self._display.Clear() def render(self, canvas): - self._display.display(canvas) + buf = self._display.getbuffer(canvas) + self._display.display(buf) def clear(self): self._display.Clear() diff --git a/pwnagotchi/ui/hw/waveshare5in65f.py b/pwnagotchi/ui/hw/waveshare5in65f.py index d1bc9082..a06acf5d 100644 --- a/pwnagotchi/ui/hw/waveshare5in65f.py +++ b/pwnagotchi/ui/hw/waveshare5in65f.py @@ -38,7 +38,8 @@ class Waveshare5in65f(DisplayImpl): self._display.Clear() def render(self, canvas): - self._display.display(canvas) + buf = self._display.getbuffer(canvas) + self._display.display(buf) def clear(self): self._display.Clear() diff --git a/pwnagotchi/ui/hw/waveshare5in83.py b/pwnagotchi/ui/hw/waveshare5in83.py index 636bd1f4..aeadd918 100644 --- a/pwnagotchi/ui/hw/waveshare5in83.py +++ b/pwnagotchi/ui/hw/waveshare5in83.py @@ -38,7 +38,8 @@ class Waveshare5in83(DisplayImpl): self._display.Clear() def render(self, canvas): - self._display.display(canvas) + buf = self._display.getbuffer(canvas) + self._display.display(buf) def clear(self): self._display.Clear() diff --git a/pwnagotchi/ui/hw/waveshare5in83_V2.py b/pwnagotchi/ui/hw/waveshare5in83_V2.py index dda0850d..c37c9eb2 100644 --- a/pwnagotchi/ui/hw/waveshare5in83_V2.py +++ b/pwnagotchi/ui/hw/waveshare5in83_V2.py @@ -38,7 +38,8 @@ class Waveshare5in83V2(DisplayImpl): self._display.Clear() def render(self, canvas): - self._display.display(canvas) + buf = self._display.getbuffer(canvas) + self._display.display(buf) def clear(self): self._display.Clear() diff --git a/pwnagotchi/ui/hw/waveshare5in83b_V2.py b/pwnagotchi/ui/hw/waveshare5in83b_V2.py index 9b116c00..79fe7599 100644 --- a/pwnagotchi/ui/hw/waveshare5in83b_V2.py +++ b/pwnagotchi/ui/hw/waveshare5in83b_V2.py @@ -38,7 +38,8 @@ class Waveshare5in83bV2(DisplayImpl): self._display.Clear() def render(self, canvas): - self._display.display(canvas) + buf = self._display.getbuffer(canvas) + self._display.display(buf, None) def clear(self): self._display.Clear() diff --git a/pwnagotchi/ui/hw/waveshare5in83bc.py b/pwnagotchi/ui/hw/waveshare5in83bc.py index 8ab1d044..342f59dd 100644 --- a/pwnagotchi/ui/hw/waveshare5in83bc.py +++ b/pwnagotchi/ui/hw/waveshare5in83bc.py @@ -38,7 +38,8 @@ class Waveshare5in83bc(DisplayImpl): self._display.Clear() def render(self, canvas): - self._display.display(canvas) + buf = self._display.getbuffer(canvas) + self._display.display(buf, None) def clear(self): self._display.Clear() diff --git a/pwnagotchi/ui/hw/waveshare7in3f.py b/pwnagotchi/ui/hw/waveshare7in3f.py index 3a35d112..7f1317d5 100644 --- a/pwnagotchi/ui/hw/waveshare7in3f.py +++ b/pwnagotchi/ui/hw/waveshare7in3f.py @@ -38,7 +38,8 @@ class Waveshare7in3f(DisplayImpl): self._display.Clear() def render(self, canvas): - self._display.display(canvas) + buf = self._display.getbuffer(canvas) + self._display.display(buf) def clear(self): self._display.Clear() diff --git a/pwnagotchi/ui/hw/waveshare7in3g.py b/pwnagotchi/ui/hw/waveshare7in3g.py index ee9f13bf..5465cdbd 100644 --- a/pwnagotchi/ui/hw/waveshare7in3g.py +++ b/pwnagotchi/ui/hw/waveshare7in3g.py @@ -38,7 +38,8 @@ class Waveshare7in3g(DisplayImpl): self._display.Clear() def render(self, canvas): - self._display.display(canvas) + buf = self._display.getbuffer(canvas) + self._display.display(buf) def clear(self): self._display.Clear() diff --git a/pwnagotchi/ui/hw/waveshare7in5.py b/pwnagotchi/ui/hw/waveshare7in5.py index bb999563..6c756d95 100644 --- a/pwnagotchi/ui/hw/waveshare7in5.py +++ b/pwnagotchi/ui/hw/waveshare7in5.py @@ -38,7 +38,8 @@ class Waveshare7in5(DisplayImpl): self._display.Clear() def render(self, canvas): - self._display.display(canvas) + buf = self._display.getbuffer(canvas) + self._display.display(buf) def clear(self): self._display.Clear() diff --git a/pwnagotchi/ui/hw/waveshare7in5_HD.py b/pwnagotchi/ui/hw/waveshare7in5_HD.py index 34fa7b53..2004889a 100644 --- a/pwnagotchi/ui/hw/waveshare7in5_HD.py +++ b/pwnagotchi/ui/hw/waveshare7in5_HD.py @@ -38,7 +38,8 @@ class Waveshare7in5HD(DisplayImpl): self._display.Clear() def render(self, canvas): - self._display.display(canvas) + buf = self._display.getbuffer(canvas) + self._display.display(buf) def clear(self): self._display.Clear() diff --git a/pwnagotchi/ui/hw/waveshare7in5_V2.py b/pwnagotchi/ui/hw/waveshare7in5_V2.py index 524b8d3c..3d11ab03 100644 --- a/pwnagotchi/ui/hw/waveshare7in5_V2.py +++ b/pwnagotchi/ui/hw/waveshare7in5_V2.py @@ -51,7 +51,8 @@ class Waveshare7in5V2(DisplayImpl): self._display.Clear() def render(self, canvas): - self._display.display(canvas) + buf = self._display.getbuffer(canvas) + self._display.display(buf) def clear(self): self._display.Clear() diff --git a/pwnagotchi/ui/hw/waveshare7in5b_HD.py b/pwnagotchi/ui/hw/waveshare7in5b_HD.py index 5f04e5d2..550ef2f5 100644 --- a/pwnagotchi/ui/hw/waveshare7in5b_HD.py +++ b/pwnagotchi/ui/hw/waveshare7in5b_HD.py @@ -38,7 +38,8 @@ class Waveshare7in5bHD(DisplayImpl): self._display.Clear() def render(self, canvas): - self._display.display(canvas) + buf = self._display.getbuffer(canvas) + self._display.display(buf, None) def clear(self): self._display.Clear() diff --git a/pwnagotchi/ui/hw/waveshare7in5b_V2.py b/pwnagotchi/ui/hw/waveshare7in5b_V2.py index 8463a691..c7463c0a 100644 --- a/pwnagotchi/ui/hw/waveshare7in5b_V2.py +++ b/pwnagotchi/ui/hw/waveshare7in5b_V2.py @@ -38,7 +38,8 @@ class Waveshare7in5bV2(DisplayImpl): self._display.Clear() def render(self, canvas): - self._display.display(canvas) + buf = self._display.getbuffer(canvas) + self._display.display(buf, None) def clear(self): self._display.Clear() diff --git a/pwnagotchi/ui/hw/waveshare7in5bc.py b/pwnagotchi/ui/hw/waveshare7in5bc.py index fe71c597..3df29469 100644 --- a/pwnagotchi/ui/hw/waveshare7in5bc.py +++ b/pwnagotchi/ui/hw/waveshare7in5bc.py @@ -38,7 +38,8 @@ class Waveshare7in5bc(DisplayImpl): self._display.Clear() def render(self, canvas): - self._display.display(canvas) + buf = self._display.getbuffer(canvas) + self._display.display(buf, None) def clear(self): self._display.Clear() diff --git a/pwnagotchi/ui/hw/weact_2in9.py b/pwnagotchi/ui/hw/weact_2in9.py new file mode 100644 index 00000000..a87d244b --- /dev/null +++ b/pwnagotchi/ui/hw/weact_2in9.py @@ -0,0 +1,47 @@ +import logging + +import pwnagotchi.ui.fonts as fonts +from pwnagotchi.ui.hw.base import DisplayImpl + + +class WeAct2in9(DisplayImpl): + def __init__(self, config): + super(WeAct2in9, self).__init__(config, 'weact2in9') + + def layout(self): + fonts.setup(10, 8, 10, 35, 25, 9) + self._layout['width'] = 296 + self._layout['height'] = 128 + 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 WeAct 2.9 inch display") + from pwnagotchi.ui.hw.libs.weact.v2in9.epd2in9 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): + #pass + self._display.Clear(0xFF) diff --git a/pwnagotchi/ui/view.py b/pwnagotchi/ui/view.py index 50b2ffc5..3d822190 100644 --- a/pwnagotchi/ui/view.py +++ b/pwnagotchi/ui/view.py @@ -23,7 +23,21 @@ ROOT = None class View(object): def __init__(self, config, impl, state=None): - global ROOT + global ROOT, BLACK, WHITE + + self.invert = 0 + self._black = 0xFF + self._white = 0x00 + if 'invert' in config['ui'] and config['ui']['invert'] == True: + logging.debug("INVERT BLACK/WHITES:" + str(config['ui']['invert'])) + self.invert = 1 + BLACK = 0x00 + WHITE - 0xFF + self._black = 0x00 + self._white = 0xFF + + + # setup faces from the configuration in case the user customized them faces.load_from_config(config['ui']['faces']) @@ -54,10 +68,10 @@ class View(object): 'line1': Line(self._layout['line1'], color=BLACK), 'line2': Line(self._layout['line2'], color=BLACK), - 'face': Text(value=faces.SLEEP, position=self._layout['face'], color=BLACK, font=fonts.Huge), + 'face': Text(value=faces.SLEEP, position=(config['ui']['faces']['position_x'], config['ui']['faces']['position_y']), color=BLACK, font=fonts.Huge, png=config['ui']['faces']['png']), # 'friend_face': Text(value=None, position=self._layout['friend_face'], font=fonts.Bold, color=BLACK), - 'friend_name': Text(value=None, position=self._layout['friend_name'], font=fonts.BoldSmall, color=BLACK), + 'friend_name': Text(value=None, position=self._layout['friend_face'], font=fonts.BoldSmall, color=BLACK), 'name': Text(value='%s>' % 'pwnagotchi', position=self._layout['name'], color=BLACK, font=fonts.Bold), @@ -98,6 +112,11 @@ class View(object): self._state.has_element(key) def add_element(self, key, elem): + if self.invert is 1 and elem.color: + if elem.color == 0xff: + elem.color = 0x00 + elif elem.color == 0x00: + elem.color = 0xff self._state.add_element(key, elem) def remove_element(self, key): @@ -371,7 +390,7 @@ class View(object): state = self._state changes = state.changes(ignore=self._ignore_changes) if force or len(changes): - self._canvas = Image.new('1', (self._width, self._height), WHITE) + self._canvas = Image.new('1', (self._width, self._height), self._white) drawer = ImageDraw.Draw(self._canvas) plugins.on('ui_update', self) diff --git a/pwnagotchi/utils.py b/pwnagotchi/utils.py index eacf8899..72f2831c 100644 --- a/pwnagotchi/utils.py +++ b/pwnagotchi/utils.py @@ -238,14 +238,60 @@ def load_config(args): config = merge_config(additional_config, config) # the very first step is to normalize the display name, so we don't need dozens of if/elif around + # NON E-INK DISPLAYS--------------------------------------------------------------- if config['ui']['display']['type'] in ('inky', 'inkyphat'): config['ui']['display']['type'] = 'inky' + elif config['ui']['display']['type'] in ('papirus', 'papi'): config['ui']['display']['type'] = 'papirus' elif config['ui']['display']['type'] in 'oledhat': config['ui']['display']['type'] = 'oledhat' + elif config['ui']['display']['type'] in 'lcdhat': + config['ui']['display']['type'] = 'lcdhat' + + elif config['ui']['display']['type'] in ('dfrobot_1', 'df1'): + config['ui']['display']['type'] = 'dfrobot_1' + + elif config['ui']['display']['type'] in ('dfrobot_2', 'df2'): + config['ui']['display']['type'] = 'dfrobot_2' + + elif config['ui']['display']['type'] in ('waveshare144lcd', 'ws_144', 'ws144', 'waveshare_144', 'waveshare144'): + config['ui']['display']['type'] = 'waveshare144lcd' + + elif config['ui']['display']['type'] in ('spotpear24inch'): + config['ui']['display']['type'] = 'spotpear24inch' + + elif config['ui']['display']['type'] in ('displayhatmini'): + config['ui']['display']['type'] = 'displayhatmini' + + elif config['ui']['display']['type'] in ('waveshare35lcd'): + config['ui']['display']['type'] = 'waveshare35lcd' + + # E-INK DISPLAYS ------------------------------------------------------------------------ + + elif config['ui']['display']['type'] in ('waveshare1in02', 'ws1in02', 'ws102', 'waveshare_102', 'waveshare_1in02'): + config['ui']['display']['type'] = 'waveshare1in02' + + elif config['ui']['display']['type'] in ('ws_154inch', 'waveshare1in54', 'ws154inch', 'waveshare_154', 'waveshare154'): + config['ui']['display']['type'] = 'waveshare1in54' + + elif config['ui']['display']['type'] in ('ws_154inchb', 'waveshare1in54b', 'ws154inchb', 'waveshare_154b', 'waveshare154b'): + config['ui']['display']['type'] = 'waveshare1in54b' + + elif config['ui']['display']['type'] in ('waveshare1in54c', 'ws1in54c', 'ws154c', 'waveshare_154c', 'waveshare_1in54c'): + config['ui']['display']['type'] = 'waveshare1in54c' + + elif config['ui']['display']['type'] in ('ws_154inchbv2', 'waveshare1in54bv2', 'waveshare1in54b_v2', 'ws154inchbv2', 'waveshare_154bv2', 'waveshare154bv2'): + config['ui']['display']['type'] = 'waveshare1in54b_v2' + + elif config['ui']['display']['type'] in ('ws_154inchv2', 'waveshare1in54v2', 'ws154inchv2', 'waveshare_154inchv2', 'waveshare154v2', "waveshare1in54_v2"): + config['ui']['display']['type'] = 'waveshare1in54_v2' + + elif config['ui']['display']['type'] in ('waveshare1in64g', 'ws1in64g', 'ws164g', 'waveshare_164g', 'waveshare_1in64g'): + config['ui']['display']['type'] = 'waveshare1in64g' + elif config['ui']['display']['type'] in ('ws_1', 'ws1', 'waveshare_1', 'waveshare1'): config['ui']['display']['type'] = 'waveshare_1' @@ -258,50 +304,14 @@ def load_config(args): elif config['ui']['display']['type'] in ('ws_4', 'ws4', 'waveshare_4', 'waveshare4'): config['ui']['display']['type'] = 'waveshare_4' - elif config['ui']['display']['type'] in ('ws_27inch', 'ws27inch', 'waveshare2in7', 'waveshare_27inch', 'waveshare27inch'): - config['ui']['display']['type'] = 'waveshare2in7' + elif config['ui']['display']['type'] in ('waveshare2in13b_v3', 'waveshare2in13b_v3', 'ws213bv3', 'waveshare_213bv3', 'waveshare213inb_v3'): + config['ui']['display']['type'] = 'waveshare2in13b_v3' - elif config['ui']['display']['type'] in ('ws_27inchv2', 'waveshare2in7_v2', 'ws27inchv2', 'waveshare_27inchv2', 'waveshare27inchv2'): - config['ui']['display']['type'] = 'waveshare2in7_v2' + elif config['ui']['display']['type'] in ('ws_213bv4', 'waveshare2in13b_v4', 'ws213bv4', 'waveshare_213bv4', 'waveshare213inb_v4'): + config['ui']['display']['type'] = 'waveshare2in13b_v4' - elif config['ui']['display']['type'] in ('ws_27inchbv2', 'waveshare2in7b_v2', 'ws27inchbv2', 'waveshare_27inchbv2', 'waveshare27inchbv2'): - config['ui']['display']['type'] = 'waveshare2in7b_v2' - - elif config['ui']['display']['type'] in ('ws_29inch', 'waveshare2in9', 'ws29inch', 'waveshare_29inch', 'waveshare29inch'): - config['ui']['display']['type'] = 'waveshare2in9' - - elif config['ui']['display']['type'] in ('ws_29inchv2', 'waveshare2in9_v2', 'ws29inchv2', 'waveshare_29inchv2', 'waveshare29inchv2'): - config['ui']['display']['type'] = 'waveshare2in9_v2' - - elif config['ui']['display']['type'] in ('ws_29inchbv3', 'waveshare2in9b_v3', 'ws29inchbv3', 'waveshare_29inchbv3', 'waveshare29inchbv3'): - config['ui']['display']['type'] = 'waveshare2in9b_v3' - - elif config['ui']['display']['type'] in ('ws_29inchbv4', 'waveshare2in9b_v4', 'ws29inchbv4', 'waveshare_29inchbv4', 'waveshare29inchbv4'): - config['ui']['display']['type'] = 'waveshare2in9b_v4' - - elif config['ui']['display']['type'] in 'lcdhat': - config['ui']['display']['type'] = 'lcdhat' - - elif config['ui']['display']['type'] in ('dfrobot_1', 'df1'): - config['ui']['display']['type'] = 'dfrobot_1' - - elif config['ui']['display']['type'] in ('dfrobot_2', 'df2'): - config['ui']['display']['type'] = 'dfrobot_2' - - elif config['ui']['display']['type'] in ('ws_154inch', 'waveshare1in54', 'ws154inch', 'waveshare_154inch', 'waveshare154inch'): - config['ui']['display']['type'] = 'waveshare1in54' - - elif config['ui']['display']['type'] in ('ws_154inchb', 'waveshare1in54b', 'ws154inchb', 'waveshare_154inchb', 'waveshare154inchb'): - config['ui']['display']['type'] = 'waveshare1in54b' - - elif config['ui']['display']['type'] in ('ws_154inchbv2', 'waveshare1in54bv2', 'ws154inchbv2', 'waveshare_154inchbv2', 'waveshare154inchbv2'): - config['ui']['display']['type'] = 'waveshare1in54b_v2' - - elif config['ui']['display']['type'] in ('ws_154inchv2', 'waveshare1in54v2', 'ws154inchv2', 'waveshare_154inchv2', 'waveshare154inchv2'): - config['ui']['display']['type'] = 'waveshare1in54_v2' - - elif config['ui']['display']['type'] in ('waveshare144lcd', 'ws_144inch', 'ws144inch', 'waveshare_144inch', 'waveshare144inch'): - config['ui']['display']['type'] = 'waveshare144lcd' + elif config['ui']['display']['type'] in ('ws_213bc', 'ws213bc', 'waveshare2in13bc', 'waveshare_213bc', 'waveshare213bc'): + config['ui']['display']['type'] = 'waveshare2in13bc' elif config['ui']['display']['type'] in ('ws_213d', 'ws213d', 'waveshare2in13d', 'waveshare_213d', 'waveshare213d'): config['ui']['display']['type'] = 'waveshare2in13d' @@ -309,123 +319,121 @@ def load_config(args): elif config['ui']['display']['type'] in ('ws_213g', 'waveshare2in13g', 'waveshare213g', 'ws213g', 'waveshare_213g'): config['ui']['display']['type'] = 'waveshare2in13g' - elif config['ui']['display']['type'] in ('ws_213bc', 'ws213bc', 'waveshare2in13bc', 'waveshare_213bc', 'waveshare213bc'): - config['ui']['display']['type'] = 'waveshare2in13bc' - - elif config['ui']['display']['type'] in ('ws_213bv4', 'waveshare2in13b_v4', 'ws213bv4', 'waveshare_213bv4', 'waveshare213inb_v4'): - config['ui']['display']['type'] = 'waveshare2in13b_v4' - - elif config['ui']['display']['type'] in 'spotpear24inch': - config['ui']['display']['type'] = 'spotpear24inch' - - elif config['ui']['display']['type'] in 'displayhatmini': - config['ui']['display']['type'] = 'displayhatmini' - - elif config['ui']['display']['type'] in 'waveshare35lcd': - config['ui']['display']['type'] = 'waveshare35lcd' - - elif config['ui']['display']['type'] in 'waveshare1in54c': - config['ui']['display']['type'] = 'waveshare1in54c' - - elif config['ui']['display']['type'] in 'waveshare1in64g': - config['ui']['display']['type'] = 'waveshare1in64g' - - elif config['ui']['display']['type'] in 'waveshare1in02': - config['ui']['display']['type'] = 'waveshare1in02' - - elif config['ui']['display']['type'] in 'waveshare2in9bc': - config['ui']['display']['type'] = 'waveshare2in9bc' - - elif config['ui']['display']['type'] in 'waveshare2in9d': - config['ui']['display']['type'] = 'waveshare2in9d' - - elif config['ui']['display']['type'] in 'waveshare2in13b_v3': - config['ui']['display']['type'] = 'waveshare2in13b_v3' - - elif config['ui']['display']['type'] in 'waveshare2in36g': + elif config['ui']['display']['type'] in ('ws_2in36g', 'waveshare2in36g', 'waveshare236g', 'ws236g', 'waveshare_236g'): config['ui']['display']['type'] = 'waveshare2in36g' - elif config['ui']['display']['type'] in 'waveshare2in66': + elif config['ui']['display']['type'] in ('ws_2in66', 'waveshare2in66', 'waveshare266', 'ws266', 'waveshare_266'): config['ui']['display']['type'] = 'waveshare2in66' - elif config['ui']['display']['type'] in 'waveshare2in66b': + elif config['ui']['display']['type'] in ('ws_2in66b', 'waveshare2in66b', 'waveshare266b', 'ws266b', 'waveshare_266b'): config['ui']['display']['type'] = 'waveshare2in66b' - elif config['ui']['display']['type'] in 'waveshare2in66g': + elif config['ui']['display']['type'] in ('ws_2in66g', 'waveshare2in66g', 'waveshare266g', 'ws266g', 'waveshare_266g'): config['ui']['display']['type'] = 'waveshare2in66g' - elif config['ui']['display']['type'] in 'waveshare3in0g': + elif config['ui']['display']['type'] in ('ws_27inch', 'ws27inch', 'waveshare2in7', 'waveshare_27inch', 'waveshare27'): + config['ui']['display']['type'] = 'waveshare2in7' + + elif config['ui']['display']['type'] in ('ws_2in7v2', 'waveshare2in7_v2', 'waveshare2in7v2', 'ws27inchv2', 'waveshare_27v2', 'waveshare27v2'): + config['ui']['display']['type'] = 'waveshare2in7_v2' + + elif config['ui']['display']['type'] in ('ws_2in7bv2', 'waveshare2in7b_v2', 'waveshare2in7bv2', 'ws27inchbv2', 'waveshare_27bv2', 'waveshare27bv2'): + config['ui']['display']['type'] = 'waveshare2in7b_v2' + + elif config['ui']['display']['type'] in ('ws_2in9', 'waveshare2in9', 'ws29inch', 'waveshare_29inch', 'waveshare29inch'): + config['ui']['display']['type'] = 'waveshare2in9' + + elif config['ui']['display']['type'] in ('ws_2in9bc', 'waveshare2in9bc', 'ws2in9bc', 'ws29bc', 'waveshare_29bc', 'waveshare_2in9bc'): + config['ui']['display']['type'] = 'waveshare2in9bc' + + elif config['ui']['display']['type'] in ('ws_2in9d', 'waveshare2in9d', 'ws2in9d', 'ws29d', 'waveshare_29d', 'waveshare_2in9d'): + config['ui']['display']['type'] = 'waveshare2in9d' + + elif config['ui']['display']['type'] in ('ws_2in9v2', 'waveshare2in9_v2', 'waveshare2in9v2', 'ws2in9v2', 'waveshare_29v2', 'waveshare29v2'): + config['ui']['display']['type'] = 'waveshare2in9_v2' + + elif config['ui']['display']['type'] in ('ws_2in9bv3', 'waveshare2in9b_v3', 'waveshare2in9bv3', 'ws2in9bv3', 'waveshare_29bv3', 'waveshare29bv3'): + config['ui']['display']['type'] = 'waveshare2in9b_v3' + + elif config['ui']['display']['type'] in ('ws_2in9bv4', 'waveshare2in9b_v4', 'waveshare2in9bv4', 'ws2in9bv4', 'waveshare_29bv4', 'waveshare29bv4'): + config['ui']['display']['type'] = 'waveshare2in9b_v4' + + elif config['ui']['display']['type'] in ('ws_3in0g', 'waveshare3in0g', 'ws3in0g', 'waveshare_30g', 'waveshare30g'): config['ui']['display']['type'] = 'waveshare3in0g' - elif config['ui']['display']['type'] in 'waveshare3in7': + elif config['ui']['display']['type'] in ('ws_3in7', 'waveshare3in7', 'ws3in7', 'waveshare_37', 'waveshare37'): config['ui']['display']['type'] = 'waveshare3in7' - elif config['ui']['display']['type'] in 'waveshare3in52': + elif config['ui']['display']['type'] in ('ws_3in52', 'waveshare3in52', 'ws3in52', 'waveshare_352', 'waveshare352'): config['ui']['display']['type'] = 'waveshare3in52' - elif config['ui']['display']['type'] in 'waveshare4in01f': + elif config['ui']['display']['type'] in ('ws_4in01f', 'waveshare4in01f', 'ws4in01f', 'waveshare_401f', 'waveshare401f'): config['ui']['display']['type'] = 'waveshare4in01f' - elif config['ui']['display']['type'] in 'waveshare4in2': + elif config['ui']['display']['type'] in ('ws_4in2', 'waveshare4in2', 'ws4in2', 'waveshare_42', 'waveshare42'): config['ui']['display']['type'] = 'waveshare4in2' - elif config['ui']['display']['type'] in 'waveshare4in2_v2': + elif config['ui']['display']['type'] in ('ws_4in2v2', 'waveshare4in2v2', 'ws4in2v2', 'waveshare_42v2', 'waveshare42v2'): config['ui']['display']['type'] = 'waveshare4in2_v2' - elif config['ui']['display']['type'] in 'waveshare4in2b_v2': + elif config['ui']['display']['type'] in ('ws_4in2bv2', 'waveshare4in2bv2', 'ws4in2bv2', 'waveshare_42bv2', 'waveshare42bv2'): config['ui']['display']['type'] = 'waveshare4in2b_v2' - elif config['ui']['display']['type'] in 'waveshare4in2bc': + elif config['ui']['display']['type'] in ('ws_4in2bc', 'waveshare4in2bc', 'ws4in2bc', 'waveshare_42bc', 'waveshare42bc'): config['ui']['display']['type'] = 'waveshare4in2bc' - elif config['ui']['display']['type'] in 'waveshare4in26': + elif config['ui']['display']['type'] in ('ws_4in26', 'waveshare4in26', 'ws4in26', 'waveshare_426', 'waveshare426'): config['ui']['display']['type'] = 'waveshare4in26' - elif config['ui']['display']['type'] in 'waveshare4in37g': + elif config['ui']['display']['type'] in ('ws_4in37g', 'waveshare4in37g', 'ws4in37g', 'waveshare_37g', 'waveshare437g'): config['ui']['display']['type'] = 'waveshare4in37g' - elif config['ui']['display']['type'] in 'waveshare5in65f': + elif config['ui']['display']['type'] in ('ws_5in65f', 'waveshare5in65f', 'ws5in65f', 'waveshare_565f', 'waveshare565f'): config['ui']['display']['type'] = 'waveshare5in65f' - elif config['ui']['display']['type'] in 'waveshare5in83': + elif config['ui']['display']['type'] in ('ws_5in83', 'waveshare5in83', 'ws5in83', 'waveshare_583', 'waveshare583'): config['ui']['display']['type'] = 'waveshare5in83' - elif config['ui']['display']['type'] in 'waveshare5in83_v2': + elif config['ui']['display']['type'] in ('ws_5in83v2', 'waveshare5in83v2', 'ws5in83v2', 'waveshare_583v2', 'waveshare583v2'): config['ui']['display']['type'] = 'waveshare5in83_v2' - elif config['ui']['display']['type'] in 'waveshare5in83b_v2': + elif config['ui']['display']['type'] in ('ws_5in83bv2', 'waveshare5in83bv2', 'ws5in83bv2', 'waveshare_583bv2', 'waveshare583bv2'): config['ui']['display']['type'] = 'waveshare5in83b_v2' - elif config['ui']['display']['type'] in 'waveshare5in83bc': + elif config['ui']['display']['type'] in ('ws_5in83bc', 'waveshare5in83bc', 'ws5in83bc', 'waveshare_583bc', 'waveshare583bc'): config['ui']['display']['type'] = 'waveshare5in83bc' - elif config['ui']['display']['type'] in 'waveshare7in3f': + elif config['ui']['display']['type'] in ('ws_7in3f', 'waveshare7in3f', 'ws7in3f', 'waveshare_73f', 'waveshare73f'): config['ui']['display']['type'] = 'waveshare7in3f' - elif config['ui']['display']['type'] in 'waveshare7in3g': + elif config['ui']['display']['type'] in ('ws_7in3g', 'waveshare7in3g', 'ws7in3g', 'waveshare_73g', 'waveshare73g'): config['ui']['display']['type'] = 'waveshare7in3g' - elif config['ui']['display']['type'] in 'waveshare7in5': + elif config['ui']['display']['type'] in ('ws_7in5', 'waveshare7in5', 'ws7in5', 'waveshare_75', 'waveshare75'): config['ui']['display']['type'] = 'waveshare7in5' - elif config['ui']['display']['type'] in 'waveshare7in5_HD': + elif config['ui']['display']['type'] in ('ws_7in5hd', 'waveshare7in5hd', 'ws7in5hd', 'waveshare_75hd', 'waveshare75hd'): config['ui']['display']['type'] = 'waveshare7in5_HD' - elif config['ui']['display']['type'] in 'waveshare7in5_v2': + elif config['ui']['display']['type'] in ('ws_7in5v2', 'waveshare7in5v2', 'ws7in5v2', 'waveshare_75v2', 'waveshare75v2'): config['ui']['display']['type'] = 'waveshare7in5_v2' - elif config['ui']['display']['type'] in 'waveshare7in5b_HD': + elif config['ui']['display']['type'] in ('ws_7in5bhd', 'waveshare7in5bhd', 'ws7in5bhd', 'waveshare_75bhd', 'waveshare75bhd'): config['ui']['display']['type'] = 'waveshare7in5b_HD' - elif config['ui']['display']['type'] in 'waveshare7in5b_v2': + elif config['ui']['display']['type'] in ('ws_7in5bv2', 'waveshare7in5bv2', 'ws7in5bv2', 'waveshare_75bv2', 'waveshare75bv2'): config['ui']['display']['type'] = 'waveshare7in5b_v2' - elif config['ui']['display']['type'] in 'waveshare7in5bc': + elif config['ui']['display']['type'] in ('ws_7in5bc', 'waveshare7in5bc', 'ws7in5bc', 'waveshare_75bc', 'waveshare75bc'): config['ui']['display']['type'] = 'waveshare7in5bc' - elif config['ui']['display']['type'] in 'waveshare13in3k': + elif config['ui']['display']['type'] in ('ws_13in3k', 'waveshare13in3k', 'ws13in3k', 'waveshare_133k', 'waveshare133k'): config['ui']['display']['type'] = 'waveshare13in3k' + # WeAct e-ink + elif config['ui']['display']['type'] in ('weact2in9', 'weact29in'): + config['ui']['display']['type'] = 'weact2in9' + else: print("unsupported display type %s" % config['ui']['display']['type']) sys.exit(1) diff --git a/pyproject.toml b/pyproject.toml new file mode 100644 index 00000000..ff8eaa25 --- /dev/null +++ b/pyproject.toml @@ -0,0 +1,60 @@ +[build-system] +requires = ["setuptools"] +build-backend = "setuptools.build_meta" + +[project] +name = "pwnagotchi" +dynamic = ["version"] +dependencies = [ + "Pillow", + "PyYAML", + "RPi.GPIO", + "file-read-backwards", + "flask", + "flask-cors", + "flask-wtf", + "gast", + "gym", + "inky", + "pycryptodome", + "pydrive2", + "python-dateutil", + "requests", + "rpi_hardware_pwm", + "scapy", + "shimmy", + "smbus2", + "spidev", + "stable_baselines3", + "toml", + "torch", + "torchvision", + "tweepy", + "websockets" +] +requires-python = ">=3.9" +authors = [ + {name = "Evilsocket", email = "evilsocket@gmail.com"}, + {name = "Jayofelony", email = "oudshoorn.jeroen@gmail.com"} +] +maintainers = [ + {name = "Jayofelony", email = "oudshoorn.jeroen@gmail.com"} +] +description = "(⌐■_■) - Deep Reinforcement Learning instrumenting bettercap for WiFI pwning." +readme = "README.md" +license = {file = "LICENSE.md"} +classifiers = [ + 'Programming Language :: Python :: 3', + 'Development Status :: 5 - Production/Stable', + 'License :: OSI Approved :: GNU General Public License (GPL)', + 'Environment :: Console', +] + +[project.urls] +Homepage = "https://pwnagotchi.org/" +Documentation = "https://pwnagotchi.org/" +Repository = "https://github.com/jayofelony/pwnagotchi.git" +Issues = "https://github.com/jayofelony/pwnagotchi/issues" + +[project.scripts] +pwnagotchi_cli = "bin.pwnagotchi:pwnagotchi_cli" \ No newline at end of file diff --git a/requirements.txt b/requirements.txt index 13f0fea6..ab51bdcb 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,25 +1,26 @@ -Pillow +gym +shimmy +pycryptodome +requests PyYAML -RPi.GPIO +scapy +tweepy file-read-backwards +inky +smbus2 +Pillow +spidev +gast flask flask-cors flask-wtf -gast -gym -inky -pycryptodome -pydrive2 -python-dateutil -requests -rpi_hardware_pwm -scapy -shimmy -smbus2 -spidev -stable_baselines3 +dbus-python toml +python-dateutil +websockets torch torchvision -tweepy -websockets \ No newline at end of file +stable_baselines3 +RPi.GPIO +rpi_hardware_pwm +pydrive2 \ No newline at end of file diff --git a/setup.py b/setup.py index 0e187ef8..6202183a 100644 --- a/setup.py +++ b/setup.py @@ -8,6 +8,7 @@ import os import re import shutil import warnings +import platform log = logging.getLogger(__name__) @@ -31,7 +32,10 @@ def install_file(source_filename, dest_filename): def install_system_files(): setup_path = os.path.dirname(__file__) - data_path = os.path.join(setup_path, "builder/data") + if platform.machine().startswith('arm'): + data_path = os.path.join(setup_path, "builder/data/32bit") + elif platform.machine().startswith('aarch'): + data_path = os.path.join(setup_path, "builder/data/64bit") for source_filename in glob.glob("%s/**" % data_path, recursive=True): if os.path.isfile(source_filename): @@ -94,7 +98,7 @@ setup(name='pwnagotchi', "install": CustomInstall, }, scripts=['bin/pwnagotchi'], - package_data={'pwnagotchi': ['defaults.yml', 'pwnagotchi/defaults.yml', 'locale/*/LC_MESSAGES/*.mo']}, + package_data={'pwnagotchi': ['defaults.toml', 'pwnagotchi/defaults.toml', 'locale/*/LC_MESSAGES/*.mo']}, include_package_data=True, packages=find_packages(), classifiers=[