mirror of
https://github.com/jayofelony/pwnagotchi.git
synced 2025-07-01 18:37:27 -04:00
Merge branch 'noai' of https://github.com/V0r-T3x/pwnagotchi into noai
This commit is contained in:
55
.github/workflows/publish.yml
vendored
55
.github/workflows/publish.yml
vendored
@ -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
|
|
3
.gitignore
vendored
3
.gitignore
vendored
@ -1,2 +1 @@
|
|||||||
|
*.pyc
|
||||||
*.pyc
|
|
@ -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
|
|
@ -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
|
|
@ -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
|
|
@ -1,2 +0,0 @@
|
|||||||
allow-hotplug eth0
|
|
||||||
iface eth0 inet dhcp
|
|
@ -1,2 +0,0 @@
|
|||||||
auto lo usb0
|
|
||||||
iface lo inet loopback
|
|
@ -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
|
|
@ -1,2 +0,0 @@
|
|||||||
allow-hotplug wlan0
|
|
||||||
iface wlan0 inet static
|
|
@ -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
|
|
@ -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
|
|
@ -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
|
|
@ -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
|
|
@ -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/"
|
|
@ -1,15 +0,0 @@
|
|||||||
client_config_backend: file
|
|
||||||
client_config_file: /root/client_secrets.json
|
|
||||||
client_config:
|
|
||||||
client_id: <YOUR CLIENT ID>
|
|
||||||
client_secret: <YOUR 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
|
|
@ -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
|
|
@ -1,148 +0,0 @@
|
|||||||
#!/usr/bin/env python3
|
|
||||||
|
|
||||||
from http.server import HTTPServer, BaseHTTPRequestHandler
|
|
||||||
from urllib.parse import parse_qsl
|
|
||||||
|
|
||||||
|
|
||||||
_HTML_FORM_TEMPLATE = """
|
|
||||||
<!DOCTYPE html>
|
|
||||||
<html>
|
|
||||||
<head>
|
|
||||||
<title>Decryption</title>
|
|
||||||
<style>
|
|
||||||
body {{ text-align: center; padding: 150px; }}
|
|
||||||
h1 {{ font-size: 50px; }}
|
|
||||||
body {{ font: 20px Helvetica, sans-serif; color: #333; }}
|
|
||||||
article {{ display: block; text-align: center; width: 650px; margin: 0 auto;}}
|
|
||||||
input {{
|
|
||||||
padding: 12px 20px;
|
|
||||||
margin: 8px 0;
|
|
||||||
box-sizing: border-box;
|
|
||||||
border: 1px solid #ccc;
|
|
||||||
}}
|
|
||||||
input[type=password] {{
|
|
||||||
width: 75%;
|
|
||||||
font-size: 24px;
|
|
||||||
}}
|
|
||||||
input[type=submit] {{
|
|
||||||
cursor: pointer;
|
|
||||||
width: 75%;
|
|
||||||
}}
|
|
||||||
input[type=submit]:hover {{
|
|
||||||
background-color: #d9d9d9;
|
|
||||||
}}
|
|
||||||
</style>
|
|
||||||
</head>
|
|
||||||
<body>
|
|
||||||
<article>
|
|
||||||
<h1>Decryption</h1>
|
|
||||||
<p>Some of your files are encrypted.</p>
|
|
||||||
<p>Please provide the decryption password.</p>
|
|
||||||
<div>
|
|
||||||
<form action="/set-password" method="POST">
|
|
||||||
{password_fields}
|
|
||||||
<input type="submit" value="Submit">
|
|
||||||
</form>
|
|
||||||
</div>
|
|
||||||
</article>
|
|
||||||
</body>
|
|
||||||
</html>
|
|
||||||
"""
|
|
||||||
|
|
||||||
POST_RESPONSE = """
|
|
||||||
<!DOCTYPE html>
|
|
||||||
<html>
|
|
||||||
<head>
|
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
|
||||||
<style>
|
|
||||||
/* Center the loader */
|
|
||||||
#loader {
|
|
||||||
position: absolute;
|
|
||||||
left: 50%;
|
|
||||||
top: 50%;
|
|
||||||
z-index: 1;
|
|
||||||
width: 150px;
|
|
||||||
height: 150px;
|
|
||||||
margin: -75px 0 0 -75px;
|
|
||||||
border: 16px solid #f3f3f3;
|
|
||||||
border-radius: 50%;
|
|
||||||
border-top: 16px solid #3498db;
|
|
||||||
width: 120px;
|
|
||||||
height: 120px;
|
|
||||||
-webkit-animation: spin 2s linear infinite;
|
|
||||||
animation: spin 2s linear infinite;
|
|
||||||
}
|
|
||||||
|
|
||||||
@-webkit-keyframes spin {
|
|
||||||
0% { -webkit-transform: rotate(0deg); }
|
|
||||||
100% { -webkit-transform: rotate(360deg); }
|
|
||||||
}
|
|
||||||
|
|
||||||
@keyframes spin {
|
|
||||||
0% { transform: rotate(0deg); }
|
|
||||||
100% { transform: rotate(360deg); }
|
|
||||||
}
|
|
||||||
|
|
||||||
#myDiv {
|
|
||||||
display: none;
|
|
||||||
text-align: center;
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
<script type="text/javascript">
|
|
||||||
|
|
||||||
function checkPwnagotchi() {
|
|
||||||
var target = 'http://' + document.location.hostname + ':8080/';
|
|
||||||
var xhr = new XMLHttpRequest();
|
|
||||||
xhr.open('GET', target);
|
|
||||||
xhr.onreadystatechange = function () {
|
|
||||||
if (xhr.readyState == 4) {
|
|
||||||
if (xhr.status == 200 || xhr.status == 401) {
|
|
||||||
window.location.replace(target);
|
|
||||||
}else{
|
|
||||||
setTimeout(checkPwnagotchi, 1000);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
xhr.send();
|
|
||||||
}
|
|
||||||
|
|
||||||
setTimeout(checkPwnagotchi, 1000);
|
|
||||||
|
|
||||||
</script>
|
|
||||||
</head>
|
|
||||||
<body style="margin:0;">
|
|
||||||
|
|
||||||
<div id="loader"></div>
|
|
||||||
|
|
||||||
</body>
|
|
||||||
</html>
|
|
||||||
"""
|
|
||||||
|
|
||||||
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(['<label for="{m}">Passphrase for {m}:</label>\n<input type="password" id="{m}" name="{m}" value=""><br>'.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()
|
|
@ -1,2 +0,0 @@
|
|||||||
#!/usr/bin/env bash
|
|
||||||
sudo /usr/bin/tvservice -o
|
|
@ -1,2 +0,0 @@
|
|||||||
#!/usr/bin/env bash
|
|
||||||
sudo /usr/bin/tvservice -p
|
|
@ -1,3 +0,0 @@
|
|||||||
#!/usr/bin/env bash
|
|
||||||
source /usr/bin/pwnlib
|
|
||||||
start_monitor_interface
|
|
@ -1,3 +0,0 @@
|
|||||||
#!/usr/bin/env bash
|
|
||||||
source /usr/bin/pwnlib
|
|
||||||
stop_monitor_interface
|
|
@ -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
|
|
@ -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
|
|
||||||
</tmp/.pwnagotchi-secret-"$mapping" read -r SECRET
|
|
||||||
if ! test -b /dev/disk/by-id/dm-uuid-*"$(cryptsetup luksUUID "$container" | tr -d -)"*; then
|
|
||||||
if echo -n "$SECRET" | cryptsetup luksOpen -d- "$container" "$mapping" >/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 <<EOF
|
|
||||||
ctrl_interface=DIR=/var/run/wpa_supplicant GROUP=netdev
|
|
||||||
update_config=1
|
|
||||||
ap_scan=2
|
|
||||||
|
|
||||||
network={
|
|
||||||
ssid="DECRYPT-ME"
|
|
||||||
mode=2
|
|
||||||
key_mgmt=WPA-PSK
|
|
||||||
psk="pwnagotchi"
|
|
||||||
frequency=2437
|
|
||||||
}
|
|
||||||
EOF
|
|
||||||
>/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 </root/.pwnagotchi-crypted
|
|
||||||
|
|
||||||
# overwrite passwords
|
|
||||||
python3 -c 'print("A"*4096)' | tee /tmp/.pwnagotchi-secret-* >/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
|
|
||||||
}
|
|
@ -9,7 +9,6 @@ _name = None
|
|||||||
config = None
|
config = None
|
||||||
_cpu_stats = {}
|
_cpu_stats = {}
|
||||||
|
|
||||||
|
|
||||||
def set_name(new_name):
|
def set_name(new_name):
|
||||||
if new_name is None:
|
if new_name is None:
|
||||||
return
|
return
|
||||||
|
@ -2,6 +2,7 @@ import logging
|
|||||||
|
|
||||||
import pwnagotchi.plugins as plugins
|
import pwnagotchi.plugins as plugins
|
||||||
from pwnagotchi.ai.epoch import Epoch
|
from pwnagotchi.ai.epoch import Epoch
|
||||||
|
import os
|
||||||
|
|
||||||
|
|
||||||
# basic mood system
|
# basic mood system
|
||||||
@ -136,7 +137,12 @@ class Automata(object):
|
|||||||
self.set_grateful()
|
self.set_grateful()
|
||||||
|
|
||||||
plugins.on('epoch', self, self._epoch.epoch - 1, self._epoch.data())
|
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']:
|
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)
|
logging.critical("%d epochs without visible access points -> restarting ...", self._epoch.blind_for)
|
||||||
self._restart()
|
self._restart()
|
||||||
|
2
bin/pwnagotchi → pwnagotchi/cli.py
Executable file → Normal file
2
bin/pwnagotchi → pwnagotchi/cli.py
Executable file → Normal file
@ -1,4 +1,3 @@
|
|||||||
#!/usr/bin/python3
|
|
||||||
import logging
|
import logging
|
||||||
import argparse
|
import argparse
|
||||||
import time
|
import time
|
||||||
@ -14,7 +13,6 @@ from pwnagotchi import utils
|
|||||||
from pwnagotchi.google import cmd as google_cmd
|
from pwnagotchi.google import cmd as google_cmd
|
||||||
from pwnagotchi.plugins import cmd as plugins_cmd
|
from pwnagotchi.plugins import cmd as plugins_cmd
|
||||||
from pwnagotchi import log
|
from pwnagotchi import log
|
||||||
from pwnagotchi import restart
|
|
||||||
from pwnagotchi import fs
|
from pwnagotchi import fs
|
||||||
from pwnagotchi.utils import DottedTomlEncoder, parse_version as version_to_tuple
|
from pwnagotchi.utils import DottedTomlEncoder, parse_version as version_to_tuple
|
||||||
|
|
Binary file not shown.
@ -174,7 +174,7 @@ class BTNap:
|
|||||||
"""
|
"""
|
||||||
Wait for device
|
Wait for device
|
||||||
|
|
||||||
returns device if found None if not
|
returns device if found, None if not
|
||||||
"""
|
"""
|
||||||
logging.debug("BT-TETHER: Waiting for device")
|
logging.debug("BT-TETHER: Waiting for device")
|
||||||
|
|
||||||
|
233
pwnagotchi/plugins/default/gps_listener.py
Normal file
233
pwnagotchi/plugins/default/gps_listener.py
Normal file
@ -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())
|
75
pwnagotchi/plugins/default/wittypi.py
Normal file
75
pwnagotchi/plugins/default/wittypi.py
Normal file
@ -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))
|
@ -1,12 +1,12 @@
|
|||||||
# board GPIO:
|
# board GPIO:
|
||||||
# Key1:
|
# Key1: GPIO 16
|
||||||
# Key2:
|
# Key2: GPIO 20
|
||||||
# Key3:
|
# Key3: GPIO 21
|
||||||
# Key4:
|
# Key4: GPIO 26
|
||||||
#
|
# IR: GPIO 23
|
||||||
# Touch chipset:
|
# Touch chipset:
|
||||||
# HW info: https://argon40.com/products/pod-display-2-8inch
|
# 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
|
import logging
|
||||||
|
|
||||||
@ -44,6 +44,9 @@ class ArgonPod(DisplayImpl):
|
|||||||
|
|
||||||
def initialize(self):
|
def initialize(self):
|
||||||
logging.info("Initializing Argon Pod display")
|
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
|
from pwnagotchi.ui.hw.libs.argon.argonpod.ILI9341 import ILI9341
|
||||||
self._display = ILI9341(0, 0, 22, 18)
|
self._display = ILI9341(0, 0, 22, 18)
|
||||||
|
|
||||||
|
@ -1,9 +1,7 @@
|
|||||||
import logging
|
import logging
|
||||||
|
|
||||||
import pwnagotchi.ui.fonts as fonts
|
import pwnagotchi.ui.fonts as fonts
|
||||||
from pwnagotchi.ui.hw.base import DisplayImpl
|
from pwnagotchi.ui.hw.base import DisplayImpl
|
||||||
|
|
||||||
|
|
||||||
class DisplayHatMini(DisplayImpl):
|
class DisplayHatMini(DisplayImpl):
|
||||||
def __init__(self, config):
|
def __init__(self, config):
|
||||||
super(DisplayHatMini, self).__init__(config, 'displayhatmini')
|
super(DisplayHatMini, self).__init__(config, 'displayhatmini')
|
||||||
@ -29,11 +27,14 @@ class DisplayHatMini(DisplayImpl):
|
|||||||
'font': fonts.status_font(fonts.Medium),
|
'font': fonts.status_font(fonts.Medium),
|
||||||
'max': 20
|
'max': 20
|
||||||
}
|
}
|
||||||
|
|
||||||
return self._layout
|
return self._layout
|
||||||
|
|
||||||
def initialize(self):
|
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
|
from pwnagotchi.ui.hw.libs.pimoroni.displayhatmini.ST7789 import ST7789
|
||||||
self._display = ST7789(0,1,9,13)
|
self._display = ST7789(0,1,9,13)
|
||||||
|
|
||||||
|
@ -8,7 +8,7 @@
|
|||||||
# ui.display.blcolor = "olive"
|
# ui.display.blcolor = "olive"
|
||||||
#
|
#
|
||||||
# Contrast should be between 30-50, default is 40
|
# 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:
|
# Available backlight colors:
|
||||||
# white, grey, maroon, red, purple, fuchsia, green,
|
# white, grey, maroon, red, purple, fuchsia, green,
|
||||||
# lime, olive, yellow, navy, blue, teal, aqua
|
# lime, olive, yellow, navy, blue, teal, aqua
|
||||||
@ -48,10 +48,16 @@ class GfxHat(DisplayImpl):
|
|||||||
def initialize(self):
|
def initialize(self):
|
||||||
contrast = self._config['contrast'] if 'contrast' in self._config else 40
|
contrast = self._config['contrast'] if 'contrast' in self._config else 40
|
||||||
blcolor = self._config['blcolor'] if 'blcolor' in self._config else 'OLIVE'
|
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))
|
||||||
logging.info("initializing Pimoroni GfxHat - Contrast: %d Backlight color: %s" % (contrast, blcolor))
|
logging.info("Available config options: ui.display.contrast and ui.display.color")
|
||||||
from pwnagotchi.ui.hw.libs.pimoroni.gfxhat.epd import EPD
|
logging.info("Contrast should be between 30-50, default is 40")
|
||||||
self._display = EPD(contrast=contrast)
|
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.Init(color_name=blcolor)
|
||||||
self._display.Clear()
|
self._display.Clear()
|
||||||
|
|
||||||
|
@ -52,11 +52,11 @@ class I2COled(DisplayImpl):
|
|||||||
i2caddr = self._config['i2c_addr'] if 'i2c_addr' in self._config else 0x3C
|
i2caddr = self._config['i2c_addr'] if 'i2c_addr' in self._config else 0x3C
|
||||||
width = self._config['width'] if 'width' in self._config else 128
|
width = self._config['width'] if 'width' in self._config else 128
|
||||||
height = self._config['height'] if 'height' in self._config else 64
|
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.oled import OLED
|
||||||
|
self._display = OLED(address=i2caddr, width=width, height=height)
|
||||||
from pwnagotchi.ui.hw.libs.i2coled.epd import EPD
|
|
||||||
self._display = EPD(address=i2caddr, width=width, height=height)
|
|
||||||
self._display.Init()
|
self._display.Init()
|
||||||
self._display.Clear()
|
self._display.Clear()
|
||||||
|
|
||||||
|
@ -9,7 +9,7 @@ EPD_HEIGHT = 64
|
|||||||
# disp = SSD1306.SSD1306_96_16(96, 16, address=0x3C)
|
# 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
|
# 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):
|
def __init__(self, address=0x3C, width=EPD_WIDTH, height=EPD_HEIGHT):
|
||||||
self.width = width
|
self.width = width
|
@ -9,7 +9,7 @@ LED_MAP = [2, 1, 0, 5, 4, 3]
|
|||||||
def setup():
|
def setup():
|
||||||
"""Set up the backlight on GFX HAT."""
|
"""Set up the backlight on GFX HAT."""
|
||||||
global _sn3218
|
global _sn3218
|
||||||
import sn3218 as _sn3218
|
from . import sn3218 as _sn3218
|
||||||
|
|
||||||
_sn3218.enable()
|
_sn3218.enable()
|
||||||
_sn3218.enable_leds(0b111111111111111111)
|
_sn3218.enable_leds(0b111111111111111111)
|
||||||
|
@ -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)
|
|
@ -1,57 +1,55 @@
|
|||||||
"""Library for the GFX HAT ST7567 SPI LCD."""
|
from . import st7567
|
||||||
from .st7567 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():
|
def __init__(self, contrast=CONTRAST, blcolor=('OLIVE')):
|
||||||
"""Clear GFX HAT's display buffer."""
|
self.disp = st7567.ST7567()
|
||||||
st7567.clear()
|
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):
|
def Clear(self):
|
||||||
"""Set a single pixel in GTX HAT's display buffer.
|
self.disp.clear()
|
||||||
|
|
||||||
:param x: X position (from 0 to 127)
|
def Display(self, image):
|
||||||
:param y: Y position (from 0 to 63)
|
self.disp.show(image)
|
||||||
: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
|
|
214
pwnagotchi/ui/hw/libs/pimoroni/gfxhat/sn3218.py
Normal file
214
pwnagotchi/ui/hw/libs/pimoroni/gfxhat/sn3218.py
Normal file
@ -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()
|
@ -1,6 +1,6 @@
|
|||||||
# board GPIO:
|
# board GPIO:
|
||||||
# A: GPIO22
|
# A: GPIO23
|
||||||
# B: GPIO23
|
# B: GPIO24
|
||||||
#
|
#
|
||||||
# HW datasheet: https://learn.adafruit.com/adafruit-1-3-color-tft-bonnet-for-raspberry-pi/overview
|
# 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
|
return self._layout
|
||||||
|
|
||||||
def initialize(self):
|
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
|
from pwnagotchi.ui.hw.libs.adafruit.minipitft.ST7789 import ST7789
|
||||||
self._display = ST7789(0,0,25,22)
|
self._display = ST7789(0,0,25,22)
|
||||||
|
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
# board GPIO:
|
# board GPIO:
|
||||||
# A: GPIO22
|
# A: GPIO23
|
||||||
# B: GPIO23
|
# B: GPIO24
|
||||||
#
|
#
|
||||||
# HW datasheet: https://learn.adafruit.com/adafruit-1-3-color-tft-bonnet-for-raspberry-pi/overview
|
# 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):
|
def initialize(self):
|
||||||
logging.info("initializing Adafruit Mini Pi Tft 135x240")
|
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
|
from pwnagotchi.ui.hw.libs.adafruit.minipitft2.ST7789 import ST7789
|
||||||
self._display = ST7789(0,0,25,22)
|
self._display = ST7789(0,0,25,22)
|
||||||
|
|
||||||
|
@ -45,6 +45,10 @@ class PirateAudio(DisplayImpl):
|
|||||||
|
|
||||||
def initialize(self):
|
def initialize(self):
|
||||||
logging.info("Initializing PirateAudio - display only")
|
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
|
from pwnagotchi.ui.hw.libs.pimoroni.pirateaudio.ST7789 import ST7789
|
||||||
self._display = ST7789(0,1,9,13)
|
self._display = ST7789(0,1,9,13)
|
||||||
|
|
||||||
|
@ -50,6 +50,9 @@ class Pitft(DisplayImpl):
|
|||||||
|
|
||||||
def initialize(self):
|
def initialize(self):
|
||||||
logging.info("Initializing adafruit pitft 320x240 screen")
|
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
|
from pwnagotchi.ui.hw.libs.adafruit.pitft.ILI9341 import ILI9341
|
||||||
self._display = ILI9341(0, 0, 25, 18)
|
self._display = ILI9341(0, 0, 25, 18)
|
||||||
|
|
||||||
|
@ -44,6 +44,9 @@ class TftBonnet(DisplayImpl):
|
|||||||
|
|
||||||
def initialize(self):
|
def initialize(self):
|
||||||
logging.info("initializing Adafruit Tft Bonnet")
|
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
|
from pwnagotchi.ui.hw.libs.adafruit.tftbonnet.ST7789 import ST7789
|
||||||
self._display = ST7789(0,0,25,26)
|
self._display = ST7789(0,0,25,26)
|
||||||
|
|
||||||
|
@ -49,6 +49,7 @@ class Waveshareoledlcd(DisplayImpl):
|
|||||||
|
|
||||||
def initialize(self):
|
def initialize(self):
|
||||||
logging.info("initializing Waveshare OLED/LCD hat")
|
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
|
from pwnagotchi.ui.hw.libs.waveshare.oled.oledlcd.ST7789 import ST7789
|
||||||
self._display = ST7789(0,0,22,18)
|
self._display = ST7789(0,0,22,18)
|
||||||
|
|
||||||
|
@ -49,6 +49,7 @@ class Waveshareoledlcdvert(DisplayImpl):
|
|||||||
|
|
||||||
def initialize(self):
|
def initialize(self):
|
||||||
logging.info("initializing Waveshare OLED/LCD hat vertical mode")
|
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
|
from pwnagotchi.ui.hw.libs.waveshare.oled.oledlcd.ST7789vert import ST7789
|
||||||
self._display = ST7789(0,0,22,18)
|
self._display = ST7789(0,0,22,18)
|
||||||
|
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
#import _thread
|
# import _thread
|
||||||
import threading
|
import threading
|
||||||
import logging
|
import logging
|
||||||
import random
|
import random
|
||||||
@ -6,7 +6,6 @@ import time
|
|||||||
from threading import Lock
|
from threading import Lock
|
||||||
|
|
||||||
from PIL import ImageDraw
|
from PIL import ImageDraw
|
||||||
from PIL import ImageColor as colors
|
|
||||||
|
|
||||||
import pwnagotchi
|
import pwnagotchi
|
||||||
import pwnagotchi.plugins as plugins
|
import pwnagotchi.plugins as plugins
|
||||||
@ -19,105 +18,28 @@ from pwnagotchi.ui.components import *
|
|||||||
from pwnagotchi.ui.state import State
|
from pwnagotchi.ui.state import State
|
||||||
from pwnagotchi.voice import Voice
|
from pwnagotchi.voice import Voice
|
||||||
|
|
||||||
WHITE = 0x00 # white is actually black on jays image
|
WHITE = 0x00 # white is actually black on jays image
|
||||||
BLACK = 0xFF # black is actually white 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
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
class View(object):
|
class View(object):
|
||||||
def __init__(self, config, impl, state=None):
|
def __init__(self, config, impl, state=None):
|
||||||
global ROOT, BLACK, WHITE
|
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.invert = 0
|
||||||
|
self._black = 0xFF
|
||||||
|
self._white = 0x00
|
||||||
if 'invert' in config['ui'] and config['ui']['invert'] == True:
|
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
|
self.invert = 1
|
||||||
tmp = self.FOREGROUND
|
BLACK = 0x00
|
||||||
self.FOREGROUND = self.FOREGROUND
|
WHITE = 0xFF
|
||||||
self.FOREGROUND = tmp
|
self._black = 0x00
|
||||||
|
self._white = 0xFF
|
||||||
|
|
||||||
# setup faces from the configuration in case the user customized them
|
# setup faces from the configuration in case the user customized them
|
||||||
faces.load_from_config(config['ui']['faces'])
|
faces.load_from_config(config['ui']['faces'])
|
||||||
|
|
||||||
self._agent = None
|
self._agent = None
|
||||||
self._render_cbs = []
|
self._render_cbs = []
|
||||||
self._config = config
|
self._config = config
|
||||||
@ -130,40 +52,42 @@ class View(object):
|
|||||||
self._width = self._layout['width']
|
self._width = self._layout['width']
|
||||||
self._height = self._layout['height']
|
self._height = self._layout['height']
|
||||||
self._state = State(state={
|
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,
|
label_font=fonts.Bold,
|
||||||
text_font=fonts.Medium),
|
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,
|
label_font=fonts.Bold,
|
||||||
text_font=fonts.Medium),
|
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,
|
label_font=fonts.Bold,
|
||||||
text_font=fonts.Medium),
|
text_font=fonts.Medium),
|
||||||
|
|
||||||
'line1': Line(self._layout['line1'], color=self.FOREGROUND),
|
'line1': Line(self._layout['line1'], color=BLACK),
|
||||||
'line2': Line(self._layout['line2'], color=self.FOREGROUND),
|
'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_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=self.FOREGROUND),
|
'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(),
|
'status': Text(value=self._voice.default(),
|
||||||
position=self._layout['status']['pos'],
|
position=self._layout['status']['pos'],
|
||||||
color=self.FOREGROUND,
|
color=BLACK,
|
||||||
font=self._layout['status']['font'],
|
font=self._layout['status']['font'],
|
||||||
wrap=True,
|
wrap=True,
|
||||||
# the current maximum number of characters per line, assuming each character is 6 pixels wide
|
# the current maximum number of characters per line, assuming each character is 6 pixels wide
|
||||||
max_length=self._layout['status']['max']),
|
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,
|
position=self._layout['shakes'], label_font=fonts.Bold,
|
||||||
text_font=fonts.Medium),
|
text_font=fonts.Medium),
|
||||||
'mode': Text(value='AUTO', position=self._layout['mode'],
|
'mode': Text(value='AUTO', position=self._layout['mode'],
|
||||||
font=fonts.Bold, color=self.FOREGROUND),
|
font=fonts.Bold, color=BLACK),
|
||||||
})
|
})
|
||||||
|
|
||||||
if state:
|
if state:
|
||||||
@ -173,8 +97,8 @@ class View(object):
|
|||||||
plugins.on('ui_setup', self)
|
plugins.on('ui_setup', self)
|
||||||
|
|
||||||
if config['ui']['fps'] > 0.0:
|
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 = ()
|
self._ignore_changes = ()
|
||||||
else:
|
else:
|
||||||
logging.warning("ui.fps is 0, the display will only update for major changes")
|
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)
|
self._state.has_element(key)
|
||||||
|
|
||||||
def add_element(self, key, elem):
|
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:
|
if elem.color == 0xff:
|
||||||
elem.color = 0x00
|
elem.color = 0x00
|
||||||
elif elem.color == 0x00:
|
elif elem.color == 0x00:
|
||||||
@ -250,7 +174,8 @@ class View(object):
|
|||||||
self.set('uptime', last_session.duration)
|
self.set('uptime', last_session.duration)
|
||||||
self.set('channel', '-')
|
self.set('channel', '-')
|
||||||
self.set('aps', "%d" % last_session.associated)
|
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.set_closest_peer(last_session.last_peer, last_session.peers)
|
||||||
self.update()
|
self.update()
|
||||||
|
|
||||||
@ -340,7 +265,7 @@ class View(object):
|
|||||||
|
|
||||||
def wait(self, secs, sleeping=True):
|
def wait(self, secs, sleeping=True):
|
||||||
was_normal = self.is_normal()
|
was_normal = self.is_normal()
|
||||||
part = secs/10.0
|
part = secs / 10.0
|
||||||
|
|
||||||
for step in range(0, 10):
|
for step in range(0, 10):
|
||||||
# if we weren't in a normal state before going
|
# if we weren't in a normal state before going
|
||||||
@ -468,13 +393,13 @@ class View(object):
|
|||||||
state = self._state
|
state = self._state
|
||||||
changes = state.changes(ignore=self._ignore_changes)
|
changes = state.changes(ignore=self._ignore_changes)
|
||||||
if force or len(changes):
|
if force or len(changes):
|
||||||
self._canvas = Image.new(self.mode, (self._width, self._height), self.BACKGROUND)
|
self._canvas = Image.new('1', (self._width, self._height), self._white)
|
||||||
drawer = ImageDraw.Draw(self._canvas, self.mode)
|
drawer = ImageDraw.Draw(self._canvas)
|
||||||
|
|
||||||
plugins.on('ui_update', self)
|
plugins.on('ui_update', self)
|
||||||
|
|
||||||
for key, lv in state.items():
|
for key, lv in state.items():
|
||||||
#lv is a ui element
|
# lv is a ui element
|
||||||
lv.draw(self._canvas, drawer)
|
lv.draw(self._canvas, drawer)
|
||||||
|
|
||||||
web.update_frame(self._canvas)
|
web.update_frame(self._canvas)
|
||||||
|
@ -1,38 +1,18 @@
|
|||||||
[build-system]
|
[build-system]
|
||||||
requires = ["setuptools", "wheel"]
|
requires = ["setuptools"]
|
||||||
build-backend = "setuptools.build_meta"
|
build-backend = "setuptools.build_meta"
|
||||||
|
|
||||||
[project]
|
[project]
|
||||||
name = "pwnagotchi"
|
name = "pwnagotchi"
|
||||||
dynamic = ["version"]
|
dynamic = ["version"]
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"PyYAML",
|
"PyYAML", "dbus-python", "file-read-backwards", "flask", "flask-cors",
|
||||||
"dbus-python",
|
"flask-wtf", "gast", "gpiozero", "inky", "numpy", "pycryptodome", "pydrive2", "python-dateutil",
|
||||||
"file-read-backwards",
|
"requests", "rpi-lgpio", "rpi_hardware_pwm", "scapy", "setuptools", "shimmy", "smbus", "smbus2",
|
||||||
"flask",
|
"spidev", "toml", "tweepy", "websockets",
|
||||||
"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 = [
|
authors = [
|
||||||
{name = "Evilsocket", email = "evilsocket@gmail.com"},
|
{name = "Evilsocket", email = "evilsocket@gmail.com"},
|
||||||
{name = "Jayofelony", email = "oudshoorn.jeroen@gmail.com"}
|
{name = "Jayofelony", email = "oudshoorn.jeroen@gmail.com"}
|
||||||
@ -44,12 +24,15 @@ description = "(⌐■_■) - Deep Reinforcement Learning instrumenting betterca
|
|||||||
readme = "README.md"
|
readme = "README.md"
|
||||||
license = {file = "LICENSE.md"}
|
license = {file = "LICENSE.md"}
|
||||||
classifiers = [
|
classifiers = [
|
||||||
'Programming Language :: Python :: 3',
|
'Programming Language :: Python :: 3.11',
|
||||||
'Development Status :: 5 - Production/Stable',
|
'Development Status :: 5 - Production/Stable',
|
||||||
'License :: OSI Approved :: GNU General Public License (GPL)',
|
'License :: OSI Approved :: GNU General Public License (GPL)',
|
||||||
'Environment :: Console',
|
'Environment :: Console',
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[tool.setuptools.dynamic]
|
||||||
|
version = {attr = "pwnagotchi.__version__"}
|
||||||
|
|
||||||
[project.urls]
|
[project.urls]
|
||||||
Homepage = "https://pwnagotchi.org/"
|
Homepage = "https://pwnagotchi.org/"
|
||||||
Documentation = "https://github.com/jayofelony/pwnagotchi/wiki"
|
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"
|
Download = "https://github.com/jayofelony/pwnagotchi/releases/latest"
|
||||||
|
|
||||||
[project.scripts]
|
[project.scripts]
|
||||||
pwnagotchi_cli = "bin.pwnagotchi:pwnagotchi_cli"
|
pwnagotchi = "pwnagotchi.cli:pwnagotchi_cli"
|
||||||
|
@ -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
|
|
@ -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
|
|
@ -32,28 +32,31 @@ OUTPUT=${OUTPUT:-${UNIT_HOSTNAME}-backup-$(date +%s).tgz}
|
|||||||
# username to use for ssh
|
# username to use for ssh
|
||||||
UNIT_USERNAME=${UNIT_USERNAME:-pi}
|
UNIT_USERNAME=${UNIT_USERNAME:-pi}
|
||||||
# what to backup
|
# what to backup
|
||||||
FILES_TO_BACKUP="/root/brain.nn \
|
FILES_TO_BACKUP="
|
||||||
/root/brain.json \
|
/boot/firmware/cmdline.txt \
|
||||||
/root/.api-report.json \
|
/boot/firmware/config.txt \
|
||||||
/root/.ssh \
|
/root/settings.yaml \
|
||||||
/root/.bashrc \
|
/root/client_secrets.json \
|
||||||
/root/.profile \
|
/root/.api-report.json \
|
||||||
/root/handshakes \
|
/root/.ssh \
|
||||||
/root/peers \
|
/root/.bashrc \
|
||||||
/etc/pwnagotchi/ \
|
/root/.profile \
|
||||||
/etc/ssh/ \
|
/root/handshakes \
|
||||||
/var/log/pwnagotchi.log \
|
/root/peers \
|
||||||
/var/log/pwnagotchi*.gz \
|
/etc/modprobe.d/g_ether.conf \
|
||||||
/home/pi/.ssh \
|
/etc/pwnagotchi/ \
|
||||||
/home/pi/.bashrc \
|
/etc/ssh/ \
|
||||||
/home/pi/.profile \
|
/etc/pwnagotchi/log/pwnagotchi.log \
|
||||||
/root/.api-report.json \
|
/etc/pwnagotchi/log/pwnagotchi*.gz \
|
||||||
/root/.auto-update \
|
/home/pi/.ssh \
|
||||||
/root/.bt-tether* \
|
/home/pi/.bashrc \
|
||||||
/root/.net_pos_saved \
|
/home/pi/.profile \
|
||||||
/root/.ohc_uploads \
|
/root/.api-report.json \
|
||||||
/root/.wigle_uploads \
|
/root/.auto-update \
|
||||||
/root/.wpa_sec_uploads"
|
/root/.bt-tether* \
|
||||||
|
/root/.ohc_uploads \
|
||||||
|
/root/.wigle_uploads \
|
||||||
|
/root/.wpa_sec_uploads"
|
||||||
|
|
||||||
ping -c 1 "${UNIT_HOSTNAME}" > /dev/null 2>&1 || {
|
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."
|
echo "@ unit ${UNIT_HOSTNAME} can't be reached, make sure it's connected and a static IP assigned to the USB interface."
|
||||||
|
@ -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())
|
|
@ -1,6 +0,0 @@
|
|||||||
#!/bin/bash
|
|
||||||
|
|
||||||
rm -rf build dist pwnagotchi.egg-info &&
|
|
||||||
python3 setup.py sdist bdist_wheel &&
|
|
||||||
clear &&
|
|
||||||
twine upload dist/*
|
|
110
setup.py
110
setup.py
@ -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',
|
|
||||||
])
|
|
Reference in New Issue
Block a user