diff --git a/.github/workflows/publish.yml b/.github/workflows/publish.yml deleted file mode 100644 index ed066f4e..00000000 --- a/.github/workflows/publish.yml +++ /dev/null @@ -1,55 +0,0 @@ -name: Publish - -on: - workflow_dispatch: - -jobs: - build: - name: ${{ matrix.name }} - runs-on: ubuntu-latest - strategy: - matrix: - include: - - name: "Raspberry Pi 32-bit" - id: "32bit" - - name: "Raspberry Pi 64-bit" - id: "64bit" - - steps: - - uses: actions/checkout@v4 - with: - path: publish/build - - - name: Extract version from file - id: get_version - run: | - VERSION=$(cut -d "'" -f2 < publish/build/pwnagotchi/_version.py) - echo "VERSION=$VERSION" >> $GITHUB_ENV - - - name: Install qemu dependencies - run: sudo apt update && sudo apt install qemu-user-static qemu-utils xz-utils -y - - - name: Build ${{ matrix.name }} img file - run: cd publish/build; ls -la .; pwd; make packer; make ${{ matrix.id }} - - - name: Change name of .img.xz to add version - run: | - sudo chown runner:docker "pwnagotchi-${{ matrix.id }}.img" - mv "pwnagotchi-${{ matrix.id }}.img" "pwnagotchi-${{ env.VERSION }}-${{ matrix.id }}.img" - - - name: PiShrink - run: | - wget https://raw.githubusercontent.com/Drewsif/PiShrink/master/pishrink.sh - chmod +x pishrink.sh - sudo mv pishrink.sh /usr/local/bin - sudo pishrink.sh -aZ "pwnagotchi-${{ env.VERSION }}-${{ matrix.id }}.img" - - - name: Release - uses: softprops/action-gh-release@v2 - with: - prerelease: false - make_latest: true - tag_name: v${{ env.VERSION }} - name: Pwnagotchi v${{ env.VERSION }} - files: pwnagotchi-${{ env.VERSION }}-${{ matrix.id }}.img.xz - generate_release_notes: true diff --git a/.gitignore b/.gitignore index fd20fddf..7e99e367 100644 --- a/.gitignore +++ b/.gitignore @@ -1,2 +1 @@ - -*.pyc +*.pyc \ No newline at end of file diff --git a/apt_packages.txt b/apt_packages.txt deleted file mode 100644 index e69de29b..00000000 diff --git a/builder/data/etc/bash_completion.d/pwnagotchi_completion.sh b/builder/data/etc/bash_completion.d/pwnagotchi_completion.sh deleted file mode 100644 index 68edc4e9..00000000 --- a/builder/data/etc/bash_completion.d/pwnagotchi_completion.sh +++ /dev/null @@ -1,36 +0,0 @@ -_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/etc/dphys-swapfile b/builder/data/etc/dphys-swapfile deleted file mode 100644 index 1c908a10..00000000 --- a/builder/data/etc/dphys-swapfile +++ /dev/null @@ -1,26 +0,0 @@ -# /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/modules-load.d/modules.conf b/builder/data/etc/modules-load.d/modules.conf deleted file mode 100644 index bb4fa86c..00000000 --- a/builder/data/etc/modules-load.d/modules.conf +++ /dev/null @@ -1,6 +0,0 @@ -# /etc/modules: kernel modules to load at boot time. -# -# This file contains the names of kernel modules that should be loaded -# at boot time, one per line. Lines beginning with "#" are ignored. -# Parameters can be specified after the module name. -i2c-dev \ No newline at end of file diff --git a/builder/data/etc/network/interfaces.d/eth0-cfg b/builder/data/etc/network/interfaces.d/eth0-cfg deleted file mode 100644 index 2166051a..00000000 --- a/builder/data/etc/network/interfaces.d/eth0-cfg +++ /dev/null @@ -1,2 +0,0 @@ -allow-hotplug eth0 -iface eth0 inet dhcp \ No newline at end of file diff --git a/builder/data/etc/network/interfaces.d/lo-cfg b/builder/data/etc/network/interfaces.d/lo-cfg deleted file mode 100644 index a283b0ff..00000000 --- a/builder/data/etc/network/interfaces.d/lo-cfg +++ /dev/null @@ -1,2 +0,0 @@ -auto lo usb0 -iface lo inet loopback \ No newline at end of file diff --git a/builder/data/etc/network/interfaces.d/usb0-cfg b/builder/data/etc/network/interfaces.d/usb0-cfg deleted file mode 100644 index 3521780a..00000000 --- a/builder/data/etc/network/interfaces.d/usb0-cfg +++ /dev/null @@ -1,8 +0,0 @@ -allow-hotplug usb0 -iface usb0 inet static - address 10.0.0.2 - netmask 255.255.255.0 - network 10.0.0.0 - broadcast 10.0.0.255 - gateway 10.0.0.1 - metric 101 diff --git a/builder/data/etc/network/interfaces.d/wlan0-cfg b/builder/data/etc/network/interfaces.d/wlan0-cfg deleted file mode 100644 index f5425694..00000000 --- a/builder/data/etc/network/interfaces.d/wlan0-cfg +++ /dev/null @@ -1,2 +0,0 @@ -allow-hotplug wlan0 -iface wlan0 inet static \ No newline at end of file diff --git a/builder/data/etc/systemd/system/bettercap.service b/builder/data/etc/systemd/system/bettercap.service deleted file mode 100644 index ce8e8290..00000000 --- a/builder/data/etc/systemd/system/bettercap.service +++ /dev/null @@ -1,13 +0,0 @@ -[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/etc/systemd/system/bluetooth.service b/builder/data/etc/systemd/system/bluetooth.service deleted file mode 100644 index 6780e4b8..00000000 --- a/builder/data/etc/systemd/system/bluetooth.service +++ /dev/null @@ -1,20 +0,0 @@ -[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/etc/systemd/system/pwnagotchi.service b/builder/data/etc/systemd/system/pwnagotchi.service deleted file mode 100644 index daa53acc..00000000 --- a/builder/data/etc/systemd/system/pwnagotchi.service +++ /dev/null @@ -1,19 +0,0 @@ -[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 -Restart=always -RestartSec=30 -TasksMax=infinity -LimitNPROC=infinity -StandardOutput=null -StandardError=null - -[Install] -WantedBy=multi-user.target diff --git a/builder/data/etc/systemd/system/pwngrid-peer.service b/builder/data/etc/systemd/system/pwngrid-peer.service deleted file mode 100644 index d6ff2223..00000000 --- a/builder/data/etc/systemd/system/pwngrid-peer.service +++ /dev/null @@ -1,16 +0,0 @@ -[Unit] -Description=pwngrid peer service. -Documentation=https://pwnagotchi.org -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/etc/update-motd.d/01-motd deleted file mode 100755 index aa4258c9..00000000 --- a/builder/data/etc/update-motd.d/01-motd +++ /dev/null @@ -1,34 +0,0 @@ -#!/bin/sh -_hostname=$(hostname) -_version=$(cut -d"'" -f2 < /usr/local/lib/python3.11/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 --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" -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 can learn more about me at https://pwnagotchi.org/" diff --git a/builder/data/root/client_secrets.json b/builder/data/root/client_secrets.json deleted file mode 100644 index e69de29b..00000000 diff --git a/builder/data/root/settings.yaml b/builder/data/root/settings.yaml deleted file mode 100644 index 5f198bde..00000000 --- a/builder/data/root/settings.yaml +++ /dev/null @@ -1,15 +0,0 @@ -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/usr/bin/bettercap-launcher deleted file mode 100755 index b44c253b..00000000 --- a/builder/data/usr/bin/bettercap-launcher +++ /dev/null @@ -1,19 +0,0 @@ -#!/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 - -reload_brcm -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/usr/bin/decryption-webserver b/builder/data/usr/bin/decryption-webserver deleted file mode 100755 index 7eb9c52f..00000000 --- a/builder/data/usr/bin/decryption-webserver +++ /dev/null @@ -1,148 +0,0 @@ -#!/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/usr/bin/hdmioff deleted file mode 100755 index 5c32d62c..00000000 --- a/builder/data/usr/bin/hdmioff +++ /dev/null @@ -1,2 +0,0 @@ -#!/usr/bin/env bash -sudo /usr/bin/tvservice -o \ No newline at end of file diff --git a/builder/data/usr/bin/hdmion b/builder/data/usr/bin/hdmion deleted file mode 100755 index eec440fc..00000000 --- a/builder/data/usr/bin/hdmion +++ /dev/null @@ -1,2 +0,0 @@ -#!/usr/bin/env bash -sudo /usr/bin/tvservice -p \ No newline at end of file diff --git a/builder/data/usr/bin/monstart b/builder/data/usr/bin/monstart deleted file mode 100755 index 4c00a5c5..00000000 --- a/builder/data/usr/bin/monstart +++ /dev/null @@ -1,3 +0,0 @@ -#!/usr/bin/env bash -source /usr/bin/pwnlib -start_monitor_interface diff --git a/builder/data/usr/bin/monstop b/builder/data/usr/bin/monstop deleted file mode 100755 index c90a3aef..00000000 --- a/builder/data/usr/bin/monstop +++ /dev/null @@ -1,3 +0,0 @@ -#!/usr/bin/env bash -source /usr/bin/pwnlib -stop_monitor_interface \ No newline at end of file diff --git a/builder/data/usr/bin/pwnagotchi-launcher b/builder/data/usr/bin/pwnagotchi-launcher deleted file mode 100755 index be0ceb03..00000000 --- a/builder/data/usr/bin/pwnagotchi-launcher +++ /dev/null @@ -1,17 +0,0 @@ -#!/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 - /opt/.pwn/bin/pwnagotchi - systemctl restart bettercap -else - /opt/.pwn/bin/pwnagotchi --manual -fi diff --git a/builder/data/usr/bin/pwnlib b/builder/data/usr/bin/pwnlib deleted file mode 100755 index 7cb7b335..00000000 --- a/builder/data/usr/bin/pwnlib +++ /dev/null @@ -1,176 +0,0 @@ -#!/usr/bin/env bash - -# 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() { - ifconfig wlan0 up - sleep 3 - iw phy "$(iw phy | head -1 | cut -d" " -f2)" interface add wlan0mon type monitor - sleep 2 - 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 specified 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/pwnagotchi/__init__.py b/pwnagotchi/__init__.py index 5c4d9637..6655dac4 100644 --- a/pwnagotchi/__init__.py +++ b/pwnagotchi/__init__.py @@ -9,7 +9,6 @@ _name = None config = None _cpu_stats = {} - def set_name(new_name): if new_name is None: return diff --git a/pwnagotchi/automata.py b/pwnagotchi/automata.py index 9b7f9c8e..6e6b2e20 100644 --- a/pwnagotchi/automata.py +++ b/pwnagotchi/automata.py @@ -2,6 +2,7 @@ import logging import pwnagotchi.plugins as plugins from pwnagotchi.ai.epoch import Epoch +import os # basic mood system @@ -136,7 +137,12 @@ class Automata(object): self.set_grateful() plugins.on('epoch', self, self._epoch.epoch - 1, self._epoch.data()) - + if self._epoch.blind_for % 10 == 2: + logging.info("two blind epochs -> restarting wifi.recon...", self._epoch.blind_for) + self.run('wifi.recon on') + if self._epoch.blind_for and self._epoch.blind_for % 5 == 0: + logging.info("%d epochs without visible access points -> restarting bettercap...", self._epoch.blind_for) + os.system("systemctl restart bettercap") if self._epoch.blind_for >= self._config['main']['mon_max_blind_epochs']: logging.critical("%d epochs without visible access points -> restarting ...", self._epoch.blind_for) self._restart() diff --git a/bin/pwnagotchi b/pwnagotchi/cli.py old mode 100755 new mode 100644 similarity index 99% rename from bin/pwnagotchi rename to pwnagotchi/cli.py index 4f31cd3c..f9d6f5e1 --- a/bin/pwnagotchi +++ b/pwnagotchi/cli.py @@ -1,4 +1,3 @@ -#!/usr/bin/python3 import logging import argparse import time @@ -14,7 +13,6 @@ from pwnagotchi import utils from pwnagotchi.google import cmd as google_cmd from pwnagotchi.plugins import cmd as plugins_cmd from pwnagotchi import log -from pwnagotchi import restart from pwnagotchi import fs from pwnagotchi.utils import DottedTomlEncoder, parse_version as version_to_tuple diff --git a/pwnagotchi/locale/ja/LC_MESSAGES/voice.mo b/pwnagotchi/locale/ja/LC_MESSAGES/voice.mo index 57bfecff..f235ecfe 100644 Binary files a/pwnagotchi/locale/ja/LC_MESSAGES/voice.mo and b/pwnagotchi/locale/ja/LC_MESSAGES/voice.mo differ diff --git a/pwnagotchi/plugins/default/bt-tether.py b/pwnagotchi/plugins/default/bt-tether.py index 675f148d..cae4e765 100644 --- a/pwnagotchi/plugins/default/bt-tether.py +++ b/pwnagotchi/plugins/default/bt-tether.py @@ -174,7 +174,7 @@ class BTNap: """ Wait for device - returns device if found None if not + returns device if found, None if not """ logging.debug("BT-TETHER: Waiting for device") diff --git a/pwnagotchi/plugins/default/gps_listener.py b/pwnagotchi/plugins/default/gps_listener.py new file mode 100644 index 00000000..88d27723 --- /dev/null +++ b/pwnagotchi/plugins/default/gps_listener.py @@ -0,0 +1,233 @@ +import json +import logging +import os +import subprocess +import threading + +import pwnagotchi.plugins as plugins +import pwnagotchi.ui.fonts as fonts +from pwnagotchi.ui.components import LabeledValue +from pwnagotchi.ui.view import BLACK + +""" +# Android +# Termux:API : https://f-droid.org/en/packages/com.termux.api/ +# Termux : https://f-droid.org/en/packages/com.termux/ +pkg install termux-api socat bc + +----- +#!/data/data/com.termux/files/usr/bin/bash + +# Server details +SERVER_IP="192.168.44.45" # IP of the socat receiver +SERVER_PORT="5000" # UDP port to send data to + +# Function to calculate checksum +calculate_checksum() { + local sentence="$1" + local checksum=0 + # Loop through each character in the sentence + for ((i = 0; i < ${#sentence}; i++)); do + checksum=$((checksum ^ $(printf '%d' "'${sentence:i:1}"))) + done + # Return checksum in hexadecimal + printf "%02X" $checksum +} + +# Infinite loop to send GPS data +while true; do + # Get location data + LOCATION=$(termux-location -p gps) + + # Extract latitude, longitude, altitude, speed, and bearing + LATITUDE=$(echo "$LOCATION" | jq '.latitude') + LONGITUDE=$(echo "$LOCATION" | jq '.longitude') + ALTITUDE=$(echo "$LOCATION" | jq '.altitude') + SPEED=$(echo "$LOCATION" | jq '.speed') # Speed in meters per second + BEARING=$(echo "$LOCATION" | jq '.bearing') + + # Convert speed from meters per second to knots and km/h + SPEED_KNOTS=$(echo "$SPEED" | awk '{printf "%.1f", $1 * 1.943844}') + SPEED_KMH=$(echo "$SPEED" | awk '{printf "%.1f", $1 * 3.6}') + + # Format latitude and longitude for NMEA + LAT_DEGREES=$(printf "%.0f" "${LATITUDE%.*}") + LAT_MINUTES=$(echo "(${LATITUDE#${LAT_DEGREES}} * 60)" | bc -l) + LAT_DIRECTION=$(if (( $(echo "$LATITUDE >= 0" | bc -l) )); then echo "N"; else echo "S"; fi) + LON_DEGREES=$(printf "%.0f" "${LONGITUDE%.*}") + LON_MINUTES=$(echo "(${LONGITUDE#${LON_DEGREES}} * 60)" | bc -l) + LON_DIRECTION=$(if (( $(echo "$LONGITUDE >= 0" | bc -l) )); then echo "E"; else echo "W"; fi) + + # Format the NMEA GGA sentence + RAW_NMEA_GGA="GPGGA,123519,$(printf "%02d%07.4f" ${LAT_DEGREES#-} $LAT_MINUTES),$LAT_DIRECTION,$(printf "%03d%07.4f" ${LON_DEGREES#-} $LON_MINUTES),$LON_DIRECTION,1,08,0.9,$(printf "%.1f" $ALTITUDE),M,46.9,M,," + CHECKSUM=$(calculate_checksum "$RAW_NMEA_GGA") + NMEA_GGA="\$${RAW_NMEA_GGA}*${CHECKSUM}" + + # Format the VTG sentence + RAW_NMEA_VTG="GPVTG,$(printf "%.1f" $BEARING),T,,M,$(printf "%.1f" $SPEED_KNOTS),N,$(printf "%.1f" $SPEED_KMH),K" + CHECKSUM_VTG=$(calculate_checksum "$RAW_NMEA_VTG") + NMEA_VTG="\$${RAW_NMEA_VTG}*${CHECKSUM_VTG}" + + # Send data via UDP + echo "$NMEA_GGA" + echo "$NMEA_GGA" | socat - UDP:$SERVER_IP:$SERVER_PORT + #echo "$NMEA_VTG" + #echo "$NMEA_VTG" | socat - UDP:$SERVER_IP:$SERVER_PORT + + sleep 1 +done +----- + +# Pwnagotchi +main.plugins.gps_listener.enabled = true + +# packages +sudo apt-get install socat +""" + +class GPS(plugins.Plugin): + __author__ = 'https://github.com/krishenriksen' + __version__ = "1.0.0" + __license__ = "GPL3" + __description__ = "Receive GPS coordinates via termux-location and save whenever an handshake is captured." + + def __init__(self): + self.listen_ip = self.get_ip_address('bnep0') + self.listen_port = "5000" + self.write_virtual_serial = "/dev/ttyUSB1" + self.read_virtual_serial = "/dev/ttyUSB0" + self.baud_rate = "19200" + self.socat_process = None + self.stop_event = threading.Event() + self.status_lock = threading.Lock() + self.status = '-' + self.socat_thread = threading.Thread(target=self.run_socat) + + def get_ip_address(self, interface): + try: + result = subprocess.run( + ["ip", "addr", "show", interface], + capture_output=True, + text=True, + check=True + ) + for line in result.stdout.split('\n'): + if 'inet ' in line: + ip_address = line.strip().split()[1].split('/')[0] + return ip_address + except subprocess.CalledProcessError: + logging.warning(f"Could not get IP address for interface {interface}") + return None + + def set_status(self, status): + with self.status_lock: + self.status = status + + def get_status(self): + with self.status_lock: + return self.status + + def on_loaded(self): + logging.info("GPS Listener plugin loaded") + self.cleanup_virtual_serial_ports() + self.create_virtual_serial_ports() + self.socat_thread.start() + + def cleanup_virtual_serial_ports(self): + if os.path.exists(self.write_virtual_serial): + self.log.info(f"Removing old {self.write_virtual_serial}") + os.remove(self.write_virtual_serial) + + if os.path.exists(self.read_virtual_serial): + self.log.info(f"Removing old {self.read_virtual_serial}") + os.remove(self.read_virtual_serial) + + def create_virtual_serial_ports(self): + self.socat_process = subprocess.Popen( + ["socat", "-d", "-d", f"pty,link={self.write_virtual_serial},mode=777", + f"pty,link={self.read_virtual_serial},mode=777"], + stdout=subprocess.PIPE, + stderr=subprocess.PIPE, + ) + + def run_socat(self): + while not self.stop_event.is_set(): + self.socat_process = subprocess.Popen( + ["socat", f"UDP-RECVFROM:{self.listen_port},reuseaddr,bind={self.listen_ip}", "-"], + stdout=subprocess.PIPE, + stderr=subprocess.PIPE, + text=True + ) + + self.set_status('C') + + with open(self.write_virtual_serial, 'w') as serial_port: + for line in self.socat_process.stdout: + if self.stop_event.is_set(): + break + serial_port.write(line) + serial_port.flush() # Ensure the data is written immediately + self.status = 'C' + + self.socat_process.wait() + if self.stop_event.is_set(): + break + + self.set_status('-') + + def cleanup(self): + if self.socat_process: + self.socat_process.terminate() + self.socat_process.wait() # Ensure the process is reaped + self.stop_event.set() + self.socat_thread.join() + self.cleanup_virtual_serial_ports() + + def on_ready(self, agent): + if os.path.exists(self.read_virtual_serial): + logging.info( + f"enabling bettercap's gps module for {self.read_virtual_serial}" + ) + try: + agent.run("gps off") + except Exception: + logging.info(f"bettercap gps module was already off") + pass + + agent.run(f"set gps.device {self.read_virtual_serial}") + agent.run(f"set gps.baudrate {self.baud_rate}") + agent.run("gps on") + + logging.info(f"bettercap gps module enabled on {self.read_virtual_serial}") + else: + self.set_status('NF') + logging.warning("no GPS detected") + + def on_handshake(self, agent, filename, access_point, client_station): + info = agent.session() + coordinates = info["gps"] + gps_filename = filename.replace(".pcap", ".gps.json") + + if coordinates and all([ + # avoid 0.000... measurements + coordinates["Latitude"], coordinates["Longitude"] + ]): + self.set_status('S') + logging.info(f"saving GPS to {gps_filename} ({coordinates})") + with open(gps_filename, "w+t") as fp: + json.dump(coordinates, fp) + else: + logging.warning("not saving GPS. Couldn't find location.") + + def on_ui_setup(self, ui): + with ui._lock: + ui.add_element('gps', LabeledValue(color=BLACK, label='GPS', value='-', position=(ui.width() / 2 - 40, 0), label_font=fonts.Bold, text_font=fonts.Medium)) + + def on_unload(self, ui): + self.cleanup() + + with ui._lock: + ui.remove_element('gps') + + def on_ui_update(self, ui): + ui.set('gps', self.get_status()) diff --git a/pwnagotchi/plugins/default/wittypi.py b/pwnagotchi/plugins/default/wittypi.py new file mode 100644 index 00000000..fce3e5b6 --- /dev/null +++ b/pwnagotchi/plugins/default/wittypi.py @@ -0,0 +1,75 @@ +# Witty Pi 4 L3V7 +# +import logging +import pwnagotchi.plugins as plugins +import pwnagotchi.ui.fonts as fonts +from pwnagotchi.ui.components import LabeledValue +from pwnagotchi.ui.view import BLACK + +class UPS: + I2C_MC_ADDRESS = 0x08 + I2C_VOLTAGE_IN_I = 1 + I2C_VOLTAGE_IN_D = 2 + I2C_CURRENT_OUT_I = 5 + I2C_CURRENT_OUT_D = 6 + I2C_POWER_MODE = 7 + + def __init__(self): + # only import when the module is loaded and enabled + import smbus + # 0 = /dev/i2c-0 (port I2C0), 1 = /dev/i2c-1 (port I2C1) + self._bus = smbus.SMBus(1) + + def voltage(self): + try: + i = self._bus.read_byte_data(self.I2C_MC_ADDRESS, self.I2C_VOLTAGE_IN_I) + d = self._bus.read_byte_data(self.I2C_MC_ADDRESS, self.I2C_VOLTAGE_IN_D) + return (i + d / 100) + except Exception as e: + logging.info(f"register={i} failed (exception={e})") + return 0.0 + + def current(self): + try: + i = self._bus.read_byte_data(self.I2C_MC_ADDRESS, self.I2C_CURRENT_OUT_I) + d = self._bus.read_byte_data(self.I2C_MC_ADDRESS, self.I2C_CURRENT_OUT_D) + return (i + d / 100) + except Exception as e: + logging.info(f"register={i} failed (exception={e})") + return 0.0 + + def capacity(self): + voltage = max(3.1, min(self.voltage(), 4.2)) # Clamp voltage + return round((voltage - 3.1) / (4.2 - 3.1) * 100) + + def charging(self): + try: + dc = self._bus.read_byte_data(self.I2C_MC_ADDRESS, self.I2C_POWER_MODE) + return '+' if dc == 0 else '-' + except: + return '-' + +class WittyPi(plugins.Plugin): + __author__ = 'https://github.com/krishenriksen' + __version__ = '1.0.0' + __license__ = 'GPL3' + __description__ = 'A plugin that will display battery info from Witty Pi 4 L3V7' + + def __init__(self): + self.ups = None + + def on_loaded(self): + self.ups = UPS() + logging.info("wittypi plugin loaded.") + + def on_ui_setup(self, ui): + ui.add_element('ups', LabeledValue(color=BLACK, label='UPS', value='0%', position=(ui.width() / 2 + 15, 0), label_font=fonts.Bold, text_font=fonts.Medium)) + + def on_unload(self, ui): + with ui._lock: + ui.remove_element('ups') + + def on_ui_update(self, ui): + capacity = self.ups.capacity() + charging = self.ups.charging() + ui.set('ups', "%2i%s" % (capacity, charging)) diff --git a/pwnagotchi/ui/hw/argonpod.py b/pwnagotchi/ui/hw/argonpod.py index 1749df34..cb416b57 100644 --- a/pwnagotchi/ui/hw/argonpod.py +++ b/pwnagotchi/ui/hw/argonpod.py @@ -1,12 +1,12 @@ # board GPIO: -# Key1: -# Key2: -# Key3: -# Key4: -# +# Key1: GPIO 16 +# Key2: GPIO 20 +# Key3: GPIO 21 +# Key4: GPIO 26 +# IR: GPIO 23 # Touch chipset: # HW info: https://argon40.com/products/pod-display-2-8inch -# HW datasheet: +# HW MANUAL: https://cdn.shopify.com/s/files/1/0556/1660/2177/files/ARGON_POD_MANUAL.pdf?v=1668390711%0A import logging @@ -44,6 +44,9 @@ class ArgonPod(DisplayImpl): def initialize(self): logging.info("Initializing Argon Pod display") + logging.info("Available pins for GPIO Buttons: 16, 20, 21, 26") + logging.info("IR available on GPIO 23") + logging.info("Backlight pin available on GPIO 18") from pwnagotchi.ui.hw.libs.argon.argonpod.ILI9341 import ILI9341 self._display = ILI9341(0, 0, 22, 18) diff --git a/pwnagotchi/ui/hw/displayhatmini.py b/pwnagotchi/ui/hw/displayhatmini.py index 55cb86ef..ba9cbb8a 100644 --- a/pwnagotchi/ui/hw/displayhatmini.py +++ b/pwnagotchi/ui/hw/displayhatmini.py @@ -1,9 +1,7 @@ import logging - import pwnagotchi.ui.fonts as fonts from pwnagotchi.ui.hw.base import DisplayImpl - class DisplayHatMini(DisplayImpl): def __init__(self, config): super(DisplayHatMini, self).__init__(config, 'displayhatmini') @@ -29,11 +27,14 @@ class DisplayHatMini(DisplayImpl): 'font': fonts.status_font(fonts.Medium), 'max': 20 } - return self._layout def initialize(self): - logging.info("initializing Display Hat Mini") + logging.info("Initializing Display Hat Mini") + logging.info("Available pins for GPIO Buttons A/B/X/Y: 5, 6, 16, 24") + logging.info("Available pins for RGB Led: 17, 27, 22") + logging.info("Backlight pin available on GPIO 13") + logging.info("I2C bus available on stemma QT and Breakout Garden headers") from pwnagotchi.ui.hw.libs.pimoroni.displayhatmini.ST7789 import ST7789 self._display = ST7789(0,1,9,13) diff --git a/pwnagotchi/ui/hw/gfxhat.py b/pwnagotchi/ui/hw/gfxhat.py index ced3d5ea..51160402 100644 --- a/pwnagotchi/ui/hw/gfxhat.py +++ b/pwnagotchi/ui/hw/gfxhat.py @@ -8,7 +8,7 @@ # ui.display.blcolor = "olive" # # Contrast should be between 30-50, default is 40 -# Backlight are predefined in the epd.py +# Backlight are predefined in the lcd.py # Available backlight colors: # white, grey, maroon, red, purple, fuchsia, green, # lime, olive, yellow, navy, blue, teal, aqua @@ -48,10 +48,16 @@ class GfxHat(DisplayImpl): def initialize(self): contrast = self._config['contrast'] if 'contrast' in self._config else 40 blcolor = self._config['blcolor'] if 'blcolor' in self._config else 'OLIVE' - logging.info("initializing Pimoroni GfxHat") - logging.info("initializing Pimoroni GfxHat - Contrast: %d Backlight color: %s" % (contrast, blcolor)) - from pwnagotchi.ui.hw.libs.pimoroni.gfxhat.epd import EPD - self._display = EPD(contrast=contrast) + logging.info("Initializing Pimoroni GfxHat - Contrast: %d Backlight color: %s" % (contrast, blcolor)) + logging.info("Available config options: ui.display.contrast and ui.display.color") + logging.info("Contrast should be between 30-50, default is 40") + logging.info("Backlight are predefined in the lcd.py") + logging.info("Available backlight colors:") + logging.info("white, grey, maroon, red, purple, fuchsia, green,") + logging.info("lime, olive, yellow, navy, blue, teal, aqua") + logging.info("Touch control work in progress (6 touch buttons with short and long press and LED feedback)") + from pwnagotchi.ui.hw.libs.pimoroni.gfxhat.lcd import LCD + self._display = LCD(contrast=contrast) self._display.Init(color_name=blcolor) self._display.Clear() diff --git a/pwnagotchi/ui/hw/i2coled.py b/pwnagotchi/ui/hw/i2coled.py index 0f58e4bb..402c3657 100644 --- a/pwnagotchi/ui/hw/i2coled.py +++ b/pwnagotchi/ui/hw/i2coled.py @@ -52,11 +52,11 @@ class I2COled(DisplayImpl): i2caddr = self._config['i2c_addr'] if 'i2c_addr' in self._config else 0x3C width = self._config['width'] if 'width' in self._config else 128 height = self._config['height'] if 'height' in self._config else 64 + logging.info("Initializing SSD1306 based %dx%d I2C Oled Display on address 0x%X" % (width, height, i2caddr)) + logging.info("Available config options: ui.display.width, ui.display.height and ui.display.i2caddr") - logging.info("initializing %dx%d I2C Oled Display on address 0x%X" % (width, height, i2caddr)) - - from pwnagotchi.ui.hw.libs.i2coled.epd import EPD - self._display = EPD(address=i2caddr, width=width, height=height) + from pwnagotchi.ui.hw.libs.i2coled.oled import OLED + self._display = OLED(address=i2caddr, width=width, height=height) self._display.Init() self._display.Clear() diff --git a/pwnagotchi/ui/hw/libs/i2coled/epd.py b/pwnagotchi/ui/hw/libs/i2coled/oled.py similarity index 95% rename from pwnagotchi/ui/hw/libs/i2coled/epd.py rename to pwnagotchi/ui/hw/libs/i2coled/oled.py index 12023664..347afb4d 100644 --- a/pwnagotchi/ui/hw/libs/i2coled/epd.py +++ b/pwnagotchi/ui/hw/libs/i2coled/oled.py @@ -9,7 +9,7 @@ EPD_HEIGHT = 64 # disp = SSD1306.SSD1306_96_16(96, 16, address=0x3C) # If you change for different resolution, you have to modify the layout in pwnagotchi/ui/hw/i2coled.py -class EPD(object): +class OLED(object): def __init__(self, address=0x3C, width=EPD_WIDTH, height=EPD_HEIGHT): self.width = width diff --git a/pwnagotchi/ui/hw/libs/pimoroni/gfxhat/backlight.py b/pwnagotchi/ui/hw/libs/pimoroni/gfxhat/backlight.py index 98ccc885..59a15f1f 100644 --- a/pwnagotchi/ui/hw/libs/pimoroni/gfxhat/backlight.py +++ b/pwnagotchi/ui/hw/libs/pimoroni/gfxhat/backlight.py @@ -9,7 +9,7 @@ LED_MAP = [2, 1, 0, 5, 4, 3] def setup(): """Set up the backlight on GFX HAT.""" global _sn3218 - import sn3218 as _sn3218 + from . import sn3218 as _sn3218 _sn3218.enable() _sn3218.enable_leds(0b111111111111111111) diff --git a/pwnagotchi/ui/hw/libs/pimoroni/gfxhat/epd.py b/pwnagotchi/ui/hw/libs/pimoroni/gfxhat/epd.py deleted file mode 100644 index 48768e0f..00000000 --- a/pwnagotchi/ui/hw/libs/pimoroni/gfxhat/epd.py +++ /dev/null @@ -1,55 +0,0 @@ -from . import st7567 -from . import backlight -CONTRAST = 40 - -# Define RGB colors -WHITE = (255, 255, 255) -GREY = (255, 255, 255) -MAROON = (128, 0, 0) -RED = (255, 0, 0) -PURPLE = (128, 0, 128) -FUCHSIA = (255, 0, 255) -GREEN = (0, 128, 0) -LIME = (0, 255, 0) -OLIVE = (128, 128, 0) -YELLOW = (255, 255, 0) -NAVY = (0, 0, 128) -BLUE = (0, 0, 255) -TEAL = (0, 128, 128) -AQUA = (0, 255, 255) - -# Map color names to RGB values -color_map = { - 'WHITE': WHITE, - 'GREY' : GREY, - 'MAROON': MAROON, - 'RED': RED, - 'PURPLE': PURPLE, - 'FUCHSIA': FUCHSIA, - 'GREEN' : GREEN, - 'LIME' : LIME, - 'OLIVE' : OLIVE, - 'YELLOW' : YELLOW, - 'NAVY' : NAVY, - 'BLUE' : BLUE, - 'TEAL' : TEAL, - 'AQUA' : AQUA -} - -class EPD(object): - - def __init__(self, contrast=CONTRAST, blcolor=('OLIVE')): - self.disp = st7567.ST7567() - self.disp.contrast(contrast) - - def Init(self, color_name): - self.disp.setup() - blcolor = color_map.get(color_name.upper(), OLIVE) # Default to olive if color not found - backlight.set_all(*blcolor) - backlight.show() - - def Clear(self): - self.disp.clear() - - def Display(self, image): - self.disp.show(image) \ No newline at end of file diff --git a/pwnagotchi/ui/hw/libs/pimoroni/gfxhat/lcd.py b/pwnagotchi/ui/hw/libs/pimoroni/gfxhat/lcd.py index 9ba0e4aa..b6644d25 100644 --- a/pwnagotchi/ui/hw/libs/pimoroni/gfxhat/lcd.py +++ b/pwnagotchi/ui/hw/libs/pimoroni/gfxhat/lcd.py @@ -1,57 +1,55 @@ -"""Library for the GFX HAT ST7567 SPI LCD.""" -from .st7567 import ST7567 +from . import st7567 +from . import backlight +CONTRAST = 40 -st7567 = ST7567() +# Define RGB colors +WHITE = (255, 255, 255) +GREY = (255, 255, 255) +MAROON = (128, 0, 0) +RED = (255, 0, 0) +PURPLE = (128, 0, 128) +FUCHSIA = (255, 0, 255) +GREEN = (0, 128, 0) +LIME = (0, 255, 0) +OLIVE = (128, 128, 0) +YELLOW = (255, 255, 0) +NAVY = (0, 0, 128) +BLUE = (0, 0, 255) +TEAL = (0, 128, 128) +AQUA = (0, 255, 255) -dimensions = st7567.dimensions +# Map color names to RGB values +color_map = { + 'WHITE': WHITE, + 'GREY' : GREY, + 'MAROON': MAROON, + 'RED': RED, + 'PURPLE': PURPLE, + 'FUCHSIA': FUCHSIA, + 'GREEN' : GREEN, + 'LIME' : LIME, + 'OLIVE' : OLIVE, + 'YELLOW' : YELLOW, + 'NAVY' : NAVY, + 'BLUE' : BLUE, + 'TEAL' : TEAL, + 'AQUA' : AQUA +} +class LCD(object): -def clear(): - """Clear GFX HAT's display buffer.""" - st7567.clear() + def __init__(self, contrast=CONTRAST, blcolor=('OLIVE')): + self.disp = st7567.ST7567() + self.disp.contrast(contrast) + def Init(self, color_name): + self.disp.setup() + blcolor = color_map.get(color_name.upper(), OLIVE) # Default to olive if color not found + backlight.set_all(*blcolor) + backlight.show() -def set_pixel(x, y, value): - """Set a single pixel in GTX HAT's display buffer. + def Clear(self): + self.disp.clear() - :param x: X position (from 0 to 127) - :param y: Y position (from 0 to 63) - :param value: pixel state 1 = On, 0 = Off - - """ - st7567.set_pixel(x, y, value) - - -def show(): - """Update GFX HAT with the current buffer contents.""" - st7567.show() - - -def contrast(value): - """Change GFX HAT LCD contrast.""" - st7567.contrast(value) - - -def rotation(r=0): - """Set the display rotation. - - :param r: Specify the rotation in degrees: 0, or 180 - - """ - if r == 0: - st7567.rotated = False - - elif r == 180: - st7567.rotated = True - - else: - raise ValueError('Rotation must be 0 or 180 degrees') - - -def get_rotation(): - """Get the display rotation value. - - Returns an integer, either 0, or 180 - - """ - return 180 if st7567.rotated else 0 + def Display(self, image): + self.disp.show(image) \ No newline at end of file diff --git a/pwnagotchi/ui/hw/libs/pimoroni/gfxhat/sn3218.py b/pwnagotchi/ui/hw/libs/pimoroni/gfxhat/sn3218.py new file mode 100644 index 00000000..5ea24d29 --- /dev/null +++ b/pwnagotchi/ui/hw/libs/pimoroni/gfxhat/sn3218.py @@ -0,0 +1,214 @@ +import sys +import warnings + +__version__ = '1.2.7' + +I2C_ADDRESS = 0x54 +CMD_ENABLE_OUTPUT = 0x00 +CMD_SET_PWM_VALUES = 0x01 +CMD_ENABLE_LEDS = 0x13 +CMD_UPDATE = 0x16 +CMD_RESET = 0x17 + +if sys.version_info < (3, ): + SMBUS = "python-smbus" +else: + SMBUS = "python3-smbus" + +# Helper function to avoid exception chaining output in python3. +# use exec to shield newer syntax from older python +if sys.version_info < (3, 3): + def _raise_from_none(exc): + raise exc +else: + exec("def _raise_from_none(exc): raise exc from None") + +try: + from smbus import SMBus +except ImportError: + err_string = "This library requires {smbus}\nInstall with: sudo apt install {smbus}".format(smbus=SMBUS) + import_error = ImportError(err_string) + _raise_from_none(import_error) + +def i2c_bus_id(): + """ + Returns the i2c bus ID. + """ + with open('/proc/cpuinfo') as cpuinfo: + revision = [l[12:-1] for l in cpuinfo if l[:8] == "Revision"][0] + # https://www.raspberrypi.org/documentation/hardware/raspberrypi/revision-codes/README.md + return 1 if int(revision, 16) >= 4 else 0 + + +def enable(): + """ + Enables output. + """ + i2c.write_i2c_block_data(I2C_ADDRESS, CMD_ENABLE_OUTPUT, [0x01]) + + +def disable(): + """ + Disables output. + """ + i2c.write_i2c_block_data(I2C_ADDRESS, CMD_ENABLE_OUTPUT, [0x00]) + + +def reset(): + """ + Resets all internal registers. + """ + i2c.write_i2c_block_data(I2C_ADDRESS, CMD_RESET, [0xFF]) + + +def enable_leds(enable_mask): + """ + Enables or disables each LED channel. The first 18 bit values are + used to determine the state of each channel (1=on, 0=off) if fewer + than 18 bits are provided the remaining channels are turned off. + + Args: + enable_mask (int): up to 18 bits of data + Raises: + TypeError: if enable_mask is not an integer. + """ + if not isinstance(enable_mask, int): + raise TypeError("enable_mask must be an integer") + + i2c.write_i2c_block_data(I2C_ADDRESS, CMD_ENABLE_LEDS, + [enable_mask & 0x3F, (enable_mask >> 6) & 0x3F, (enable_mask >> 12) & 0X3F]) + i2c.write_i2c_block_data(I2C_ADDRESS, CMD_UPDATE, [0xFF]) + + +def channel_gamma(channel, gamma_table): + """ + Overrides the gamma table for a single channel. + + Args: + channel (int): channel number + gamma_table (list): list of 256 gamma correction values + Raises: + TypeError: if channel is not an integer. + ValueError: if channel is not in the range 0..17. + TypeError: if gamma_table is not a list. + """ + global channel_gamma_table + + if not isinstance(channel, int): + raise TypeError("channel must be an integer") + + if channel not in range(18): + raise ValueError("channel be an integer in the range 0..17") + + if not isinstance(gamma_table, list) or len(gamma_table) != 256: + raise TypeError("gamma_table must be a list of 256 integers") + + channel_gamma_table[channel] = gamma_table + + +def output(values): + """ + Outputs a new set of values to the driver. + + Args: + values (list): channel number + Raises: + TypeError: if values is not a list of 18 integers. + """ + if not isinstance(values, list) or len(values) != 18: + raise TypeError("values must be a list of 18 integers") + + i2c.write_i2c_block_data(I2C_ADDRESS, CMD_SET_PWM_VALUES, [channel_gamma_table[i][values[i]] for i in range(18)]) + i2c.write_i2c_block_data(I2C_ADDRESS, CMD_UPDATE, [0xFF]) + + +def output_raw(values): + """ + Outputs a new set of values to the driver. + Similar to output(), but does not use channel_gamma_table. + + Args: + values (list): channel number + Raises: + TypeError: if values is not a list of 18 integers. + """ + # SMBus.write_i2c_block_data does the type check, so we don't have to + if len(values) != 18: + raise TypeError("values must be a list of 18 integers") + + i2c.write_i2c_block_data(I2C_ADDRESS, CMD_SET_PWM_VALUES, values) + i2c.write_i2c_block_data(I2C_ADDRESS, CMD_UPDATE, [0xFF]) + + +try: + i2c = SMBus(i2c_bus_id()) +except IOError as e: + warntxt=""" +###### ###### ###### ###### ###### ###### ###### ###### +i2c initialization failed - is i2c enabled on this system? +See https://github.com/pimoroni/sn3218/wiki/missing-i2c +###### ###### ###### ###### ###### ###### ###### ###### +""" + warnings.warn(warntxt) + raise(e) + +# generate a good default gamma table +default_gamma_table = [int(pow(255, float(i - 1) / 255)) for i in range(256)] +channel_gamma_table = [default_gamma_table] * 18 + +enable_leds(0b111111111111111111) + +def test_cycles(): + print("sn3218 test cycles") + + import time + import math + + # enable output + enable() + enable_leds(0b111111111111111111) + + print(">> test enable mask (on/off)") + enable_mask = 0b000000000000000000 + output([0x10] * 18) + for i in range(10): + enable_mask = ~enable_mask + enable_leds(enable_mask) + time.sleep(0.15) + + print(">> test enable mask (odd/even)") + enable_mask = 0b101010101010101010 + output([0x10] * 18) + for i in range(10): + enable_mask = ~enable_mask + enable_leds(enable_mask) + time.sleep(0.15) + + print(">> test enable mask (rotate)") + enable_mask = 0b100000100000100000 + output([0x10] * 18) + for i in range(10): + enable_mask = ((enable_mask & 0x01) << 18) | enable_mask >> 1 + enable_leds(enable_mask) + time.sleep(0.15) + + print(">> test gamma gradient") + enable_mask = 0b111111111111111111 + enable_leds(enable_mask) + for i in range(256): + output([((j * (256//18)) + (i * (256//18))) % 256 for j in range(18)]) + time.sleep(0.01) + + print(">> test gamma fade") + enable_mask = 0b111111111111111111 + enable_leds(enable_mask) + for i in range(512): + output([int((math.sin(float(i)/64.0) + 1.0) * 128.0)]*18) + time.sleep(0.01) + + # turn everything off and disable output + output([0 for i in range(18)]) + disable() + +if __name__ == "__main__": + test_cycles() \ No newline at end of file diff --git a/pwnagotchi/ui/hw/minipitft.py b/pwnagotchi/ui/hw/minipitft.py index 02efff78..9f859f1d 100644 --- a/pwnagotchi/ui/hw/minipitft.py +++ b/pwnagotchi/ui/hw/minipitft.py @@ -1,6 +1,6 @@ # board GPIO: -# A: GPIO22 -# B: GPIO23 +# A: GPIO23 +# B: GPIO24 # # HW datasheet: https://learn.adafruit.com/adafruit-1-3-color-tft-bonnet-for-raspberry-pi/overview @@ -38,7 +38,10 @@ class MiniPitft(DisplayImpl): return self._layout def initialize(self): - logging.info("initializing Adafruit Mini Pi Tft 240x240") + logging.info("Initializing Adafruit Mini Pi Tft 240x240") + logging.info("Available pins for GPIO Buttons: 23, 24") + logging.info("Backlight pin available on GPIO 22") + logging.info("I2C bus available on stemma QT header") from pwnagotchi.ui.hw.libs.adafruit.minipitft.ST7789 import ST7789 self._display = ST7789(0,0,25,22) diff --git a/pwnagotchi/ui/hw/minipitft2.py b/pwnagotchi/ui/hw/minipitft2.py index d8905d05..bf9a5c71 100644 --- a/pwnagotchi/ui/hw/minipitft2.py +++ b/pwnagotchi/ui/hw/minipitft2.py @@ -1,6 +1,6 @@ # board GPIO: -# A: GPIO22 -# B: GPIO23 +# A: GPIO23 +# B: GPIO24 # # HW datasheet: https://learn.adafruit.com/adafruit-1-3-color-tft-bonnet-for-raspberry-pi/overview @@ -39,6 +39,9 @@ class MiniPitft2(DisplayImpl): def initialize(self): logging.info("initializing Adafruit Mini Pi Tft 135x240") + logging.info("Available pins for GPIO Buttons: 23, 24") + logging.info("Backlight pin available on GPIO 22") + logging.info("I2C bus available on stemma QT header") from pwnagotchi.ui.hw.libs.adafruit.minipitft2.ST7789 import ST7789 self._display = ST7789(0,0,25,22) diff --git a/pwnagotchi/ui/hw/pirateaudio.py b/pwnagotchi/ui/hw/pirateaudio.py index e55a6226..5a570b7e 100644 --- a/pwnagotchi/ui/hw/pirateaudio.py +++ b/pwnagotchi/ui/hw/pirateaudio.py @@ -45,6 +45,10 @@ class PirateAudio(DisplayImpl): def initialize(self): logging.info("Initializing PirateAudio - display only") + logging.info("Available pins for GPIO Buttons A/B/X/Y: 5, 6, 16, 20 or 24") + logging.info("refer to the pimoroni site or pinout.xyz") + logging.info("Backlight pin available on GPIO 13") + logging.info("I2S for the DAC available on pins: 18, 19 and 21") from pwnagotchi.ui.hw.libs.pimoroni.pirateaudio.ST7789 import ST7789 self._display = ST7789(0,1,9,13) diff --git a/pwnagotchi/ui/hw/pitft.py b/pwnagotchi/ui/hw/pitft.py index dbf6c358..4334af5c 100644 --- a/pwnagotchi/ui/hw/pitft.py +++ b/pwnagotchi/ui/hw/pitft.py @@ -50,6 +50,9 @@ class Pitft(DisplayImpl): def initialize(self): logging.info("Initializing adafruit pitft 320x240 screen") + logging.info("Available pins for GPIO Buttons on the 3,2inch: 17, 22, 23, 27") + logging.info("Available pins for GPIO Buttons on the 2,8inch: 26, 13, 12, 6, 5") + logging.info("Backlight pin available on GPIO 18") from pwnagotchi.ui.hw.libs.adafruit.pitft.ILI9341 import ILI9341 self._display = ILI9341(0, 0, 25, 18) diff --git a/pwnagotchi/ui/hw/tftbonnet.py b/pwnagotchi/ui/hw/tftbonnet.py index 5b014b92..fc742910 100644 --- a/pwnagotchi/ui/hw/tftbonnet.py +++ b/pwnagotchi/ui/hw/tftbonnet.py @@ -44,6 +44,9 @@ class TftBonnet(DisplayImpl): def initialize(self): logging.info("initializing Adafruit Tft Bonnet") + logging.info("Available pins for GPIO Buttons Up/Down/Left/Right/Center/A/B: 17, 22, 27, 23, 4, 5, 6") + logging.info("Backlight pin available on GPIO 26") + logging.info("I2C bus available on stemma QT header") from pwnagotchi.ui.hw.libs.adafruit.tftbonnet.ST7789 import ST7789 self._display = ST7789(0,0,25,26) diff --git a/pwnagotchi/ui/hw/waveshareoledlcd.py b/pwnagotchi/ui/hw/waveshareoledlcd.py index 19e58b31..60e82e69 100644 --- a/pwnagotchi/ui/hw/waveshareoledlcd.py +++ b/pwnagotchi/ui/hw/waveshareoledlcd.py @@ -49,6 +49,7 @@ class Waveshareoledlcd(DisplayImpl): def initialize(self): logging.info("initializing Waveshare OLED/LCD hat") + logging.info("Available pins for GPIO Buttons K1/K2/K3/K4: 4, 17, 23, 24") from pwnagotchi.ui.hw.libs.waveshare.oled.oledlcd.ST7789 import ST7789 self._display = ST7789(0,0,22,18) diff --git a/pwnagotchi/ui/hw/waveshareoledlcdvert.py b/pwnagotchi/ui/hw/waveshareoledlcdvert.py index ee817a26..2e86983b 100644 --- a/pwnagotchi/ui/hw/waveshareoledlcdvert.py +++ b/pwnagotchi/ui/hw/waveshareoledlcdvert.py @@ -49,6 +49,7 @@ class Waveshareoledlcdvert(DisplayImpl): def initialize(self): logging.info("initializing Waveshare OLED/LCD hat vertical mode") + logging.info("Available pins for GPIO Buttons K1/K2/K3/K4: 4, 17, 23, 24") from pwnagotchi.ui.hw.libs.waveshare.oled.oledlcd.ST7789vert import ST7789 self._display = ST7789(0,0,22,18) diff --git a/pwnagotchi/ui/view.py b/pwnagotchi/ui/view.py index abb5a744..d8e4ca03 100644 --- a/pwnagotchi/ui/view.py +++ b/pwnagotchi/ui/view.py @@ -1,4 +1,4 @@ -#import _thread +# import _thread import threading import logging import random @@ -6,7 +6,6 @@ import time from threading import Lock from PIL import ImageDraw -from PIL import ImageColor as colors import pwnagotchi import pwnagotchi.plugins as plugins @@ -19,105 +18,28 @@ from pwnagotchi.ui.components import * from pwnagotchi.ui.state import State from pwnagotchi.voice import Voice -WHITE = 0x00 # white is actually black on jays image -BLACK = 0xFF # black is actually white on jays image - -BACKGROUND_1 = 0 -FOREGROUND_1 = 1 - -BACKGROUND_L = 0 -FOREGROUND_L = 255 - -BACKGROUND_BGR_16 = (0,0,0) -FOREGROUND_BGR_16 = (31,63,31) - -BACKGROUND_RGB = (0,0,0) -FOREGROUND_RGB = (255,255,255) - - -ROOT = None - - - - -#1 (1-bit pixels, black and white, stored with one pixel per byte) - -#L (8-bit pixels, grayscale) - -#P (8-bit pixels, mapped to any other mode using a color palette) - -#BGR;16 (5,6,5 bits, for 65k color) - -#RGB (3x8-bit pixels, true color) - -#RGBA (4x8-bit pixels, true color with transparency mask) - -#CMYK (4x8-bit pixels, color separation) - -#YCbCr (3x8-bit pixels, color video format) - -#self.FOREGROUND is the main color -#self.BACKGROUNDGROUND is the 2ndary color, used for background - +WHITE = 0x00 # white is actually black on jays image +BLACK = 0xFF # black is actually white on jays image class View(object): def __init__(self, config, impl, state=None): global ROOT, BLACK, WHITE - - #values/code for display color mode - - self.mode = '1' # 1 = (1-bit pixels, black and white, stored with one pixel per byte) - if hasattr(impl, 'mode'): - self.mode = impl.mode - - - - match self.mode: - case '1': - self.BACKGROUND = BACKGROUND_1 - self.FOREGROUND = FOREGROUND_1 - # do stuff is color mode is 1 when View object is created. - case 'L': - self.BACKGROUND = BACKGROUND_L # black 0 to 255 - self.FOREGROUND = FOREGROUND_L - # do stuff is color mode is L when View object is created. - case 'P': - pass - # do stuff is color mode is P when View object is created. - case 'BGR;16': - self.BACKGROUND = BACKGROUND_BGR_16 #black tuple - self.FOREGROUND = FOREGROUND_BGR_16 #white tuple - case 'RGB': - self.BACKGROUND = BACKGROUND_RGB #black tuple - self.FOREGROUND = FOREGROUND_RGB #white tuple - # do stuff is color mode is RGB when View object is created. - case 'RGBA': - # do stuff is color mode is RGBA when View object is created. - pass - case 'CMYK': - # do stuff is color mode is CMYK when View object is created. - pass - case 'YCbCr': - # do stuff is color mode is YCbCr when View object is created. - pass - case _: - # do stuff when color mode doesnt exist for display - self.BACKGROUND = BACKGROUND_1 - self.FOREGROUND = FOREGROUND_1 - - + self.invert = 0 + self._black = 0xFF + self._white = 0x00 if 'invert' in config['ui'] and config['ui']['invert'] == True: - logging.debug("INVERT:" + str(config['ui']['invert'])) + logging.debug("INVERT BLACK/WHITES:" + str(config['ui']['invert'])) self.invert = 1 - tmp = self.FOREGROUND - self.FOREGROUND = self.FOREGROUND - self.FOREGROUND = tmp + 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']) - + self._agent = None self._render_cbs = [] self._config = config @@ -130,40 +52,42 @@ class View(object): self._width = self._layout['width'] self._height = self._layout['height'] self._state = State(state={ - 'channel': LabeledValue(color=self.FOREGROUND, label='CH', value='00', position=self._layout['channel'], + 'channel': LabeledValue(color=BLACK, label='CH', value='00', position=self._layout['channel'], label_font=fonts.Bold, text_font=fonts.Medium), - 'aps': LabeledValue(color=self.FOREGROUND, label='APS', value='0 (00)', position=self._layout['aps'], + 'aps': LabeledValue(color=BLACK, label='APS', value='0 (00)', position=self._layout['aps'], label_font=fonts.Bold, text_font=fonts.Medium), - 'uptime': LabeledValue(color=self.FOREGROUND, label='UP', value='00:00:00', position=self._layout['uptime'], + 'uptime': LabeledValue(color=BLACK, label='UP', value='00:00:00', position=self._layout['uptime'], label_font=fonts.Bold, text_font=fonts.Medium), - 'line1': Line(self._layout['line1'], color=self.FOREGROUND), - 'line2': Line(self._layout['line2'], color=self.FOREGROUND), + 'line1': Line(self._layout['line1'], color=BLACK), + 'line2': Line(self._layout['line2'], color=BLACK), - 'face': Text(value=faces.SLEEP, position=(config['ui']['faces']['position_x'], config['ui']['faces']['position_y']), color=self.FOREGROUND, font=fonts.Huge, png=config['ui']['faces']['png']), + '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=self.FOREGROUND), - 'friend_name': Text(value=None, position=self._layout['friend_face'], font=fonts.BoldSmall, color=self.FOREGROUND), + # 'friend_face': Text(value=None, position=self._layout['friend_face'], font=fonts.Bold, 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=self.FOREGROUND, font=fonts.Bold), + 'name': Text(value='%s>' % 'pwnagotchi', position=self._layout['name'], color=BLACK, font=fonts.Bold), 'status': Text(value=self._voice.default(), position=self._layout['status']['pos'], - color=self.FOREGROUND, + color=BLACK, font=self._layout['status']['font'], wrap=True, # the current maximum number of characters per line, assuming each character is 6 pixels wide max_length=self._layout['status']['max']), - 'shakes': LabeledValue(label='PWND ', value='0 (00)', color=self.FOREGROUND, + 'shakes': LabeledValue(label='PWND ', value='0 (00)', color=BLACK, position=self._layout['shakes'], label_font=fonts.Bold, text_font=fonts.Medium), 'mode': Text(value='AUTO', position=self._layout['mode'], - font=fonts.Bold, color=self.FOREGROUND), + font=fonts.Bold, color=BLACK), }) if state: @@ -173,8 +97,8 @@ class View(object): plugins.on('ui_setup', self) if config['ui']['fps'] > 0.0: - threading.Thread(target=self._refresh_handler, args=(), name="UI Handler", daemon = True).start() - + threading.Thread(target=self._refresh_handler, args=(), name="UI Handler", daemon=True).start() + self._ignore_changes = () else: logging.warning("ui.fps is 0, the display will only update for major changes") @@ -189,7 +113,7 @@ class View(object): self._state.has_element(key) def add_element(self, key, elem): - if self.invert is 1 and hasattr(elem, 'color'): + if self.invert is 1 and elem.color: if elem.color == 0xff: elem.color = 0x00 elif elem.color == 0x00: @@ -250,7 +174,8 @@ class View(object): self.set('uptime', last_session.duration) self.set('channel', '-') self.set('aps', "%d" % last_session.associated) - self.set('shakes', '%d (%s)' % (last_session.handshakes, utils.total_unique_handshakes(self._config['bettercap']['handshakes']))) + self.set('shakes', '%d (%s)' % ( + last_session.handshakes, utils.total_unique_handshakes(self._config['bettercap']['handshakes']))) self.set_closest_peer(last_session.last_peer, last_session.peers) self.update() @@ -340,7 +265,7 @@ class View(object): def wait(self, secs, sleeping=True): was_normal = self.is_normal() - part = secs/10.0 + part = secs / 10.0 for step in range(0, 10): # if we weren't in a normal state before going @@ -468,13 +393,13 @@ class View(object): state = self._state changes = state.changes(ignore=self._ignore_changes) if force or len(changes): - self._canvas = Image.new(self.mode, (self._width, self._height), self.BACKGROUND) - drawer = ImageDraw.Draw(self._canvas, self.mode) + self._canvas = Image.new('1', (self._width, self._height), self._white) + drawer = ImageDraw.Draw(self._canvas) plugins.on('ui_update', self) for key, lv in state.items(): - #lv is a ui element + # lv is a ui element lv.draw(self._canvas, drawer) web.update_frame(self._canvas) diff --git a/pyproject.toml b/pyproject.toml index eac141ae..11865033 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,38 +1,18 @@ [build-system] -requires = ["setuptools", "wheel"] +requires = ["setuptools"] build-backend = "setuptools.build_meta" [project] name = "pwnagotchi" dynamic = ["version"] dependencies = [ - "PyYAML", - "dbus-python", - "file-read-backwards", - "flask", - "flask-cors", - "flask-wtf", - "gast", - "gpiozero", - "inky", - "numpy", - "pycryptodome", - "pydrive2", - "python-dateutil", - "requests", - "rpi-lgpio", - "rpi_hardware_pwm", - "scapy", - "setuptools", - "shimmy", - "smbus", - "smbus2", - "spidev", - "toml", - "tweepy", - "websockets", + "PyYAML", "dbus-python", "file-read-backwards", "flask", "flask-cors", + "flask-wtf", "gast", "gpiozero", "inky", "numpy", "pycryptodome", "pydrive2", "python-dateutil", + "requests", "rpi-lgpio", "rpi_hardware_pwm", "scapy", "setuptools", "shimmy", "smbus", "smbus2", + "spidev", "toml", "tweepy", "websockets", ] -requires-python = ">=3.9" + +requires-python = ">=3.11" authors = [ {name = "Evilsocket", email = "evilsocket@gmail.com"}, {name = "Jayofelony", email = "oudshoorn.jeroen@gmail.com"} @@ -44,12 +24,15 @@ description = "(⌐■_■) - Deep Reinforcement Learning instrumenting betterca readme = "README.md" license = {file = "LICENSE.md"} classifiers = [ - 'Programming Language :: Python :: 3', + 'Programming Language :: Python :: 3.11', 'Development Status :: 5 - Production/Stable', 'License :: OSI Approved :: GNU General Public License (GPL)', 'Environment :: Console', ] +[tool.setuptools.dynamic] +version = {attr = "pwnagotchi.__version__"} + [project.urls] Homepage = "https://pwnagotchi.org/" Documentation = "https://github.com/jayofelony/pwnagotchi/wiki" @@ -59,4 +42,4 @@ Issues = "https://github.com/jayofelony/pwnagotchi/issues" Download = "https://github.com/jayofelony/pwnagotchi/releases/latest" [project.scripts] -pwnagotchi_cli = "bin.pwnagotchi:pwnagotchi_cli" +pwnagotchi = "pwnagotchi.cli:pwnagotchi_cli" diff --git a/release.stork b/release.stork deleted file mode 100755 index 023c4ecf..00000000 --- a/release.stork +++ /dev/null @@ -1,7 +0,0 @@ -#!/usr/bin/env stork -f - -version:parser "__version__\\s*=\\s*['\"]([\\d\\.ab]+)[\"']" -version:file "pwnagotchi/_version.py" -version:from_user - -git:create_tag $VERSION \ No newline at end of file diff --git a/requirements.txt b/requirements.txt deleted file mode 100644 index c6921379..00000000 --- a/requirements.txt +++ /dev/null @@ -1,25 +0,0 @@ -PyYAML -dbus-python -file-read-backwards -flask -flask-cors -flask-wtf -gast -gpiozero -inky -numpy -pycryptodome -pydrive2 -python-dateutil -requests -rpi-lgpio -rpi_hardware_pwm -scapy -setuptools -shimmy -smbus -smbus2 -spidev -toml -tweepy -websockets \ No newline at end of file diff --git a/scripts/backup.sh b/scripts/backup.sh index dd3f2349..a4504362 100755 --- a/scripts/backup.sh +++ b/scripts/backup.sh @@ -32,28 +32,31 @@ OUTPUT=${OUTPUT:-${UNIT_HOSTNAME}-backup-$(date +%s).tgz} # username to use for ssh UNIT_USERNAME=${UNIT_USERNAME:-pi} # what to backup -FILES_TO_BACKUP="/root/brain.nn \ - /root/brain.json \ - /root/.api-report.json \ - /root/.ssh \ - /root/.bashrc \ - /root/.profile \ - /root/handshakes \ - /root/peers \ - /etc/pwnagotchi/ \ - /etc/ssh/ \ - /var/log/pwnagotchi.log \ - /var/log/pwnagotchi*.gz \ - /home/pi/.ssh \ - /home/pi/.bashrc \ - /home/pi/.profile \ - /root/.api-report.json \ - /root/.auto-update \ - /root/.bt-tether* \ - /root/.net_pos_saved \ - /root/.ohc_uploads \ - /root/.wigle_uploads \ - /root/.wpa_sec_uploads" +FILES_TO_BACKUP=" + /boot/firmware/cmdline.txt \ + /boot/firmware/config.txt \ + /root/settings.yaml \ + /root/client_secrets.json \ + /root/.api-report.json \ + /root/.ssh \ + /root/.bashrc \ + /root/.profile \ + /root/handshakes \ + /root/peers \ + /etc/modprobe.d/g_ether.conf \ + /etc/pwnagotchi/ \ + /etc/ssh/ \ + /etc/pwnagotchi/log/pwnagotchi.log \ + /etc/pwnagotchi/log/pwnagotchi*.gz \ + /home/pi/.ssh \ + /home/pi/.bashrc \ + /home/pi/.profile \ + /root/.api-report.json \ + /root/.auto-update \ + /root/.bt-tether* \ + /root/.ohc_uploads \ + /root/.wigle_uploads \ + /root/.wpa_sec_uploads" ping -c 1 "${UNIT_HOSTNAME}" > /dev/null 2>&1 || { echo "@ unit ${UNIT_HOSTNAME} can't be reached, make sure it's connected and a static IP assigned to the USB interface." diff --git a/scripts/preview.py b/scripts/preview.py deleted file mode 100755 index de043ce0..00000000 --- a/scripts/preview.py +++ /dev/null @@ -1,217 +0,0 @@ -#!/usr/bin/env python3 -import sys -import os -import argparse -import yaml -import toml - -sys.path.insert(0, - os.path.join(os.path.dirname(os.path.realpath(__file__)), - '../')) - -import pwnagotchi.ui.faces as faces -from pwnagotchi.ui.display import Display -from PIL import Image - - -class CustomDisplay(Display): - - def __init__(self, config, state): - self.last_image = None - super(CustomDisplay, self).__init__(config, state) - - def _http_serve(self): - # do nothing - pass - - def _on_view_rendered(self, img): - self.last_image = img - - def get_image(self): - """ - Return the saved image - """ - return self.last_image - - -class DummyPeer: - - def __init__(self): - self.rssi = -50 - - @staticmethod - def name(): - return "beta" - - @staticmethod - def pwnd_run(): - return 50 - - @staticmethod - def pwnd_total(): - return 100 - - @staticmethod - def first_encounter(): - return 1 - - @staticmethod - def face(): - return faces.FRIEND - - -def append_images(images, horizontal=True, xmargin=0, ymargin=0): - w, h = zip(*(i.size for i in images)) - - if horizontal: - t_w = sum(w) - t_h = max(h) - else: - t_w = max(w) - t_h = sum(h) - - result = Image.new('RGB', (t_w, t_h)) - - x_offset = 0 - y_offset = 0 - - for im in images: - result.paste(im, (x_offset, y_offset)) - if horizontal: - x_offset += im.size[0] + xmargin - else: - y_offset += im.size[1] + ymargin - - return result - - -def main(): - parser = argparse.ArgumentParser(description="This program emulates\ - the pwnagotchi display") - parser.add_argument('--displays', help="Which displays to use.", nargs="+", default=["waveshare_2"]) - parser.add_argument('--lang', help="Language to use", default="en") - parser.add_argument('--output', help="Path to output image (PNG)", default="preview.png") - parser.add_argument('--show-peer', dest="showpeer", help="This options will show a dummy peer", action="store_true") - parser.add_argument('--xmargin', help="Add X-Margin", type=int, default=5) - parser.add_argument('--ymargin', help="Add Y-Margin", type=int, default=5) - args = parser.parse_args() - - config_template = ''' - main: - lang: {lang} - ui: - font: - name: 'DejaVuSansMono' - size_offset: 0 - size: 0 - fps: 0.3 - display: - enabled: false - rotation: 180 - color: black - refresh: 30 - type: {display} - web: - enabled: true - address: '::' - port: 8080 - - faces: - look_r: '( ⚆_⚆)' - look_l: '(☉_☉ )' - look_r_happy: '( ◕‿◕)' - look_l_happy: '(◕‿◕ )' - sleep: '(⇀‿‿↼)' - sleep2: '(≖‿‿≖)' - awake: '(◕‿‿◕)' - bored: '(-__-)' - intense: '(°▃▃°)' - cool: '(⌐■_■)' - happy: '(•‿‿•)' - excited: '(ᵔ◡◡ᵔ)' - grateful: '(^‿‿^)' - motivated: '(☼‿‿☼)' - demotivated: '(≖__≖)' - smart: '(✜‿‿✜)' - lonely: '(ب__ب)' - sad: '(╥☁╥ )' - friend: '(♥‿‿♥)' - broken: '(☓‿‿☓)' - debug: '(#__#)' - ''' - - list_of_displays = list() - for display_type in args.displays: - config = yaml.safe_load(config_template.format(display=display_type, - lang=args.lang)) - display = CustomDisplay(config=config, state={'name': f"{display_type}>"}) - list_of_displays.append(display) - - columns = list() - - for display in list_of_displays: - emotions = list() - if args.showpeer: - display.set_closest_peer(DummyPeer(), 10) - display.on_starting() - display.update() - emotions.append(display.get_image()) - display.on_ai_ready() - display.update() - emotions.append(display.get_image()) - display.on_normal() - display.update() - emotions.append(display.get_image()) - display.on_new_peer(DummyPeer()) - display.update() - emotions.append(display.get_image()) - display.on_lost_peer(DummyPeer()) - display.update() - emotions.append(display.get_image()) - display.on_free_channel('6') - display.update() - emotions.append(display.get_image()) - display.wait(2) - display.update() - emotions.append(display.get_image()) - display.on_bored() - display.update() - emotions.append(display.get_image()) - display.on_sad() - display.update() - emotions.append(display.get_image()) - display.on_motivated(1) - display.update() - emotions.append(display.get_image()) - display.on_demotivated(-1) - display.update() - emotions.append(display.get_image()) - display.on_excited() - display.update() - emotions.append(display.get_image()) - display.on_deauth({'mac': 'DE:AD:BE:EF:CA:FE'}) - display.update() - emotions.append(display.get_image()) - display.on_miss('test') - display.update() - emotions.append(display.get_image()) - display.on_lonely() - display.update() - emotions.append(display.get_image()) - display.on_handshakes(1) - display.update() - emotions.append(display.get_image()) - display.on_rebooting() - display.update() - emotions.append(display.get_image()) - - # append them all together (vertical) - columns.append(append_images(emotions, horizontal=False, xmargin=args.xmargin, ymargin=args.ymargin)) - - # append columns side by side - final_image = append_images(columns, horizontal=True, xmargin=args.xmargin, ymargin=args.ymargin) - final_image.save(args.output, 'PNG') - - -if __name__ == '__main__': - SystemExit(main()) diff --git a/scripts/pypi_upload.sh b/scripts/pypi_upload.sh deleted file mode 100755 index 265a56ae..00000000 --- a/scripts/pypi_upload.sh +++ /dev/null @@ -1,6 +0,0 @@ -#!/bin/bash - -rm -rf build dist pwnagotchi.egg-info && - python3 setup.py sdist bdist_wheel && - clear && - twine upload dist/* diff --git a/setup.py b/setup.py deleted file mode 100644 index 9b132347..00000000 --- a/setup.py +++ /dev/null @@ -1,110 +0,0 @@ -#!/usr/bin/env python3 -# -*- coding: utf-8 -*- -from setuptools import setup, find_packages -from setuptools.command.install import install -import glob -import logging -import os -import re -import shutil -import warnings -import platform - -log = logging.getLogger(__name__) - - -def install_file(source_filename, dest_filename): - # do not overwrite network configuration if it exists already - # https://github.com/evilsocket/pwnagotchi/issues/483 - if dest_filename.startswith('/etc/network/interfaces.d/') and os.path.exists(dest_filename): - log.info(f"{dest_filename} exists, skipping ...") - return - elif dest_filename.startswith('/root/') and os.path.exists(dest_filename): - log.info(f"{dest_filename} exists, skipping ...") - return - - log.info(f"installing {source_filename} to {dest_filename} ...") - dest_folder = os.path.dirname(dest_filename) - if not os.path.isdir(dest_folder): - os.makedirs(dest_folder) - - shutil.copy2(source_filename, dest_filename) - if dest_filename.startswith("/usr/bin/"): - os.chmod(dest_filename, 0o755) - - -def install_system_files(): - data_path = None - if os.stat("apt_packages.txt").st_size != 0: - f = open("apt_packages.txt", "r") - for x in f: - os.system(f"apt-get install -y {x}") - f.close() - setup_path = os.path.dirname(__file__) - data_path = os.path.join(setup_path, "builder/data") - - for source_filename in glob.glob("%s/**" % data_path, recursive=True): - if os.path.isfile(source_filename): - dest_filename = source_filename.replace(data_path, '') - install_file(source_filename, dest_filename) - - -def restart_services(): - # reload systemd units - os.system("systemctl daemon-reload") - - # for people updating https://github.com/evilsocket/pwnagotchi/pull/551/files - os.system("systemctl enable fstrim.timer") - - -class CustomInstall(install): - def run(self): - super().run() - if os.geteuid() != 0: - warnings.warn("Not running as root, can't install pwnagotchi system files!") - return - install_system_files() - restart_services() - - -def version(version_file): - with open(version_file, 'rt') as vf: - version_file_content = vf.read() - - version_match = re.search(r"__version__\s*=\s*[\"\']([^\"\']+)", version_file_content) - if version_match: - return version_match.groups()[0] - - return None - - -with open('requirements.txt') as fp: - required = [ - line.strip() - for line in fp - if line.strip() and not line.startswith("--") - ] - -VERSION_FILE = 'pwnagotchi/_version.py' -pwnagotchi_version = version(VERSION_FILE) - -setup(name='pwnagotchi', - version=pwnagotchi_version, - description='(⌐■_■) - Deep Reinforcement Learning instrumenting bettercap for WiFI pwning.', - author='evilsocket && the dev team', - author_email='evilsocket@gmail.com', - url='https://pwnagotchi.ai/', - license='GPL', - cmdclass={ - "install": CustomInstall, - }, - scripts=['bin/pwnagotchi'], - package_data={'pwnagotchi': ['defaults.toml', 'pwnagotchi/defaults.toml', 'locale/*/LC_MESSAGES/*.mo']}, - include_package_data=True, - packages=find_packages(), - classifiers=[ - 'Programming Language :: Python :: 3', - 'Development Status :: 5 - Production/Stable', - 'License :: OSI Approved :: GNU General Public License (GPL)', - 'Environment :: Console', - ])