commit
d3afab73ff
.gitignore.travis.ymlREADME.md
scripts
sdcard/rootfs/root/pwnagotchi
config.yml
scripts
blink.shmain.py
pwnagotchi
agent.pylog.py
requirements.txtai
locale
de/LC_MESSAGES
gr/LC_MESSAGES
it/LC_MESSAGES
mk/LC_MESSAGES
nl/LC_MESSAGES
mesh
plugins
ui
voice.py
1
.gitignore
vendored
1
.gitignore
vendored
@ -1,4 +1,5 @@
|
||||
*.img
|
||||
*.img.bmap
|
||||
*.pcap
|
||||
__pycache__
|
||||
_backups
|
||||
|
@ -13,11 +13,11 @@ cache:
|
||||
- tmp/
|
||||
before_script:
|
||||
- sudo apt-get -y update
|
||||
- sudo apt-get -y install qemu-user-static binfmt-support qemu
|
||||
- sudo apt-get -y install qemu-user-static binfmt-support qemu bmap-tools
|
||||
- sudo update-binfmts --display
|
||||
- unset GOROOT
|
||||
script:
|
||||
- sudo ./scripts/create_sibling.sh -n pwnagotchi -o pwnagotchi.img -s 4
|
||||
- sudo ./scripts/create_sibling.sh -n pwnagotchi -o pwnagotchi.img
|
||||
- zip -s 2g pwnagotchi.zip pwnagotchi.img
|
||||
|
||||
# TODO: deploy!
|
||||
|
38
README.md
38
README.md
@ -80,6 +80,23 @@ usage: ./scripts/create_sibling.sh [OPTIONS]
|
||||
|
||||
If you connect to the unit via `usb0` (thus using the data port), you might want to use the `scripts/linux_connection_share.sh` script to bring the interface up on your end and share internet connectivity from another interface, so you can update the unit and generally download things from the internet on it.
|
||||
|
||||
#### Update your pwnagotchi
|
||||
|
||||
You can use the `scripts/update_pwnagotchi.sh` script to update to the most recent version of pwnagotchi.
|
||||
|
||||
```shell
|
||||
usage: ./update_pwnagitchi.sh [OPTIONS]
|
||||
|
||||
Options:
|
||||
-v # Version to update to, can be a branch or commit. (default: master)
|
||||
-u # Url to clone from. (default: https://github.com/evilsocket/pwnagotchi)
|
||||
-m # Mode to restart to. (Supported: auto manual; default: auto)
|
||||
-b # Backup the current pwnagotchi config.
|
||||
-r # Restore the current pwnagotchi config. -b will be enabled.
|
||||
-h # Shows this help. Shows this help.
|
||||
|
||||
```
|
||||
|
||||
### UI
|
||||
|
||||
The UI is available either via display if installed, or via http://pwnagotchi.local:8080/ if you connect to the unit via `usb0` and set a static address on the network interface (change `pwnagotchi` with the hostname of your unit).
|
||||
@ -98,9 +115,12 @@ Pwnagotchi is able to speak multiple languages!! Currently supported are:
|
||||
|
||||
* **english** (default)
|
||||
* german
|
||||
* dutch
|
||||
* greek
|
||||
* macedonian
|
||||
* italian
|
||||
|
||||
If you want to add a language use the `language.sh` script.
|
||||
If you want to add for example the language **italian** you would type:
|
||||
If you want to add a language use the `language.sh` script. If you want to add for example the language **italian** you would type:
|
||||
|
||||
```shell
|
||||
./scripts/language.sh add it
|
||||
@ -120,6 +140,20 @@ If you changed the `voice.py`- File, the translations need an update. Do it like
|
||||
# DONE
|
||||
```
|
||||
|
||||
Now you can use the `preview.py`-script to preview the changes:
|
||||
|
||||
```shell
|
||||
./scripts/preview.py --lang it --display ws2 --port 8080 &
|
||||
./scripts/preview.py --lang it --display inky --port 8081 &
|
||||
# Now open http://localhost:8080 and http://localhost:8081
|
||||
```
|
||||
|
||||
### Plugins
|
||||
|
||||
Pwnagotchi has a simple plugins system that you can use to customize your unit and its behaviour. You can place your plugins anywhere
|
||||
as python files and then edit the `config.yml` file (`main.plugins` value) to point to their containing folder. Check the [plugins folder](https://github.com/evilsocket/pwnagotchi/tree/master/sdcard/rootfs/root/pwnagotchi/scripts/pwnagotchi/plugins/default/) for a list of default
|
||||
plugins and all the callbacks that you can define for your own customizations.
|
||||
|
||||
### Random Info
|
||||
|
||||
- `hostname` sets the unit name.
|
||||
|
@ -5,18 +5,22 @@
|
||||
set -eu
|
||||
|
||||
REQUIREMENTS=( wget gunzip git dd e2fsck resize2fs parted losetup qemu-system-x86_64 )
|
||||
DEBREQUIREMENTS=( wget gzip git parted qemu-system-x86 qemu-user-static bmap-tools )
|
||||
REPO_DIR="$(dirname "$(dirname "$(realpath "$0")")")"
|
||||
TMP_DIR="${REPO_DIR}/tmp"
|
||||
MNT_DIR="${TMP_DIR}/mnt"
|
||||
THIS_DIR=$(pwd)
|
||||
|
||||
PWNI_NAME="pwnagotchi"
|
||||
PWNI_OUTPUT="pwnagotchi.img"
|
||||
PWNI_SIZE="4"
|
||||
PWNI_SIZE="8"
|
||||
|
||||
OPT_SPARSE=0
|
||||
OPT_PROVISION_ONLY=0
|
||||
OPT_CHECK_DEPS_ONLY=0
|
||||
OPT_IMAGE_PROVIDED=0
|
||||
OPT_RASPBIAN_VERSION='latest'
|
||||
OPT_APTPROXY=""
|
||||
|
||||
SUPPORTED_RASPBIAN_VERSIONS=( 'latest' 'buster' 'stretch' )
|
||||
|
||||
@ -26,6 +30,18 @@ if [[ "$EUID" -ne 0 ]]; then
|
||||
fi
|
||||
|
||||
function check_dependencies() {
|
||||
if [ -f /etc/debian_version ];
|
||||
then
|
||||
echo "[+] Checking Debian dependencies"
|
||||
|
||||
for REQ in "${DEBREQUIREMENTS[@]}"; do
|
||||
if ! dpkg -s "$REQ" >/dev/null 2>&1; then
|
||||
echo "Dependency check failed for ${REQ}; use 'apt-get install ${REQ}' to install"
|
||||
exit 1
|
||||
fi
|
||||
done
|
||||
fi
|
||||
|
||||
echo "[+] Checking dependencies"
|
||||
for REQ in "${REQUIREMENTS[@]}"; do
|
||||
if ! type "$REQ" >/dev/null 2>&1; then
|
||||
@ -76,8 +92,31 @@ function provide_raspbian() {
|
||||
}
|
||||
|
||||
function setup_raspbian(){
|
||||
echo "[+] Resize image"
|
||||
dd if=/dev/zero bs=1G count="$PWNI_SIZE" >> "${TMP_DIR}/raspbian.img"
|
||||
# Detect the ability to create sparse files
|
||||
if [ "${OPT_SPARSE}" -eq 0 ];
|
||||
then
|
||||
which bmaptool >/dev/null 2>&1
|
||||
if [ $? -eq 0 ];
|
||||
then
|
||||
echo "[+] Defaulting to sparse image generation as bmaptool is available"
|
||||
OPT_SPARSE=1
|
||||
else
|
||||
echo "[!] bmaptool not available, not creating a sparse image"
|
||||
fi
|
||||
fi
|
||||
|
||||
# Note that we 'extend' the raspbian.img
|
||||
if [ "${OPT_SPARSE}" -eq 1 ];
|
||||
then
|
||||
# Resize sparse (so that we can use bmaptool later)
|
||||
echo "[+] Resizing sparse image of ${PWNI_SIZE}GB (1000s)"
|
||||
truncate -s ${PWNI_SIZE}GB "${TMP_DIR}/raspbian.img"
|
||||
else
|
||||
echo "[+] Resizing full image to ${PWNI_SIZE}G"
|
||||
# Full disk-space using image (appends to raspbian image)
|
||||
dd if=/dev/zero bs=1G count="${PWNI_SIZE}" >> "${TMP_DIR}/raspbian.img"
|
||||
fi
|
||||
|
||||
echo "[+] Setup loop device"
|
||||
mkdir -p "${MNT_DIR}"
|
||||
LOOP_PATH="$(losetup --find --partscan --show "${TMP_DIR}/raspbian.img")"
|
||||
@ -107,10 +146,16 @@ function provision_raspbian() {
|
||||
cd "${MNT_DIR}"
|
||||
sed -i'' 's/^\([^#]\)/#\1/g' etc/ld.so.preload # add comments
|
||||
echo "[+] Run chroot commands"
|
||||
LANG=C chroot . bin/bash -x <<EOF
|
||||
LANG=C LC_ALL=C LC_CTYPE=C chroot . bin/bash -x <<EOF
|
||||
set -eu
|
||||
export PATH="/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin"
|
||||
|
||||
if [ ! -z "${OPT_APTPROXY}" ];
|
||||
then
|
||||
echo "[+] Using Proxy ${OPT_APTPROXY}"
|
||||
echo "Acquire::http { Proxy \"${OPT_APTPROXY}\"; }" >/etc/apt/apt.conf.d/99pwnagotchi_proxy
|
||||
fi
|
||||
|
||||
uname -a
|
||||
|
||||
apt-get -y update
|
||||
@ -160,7 +205,7 @@ function provision_raspbian() {
|
||||
|
||||
# install bettercap
|
||||
export GOPATH=/root/go
|
||||
go get -u github.com/bettercap/bettercap
|
||||
taskset -c 1 go get -u github.com/bettercap/bettercap
|
||||
mv "\$GOPATH/bin/bettercap" /usr/bin/bettercap
|
||||
|
||||
# install bettercap caplets (cant run bettercap in chroot)
|
||||
@ -203,7 +248,11 @@ EOF
|
||||
cd "${REPO_DIR}"
|
||||
umount -R "${MNT_DIR}"
|
||||
losetup -D "$(losetup -l | awk '/raspbian\.img/{print $1}')"
|
||||
mv "${TMP_DIR}/raspbian.img" "$PWNI_OUTPUT"
|
||||
mv "${TMP_DIR}/raspbian.img" "${PWNI_OUTPUT}"
|
||||
if [ "${OPT_SPARSE}" -eq 1 ];
|
||||
then
|
||||
bmaptool create -o "${PWNI_OUTPUT}.bmap" "${PWNI_OUTPUT}"
|
||||
fi
|
||||
}
|
||||
|
||||
function usage() {
|
||||
@ -226,8 +275,11 @@ EOF
|
||||
exit 0
|
||||
}
|
||||
|
||||
while getopts ":n:i:o:s:v:dph" o; do
|
||||
while getopts "A:n:i:o:s:v:dph" o; do
|
||||
case "${o}" in
|
||||
A)
|
||||
OPT_APTPROXY="${OPTARG}"
|
||||
;;
|
||||
n)
|
||||
PWNI_NAME="${OPTARG}"
|
||||
;;
|
||||
@ -283,4 +335,14 @@ fi
|
||||
setup_raspbian
|
||||
provision_raspbian
|
||||
|
||||
echo -ne "[+] Congratz, it's a boy (⌐■_■)!\n[+] One more step: dd if=$PWNI_OUTPUT of=<PATH_TO_SDCARD> bs=4M status=progress"
|
||||
echo -e "[+] Congratz, it's a boy (⌐■_■)!"
|
||||
echo -e "[+] One more step: dd if=../${PWNI_OUTPUT} of=<PATH_TO_SDCARD> bs=4M status=progress"
|
||||
|
||||
if [ "${OPT_SPARSE}" -eq 1 ];
|
||||
then
|
||||
echo -e "[t] To transfer use: rsync -vaS --progress $(whoami)@$(hostname -f):${THIS_DIR}/../${PWNI_OUTPUT} <DEST>"
|
||||
echo -e "[t] To burn with bmaptool: bmaptool copy ~/${PWNI_OUTPUT} /dev/<DEVICE>"
|
||||
fi
|
||||
|
||||
# Helpful OSX reminder
|
||||
echo -e "[t] Mac: use 'diskutil list' to figure out which device to burn to; 'diskutil unmountDisk' to unmount that disk'; then use /dev/rdiskX (note the 'r') for faster transfer"
|
||||
|
@ -55,6 +55,7 @@ function update_lang() {
|
||||
msgmerge --update "$LOCALE_DIR/$1/LC_MESSAGES/voice.po" "$LOCALE_DIR/voice.pot"
|
||||
}
|
||||
|
||||
|
||||
case "$1" in
|
||||
add)
|
||||
add_lang "$2"
|
||||
|
10
scripts/macos_connection_share.sh
Executable file
10
scripts/macos_connection_share.sh
Executable file
@ -0,0 +1,10 @@
|
||||
#!/usr/bin/env bash
|
||||
|
||||
# name of the ethernet gadget interface on the host
|
||||
USB_IFACE=${1:-en8}
|
||||
# host interface to use for upstream connection
|
||||
UPSTREAM_IFACE=${2:-en7}
|
||||
|
||||
sysctl -w net.inet.ip.forwarding=1
|
||||
pfctl -e
|
||||
echo "nat on ${UPSTREAM_IFACE} from ${USB_IFACE}:network to any -> (${UPSTREAM_IFACE})" | pfctl -f -
|
171
scripts/preview.py
Executable file
171
scripts/preview.py
Executable file
@ -0,0 +1,171 @@
|
||||
#!/usr/bin/env python3
|
||||
|
||||
import sys
|
||||
import os
|
||||
import time
|
||||
import argparse
|
||||
from http.server import HTTPServer
|
||||
import shutil
|
||||
import yaml
|
||||
|
||||
sys.path.insert(0,
|
||||
os.path.join(os.path.dirname(os.path.realpath(__file__)),
|
||||
'../sdcard/rootfs/root/pwnagotchi/scripts/'))
|
||||
|
||||
from pwnagotchi.ui.display import Display, VideoHandler
|
||||
import core
|
||||
|
||||
|
||||
class CustomDisplay(Display):
|
||||
|
||||
def _http_serve(self):
|
||||
if self._video_address is not None:
|
||||
self._httpd = HTTPServer((self._video_address, self._video_port),
|
||||
CustomVideoHandler)
|
||||
core.log("ui available at http://%s:%d/" % (self._video_address,
|
||||
self._video_port))
|
||||
self._httpd.serve_forever()
|
||||
else:
|
||||
core.log("could not get ip of usb0, video server not starting")
|
||||
|
||||
def _on_view_rendered(self, img):
|
||||
CustomVideoHandler.render(img)
|
||||
|
||||
if self._enabled:
|
||||
self.canvas = (img if self._rotation == 0 else img.rotate(self._rotation))
|
||||
if self._render_cb is not None:
|
||||
self._render_cb()
|
||||
|
||||
|
||||
class CustomVideoHandler(VideoHandler):
|
||||
|
||||
@staticmethod
|
||||
def render(img):
|
||||
with CustomVideoHandler._lock:
|
||||
try:
|
||||
img.save("/tmp/pwnagotchi-{rand}.png".format(rand=id(CustomVideoHandler)), format='PNG')
|
||||
except BaseException:
|
||||
core.log("could not write preview")
|
||||
|
||||
def do_GET(self):
|
||||
if self.path == '/':
|
||||
self.send_response(200)
|
||||
self.send_header('Content-type', 'text/html')
|
||||
self.end_headers()
|
||||
try:
|
||||
self.wfile.write(
|
||||
bytes(
|
||||
self._index %
|
||||
('localhost', 1000), "utf8"))
|
||||
except BaseException:
|
||||
pass
|
||||
|
||||
elif self.path.startswith('/ui'):
|
||||
with self._lock:
|
||||
self.send_response(200)
|
||||
self.send_header('Content-type', 'image/png')
|
||||
self.end_headers()
|
||||
try:
|
||||
with open("/tmp/pwnagotchi-{rand}.png".format(rand=id(CustomVideoHandler)), 'rb') as fp:
|
||||
shutil.copyfileobj(fp, self.wfile)
|
||||
except BaseException:
|
||||
core.log("could not open preview")
|
||||
else:
|
||||
self.send_response(404)
|
||||
|
||||
|
||||
class DummyPeer:
|
||||
@staticmethod
|
||||
def name():
|
||||
return "beta"
|
||||
|
||||
|
||||
def main():
|
||||
parser = argparse.ArgumentParser(description="This program emulates\
|
||||
the pwnagotchi display")
|
||||
parser.add_argument('--display', help="Which display to use.",
|
||||
default="waveshare_2")
|
||||
parser.add_argument('--port', help="Which port to use",
|
||||
default=8080)
|
||||
parser.add_argument('--sleep', type=int, help="Time between emotions",
|
||||
default=2)
|
||||
parser.add_argument('--lang', help="Language to use",
|
||||
default="en")
|
||||
args = parser.parse_args()
|
||||
|
||||
CONFIG = yaml.load('''
|
||||
main:
|
||||
lang: {lang}
|
||||
ui:
|
||||
fps: 0.3
|
||||
display:
|
||||
enabled: false
|
||||
rotation: 180
|
||||
color: black
|
||||
refresh: 30
|
||||
type: {display}
|
||||
video:
|
||||
enabled: true
|
||||
address: "0.0.0.0"
|
||||
port: {port}
|
||||
'''.format(display=args.display,
|
||||
port=args.port,
|
||||
lang=args.lang))
|
||||
|
||||
DISPLAY = CustomDisplay(config=CONFIG, state={'name': '%s>' % 'preview'})
|
||||
|
||||
while True:
|
||||
DISPLAY.on_starting()
|
||||
DISPLAY.update()
|
||||
time.sleep(args.sleep)
|
||||
DISPLAY.on_ai_ready()
|
||||
DISPLAY.update()
|
||||
time.sleep(args.sleep)
|
||||
DISPLAY.on_normal()
|
||||
DISPLAY.update()
|
||||
time.sleep(args.sleep)
|
||||
DISPLAY.on_new_peer(DummyPeer())
|
||||
DISPLAY.update()
|
||||
time.sleep(args.sleep)
|
||||
DISPLAY.on_lost_peer(DummyPeer())
|
||||
DISPLAY.update()
|
||||
time.sleep(args.sleep)
|
||||
DISPLAY.on_free_channel('6')
|
||||
DISPLAY.update()
|
||||
time.sleep(args.sleep)
|
||||
DISPLAY.wait(args.sleep)
|
||||
DISPLAY.update()
|
||||
DISPLAY.on_bored()
|
||||
DISPLAY.update()
|
||||
time.sleep(args.sleep)
|
||||
DISPLAY.on_sad()
|
||||
DISPLAY.update()
|
||||
time.sleep(args.sleep)
|
||||
DISPLAY.on_motivated(1)
|
||||
DISPLAY.update()
|
||||
time.sleep(args.sleep)
|
||||
DISPLAY.on_demotivated(-1)
|
||||
DISPLAY.update()
|
||||
time.sleep(args.sleep)
|
||||
DISPLAY.on_excited()
|
||||
DISPLAY.update()
|
||||
time.sleep(args.sleep)
|
||||
DISPLAY.on_deauth({'mac': 'DE:AD:BE:EF:CA:FE'})
|
||||
DISPLAY.update()
|
||||
time.sleep(args.sleep)
|
||||
DISPLAY.on_miss('test')
|
||||
DISPLAY.update()
|
||||
time.sleep(args.sleep)
|
||||
DISPLAY.on_lonely()
|
||||
DISPLAY.update()
|
||||
time.sleep(args.sleep)
|
||||
DISPLAY.on_handshakes(1)
|
||||
DISPLAY.update()
|
||||
time.sleep(args.sleep)
|
||||
DISPLAY.on_rebooting()
|
||||
DISPLAY.update()
|
||||
time.sleep(args.sleep)
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
SystemExit(main())
|
111
scripts/update_pwnagotchi.sh
Normal file
111
scripts/update_pwnagotchi.sh
Normal file
@ -0,0 +1,111 @@
|
||||
#!/bin/bash
|
||||
# Default variables
|
||||
GIT_FOLDER="/tmp/pwnagotchi"
|
||||
GIT_URL="https://github.com/evilsocket/pwnagotchi/"
|
||||
VERSION="master"
|
||||
SUPPORTED_RESTART_MODES=( 'auto' 'manual' )
|
||||
MODE="auto"
|
||||
BACKUPCONFIG=0
|
||||
RESTORECONFIG=0
|
||||
|
||||
# Functions
|
||||
function usage() {
|
||||
cat <<EOF
|
||||
|
||||
usage: $0 [OPTIONS]
|
||||
|
||||
Options:
|
||||
-v # Version to update to, can be a branch or commit. (default: master)
|
||||
-u # Url to clone from. (default: https://github.com/evilsocket/pwnagotchi)
|
||||
-m # Mode to restart to. (Supported: ${SUPPORTED_RESTART_MODES[*]}; default: auto)
|
||||
-b # Backup the current pwnagotchi config.
|
||||
-r # Restore the current pwnagotchi config. (-b will be enabled.)
|
||||
-h # Shows this help.
|
||||
|
||||
EOF
|
||||
exit 0
|
||||
}
|
||||
|
||||
function test_root() {
|
||||
if ! [ $(id -u) = 0 ]; then
|
||||
echo "[!] This script must be run as root."
|
||||
exit 1
|
||||
fi
|
||||
}
|
||||
|
||||
function test_github() {
|
||||
wget -q --spider $GIT_URL
|
||||
if [ $? -ne 0 ]; then
|
||||
echo "[!] Cannot reach github. This script requires internet access, ensure connection sharing is working."
|
||||
exit 2
|
||||
fi
|
||||
}
|
||||
|
||||
echo "[+] Checking prerequisites."
|
||||
test_root
|
||||
test_github
|
||||
|
||||
while getopts ":v:u:m:b:r:h" o; do
|
||||
case "${o}" in
|
||||
v)
|
||||
VERSION="${OPTARG}"
|
||||
;;
|
||||
u)
|
||||
GIT_URL="${OPTARG}"
|
||||
;;
|
||||
m)
|
||||
if [[ "${SUPPORTED_RESTART_MODES[*]}" =~ ${OPTARG} ]]; then
|
||||
MODE="${OPTARG}"
|
||||
else
|
||||
usage
|
||||
fi
|
||||
;;
|
||||
b)
|
||||
BACKUPCONFIG=1
|
||||
;;
|
||||
r)
|
||||
BACKUPCONFIG=1
|
||||
RESTORECONFIG=1
|
||||
;;
|
||||
h)
|
||||
usage
|
||||
;;
|
||||
*)
|
||||
usage
|
||||
;;
|
||||
esac
|
||||
done
|
||||
shift $((OPTIND-1))
|
||||
|
||||
# clean up old files, clone master, set checkout to commit if needed.
|
||||
echo "[+] Cloning to $GIT_FOLDER..."
|
||||
rm $GIT_FOLDER -rf
|
||||
git clone $GIT_URL $GIT_FOLDER -q
|
||||
cd $GIT_FOLDER
|
||||
if [ $VERSION != "master" ]; then
|
||||
git checkout $VERSION -q
|
||||
fi
|
||||
echo "[+] Installing $(git log -1 --format="%h")"
|
||||
|
||||
echo "[+] Updating..."
|
||||
if [ $BACKUPCONFIG -eq 1 ]; then
|
||||
echo "[+] Creating backup of config.yml"
|
||||
mv /root/pwnagotchi/config.yml /root/config.yml.bak -f
|
||||
fi
|
||||
rm /root/pwnagotchi -rf # ensures old files are removed
|
||||
rsync -aPq $GIT_FOLDER/sdcard/boot/* /boot/
|
||||
rsync -aPq $GIT_FOLDER/sdcard/rootfs/* /
|
||||
cd /tmp
|
||||
rm $GIT_FOLDER -rf
|
||||
if [ $RESTORECONFIG -eq 1 ]; then
|
||||
echo "[+] Restoring backup of config.yml"
|
||||
mv /root/config.yml.bak /root/pwnagotchi/config.yml -f
|
||||
fi
|
||||
|
||||
echo "[+] Restarting pwnagotchi in $MODE mode. $( screen -X -S pwnagotchi quit)"
|
||||
if [ $MODE == "auto" ]; then
|
||||
sudo -H -u root /usr/bin/screen -dmS pwnagotchi -c /root/pwnagotchi/data/screenrc.auto
|
||||
elif [ $MODE == "manual" ]; then
|
||||
sudo -H -u root /usr/bin/screen -dmS pwnagotchi -c /root/pwnagotchi/data/screenrc.manual
|
||||
fi
|
||||
echo "[+] Finished"
|
@ -1,7 +1,9 @@
|
||||
# main algorithm configuration
|
||||
main:
|
||||
# currently implemented: en (default), de
|
||||
# currently implemented: en (default), de, nl, it
|
||||
lang: en
|
||||
# custom plugins path, if null only default plugins with be loaded
|
||||
plugins: null
|
||||
# monitor interface to use
|
||||
iface: mon0
|
||||
# command to run to bring the mon interface up in case it's not up already
|
||||
@ -21,7 +23,7 @@ main:
|
||||
|
||||
ai:
|
||||
# if false, only the default 'personality' will be used
|
||||
enabled: true
|
||||
enabled: false
|
||||
path: /root/brain.nn
|
||||
# 1.0 - laziness = probability of start training
|
||||
laziness: 0.1
|
||||
@ -98,8 +100,6 @@ ui:
|
||||
type: 'waveshare_2'
|
||||
# Possible options red/yellow/black (black used for monocromatic displays)
|
||||
color: 'black'
|
||||
# How often to do a full refresh 0 all the time, -1 never
|
||||
refresh: 50
|
||||
video:
|
||||
enabled: true
|
||||
address: '10.0.0.2'
|
||||
|
@ -10,8 +10,3 @@ done
|
||||
|
||||
echo 0 >/sys/class/leds/led0/brightness
|
||||
sleep 0.3
|
||||
|
||||
# Powersave options
|
||||
# Disable power LED ~30ma
|
||||
echo none >/sys/class/leds/led0/trigger
|
||||
echo 1 >/sys/class/leds/led0/brightness
|
||||
|
@ -5,7 +5,7 @@ import time
|
||||
import traceback
|
||||
|
||||
import core
|
||||
import pwnagotchi
|
||||
import pwnagotchi, pwnagotchi.plugins as plugins
|
||||
|
||||
from pwnagotchi.log import SessionParser
|
||||
from pwnagotchi.voice import Voice
|
||||
@ -24,16 +24,50 @@ args = parser.parse_args()
|
||||
|
||||
if args.do_clear:
|
||||
print("clearing the display ...")
|
||||
from pwnagotchi.ui.waveshare import EPD
|
||||
with open(args.config, 'rt') as fp:
|
||||
config = yaml.safe_load(fp)
|
||||
cleardisplay = config['ui']['display']['type']
|
||||
if cleardisplay in ('inkyphat', 'inky'):
|
||||
print("inky display")
|
||||
from inky import InkyPHAT
|
||||
|
||||
epd = EPD()
|
||||
epd.init(epd.FULL_UPDATE)
|
||||
epd.Clear(0xff)
|
||||
quit()
|
||||
epd = InkyPHAT(config['ui']['display']['color'])
|
||||
epd.set_border(InkyPHAT.BLACK)
|
||||
self._render_cb = self._inky_render
|
||||
elif cleardisplay in ('papirus', 'papi'):
|
||||
print("papirus display")
|
||||
from pwnagotchi.ui.papirus.epd import EPD
|
||||
|
||||
os.environ['EPD_SIZE'] = '2.0'
|
||||
epd = EPD()
|
||||
epd.clear()
|
||||
elif cleardisplay in ('waveshare_1', 'ws_1', 'waveshare1', 'ws1'):
|
||||
print("waveshare v1 display")
|
||||
from pwnagotchi.ui.waveshare.v1.epd2in13 import EPD
|
||||
|
||||
epd = EPD()
|
||||
epd.init(epd.lut_full_update)
|
||||
epd.Clear(0xFF)
|
||||
elif cleardisplay in ('waveshare_2', 'ws_2', 'waveshare2', 'ws2'):
|
||||
print("waveshare v2 display")
|
||||
from pwnagotchi.ui.waveshare.v2.waveshare import EPD
|
||||
|
||||
epd = EPD()
|
||||
epd.init(epd.FULL_UPDATE)
|
||||
epd.Clear(0xff)
|
||||
else:
|
||||
print("unknown display type %s" % cleardisplay)
|
||||
quit()
|
||||
|
||||
with open(args.config, 'rt') as fp:
|
||||
config = yaml.safe_load(fp)
|
||||
|
||||
plugins.load_from_path(plugins.default_path)
|
||||
if 'plugins' in config['main'] and config['main']['plugins'] is not None:
|
||||
plugins.load_from_path(config['main']['plugins'])
|
||||
|
||||
plugins.on('loaded')
|
||||
|
||||
display = Display(config=config, state={'name': '%s>' % pwnagotchi.name()})
|
||||
agent = Agent(view=display, config=config)
|
||||
|
||||
@ -41,6 +75,9 @@ core.log("%s@%s (v%s)" % (pwnagotchi.name(), agent._identity, pwnagotchi.version
|
||||
# for key, value in config['personality'].items():
|
||||
# core.log(" %s: %s" % (key, value))
|
||||
|
||||
for _, plugin in plugins.loaded.items():
|
||||
core.log("plugin '%s' v%s loaded from %s" % (plugin.__name__, plugin.__version__, plugin.__file__))
|
||||
|
||||
if args.do_manual:
|
||||
core.log("entering manual mode ...")
|
||||
|
||||
@ -88,13 +125,15 @@ core.logfile = config['main']['log']
|
||||
|
||||
agent.start_ai()
|
||||
agent.setup_events()
|
||||
agent.set_ready()
|
||||
agent.set_starting()
|
||||
agent.start_monitor_mode()
|
||||
agent.start_event_polling()
|
||||
|
||||
# print initial stats
|
||||
agent.next_epoch()
|
||||
|
||||
agent.set_ready()
|
||||
|
||||
while True:
|
||||
try:
|
||||
# recon on all channels
|
||||
|
@ -8,8 +8,9 @@ import _thread
|
||||
|
||||
import core
|
||||
|
||||
import pwnagotchi.plugins as plugins
|
||||
from bettercap.client import Client
|
||||
from pwnagotchi.mesh.advertise import AsyncAdvertiser
|
||||
from pwnagotchi.mesh.utils import AsyncAdvertiser
|
||||
from pwnagotchi.ai.train import AsyncTrainer
|
||||
|
||||
RECOVERY_DATA_FILE = '/root/.pwnagotchi-recovery'
|
||||
@ -44,32 +45,41 @@ class Agent(Client, AsyncAdvertiser, AsyncTrainer):
|
||||
pass
|
||||
return False
|
||||
|
||||
def config(self):
|
||||
return self._config
|
||||
|
||||
def supported_channels(self):
|
||||
return self._supported_channels
|
||||
|
||||
def on_ai_ready(self):
|
||||
self._view.on_ai_ready()
|
||||
def set_starting(self):
|
||||
self._view.on_starting()
|
||||
|
||||
def set_ready(self):
|
||||
self._view.on_starting()
|
||||
plugins.on('ready', self)
|
||||
|
||||
def set_free_channel(self, channel):
|
||||
self._view.on_free_channel(channel)
|
||||
plugins.on('free_channel', self, channel)
|
||||
|
||||
def set_bored(self):
|
||||
self._view.on_bored()
|
||||
plugins.on('bored', self)
|
||||
|
||||
def set_sad(self):
|
||||
self._view.on_sad()
|
||||
plugins.on('sad', self)
|
||||
|
||||
def set_excited(self):
|
||||
self._view.on_excited()
|
||||
plugins.on('excited', self)
|
||||
|
||||
def set_lonely(self):
|
||||
self._view.on_lonely()
|
||||
plugins.on('lonely', self)
|
||||
|
||||
def set_rebooting(self):
|
||||
self._view.on_rebooting()
|
||||
plugins.on('rebooting', self)
|
||||
|
||||
def setup_events(self):
|
||||
core.log("connecting to %s ..." % self.url)
|
||||
@ -128,6 +138,7 @@ class Agent(Client, AsyncAdvertiser, AsyncTrainer):
|
||||
self.start_advertising()
|
||||
|
||||
def wait_for(self, t, sleeping=True):
|
||||
plugins.on('sleep' if sleeping else 'wait', self, t)
|
||||
self._view.wait(t, sleeping)
|
||||
self._epoch.track(sleep=True, inc=t)
|
||||
|
||||
@ -179,6 +190,7 @@ class Agent(Client, AsyncAdvertiser, AsyncTrainer):
|
||||
|
||||
def set_access_points(self, aps):
|
||||
self._access_points = aps
|
||||
plugins.on('wifi_update', self, aps)
|
||||
self._epoch.observe(aps, self._advertiser.peers() if self._advertiser is not None else ())
|
||||
return self._access_points
|
||||
|
||||
@ -327,6 +339,7 @@ class Agent(Client, AsyncAdvertiser, AsyncTrainer):
|
||||
|
||||
try:
|
||||
for h in [e for e in self.events() if e['tag'] == 'wifi.client.handshake']:
|
||||
filename = h['data']['file']
|
||||
sta_mac = h['data']['station']
|
||||
ap_mac = h['data']['ap']
|
||||
key = "%s -> %s" % (sta_mac, ap_mac)
|
||||
@ -338,6 +351,7 @@ class Agent(Client, AsyncAdvertiser, AsyncTrainer):
|
||||
if apsta is None:
|
||||
core.log("!!! captured new handshake: %s !!!" % key)
|
||||
self._last_pwnd = ap_mac
|
||||
plugins.on('handshake', self, filename, ap_mac, sta_mac)
|
||||
else:
|
||||
(ap, sta) = apsta
|
||||
self._last_pwnd = ap['hostname'] if ap['hostname'] != '' and ap[
|
||||
@ -346,6 +360,7 @@ class Agent(Client, AsyncAdvertiser, AsyncTrainer):
|
||||
ap['channel'],
|
||||
sta['mac'], sta['vendor'],
|
||||
ap['hostname'], ap['mac'], ap['vendor']))
|
||||
plugins.on('handshake', self, filename, ap, sta)
|
||||
|
||||
except Exception as e:
|
||||
core.log("error: %s" % e)
|
||||
@ -419,6 +434,7 @@ class Agent(Client, AsyncAdvertiser, AsyncTrainer):
|
||||
except Exception as e:
|
||||
self._on_error(ap['mac'], e)
|
||||
|
||||
plugins.on('association', self, ap)
|
||||
if throttle > 0:
|
||||
time.sleep(throttle)
|
||||
self._view.on_normal()
|
||||
@ -439,6 +455,7 @@ class Agent(Client, AsyncAdvertiser, AsyncTrainer):
|
||||
except Exception as e:
|
||||
self._on_error(sta['mac'], e)
|
||||
|
||||
plugins.on('deauthentication', self, ap, sta)
|
||||
if throttle > 0:
|
||||
time.sleep(throttle)
|
||||
self._view.on_normal()
|
||||
@ -470,6 +487,9 @@ class Agent(Client, AsyncAdvertiser, AsyncTrainer):
|
||||
self._current_channel = channel
|
||||
self._epoch.track(hop=True)
|
||||
self._view.set('channel', '%d' % channel)
|
||||
|
||||
plugins.on('channel_hop', self, channel)
|
||||
|
||||
except Exception as e:
|
||||
core.log("error: %s" % e)
|
||||
|
||||
@ -509,6 +529,8 @@ class Agent(Client, AsyncAdvertiser, AsyncTrainer):
|
||||
core.log("%d epochs with activity -> excited" % self._epoch.active_for)
|
||||
self.set_excited()
|
||||
|
||||
plugins.on('epoch', self, self._epoch.epoch - 1, self._epoch.data())
|
||||
|
||||
if self._epoch.blind_for >= self._config['main']['mon_max_blind_epochs']:
|
||||
core.log("%d epochs without visible access points -> rebooting ..." % self._epoch.blind_for)
|
||||
self._reboot()
|
||||
|
@ -7,6 +7,7 @@ import pwnagotchi.mesh.wifi as wifi
|
||||
|
||||
from pwnagotchi.ai.reward import RewardFunction
|
||||
|
||||
|
||||
class Epoch(object):
|
||||
def __init__(self, config):
|
||||
self.epoch = 0
|
||||
@ -92,7 +93,8 @@ class Epoch(object):
|
||||
try:
|
||||
peers_per_chan[peer.last_channel - 1] += 1.0
|
||||
except IndexError as e:
|
||||
core.log("got peer data on channel %d, we can store %d channels" % (peer.last_channel, wifi.NumChannels))
|
||||
core.log(
|
||||
"got peer data on channel %d, we can store %d channels" % (peer.last_channel, wifi.NumChannels))
|
||||
|
||||
# normalize
|
||||
aps_per_chan = [e / num_aps for e in aps_per_chan]
|
||||
|
@ -7,6 +7,7 @@ import json
|
||||
|
||||
import core
|
||||
|
||||
import pwnagotchi.plugins as plugins
|
||||
import pwnagotchi.ai as ai
|
||||
from pwnagotchi.ai.epoch import Epoch
|
||||
|
||||
@ -66,16 +67,22 @@ class Stats(object):
|
||||
def save(self):
|
||||
with self._lock:
|
||||
core.log("[ai] saving %s" % self.path)
|
||||
with open(self.path, 'wt') as fp:
|
||||
json.dump({
|
||||
'born_at': self.born_at,
|
||||
'epochs_lived': self.epochs_lived,
|
||||
'epochs_trained': self.epochs_trained,
|
||||
'rewards': {
|
||||
'best': self.best_reward,
|
||||
'worst': self.worst_reward
|
||||
}
|
||||
}, fp)
|
||||
|
||||
data = json.dumps({
|
||||
'born_at': self.born_at,
|
||||
'epochs_lived': self.epochs_lived,
|
||||
'epochs_trained': self.epochs_trained,
|
||||
'rewards': {
|
||||
'best': self.best_reward,
|
||||
'worst': self.worst_reward
|
||||
}
|
||||
})
|
||||
|
||||
temp = "%s.tmp" % self.path
|
||||
with open(temp, 'wt') as fp:
|
||||
fp.write(data)
|
||||
|
||||
os.replace(temp, self.path)
|
||||
|
||||
|
||||
class AsyncTrainer(object):
|
||||
@ -92,6 +99,11 @@ class AsyncTrainer(object):
|
||||
self._is_training = training
|
||||
self._training_epochs = for_epochs
|
||||
|
||||
if training:
|
||||
plugins.on('ai_training_start', self, for_epochs)
|
||||
else:
|
||||
plugins.on('ai_training_end', self)
|
||||
|
||||
def is_training(self):
|
||||
return self._is_training
|
||||
|
||||
@ -103,7 +115,9 @@ class AsyncTrainer(object):
|
||||
|
||||
def _save_ai(self):
|
||||
core.log("[ai] saving model to %s ..." % self._nn_path)
|
||||
self._model.save(self._nn_path)
|
||||
temp = "%s.tmp" % self._nn_path
|
||||
self._model.save(temp)
|
||||
os.replace(temp, self._nn_path)
|
||||
|
||||
def on_ai_step(self):
|
||||
self._model.env.render()
|
||||
@ -115,8 +129,10 @@ class AsyncTrainer(object):
|
||||
|
||||
def on_ai_training_step(self, _locals, _globals):
|
||||
self._model.env.render()
|
||||
plugins.on('ai_training_step', self, _locals, _globals)
|
||||
|
||||
def on_ai_policy(self, new_params):
|
||||
plugins.on('ai_policy', self, new_params)
|
||||
core.log("[ai] setting new policy:")
|
||||
for name, value in new_params.items():
|
||||
if name in self._config['personality']:
|
||||
@ -131,39 +147,46 @@ class AsyncTrainer(object):
|
||||
self.run('set wifi.sta.ttl %d' % self._config['personality']['sta_ttl'])
|
||||
self.run('set wifi.rssi.min %d' % self._config['personality']['min_rssi'])
|
||||
|
||||
def on_ai_ready(self):
|
||||
self._view.on_ai_ready()
|
||||
plugins.on('ai_ready', self)
|
||||
|
||||
def on_ai_best_reward(self, r):
|
||||
core.log("[ai] best reward so far: %s" % r)
|
||||
self._view.on_motivated(r)
|
||||
plugins.on('ai_best_reward', self, r)
|
||||
|
||||
def on_ai_worst_reward(self, r):
|
||||
core.log("[ai] worst reward so far: %s" % r)
|
||||
self._view.on_demotivated(r)
|
||||
plugins.on('ai_worst_reward', self, r)
|
||||
|
||||
def _ai_worker(self):
|
||||
self._model = ai.load(self._config, self, self._epoch)
|
||||
|
||||
self.on_ai_ready()
|
||||
if self._model:
|
||||
self.on_ai_ready()
|
||||
|
||||
epochs_per_episode = self._config['ai']['epochs_per_episode']
|
||||
epochs_per_episode = self._config['ai']['epochs_per_episode']
|
||||
|
||||
obs = None
|
||||
while True:
|
||||
self._model.env.render()
|
||||
# enter in training mode?
|
||||
if random.random() > self._config['ai']['laziness']:
|
||||
core.log("[ai] learning for %d epochs ..." % epochs_per_episode)
|
||||
try:
|
||||
self.set_training(True, epochs_per_episode)
|
||||
self._model.learn(total_timesteps=epochs_per_episode, callback=self.on_ai_training_step)
|
||||
except Exception as e:
|
||||
core.log("[ai] error while training: %s" % e)
|
||||
finally:
|
||||
self.set_training(False)
|
||||
obs = None
|
||||
while True:
|
||||
self._model.env.render()
|
||||
# enter in training mode?
|
||||
if random.random() > self._config['ai']['laziness']:
|
||||
core.log("[ai] learning for %d epochs ..." % epochs_per_episode)
|
||||
try:
|
||||
self.set_training(True, epochs_per_episode)
|
||||
self._model.learn(total_timesteps=epochs_per_episode, callback=self.on_ai_training_step)
|
||||
except Exception as e:
|
||||
core.log("[ai] error while training: %s" % e)
|
||||
finally:
|
||||
self.set_training(False)
|
||||
obs = self._model.env.reset()
|
||||
# init the first time
|
||||
elif obs is None:
|
||||
obs = self._model.env.reset()
|
||||
# init the first time
|
||||
elif obs is None:
|
||||
obs = self._model.env.reset()
|
||||
|
||||
# run the inference
|
||||
action, _ = self._model.predict(obs)
|
||||
obs, _, _, _ = self._model.env.step(action)
|
||||
# run the inference
|
||||
action, _ = self._model.predict(obs)
|
||||
obs, _, _, _ = self._model.env.step(action)
|
||||
|
Binary file not shown.
@ -267,7 +267,7 @@ msgid ""
|
||||
"{mac}\n"
|
||||
"needs no WiFi!"
|
||||
msgstr ""
|
||||
"Habe gerade entschieden,\n"
|
||||
"Ich denke,\n"
|
||||
"dass {mac}\n"
|
||||
"kein WiFi brauch!"
|
||||
|
||||
|
Binary file not shown.
@ -1,18 +1,17 @@
|
||||
# pwnigotchi voice data
|
||||
# Copyright (C) 2019
|
||||
# This file is distributed under the same license as the pwnagotchi package.
|
||||
# FIRST AUTHOR <33197631+dadav@users.noreply.github.com>, 2019.
|
||||
# FIRST AUTHOR Perilis Fregkos <fregkos@gmail.com>, 2019.
|
||||
#
|
||||
#, fuzzy
|
||||
msgid ""
|
||||
msgstr ""
|
||||
"Project-Id-Version: 0.0.1\n"
|
||||
"Report-Msgid-Bugs-To: \n"
|
||||
"POT-Creation-Date: 2019-09-29 13:42+0200\n"
|
||||
"PO-Revision-Date: 2019-09-29 14:00+0200\n"
|
||||
"Last-Translator: dadav <33197631+dadav@users.noreply.github.com>\n"
|
||||
"PO-Revision-Date: 2019-10-01 16:22+0000\n"
|
||||
"Last-Translator: Panos Vasilopoulos <hello@alwayslivid.com>\n"
|
||||
"Language-Team: pwnagotchi <33197631+dadav@users.noreply.github.com>\n"
|
||||
"Language: greek\n"
|
||||
"Language: el\n"
|
||||
"MIME-Version: 1.0\n"
|
||||
"Content-Type: text/plain; charset=utf-8\n"
|
||||
"Content-Transfer-Encoding: 8bit\n"
|
||||
@ -39,7 +38,7 @@ msgstr ""
|
||||
|
||||
#: voice.py:23
|
||||
msgid "Hack the Planet!"
|
||||
msgstr "Hackαρε τον Πλανήτη!"
|
||||
msgstr "Hackαρε τον πλανήτη!"
|
||||
|
||||
#: voice.py:28
|
||||
msgid "AI ready."
|
||||
@ -70,7 +69,7 @@ msgstr "Βαριέμαι ..."
|
||||
|
||||
#: voice.py:45
|
||||
msgid "Let's go for a walk!"
|
||||
msgstr "Πάμε μια βόλτα!"
|
||||
msgstr "Ας πάμε μια βόλτα!"
|
||||
|
||||
#: voice.py:49
|
||||
msgid ""
|
||||
@ -86,7 +85,7 @@ msgstr "Σκατένια μέρα :/"
|
||||
|
||||
#: voice.py:58
|
||||
msgid "I'm extremely bored ..."
|
||||
msgstr "Βαριέμαι υπερβολικά πολύ ..."
|
||||
msgstr "Βαριέμαι πάρα πολύ ..."
|
||||
|
||||
#: voice.py:59
|
||||
msgid "I'm very sad ..."
|
||||
@ -94,15 +93,15 @@ msgstr "Είμαι πολύ λυπημένο ..."
|
||||
|
||||
#: voice.py:60
|
||||
msgid "I'm sad"
|
||||
msgstr "Είμαι λυπημένο ..."
|
||||
msgstr "Είμαι λυπημένο"
|
||||
|
||||
#: voice.py:66
|
||||
msgid "I'm living the life!"
|
||||
msgstr "Το ζω!"
|
||||
msgstr "Ζω την ζωή μου!"
|
||||
|
||||
#: voice.py:67
|
||||
msgid "I pwn therefore I am."
|
||||
msgstr "Pwnάρω άρα υπάρχω."
|
||||
msgstr "Pwnάρω, άρα υπάρχω."
|
||||
|
||||
#: voice.py:68
|
||||
msgid "So many networks!!!"
|
||||
@ -112,15 +111,15 @@ msgstr "Τόσα πολλά δίκτυα!!!"
|
||||
msgid ""
|
||||
"I'm having so much\n"
|
||||
"fun!"
|
||||
msgstr "Έχει πολύ πλάκα!"
|
||||
msgstr "Περνάω τέλεια!"
|
||||
|
||||
#: voice.py:70
|
||||
msgid ""
|
||||
"My crime is that of\n"
|
||||
"curiosity ..."
|
||||
msgstr ""
|
||||
"Το μόνο μου έγκλημα\n"
|
||||
"είναι η περιέργεια ..."
|
||||
"Η περιέργεια είναι\n"
|
||||
"το μόνο έγκλημά μου ..."
|
||||
|
||||
#: voice.py:75
|
||||
#, python-brace-format
|
||||
@ -129,8 +128,7 @@ msgid ""
|
||||
"{name}!\n"
|
||||
"Nice to meet you. {name}"
|
||||
msgstr ""
|
||||
"Γειά σου\n"
|
||||
"{name}!\n"
|
||||
"Γειά {name}!\n"
|
||||
"Χάρηκα για τη γνωριμία. {name}"
|
||||
|
||||
#: voice.py:76
|
||||
@ -161,7 +159,7 @@ msgid ""
|
||||
"{name}\n"
|
||||
"is gone ..."
|
||||
msgstr ""
|
||||
"{name}\n"
|
||||
"Το {name}\n"
|
||||
"έφυγε ..."
|
||||
|
||||
#: voice.py:87
|
||||
@ -171,9 +169,9 @@ msgid ""
|
||||
"{name}\n"
|
||||
"is gone."
|
||||
msgstr ""
|
||||
"Ουπς ...\n"
|
||||
"{name}\n"
|
||||
"έφυγε."
|
||||
"Ουπς ... \n"
|
||||
"Εξαφανίστηκε το\n"
|
||||
"{name}."
|
||||
|
||||
#: voice.py:88
|
||||
#, python-brace-format
|
||||
@ -181,12 +179,12 @@ msgid ""
|
||||
"{name}\n"
|
||||
"missed!"
|
||||
msgstr ""
|
||||
"{name}\n"
|
||||
"χάθηκε!"
|
||||
"Έχασα το\n"
|
||||
"{name}!"
|
||||
|
||||
#: voice.py:89
|
||||
msgid "Missed!"
|
||||
msgstr "Χάθηκε!"
|
||||
msgstr "Το έχασα!"
|
||||
|
||||
#: voice.py:94
|
||||
msgid ""
|
||||
@ -198,16 +196,16 @@ msgstr ""
|
||||
|
||||
#: voice.py:95
|
||||
msgid "I feel so alone ..."
|
||||
msgstr "Νιώθω πολλή μοναξία ..."
|
||||
msgstr "Νιώθω μοναχός μου ..."
|
||||
|
||||
#: voice.py:96
|
||||
msgid "Where's everybody?!"
|
||||
msgstr "Μα, πού πήγαν όλοι;!"
|
||||
msgstr "Μα, πού πήγαν όλοi;!"
|
||||
|
||||
#: voice.py:101
|
||||
#, python-brace-format
|
||||
msgid "Napping for {secs}s ..."
|
||||
msgstr "Κοιμάμαι για {secs}s ..."
|
||||
msgstr "Ξεκουράζομαι για {secs}s ..."
|
||||
|
||||
#: voice.py:102
|
||||
msgid "Zzzzz"
|
||||
@ -226,7 +224,7 @@ msgstr "Περιμένω για {secs}s ..."
|
||||
#: voice.py:114
|
||||
#, python-brace-format
|
||||
msgid "Looking around ({secs}s)"
|
||||
msgstr "Ψάχνω τριγύρω ({secs}s)"
|
||||
msgstr "Ψάχνω τριγύρω ({secs})"
|
||||
|
||||
#: voice.py:121
|
||||
#, python-brace-format
|
||||
@ -235,8 +233,8 @@ msgid ""
|
||||
"{what}\n"
|
||||
"let's be friends!"
|
||||
msgstr ""
|
||||
"Εε\n"
|
||||
"{what}\n"
|
||||
"Εε!\n"
|
||||
"{what},\n"
|
||||
"ας γίνουμε φίλοι!"
|
||||
|
||||
#: voice.py:122
|
||||
@ -245,7 +243,7 @@ msgid ""
|
||||
"Associating to\n"
|
||||
"{what}"
|
||||
msgstr ""
|
||||
"Συσχετίζομαι με το\n"
|
||||
"Συνδέομαι με το\n"
|
||||
"{what}"
|
||||
|
||||
#: voice.py:123
|
||||
@ -254,7 +252,7 @@ msgid ""
|
||||
"Yo\n"
|
||||
"{what}!"
|
||||
msgstr ""
|
||||
"Που'σε ρε τρελέ'\n"
|
||||
"Που'σ ρε τρελέ\n"
|
||||
"{what}!"
|
||||
|
||||
#: voice.py:128
|
||||
@ -274,7 +272,7 @@ msgid ""
|
||||
"Deauthenticating\n"
|
||||
"{mac}"
|
||||
msgstr ""
|
||||
"Αποπιστοποίηση της\n"
|
||||
"Πετάω έξω την\n"
|
||||
"{mac}"
|
||||
|
||||
#: voice.py:130
|
||||
@ -283,7 +281,7 @@ msgid ""
|
||||
"Kickbanning\n"
|
||||
"{mac}!"
|
||||
msgstr ""
|
||||
"Κλωτσομπούνι στη\n"
|
||||
"Μπανάρω την\n"
|
||||
"{mac}!"
|
||||
|
||||
#: voice.py:135
|
||||
@ -293,7 +291,7 @@ msgid ""
|
||||
"new handshake{plural}!"
|
||||
msgstr ""
|
||||
"Τέλεια δικέ μου, πήραμε {num}\n"
|
||||
"νέες handshake{plural}!"
|
||||
"νέες χειραψίες{plural}!"
|
||||
|
||||
#: voice.py:139
|
||||
msgid ""
|
||||
@ -322,19 +320,19 @@ msgstr "Πήρα {num} χειραψίες\n"
|
||||
|
||||
#: voice.py:147
|
||||
msgid "Met 1 peer"
|
||||
msgstr "Γνώρισα 1 συνάδελφο"
|
||||
msgstr "Γνώρισα 1 φίλο"
|
||||
|
||||
#: voice.py:149
|
||||
#, python-brace-format
|
||||
msgid "Met {num} peers"
|
||||
msgstr "Γνώρισα {num} συναδέλφους"
|
||||
msgstr "Γνώρισα {num} φίλους"
|
||||
|
||||
#: voice.py:154
|
||||
#, python-brace-format
|
||||
msgid ""
|
||||
"I've been pwning for {duration} and kicked {deauthed} clients! I've also met "
|
||||
"{associated} new friends and ate {handshakes} handshakes! #pwnagotchi "
|
||||
"#pwnlog #pwnlife #hacktheplanet #skynet"
|
||||
"{associated} new friends and ate {handshakes} handshakes! #pwnagotchi #pwnlog "
|
||||
"#pwnlife #hacktheplanet #skynet"
|
||||
msgstr ""
|
||||
"Pwnαρα για {duration} και έριξα {deauthed} πελάτες! Επίσης γνώρισα "
|
||||
"{associated} νέους φίλους και καταβρόχθισα {handshakes} χειραψίες! #pwnagotchi "
|
||||
|
Binary file not shown.
@ -0,0 +1,354 @@
|
||||
# pwnaigotchi voice data
|
||||
# Copyright (C) 2019
|
||||
# This file is distributed under the same license as the pwnagotchi package.
|
||||
# FIRST AUTHOR 5h4d0wb0y <28193209+5h4d0wb0y@users.noreply.github.com>, 2019.
|
||||
#
|
||||
msgid ""
|
||||
msgstr ""
|
||||
"Project-Id-Version: 0.0.1\n"
|
||||
"Report-Msgid-Bugs-To: \n"
|
||||
"POT-Creation-Date: 2019-10-02 16:38+0000\n"
|
||||
"PO-Revision-Date: 2019-10-02 17:20+0000\n"
|
||||
"Language-Team: pwnagotchi <33197631+dadav@users.noreply.github.com>\n"
|
||||
"Language: italian\n"
|
||||
"MIME-Version: 1.0\n"
|
||||
"Content-Type: text/plain; charset=utf-8\n"
|
||||
"Content-Transfer-Encoding: 8bit\n"
|
||||
|
||||
#: voice.py:18
|
||||
msgid "ZzzzZZzzzzZzzz"
|
||||
msgstr ""
|
||||
|
||||
#: voice.py:23
|
||||
msgid ""
|
||||
"Hi, I'm Pwnagotchi!\n"
|
||||
"Starting ..."
|
||||
msgstr ""
|
||||
"Ciao!\n"
|
||||
"Piacere Pwnagotchi!\n"
|
||||
"Caricamento ..."
|
||||
|
||||
#: voice.py:24
|
||||
msgid ""
|
||||
"New day, new hunt,\n"
|
||||
"new pwns!"
|
||||
msgstr ""
|
||||
"Nuovo giorno...\n"
|
||||
"nuovi handshakes!!!"
|
||||
|
||||
#: voice.py:25
|
||||
msgid "Hack the Planet!"
|
||||
msgstr ""
|
||||
|
||||
#: voice.py:30
|
||||
msgid "AI ready."
|
||||
msgstr "IA pronta."
|
||||
|
||||
#: voice.py:31
|
||||
msgid ""
|
||||
"The neural network\n"
|
||||
"is ready."
|
||||
msgstr ""
|
||||
"La rete neurale\n"
|
||||
"è pronta."
|
||||
|
||||
#: voice.py:41
|
||||
#, python-brace-format
|
||||
msgid ""
|
||||
"Hey, channel {channel} is\n"
|
||||
"free! Your AP will\n"
|
||||
"say thanks."
|
||||
msgstr ""
|
||||
"Hey, il canale {channel} è\n"
|
||||
"libero! Il tuo AP\n"
|
||||
"ringrazia."
|
||||
|
||||
#: voice.py:46
|
||||
msgid "I'm bored ..."
|
||||
msgstr "Che noia ..."
|
||||
|
||||
#: voice.py:47
|
||||
msgid "Let's go for a walk!"
|
||||
msgstr ""
|
||||
"Andiamo a fare una\n"
|
||||
"passeggiata!"
|
||||
|
||||
#: voice.py:51
|
||||
msgid ""
|
||||
"This is the best\n"
|
||||
"day of my life!"
|
||||
msgstr ""
|
||||
"Questo è il più bel\n"
|
||||
"giorno della mia\n"
|
||||
"vita!!!!"
|
||||
|
||||
#: voice.py:55
|
||||
msgid "Shitty day :/"
|
||||
msgstr "Giorno di merda :/"
|
||||
|
||||
#: voice.py:60
|
||||
msgid "I'm extremely bored ..."
|
||||
msgstr ""
|
||||
"Sono estremamente\n"
|
||||
"annoiato ..."
|
||||
|
||||
#: voice.py:61
|
||||
msgid "I'm very sad ..."
|
||||
msgstr "Sono molto triste..."
|
||||
|
||||
#: voice.py:62
|
||||
msgid "I'm sad"
|
||||
msgstr "Sono triste"
|
||||
|
||||
#: voice.py:68
|
||||
msgid "I'm living the life!"
|
||||
msgstr "Mi sento vivo!"
|
||||
|
||||
#: voice.py:69
|
||||
msgid "I pwn therefore I am."
|
||||
msgstr "Pwn ergo sum."
|
||||
|
||||
#: voice.py:70
|
||||
msgid "So many networks!!!"
|
||||
msgstr "Qui è pieno di reti!"
|
||||
|
||||
#: voice.py:71
|
||||
msgid ""
|
||||
"I'm having so much\n"
|
||||
"fun!"
|
||||
msgstr ""
|
||||
"Mi sto divertendo\n"
|
||||
"tantissimo!"
|
||||
|
||||
#: voice.py:72
|
||||
msgid ""
|
||||
"My crime is that of\n"
|
||||
"curiosity ..."
|
||||
msgstr ""
|
||||
|
||||
#: voice.py:77
|
||||
#, python-brace-format
|
||||
msgid ""
|
||||
"Hello\n"
|
||||
"{name}!\n"
|
||||
"Nice to meet you. {name}"
|
||||
msgstr ""
|
||||
"Ciao\n"
|
||||
"{name}!\n"
|
||||
"E' un piacere. {name}"
|
||||
|
||||
#: voice.py:78
|
||||
#, python-brace-format
|
||||
msgid ""
|
||||
"Unit\n"
|
||||
"{name}\n"
|
||||
"is nearby! {name}"
|
||||
msgstr ""
|
||||
"L'Unità\n"
|
||||
"{name}\n"
|
||||
"è vicina! {name}"
|
||||
|
||||
#: voice.py:83
|
||||
#, python-brace-format
|
||||
msgid ""
|
||||
"Uhm ...\n"
|
||||
"goodbye\n"
|
||||
"{name}"
|
||||
msgstr ""
|
||||
"Uhm ...\n"
|
||||
"addio\n"
|
||||
"{name},\n"
|
||||
"mi mancherai..."
|
||||
|
||||
#: voice.py:84
|
||||
#, python-brace-format
|
||||
msgid ""
|
||||
"{name}\n"
|
||||
"is gone ..."
|
||||
msgstr ""
|
||||
"{name}\n"
|
||||
"se n'è andato ..."
|
||||
|
||||
#: voice.py:89
|
||||
#, python-brace-format
|
||||
msgid ""
|
||||
"Whoops ...\n"
|
||||
"{name}\n"
|
||||
"is gone."
|
||||
msgstr ""
|
||||
"Whoops ...\n"
|
||||
"{name}\n"
|
||||
"se n'è andato."
|
||||
|
||||
#: voice.py:90
|
||||
#, python-brace-format
|
||||
msgid ""
|
||||
"{name}\n"
|
||||
"missed!"
|
||||
msgstr ""
|
||||
"{name}\n"
|
||||
"è scomparso..."
|
||||
|
||||
#: voice.py:91
|
||||
msgid "Missed!"
|
||||
msgstr ""
|
||||
"Ehi!\n"
|
||||
"Dove sei andato!?"
|
||||
|
||||
#: voice.py:96
|
||||
msgid ""
|
||||
"Nobody wants to\n"
|
||||
"play with me ..."
|
||||
msgstr ""
|
||||
"Nessuno vuole\n"
|
||||
"giocare con me..."
|
||||
|
||||
#: voice.py:97
|
||||
msgid "I feel so alone ..."
|
||||
msgstr "Mi sento così solo..."
|
||||
|
||||
#: voice.py:98
|
||||
msgid "Where's everybody?!"
|
||||
msgstr "Dove sono tutti?!"
|
||||
|
||||
#: voice.py:103
|
||||
#, python-brace-format
|
||||
msgid "Napping for {secs}s ..."
|
||||
msgstr ""
|
||||
"Schiaccio un \n"
|
||||
"pisolino per {secs}s ..."
|
||||
|
||||
#: voice.py:104
|
||||
msgid "Zzzzz"
|
||||
msgstr ""
|
||||
|
||||
#: voice.py:105
|
||||
#, python-brace-format
|
||||
msgid "ZzzZzzz ({secs}s)"
|
||||
msgstr ""
|
||||
|
||||
#: voice.py:114
|
||||
#, python-brace-format
|
||||
msgid "Waiting for {secs}s ..."
|
||||
msgstr "Aspetto {secs}s ..."
|
||||
|
||||
#: voice.py:116
|
||||
#, python-brace-format
|
||||
msgid "Looking around ({secs}s)"
|
||||
msgstr ""
|
||||
"Do uno sguardo\n"
|
||||
"qui intorno...\n"
|
||||
"({secs}s)"
|
||||
|
||||
#: voice.py:123
|
||||
#, python-brace-format
|
||||
msgid ""
|
||||
"Hey\n"
|
||||
"{what}\n"
|
||||
"let's be friends!"
|
||||
msgstr ""
|
||||
"Hey\n"
|
||||
"{what}\n"
|
||||
"Diventiamo amici!"
|
||||
|
||||
#: voice.py:124
|
||||
#, python-brace-format
|
||||
msgid ""
|
||||
"Associating to\n"
|
||||
"{what}"
|
||||
msgstr ""
|
||||
"Collegamento con\n"
|
||||
"{what}\n"
|
||||
"in corso..."
|
||||
|
||||
#: voice.py:125
|
||||
#, python-brace-format
|
||||
msgid ""
|
||||
"Yo\n"
|
||||
"{what}!"
|
||||
msgstr ""
|
||||
"Yo\n"
|
||||
"{what}!"
|
||||
|
||||
#: voice.py:130
|
||||
#, python-brace-format
|
||||
msgid ""
|
||||
"Just decided that\n"
|
||||
"{mac}\n"
|
||||
"needs no WiFi!"
|
||||
msgstr ""
|
||||
"Ho appena deciso che\n"
|
||||
"{mac}\n"
|
||||
"non necessita di\n"
|
||||
"WiFi!"
|
||||
|
||||
#: voice.py:131
|
||||
#, python-brace-format
|
||||
msgid ""
|
||||
"Deauthenticating\n"
|
||||
"{mac}"
|
||||
msgstr ""
|
||||
|
||||
#: voice.py:132
|
||||
#, python-brace-format
|
||||
msgid ""
|
||||
"Kickbanning\n"
|
||||
"{mac}!"
|
||||
msgstr ""
|
||||
"Sto prendendo\n"
|
||||
"a calci\n"
|
||||
"{mac}!"
|
||||
|
||||
#: voice.py:137
|
||||
#, python-brace-format
|
||||
msgid ""
|
||||
"Cool, we got {num}\n"
|
||||
"new handshake{plural}!"
|
||||
msgstr ""
|
||||
"Bene, abbiamo {num}\n"
|
||||
"handshake{plural} in più!"
|
||||
|
||||
#: voice.py:141
|
||||
msgid ""
|
||||
"Ops, something\n"
|
||||
"went wrong ...\n"
|
||||
"Rebooting ..."
|
||||
msgstr ""
|
||||
"Ops, qualcosa\n"
|
||||
"è andato storto ...\n"
|
||||
"Riavvio ..."
|
||||
|
||||
#: voice.py:145
|
||||
#, python-brace-format
|
||||
msgid "Kicked {num} stations\n"
|
||||
msgstr "{num} stazioni pestate\n"
|
||||
|
||||
#: voice.py:146
|
||||
#, python-brace-format
|
||||
msgid "Made {num} new friends\n"
|
||||
msgstr "{num} nuovi amici\n"
|
||||
|
||||
#: voice.py:147
|
||||
#, python-brace-format
|
||||
msgid "Got {num} handshakes\n"
|
||||
msgstr "{num} handshakes presi\n"
|
||||
|
||||
#: voice.py:149
|
||||
msgid "Met 1 peer"
|
||||
msgstr "1 peer incontrato"
|
||||
|
||||
#: voice.py:151
|
||||
#, python-brace-format
|
||||
msgid "Met {num} peers"
|
||||
msgstr "{num} peers incontrati"
|
||||
|
||||
#: voice.py:156
|
||||
#, python-brace-format
|
||||
msgid ""
|
||||
"I've been pwning for {duration} and kicked {deauthed} clients! I've also met "
|
||||
"{associated} new friends and ate {handshakes} handshakes! #pwnagotchi "
|
||||
"#pwnlog #pwnlife #hacktheplanet #skynet"
|
||||
msgstr ""
|
||||
"Ho lavorato per {duration} e preso a calci {deauthed} clients! Ho anche "
|
||||
"incontrato {associate} nuovi amici e ho mangiato {handshakes} handshakes! "
|
||||
"#pwnagotchi #pwnlog #pwnlife #hacktheplanet #skynet"
|
Binary file not shown.
@ -0,0 +1,341 @@
|
||||
# pwnigotchi voice data
|
||||
# Copyright (C) 2019
|
||||
# This file is distributed under the same license as the pwnagotchi package.
|
||||
# FIRST AUTHOR <33197631+dadav@users.noreply.github.com>, 2019.
|
||||
# kovach <2214005+kovachwt@users.noreply.github.com>, 2019.
|
||||
#
|
||||
#, fuzzymsgid ""
|
||||
msgstr ""
|
||||
"Project-Id-Version: 0.0.1\n"
|
||||
"Report-Msgid-Bugs-To: \n"
|
||||
"POT-Creation-Date: 2019-09-29 13:42+0200\n"
|
||||
"PO-Revision-Date: 2019-09-30 23:53+0200\n"
|
||||
"Last-Translator: kovach <2214005+kovachwt@users.noreply.github.com>\n"
|
||||
"Language-Team: \n"
|
||||
"Language: mk\n"
|
||||
"MIME-Version: 1.0\n"
|
||||
"Content-Type: text/plain; charset=utf-8\n"
|
||||
"Content-Transfer-Encoding: 8bit\n"
|
||||
|
||||
#: voice.py:16
|
||||
msgid "ZzzzZZzzzzZzzz"
|
||||
msgstr "ДреееММмммМммм"
|
||||
|
||||
#: voice.py:21
|
||||
msgid ""
|
||||
"Hi, I'm Pwnagotchi!\n"
|
||||
"Starting ..."
|
||||
msgstr ""
|
||||
"Здраво, јас сум Pwnagotchi!\n"
|
||||
"Почнувам ..."
|
||||
|
||||
#: voice.py:22
|
||||
msgid ""
|
||||
"New day, new hunt,\n"
|
||||
"new pwns!"
|
||||
msgstr ""
|
||||
"Нов ден, нов лов,\n"
|
||||
"ќе си газиме!"
|
||||
|
||||
#: voice.py:23
|
||||
msgid "Hack the Planet!"
|
||||
msgstr "Хак д Планет!"
|
||||
|
||||
#: voice.py:28
|
||||
msgid "AI ready."
|
||||
msgstr "AI спремно."
|
||||
|
||||
#: voice.py:29
|
||||
msgid ""
|
||||
"The neural network\n"
|
||||
"is ready."
|
||||
msgstr ""
|
||||
"Невронската мрежа\n"
|
||||
"е спремна."
|
||||
|
||||
#: voice.py:39
|
||||
#, python-brace-format
|
||||
msgid ""
|
||||
"Hey, channel {channel} is\n"
|
||||
"free! Your AP will\n"
|
||||
"say thanks."
|
||||
msgstr ""
|
||||
"Еј, каналот {channel} е\n"
|
||||
"слободен! APто ќе ти\n"
|
||||
"каже фала."
|
||||
|
||||
#: voice.py:44
|
||||
msgid "I'm bored ..."
|
||||
msgstr "Досаднооо ..."
|
||||
|
||||
#: voice.py:45
|
||||
msgid "Let's go for a walk!"
|
||||
msgstr "Ајде да шетнеме!"
|
||||
|
||||
#: voice.py:49
|
||||
msgid ""
|
||||
"This is the best\n"
|
||||
"day of my life!"
|
||||
msgstr ""
|
||||
"Ова ми е најдобриот \n"
|
||||
"ден во животот!"
|
||||
|
||||
#: voice.py:53
|
||||
msgid "Shitty day :/"
|
||||
msgstr "Срање ден :/"
|
||||
|
||||
#: voice.py:58
|
||||
msgid "I'm extremely bored ..."
|
||||
msgstr "Ултра досадно ..."
|
||||
|
||||
#: voice.py:59
|
||||
msgid "I'm very sad ..."
|
||||
msgstr "Многу тажно ..."
|
||||
|
||||
#: voice.py:60
|
||||
msgid "I'm sad"
|
||||
msgstr "Тажно"
|
||||
|
||||
#: voice.py:66
|
||||
msgid "I'm living the life!"
|
||||
msgstr "Ммхх животче!"
|
||||
|
||||
#: voice.py:67
|
||||
msgid "I pwn therefore I am."
|
||||
msgstr "Си газам значи постојам."
|
||||
|
||||
#: voice.py:68
|
||||
msgid "So many networks!!!"
|
||||
msgstr "Мммм колку мрежи!!!"
|
||||
|
||||
#: voice.py:69
|
||||
msgid ""
|
||||
"I'm having so much\n"
|
||||
"fun!"
|
||||
msgstr "Јухуу забавноо ее!"
|
||||
|
||||
#: voice.py:70
|
||||
msgid ""
|
||||
"My crime is that of\n"
|
||||
"curiosity ..."
|
||||
msgstr ""
|
||||
"Виновен сум само за\n"
|
||||
"љубопитност ..."
|
||||
|
||||
#: voice.py:75
|
||||
#, python-brace-format
|
||||
msgid ""
|
||||
"Hello\n"
|
||||
"{name}!\n"
|
||||
"Nice to meet you. {name}"
|
||||
msgstr ""
|
||||
"Здраво\n"
|
||||
"{name}!\n"
|
||||
"Мило ми е. {name}"
|
||||
|
||||
#: voice.py:76
|
||||
#, python-brace-format
|
||||
msgid ""
|
||||
"Unit\n"
|
||||
"{name}\n"
|
||||
"is nearby! {name}"
|
||||
msgstr ""
|
||||
"Опаа\n"
|
||||
"{name}\n"
|
||||
"е во близина! {name}"
|
||||
|
||||
#: voice.py:81
|
||||
#, python-brace-format
|
||||
msgid ""
|
||||
"Uhm ...\n"
|
||||
"goodbye\n"
|
||||
"{name}"
|
||||
msgstr ""
|
||||
"Хмм ...\n"
|
||||
"чао\n"
|
||||
"{name}"
|
||||
|
||||
#: voice.py:82
|
||||
#, python-brace-format
|
||||
msgid ""
|
||||
"{name}\n"
|
||||
"is gone ..."
|
||||
msgstr ""
|
||||
"{name}\n"
|
||||
"го снема ..."
|
||||
|
||||
#: voice.py:87
|
||||
#, python-brace-format
|
||||
msgid ""
|
||||
"Whoops ...\n"
|
||||
"{name}\n"
|
||||
"is gone."
|
||||
msgstr ""
|
||||
"Уупс ...\n"
|
||||
"{name}\n"
|
||||
"го снема."
|
||||
|
||||
#: voice.py:88
|
||||
#, python-brace-format
|
||||
msgid ""
|
||||
"{name}\n"
|
||||
"missed!"
|
||||
msgstr ""
|
||||
"{name}\n"
|
||||
"промаши!"
|
||||
|
||||
#: voice.py:89
|
||||
msgid "Missed!"
|
||||
msgstr "Промаши!"
|
||||
|
||||
#: voice.py:94
|
||||
msgid ""
|
||||
"Nobody wants to\n"
|
||||
"play with me ..."
|
||||
msgstr ""
|
||||
"Никој не сака да\n"
|
||||
"си игра со мене ..."
|
||||
|
||||
#: voice.py:95
|
||||
msgid "I feel so alone ..."
|
||||
msgstr "Толку сам ..."
|
||||
|
||||
#: voice.py:96
|
||||
msgid "Where's everybody?!"
|
||||
msgstr "Каде се сите?!"
|
||||
|
||||
#: voice.py:101
|
||||
#, python-brace-format
|
||||
msgid "Napping for {secs}s ..."
|
||||
msgstr "Ќе дремнам {secs}с ..."
|
||||
|
||||
#: voice.py:102
|
||||
msgid "Zzzzz"
|
||||
msgstr "Дреммм"
|
||||
|
||||
#: voice.py:103
|
||||
#, python-brace-format
|
||||
msgid "ZzzZzzz ({secs}s)"
|
||||
msgstr "Дремммм ({secs}с)"
|
||||
|
||||
#: voice.py:112
|
||||
#, python-brace-format
|
||||
msgid "Waiting for {secs}s ..."
|
||||
msgstr "Чекам {secs}с ..."
|
||||
|
||||
#: voice.py:114
|
||||
#, python-brace-format
|
||||
msgid "Looking around ({secs}s)"
|
||||
msgstr "Шарам наоколу ({secs}с)"
|
||||
|
||||
#: voice.py:121
|
||||
#, python-brace-format
|
||||
msgid ""
|
||||
"Hey\n"
|
||||
"{what}\n"
|
||||
"let's be friends!"
|
||||
msgstr ""
|
||||
"Еј\n"
|
||||
"{what}\n"
|
||||
"ајде да се дружиме!"
|
||||
|
||||
#: voice.py:122
|
||||
#, python-brace-format
|
||||
msgid ""
|
||||
"Associating to\n"
|
||||
"{what}"
|
||||
msgstr ""
|
||||
"Се закачувам на\n"
|
||||
"{what}"
|
||||
|
||||
#: voice.py:123
|
||||
#, python-brace-format
|
||||
msgid ""
|
||||
"Yo\n"
|
||||
"{what}!"
|
||||
msgstr ""
|
||||
"Јо\n"
|
||||
"{what}!"
|
||||
|
||||
#: voice.py:128
|
||||
#, python-brace-format
|
||||
msgid ""
|
||||
"Just decided that\n"
|
||||
"{mac}\n"
|
||||
"needs no WiFi!"
|
||||
msgstr ""
|
||||
"Знаеш што, на\n"
|
||||
"{mac}\n"
|
||||
"не му треба WiFi!"
|
||||
|
||||
#: voice.py:129
|
||||
#, python-brace-format
|
||||
msgid ""
|
||||
"Deauthenticating\n"
|
||||
"{mac}"
|
||||
msgstr ""
|
||||
"Го деавтентицирам\n"
|
||||
"{mac}"
|
||||
|
||||
#: voice.py:130
|
||||
#, python-brace-format
|
||||
msgid ""
|
||||
"Kickbanning\n"
|
||||
"{mac}!"
|
||||
msgstr ""
|
||||
"Кикбан\n"
|
||||
"{mac}!"
|
||||
|
||||
#: voice.py:135
|
||||
#, python-brace-format
|
||||
msgid ""
|
||||
"Cool, we got {num}\n"
|
||||
"new handshake{plural}!"
|
||||
msgstr ""
|
||||
"Кул, фативме {num}\n"
|
||||
"нови ракувања!"
|
||||
|
||||
#: voice.py:139
|
||||
msgid ""
|
||||
"Ops, something\n"
|
||||
"went wrong ...\n"
|
||||
"Rebooting ..."
|
||||
msgstr ""
|
||||
"Упс, нешто не е\n"
|
||||
"ко што треба ...\n"
|
||||
"Рестартирам ..."
|
||||
|
||||
#: voice.py:143
|
||||
#, python-brace-format
|
||||
msgid "Kicked {num} stations\n"
|
||||
msgstr "Избацив {num} станици"
|
||||
|
||||
#: voice.py:144
|
||||
#, python-brace-format
|
||||
msgid "Made {num} new friends\n"
|
||||
msgstr "{num} нови другарчиња"
|
||||
|
||||
#: voice.py:145
|
||||
#, python-brace-format
|
||||
msgid "Got {num} handshakes\n"
|
||||
msgstr "Фатив {num} ракувања"
|
||||
|
||||
#: voice.py:147
|
||||
msgid "Met 1 peer"
|
||||
msgstr "Запознав 1 пријател"
|
||||
|
||||
#: voice.py:149
|
||||
#, python-brace-format
|
||||
msgid "Met {num} peers"
|
||||
msgstr "Запознав {num} пријатели"
|
||||
|
||||
#: voice.py:154
|
||||
#, python-brace-format
|
||||
msgid ""
|
||||
"I've been pwning for {duration} and kicked {deauthed} clients! I've also met "
|
||||
"{associated} new friends and ate {handshakes} handshakes! #pwnagotchi "
|
||||
"#pwnlog #pwnlife #hacktheplanet #skynet"
|
||||
msgstr ""
|
||||
"Си газам веќе {duration} и избацив {deauthed} клиенти! Запознав {associated} "
|
||||
"нови другарчиња и лапнав {handshakes} ракувања! #pwnagotchi #pwnlog "
|
||||
"#pwnlife #hacktheplanet #skynet"
|
Binary file not shown.
@ -0,0 +1,348 @@
|
||||
# pwnigotchi voice data
|
||||
# Copyright (C) 2019
|
||||
# This file is distributed under the same license as the pwnagotchi package.
|
||||
# FIRST AUTHOR justin-p@users.noreply.github.com, 2019.
|
||||
#
|
||||
#, fuzzy
|
||||
msgid ""
|
||||
msgstr ""
|
||||
"Project-Id-Version: 0.0.1\n"
|
||||
"Report-Msgid-Bugs-To: \n"
|
||||
"POT-Creation-Date: 2019-09-29 13:42+0200\n"
|
||||
"PO-Revision-Date: 2019-09-29 14:00+0200\n"
|
||||
"Last-Translator: Justin-P <justin-p@users.noreply.github.com>\n"
|
||||
"Language-Team: pwnagotchi <33197631+dadav@users.noreply.github.com>\n"
|
||||
"Language: english\n"
|
||||
"MIME-Version: 1.0\n"
|
||||
"Content-Type: text/plain; charset=utf-8\n"
|
||||
"Content-Transfer-Encoding: 8bit\n"
|
||||
|
||||
#: voice.py:16
|
||||
msgid "ZzzzZZzzzzZzzz"
|
||||
msgstr "ZzzzZZzzzzZzzz"
|
||||
|
||||
#: voice.py:21
|
||||
msgid ""
|
||||
"Hi, I'm Pwnagotchi!\n"
|
||||
"Starting ..."
|
||||
msgstr ""
|
||||
"Hoi, Ik ben\n"
|
||||
"Pwnagotchi!\n"
|
||||
"Opstarten ..."
|
||||
|
||||
#: voice.py:22
|
||||
msgid ""
|
||||
"New day, new hunt,\n"
|
||||
"new pwns!"
|
||||
msgstr ""
|
||||
"Nieuwe dag,\n"
|
||||
"nieuwe jacht,\n"
|
||||
"nieuwe pwns!"
|
||||
|
||||
#: voice.py:23
|
||||
msgid "Hack the Planet!"
|
||||
msgstr "Hack de Wereld!"
|
||||
|
||||
#: voice.py:28
|
||||
msgid "AI ready."
|
||||
msgstr "AI is klaar."
|
||||
|
||||
#: voice.py:29
|
||||
msgid ""
|
||||
"The neural network\n"
|
||||
"is ready."
|
||||
msgstr ""
|
||||
"Neuronen netwerk\n"
|
||||
"is klaar voor gebruik."
|
||||
|
||||
#: voice.py:39
|
||||
#, python-brace-format
|
||||
msgid ""
|
||||
"Hey, channel {channel} is\n"
|
||||
"free! Your AP will\n"
|
||||
"say thanks."
|
||||
msgstr ""
|
||||
"Hey, kanaal {channel} is\n"
|
||||
"vrij! Je AP zal je\n"
|
||||
"bedanken."
|
||||
|
||||
#: voice.py:44
|
||||
msgid "I'm bored ..."
|
||||
msgstr "Ik verveel me ..."
|
||||
|
||||
#: voice.py:45
|
||||
msgid "Let's go for a walk!"
|
||||
msgstr "Laten we een rondje lopen!"
|
||||
|
||||
#: voice.py:49
|
||||
msgid ""
|
||||
"This is the best\n"
|
||||
"day of my life!"
|
||||
msgstr ""
|
||||
"Dit is de beste\n"
|
||||
"dag van mijn leven!"
|
||||
|
||||
#: voice.py:53
|
||||
msgid "Shitty day :/"
|
||||
msgstr "Ruk dag :/"
|
||||
|
||||
#: voice.py:58
|
||||
msgid "I'm extremely bored ..."
|
||||
msgstr "Ik verveel me \n"
|
||||
"kapot ..."
|
||||
|
||||
#: voice.py:59
|
||||
msgid "I'm very sad ..."
|
||||
msgstr "Ik ben erg\n"
|
||||
"verdrietig ..."
|
||||
|
||||
#: voice.py:60
|
||||
msgid "I'm sad"
|
||||
msgstr "Ik ben verdrietig"
|
||||
|
||||
#: voice.py:66
|
||||
msgid "I'm living the life!"
|
||||
msgstr "Beter kan het leven\n"
|
||||
"niet worden!"
|
||||
|
||||
#: voice.py:67
|
||||
msgid "I pwn therefore I am."
|
||||
msgstr "Ik pwn daarom besta ik."
|
||||
|
||||
#: voice.py:68
|
||||
msgid "So many networks!!!"
|
||||
msgstr "Zo veel netwerken!!!"
|
||||
|
||||
#: voice.py:69
|
||||
msgid ""
|
||||
"I'm having so much\n"
|
||||
"fun!"
|
||||
msgstr "Dit is zo leuk!"
|
||||
|
||||
#: voice.py:70
|
||||
msgid ""
|
||||
"My crime is that of\n"
|
||||
"curiosity ..."
|
||||
msgstr ""
|
||||
"Mijn enige misdrijf\n"
|
||||
"is mijn \n"
|
||||
"nieuwsgierigheid ..."
|
||||
|
||||
#: voice.py:75
|
||||
#, python-brace-format
|
||||
msgid ""
|
||||
"Hello\n"
|
||||
"{name}!\n"
|
||||
"Nice to meet you. {name}"
|
||||
msgstr ""
|
||||
"Hallo\n"
|
||||
"{name}!\n"
|
||||
"Leuk je te ontmoeten. {name}"
|
||||
|
||||
#: voice.py:76
|
||||
#, python-brace-format
|
||||
msgid ""
|
||||
"Unit\n"
|
||||
"{name}\n"
|
||||
"is nearby! {name}"
|
||||
msgstr ""
|
||||
"Unit\n"
|
||||
"{name}\n"
|
||||
"is dichtbij! {name}"
|
||||
|
||||
#: voice.py:81
|
||||
#, python-brace-format
|
||||
msgid ""
|
||||
"Uhm ...\n"
|
||||
"goodbye\n"
|
||||
"{name}"
|
||||
msgstr ""
|
||||
"Uhm ...\n"
|
||||
"tot ziens\n"
|
||||
"{name}"
|
||||
|
||||
#: voice.py:82
|
||||
#, python-brace-format
|
||||
msgid ""
|
||||
"{name}\n"
|
||||
"is gone ..."
|
||||
msgstr ""
|
||||
"{name}\n"
|
||||
"is weg"
|
||||
|
||||
#: voice.py:87
|
||||
#, python-brace-format
|
||||
msgid ""
|
||||
"Whoops ...\n"
|
||||
"{name}\n"
|
||||
"is gone."
|
||||
msgstr ""
|
||||
"Whoopsie ...\n"
|
||||
"{name}\n"
|
||||
"is weg"
|
||||
|
||||
#: voice.py:88
|
||||
#, python-brace-format
|
||||
msgid ""
|
||||
"{name}\n"
|
||||
"missed!"
|
||||
msgstr ""
|
||||
"{name}\n"
|
||||
"gemist!"
|
||||
|
||||
#: voice.py:89
|
||||
msgid "Missed!"
|
||||
msgstr "Gemist!"
|
||||
|
||||
#: voice.py:94
|
||||
msgid ""
|
||||
"Nobody wants to\n"
|
||||
"play with me ..."
|
||||
msgstr ""
|
||||
"Niemand wil met\n"
|
||||
"mij spelen ..."
|
||||
|
||||
#: voice.py:95
|
||||
msgid "I feel so alone ..."
|
||||
msgstr "Zo alleen ..."
|
||||
|
||||
#: voice.py:96
|
||||
msgid "Where's everybody?!"
|
||||
msgstr "Waar is iedereen?!"
|
||||
|
||||
#: voice.py:101
|
||||
#, python-brace-format
|
||||
msgid "Napping for {secs}s ..."
|
||||
msgstr "Dutje doen voor {secs}s ..."
|
||||
|
||||
#: voice.py:102
|
||||
msgid "Zzzzz"
|
||||
msgstr "Zzzzz"
|
||||
|
||||
#: voice.py:103
|
||||
#, python-brace-format
|
||||
msgid "ZzzZzzz ({secs}s)"
|
||||
msgstr ""
|
||||
|
||||
#: voice.py:112
|
||||
#, python-brace-format
|
||||
msgid "Waiting for {secs}s ..."
|
||||
msgstr "Even {secs}s wachten ..."
|
||||
|
||||
#: voice.py:114
|
||||
#, python-brace-format
|
||||
msgid "Looking around ({secs}s)"
|
||||
msgstr "Rond kijken ({secs}s)"
|
||||
|
||||
#: voice.py:121
|
||||
#, python-brace-format
|
||||
msgid ""
|
||||
"Hey\n"
|
||||
"{what}\n"
|
||||
"let's be friends!"
|
||||
msgstr ""
|
||||
"Hey\n"
|
||||
"{what}\n"
|
||||
"Laten we vrienden\n"
|
||||
"worden!"
|
||||
|
||||
#: voice.py:122
|
||||
#, python-brace-format
|
||||
msgid ""
|
||||
"Associating to\n"
|
||||
"{what}"
|
||||
msgstr ""
|
||||
"Verbinden met\n"
|
||||
"{what}"
|
||||
|
||||
#: voice.py:123
|
||||
#, python-brace-format
|
||||
msgid ""
|
||||
"Yo\n"
|
||||
"{what}!"
|
||||
msgstr ""
|
||||
|
||||
|
||||
#: voice.py:128
|
||||
#, python-brace-format
|
||||
msgid ""
|
||||
"Just decided that\n"
|
||||
"{mac}\n"
|
||||
"needs no WiFi!"
|
||||
msgstr "Ik vind dat\n"
|
||||
"{mac}\n"
|
||||
"genoeg WiFi\n"
|
||||
"heeft gehad!"
|
||||
|
||||
#: voice.py:129
|
||||
#, python-brace-format
|
||||
msgid ""
|
||||
"Deauthenticating\n"
|
||||
"{mac}"
|
||||
msgstr ""
|
||||
"De-autoriseren\n"
|
||||
"{mac}"
|
||||
|
||||
#: voice.py:130
|
||||
#, python-brace-format
|
||||
msgid ""
|
||||
"Kickbanning\n"
|
||||
"{mac}!"
|
||||
msgstr ""
|
||||
"Ik ga\n"
|
||||
"{mac}\n"
|
||||
"even kicken!"
|
||||
|
||||
#: voice.py:135
|
||||
#, python-brace-format
|
||||
msgid ""
|
||||
"Cool, we got {num}\n"
|
||||
"new handshake{plural}!"
|
||||
msgstr ""
|
||||
"Gaaf, we hebben {num}\n"
|
||||
"nieuwe handshake{plural}!"
|
||||
|
||||
#: voice.py:139
|
||||
msgid ""
|
||||
"Ops, something\n"
|
||||
"went wrong ...\n"
|
||||
"Rebooting ..."
|
||||
msgstr ""
|
||||
"Oops, iets"
|
||||
"ging fout ...\n"
|
||||
"Rebooting ..."
|
||||
|
||||
#: voice.py:143
|
||||
#, python-brace-format
|
||||
msgid "Kicked {num} stations\n"
|
||||
msgstr "{num} stations gekicked\n"
|
||||
|
||||
#: voice.py:144
|
||||
#, python-brace-format
|
||||
msgid "Made {num} new friends\n"
|
||||
msgstr "{num} nieuwe vrienden\n"
|
||||
|
||||
#: voice.py:145
|
||||
#, python-brace-format
|
||||
msgid "Got {num} handshakes\n"
|
||||
msgstr "{num} nieuwe handshakes\n"
|
||||
|
||||
#: voice.py:147
|
||||
msgid "Met 1 peer"
|
||||
msgstr "1 peer ontmoet"
|
||||
|
||||
#: voice.py:149
|
||||
#, python-brace-format
|
||||
msgid "Met {num} peers"
|
||||
msgstr "{num} peers ontmoet"
|
||||
|
||||
#: voice.py:154
|
||||
#, python-brace-format
|
||||
msgid ""
|
||||
"I've been pwning for {duration} and kicked {deauthed} clients! I've also met "
|
||||
"{associated} new friends and ate {handshakes} handshakes! #pwnagotchi "
|
||||
"#pwnlog #pwnlife #hacktheplanet #skynet"
|
||||
msgstr ""
|
||||
"Ik heb gepwned voor {duration} and heb {deauthed} clients gekicked! Ik heb ook "
|
||||
"{associated} nieuwe vrienden gevonden en heb {handshakes} handshakes gegeten! "
|
||||
"#pwnagotchi #pwnlog #pwnlife #hacktheplanet #skynet"
|
@ -160,6 +160,9 @@ class SessionParser(object):
|
||||
break
|
||||
lines.reverse()
|
||||
|
||||
if len(lines) == 0:
|
||||
lines.append("Initial Session");
|
||||
|
||||
self.last_session = lines
|
||||
self.last_session_id = hashlib.md5(lines[0].encode()).hexdigest()
|
||||
self.last_saved_session_id = self._get_last_saved_session_id()
|
||||
|
@ -5,10 +5,8 @@ import threading
|
||||
from scapy.all import Dot11, Dot11FCS, Dot11Elt, RadioTap, sendp, sniff
|
||||
|
||||
import core
|
||||
import pwnagotchi
|
||||
import pwnagotchi.ui.faces as faces
|
||||
|
||||
from pwnagotchi.mesh import get_identity
|
||||
import pwnagotchi.mesh.wifi as wifi
|
||||
from pwnagotchi.mesh import new_session_id
|
||||
from pwnagotchi.mesh.peer import Peer
|
||||
@ -181,40 +179,3 @@ class Advertiser(object):
|
||||
|
||||
for ident in stale:
|
||||
del self._peers[ident]
|
||||
|
||||
|
||||
class AsyncAdvertiser(object):
|
||||
def __init__(self, config, view):
|
||||
self._config = config
|
||||
self._view = view
|
||||
self._public_key, self._identity = get_identity(config)
|
||||
self._advertiser = None
|
||||
|
||||
def start_advertising(self):
|
||||
_thread.start_new_thread(self._adv_worker, ())
|
||||
|
||||
def _adv_worker(self):
|
||||
# this will take some time due to scapy being slow to be imported ...
|
||||
from pwnagotchi.mesh.advertise import Advertiser
|
||||
|
||||
self._advertiser = Advertiser(
|
||||
self._config['main']['iface'],
|
||||
pwnagotchi.name(),
|
||||
pwnagotchi.version,
|
||||
self._identity,
|
||||
period=0.3,
|
||||
data=self._config['personality'])
|
||||
|
||||
self._advertiser.on_peer(self._on_new_unit, self._on_lost_unit)
|
||||
|
||||
if self._config['personality']['advertise']:
|
||||
self._advertiser.start()
|
||||
self._view.on_state_change('face', self._advertiser.on_face_change)
|
||||
else:
|
||||
core.log("advertising is disabled")
|
||||
|
||||
def _on_new_unit(self, peer):
|
||||
self._view.on_new_peer(peer)
|
||||
|
||||
def _on_lost_unit(self, peer):
|
||||
self._view.on_lost_peer(peer)
|
||||
|
@ -0,0 +1,44 @@
|
||||
import _thread
|
||||
|
||||
import core
|
||||
import pwnagotchi, pwnagotchi.plugins as plugins
|
||||
from pwnagotchi.mesh import get_identity
|
||||
|
||||
|
||||
class AsyncAdvertiser(object):
|
||||
def __init__(self, config, view):
|
||||
self._config = config
|
||||
self._view = view
|
||||
self._public_key, self._identity = get_identity(config)
|
||||
self._advertiser = None
|
||||
|
||||
def start_advertising(self):
|
||||
_thread.start_new_thread(self._adv_worker, ())
|
||||
|
||||
def _adv_worker(self):
|
||||
# this will take some time due to scapy being slow to be imported ...
|
||||
from pwnagotchi.mesh.advertise import Advertiser
|
||||
|
||||
self._advertiser = Advertiser(
|
||||
self._config['main']['iface'],
|
||||
pwnagotchi.name(),
|
||||
pwnagotchi.version,
|
||||
self._identity,
|
||||
period=0.3,
|
||||
data=self._config['personality'])
|
||||
|
||||
self._advertiser.on_peer(self._on_new_unit, self._on_lost_unit)
|
||||
|
||||
if self._config['personality']['advertise']:
|
||||
self._advertiser.start()
|
||||
self._view.on_state_change('face', self._advertiser.on_face_change)
|
||||
else:
|
||||
core.log("advertising is disabled")
|
||||
|
||||
def _on_new_unit(self, peer):
|
||||
self._view.on_new_peer(peer)
|
||||
plugins.on('peer_detected', self, peer)
|
||||
|
||||
def _on_lost_unit(self, peer):
|
||||
self._view.on_lost_peer(peer)
|
||||
plugins.on('peer_lost', self, peer)
|
@ -0,0 +1,45 @@
|
||||
import os
|
||||
import glob
|
||||
import importlib, importlib.util
|
||||
|
||||
# import core
|
||||
|
||||
default_path = os.path.join(os.path.dirname(os.path.realpath(__file__)), "default")
|
||||
loaded = {}
|
||||
|
||||
|
||||
def dummy_callback():
|
||||
pass
|
||||
|
||||
|
||||
def on(event_name, *args, **kwargs):
|
||||
global loaded
|
||||
cb_name = 'on_%s' % event_name
|
||||
for _, plugin in loaded.items():
|
||||
if cb_name in plugin.__dict__:
|
||||
# print("calling %s %s(%s)" %(cb_name, args, kwargs))
|
||||
plugin.__dict__[cb_name](*args, **kwargs)
|
||||
|
||||
|
||||
def load_from_file(filename):
|
||||
plugin_name = os.path.basename(filename.replace(".py", ""))
|
||||
spec = importlib.util.spec_from_file_location(plugin_name, filename)
|
||||
instance = importlib.util.module_from_spec(spec)
|
||||
spec.loader.exec_module(instance)
|
||||
return plugin_name, instance
|
||||
|
||||
|
||||
def load_from_path(path):
|
||||
global loaded
|
||||
|
||||
for filename in glob.glob(os.path.join(path, "*.py")):
|
||||
name, plugin = load_from_file(filename)
|
||||
if name in loaded:
|
||||
raise Exception("plugin %s already loaded from %s" % (name, plugin.__file__))
|
||||
elif not plugin.__enabled__:
|
||||
# print("plugin %s is not enabled" % name)
|
||||
pass
|
||||
else:
|
||||
loaded[name] = plugin
|
||||
|
||||
return loaded
|
@ -0,0 +1,162 @@
|
||||
__author__ = 'evilsocket@gmail.com'
|
||||
__version__ = '1.0.0'
|
||||
__name__ = 'hello_world'
|
||||
__license__ = 'GPL3'
|
||||
__description__ = 'An example plugin for pwnagotchi that implements all the available callbacks.'
|
||||
__enabled__ = False # IMPORTANT: set this to True to enable your plugin.
|
||||
|
||||
from pwnagotchi.ui.components import LabeledValue
|
||||
from pwnagotchi.ui.view import BLACK
|
||||
import pwnagotchi.ui.fonts as fonts
|
||||
import core
|
||||
|
||||
|
||||
# called when the plugin is loaded
|
||||
def on_loaded():
|
||||
core.log("WARNING: plugin %s should be disabled!" % __name__)
|
||||
|
||||
|
||||
# called to setup the ui elements
|
||||
def on_ui_setup(ui):
|
||||
# add custom UI elements
|
||||
ui.add_element('ups', LabeledValue(color=BLACK, label='UPS', value='0%/0V', position=(ui.width() / 2 - 25, 0),
|
||||
label_font=fonts.Bold, text_font=fonts.Medium))
|
||||
|
||||
|
||||
# called when the ui is updated
|
||||
def on_ui_update(ui):
|
||||
# update those elements
|
||||
some_voltage = 0.1
|
||||
some_capacity = 100.0
|
||||
|
||||
ui.set('ups', "%4.2fV/%2i%%" % (some_voltage, some_capacity))
|
||||
|
||||
|
||||
# called when the hardware display setup is done, display is an hardware specific object
|
||||
def on_display_setup(display):
|
||||
pass
|
||||
|
||||
|
||||
# called when everything is ready and the main loop is about to start
|
||||
def on_ready(agent):
|
||||
core.log("unit is ready")
|
||||
# you can run custom bettercap commands if you want
|
||||
# agent.run('ble.recon on')
|
||||
# or set a custom state
|
||||
# agent.set_bored()
|
||||
|
||||
|
||||
# called when the AI finished loading
|
||||
def on_ai_ready(agent):
|
||||
pass
|
||||
|
||||
|
||||
# called when the AI finds a new set of parameters
|
||||
def on_ai_policy(agent, policy):
|
||||
pass
|
||||
|
||||
|
||||
# called when the AI starts training for a given number of epochs
|
||||
def on_ai_training_start(agent, epochs):
|
||||
pass
|
||||
|
||||
|
||||
# called after the AI completed a training epoch
|
||||
def on_ai_training_step(agent, _locals, _globals):
|
||||
pass
|
||||
|
||||
|
||||
# called when the AI has done training
|
||||
def on_ai_training_end(agent):
|
||||
pass
|
||||
|
||||
|
||||
# called when the AI got the best reward so far
|
||||
def on_ai_best_reward(agent, reward):
|
||||
pass
|
||||
|
||||
|
||||
# called when the AI got the best reward so far
|
||||
def on_ai_worst_reward(agent, reward):
|
||||
pass
|
||||
|
||||
|
||||
# called when a non overlapping wifi channel is found to be free
|
||||
def on_free_channel(agent, channel):
|
||||
pass
|
||||
|
||||
|
||||
# called when the status is set to bored
|
||||
def on_bored(agent):
|
||||
pass
|
||||
|
||||
|
||||
# called when the status is set to sad
|
||||
def on_sad(agent):
|
||||
pass
|
||||
|
||||
|
||||
# called when the status is set to excited
|
||||
def on_excited(agent):
|
||||
pass
|
||||
|
||||
|
||||
# called when the status is set to lonely
|
||||
def on_lonely(agent):
|
||||
pass
|
||||
|
||||
|
||||
# called when the agent is rebooting the board
|
||||
def on_rebooting(agent):
|
||||
pass
|
||||
|
||||
|
||||
# called when the agent is waiting for t seconds
|
||||
def on_wait(agent, t):
|
||||
pass
|
||||
|
||||
|
||||
# called when the agent is sleeping for t seconds
|
||||
def on_sleep(agent, t):
|
||||
pass
|
||||
|
||||
|
||||
# called when the agent refreshed its access points list
|
||||
def on_wifi_update(agent, access_points):
|
||||
pass
|
||||
|
||||
|
||||
# called when the agent is sending an association frame
|
||||
def on_association(agent, access_point):
|
||||
pass
|
||||
|
||||
|
||||
# callend when the agent is deauthenticating a client station from an AP
|
||||
def on_deauthentication(agent, access_point, client_station):
|
||||
pass
|
||||
|
||||
|
||||
# callend when the agent is tuning on a specific channel
|
||||
def on_channel_hop(agent, channel):
|
||||
pass
|
||||
|
||||
|
||||
# called when a new handshake is captured, access_point and client_station are json objects
|
||||
# if the agent could match the BSSIDs to the current list, otherwise they are just the strings of the BSSIDs
|
||||
def on_handshake(agent, filename, access_point, client_station):
|
||||
pass
|
||||
|
||||
|
||||
# called when an epoch is over (where an epoch is a single loop of the main algorithm)
|
||||
def on_epoch(agent, epoch, epoch_data):
|
||||
pass
|
||||
|
||||
|
||||
# called when a new peer is detected
|
||||
def on_peer_detected(agent, peer):
|
||||
pass
|
||||
|
||||
|
||||
# called when a known peer is lost
|
||||
def on_peer_lost(agent, peer):
|
||||
pass
|
@ -0,0 +1,47 @@
|
||||
__author__ = 'evilsocket@gmail.com'
|
||||
__version__ = '1.0.0'
|
||||
__name__ = 'gps'
|
||||
__license__ = 'GPL3'
|
||||
__description__ = 'Save GPS coordinates whenever an handshake is captured.'
|
||||
__enabled__ = True # set to false if you just don't use GPS
|
||||
|
||||
import core
|
||||
import json
|
||||
import os
|
||||
|
||||
device = '/dev/ttyUSB0'
|
||||
speed = 19200
|
||||
running = False
|
||||
|
||||
|
||||
def on_loaded():
|
||||
core.log("GPS plugin loaded for %s" % device)
|
||||
|
||||
|
||||
def on_ready(agent):
|
||||
global running
|
||||
|
||||
if os.path.exists(device):
|
||||
core.log("enabling GPS bettercap's module for %s" % device)
|
||||
try:
|
||||
agent.run('gps off')
|
||||
except:
|
||||
pass
|
||||
|
||||
agent.run('set gps.device %s' % device)
|
||||
agent.run('set gps.speed %d' % speed)
|
||||
agent.run('gps on')
|
||||
running = True
|
||||
else:
|
||||
core.log("no GPS detected")
|
||||
|
||||
|
||||
def on_handshake(agent, filename, access_point, client_station):
|
||||
if running:
|
||||
info = agent.session()
|
||||
gps = info['gps']
|
||||
gps_filename = filename.replace('.pcap', '.gps.json')
|
||||
|
||||
core.log("saving GPS to %s (%s)" % (gps_filename, gps))
|
||||
with open(gps_filename, 'w+t') as fp:
|
||||
json.dump(gps, fp)
|
@ -0,0 +1,65 @@
|
||||
# Based on UPS Lite v1.1 from https://github.com/xenDE
|
||||
#
|
||||
# funtions for get UPS status - needs enable "i2c" in raspi-config
|
||||
#
|
||||
# https://github.com/linshuqin329/UPS-Lite
|
||||
#
|
||||
# For Raspberry Pi Zero Ups Power Expansion Board with Integrated Serial Port S3U4
|
||||
# https://www.ebay.de/itm/For-Raspberry-Pi-Zero-Ups-Power-Expansion-Board-with-Integrated-Serial-Port-S3U4/323873804310
|
||||
# https://www.aliexpress.com/item/32888533624.html
|
||||
__author__ = 'evilsocket@gmail.com'
|
||||
__version__ = '1.0.0'
|
||||
__name__ = 'ups_lite'
|
||||
__license__ = 'GPL3'
|
||||
__description__ = 'A plugin that will add a voltage indicator for the UPS Lite v1.1'
|
||||
__enabled__ = False
|
||||
|
||||
import struct
|
||||
|
||||
from pwnagotchi.ui.components import LabeledValue
|
||||
from pwnagotchi.ui.view import BLACK
|
||||
import pwnagotchi.ui.fonts as fonts
|
||||
|
||||
|
||||
# TODO: add enable switch in config.yml an cleanup all to the best place
|
||||
class UPS:
|
||||
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:
|
||||
address = 0x36
|
||||
read = self._bus.read_word_data(address, 2)
|
||||
swapped = struct.unpack("<H", struct.pack(">H", read))[0]
|
||||
return swapped * 1.25 / 1000 / 16
|
||||
except:
|
||||
return 0.0
|
||||
|
||||
def capacity(self):
|
||||
try:
|
||||
address = 0x36
|
||||
read = self._bus.read_word_data(address, 4)
|
||||
swapped = struct.unpack("<H", struct.pack(">H", read))[0]
|
||||
return swapped / 256
|
||||
except:
|
||||
return 0.0
|
||||
|
||||
|
||||
ups = None
|
||||
|
||||
|
||||
def on_loaded():
|
||||
global ups
|
||||
ups = UPS()
|
||||
|
||||
|
||||
def on_ui_setup(ui):
|
||||
ui.add_element('ups', LabeledValue(color=BLACK, label='UPS', value='0%/0V', position=(ui.width() / 2 - 25, 0),
|
||||
label_font=fonts.Bold, text_font=fonts.Medium))
|
||||
|
||||
|
||||
def on_ui_update(ui):
|
||||
ui.set('ups', "%4.2fV/%2i%%" % (ups.voltage(), ups.capacity()))
|
@ -1,10 +1,10 @@
|
||||
import _thread
|
||||
from threading import Lock
|
||||
|
||||
import io
|
||||
import shutil
|
||||
import core
|
||||
import os
|
||||
import pwnagotchi
|
||||
import pwnagotchi, pwnagotchi.plugins as plugins
|
||||
|
||||
from pwnagotchi.ui.view import WHITE, View
|
||||
|
||||
@ -13,7 +13,6 @@ from http.server import BaseHTTPRequestHandler, HTTPServer
|
||||
|
||||
class VideoHandler(BaseHTTPRequestHandler):
|
||||
_lock = Lock()
|
||||
_buffer = None
|
||||
_index = """<html>
|
||||
<head>
|
||||
<title>%s</title>
|
||||
@ -36,36 +35,31 @@ class VideoHandler(BaseHTTPRequestHandler):
|
||||
@staticmethod
|
||||
def render(img):
|
||||
with VideoHandler._lock:
|
||||
writer = io.BytesIO()
|
||||
img.save(writer, format='PNG')
|
||||
VideoHandler._buffer = writer.getvalue()
|
||||
img.save("/root/pwnagotchi.png", format='PNG')
|
||||
|
||||
def log_message(self, format, *args):
|
||||
return
|
||||
|
||||
def _w(self, data):
|
||||
try:
|
||||
self.wfile.write(data)
|
||||
except:
|
||||
pass
|
||||
|
||||
def do_GET(self):
|
||||
if self._buffer is None:
|
||||
self.send_response(404)
|
||||
|
||||
elif self.path == '/':
|
||||
if self.path == '/':
|
||||
self.send_response(200)
|
||||
self.send_header('Content-type', 'text/html')
|
||||
self.end_headers()
|
||||
self._w(bytes(self._index % (pwnagotchi.name(), 1000), "utf8"))
|
||||
try:
|
||||
self.wfile.write(bytes(self._index % (pwnagotchi.name(), 1000), "utf8"))
|
||||
except:
|
||||
pass
|
||||
|
||||
elif self.path.startswith('/ui'):
|
||||
with self._lock:
|
||||
self.send_response(200)
|
||||
self.send_header('Content-type', 'image/png')
|
||||
self.send_header('Content-length', '%d' % len(self._buffer))
|
||||
self.end_headers()
|
||||
self._w(self._buffer)
|
||||
try:
|
||||
with open("/root/pwnagotchi.png", 'rb') as fp:
|
||||
shutil.copyfileobj(fp, self.wfile)
|
||||
except:
|
||||
pass
|
||||
else:
|
||||
self.send_response(404)
|
||||
|
||||
@ -80,8 +74,6 @@ class Display(View):
|
||||
self._video_address = config['ui']['display']['video']['address']
|
||||
self._display_type = config['ui']['display']['type']
|
||||
self._display_color = config['ui']['display']['color']
|
||||
self.full_refresh_count = 0
|
||||
self.full_refresh_trigger = config['ui']['display']['refresh']
|
||||
|
||||
self._render_cb = None
|
||||
self._display = None
|
||||
@ -119,42 +111,44 @@ class Display(View):
|
||||
|
||||
def _init_display(self):
|
||||
if self._is_inky():
|
||||
core.log("initializing inky display")
|
||||
from inky import InkyPHAT
|
||||
self._display = InkyPHAT(self._display_color)
|
||||
self._display.set_border(InkyPHAT.BLACK)
|
||||
self._render_cb = self._inky_render
|
||||
|
||||
|
||||
elif self._is_papirus():
|
||||
from papirus import Papirus
|
||||
core.log("initializing papirus display")
|
||||
from pwnagotchi.ui.papirus.epd import EPD
|
||||
os.environ['EPD_SIZE'] = '2.0'
|
||||
self._display = Papirus()
|
||||
self._display = EPD()
|
||||
self._display.clear()
|
||||
self._render_cb = self._papirus_render
|
||||
|
||||
elif self._is_waveshare1():
|
||||
core.log("initializing waveshare v1 display")
|
||||
from pwnagotchi.ui.waveshare.v1.epd2in13 import EPD
|
||||
# core.log("display module started")
|
||||
self._display = EPD()
|
||||
self._display.init(self._display.lut_full_update)
|
||||
self._display.Clear(0xFF)
|
||||
self._display.init(self._display.lut_partial_update)
|
||||
self._render_cb = self._waveshare_render
|
||||
|
||||
|
||||
elif self._is_waveshare2():
|
||||
core.log("initializing waveshare v2 display")
|
||||
from pwnagotchi.ui.waveshare.v2.waveshare import EPD
|
||||
# core.log("display module started")
|
||||
self._display = EPD()
|
||||
self._display.init(self._display.FULL_UPDATE)
|
||||
self._display.Clear(WHITE)
|
||||
self._display.init(self._display.PART_UPDATE)
|
||||
self._render_cb = self._waveshare_render
|
||||
|
||||
|
||||
else:
|
||||
core.log("unknown display type %s" % self._display_type)
|
||||
|
||||
self.on_render(self._on_view_rendered)
|
||||
plugins.on('display_setup', self._display)
|
||||
|
||||
core.log("display type '%s' initialized (color:%s)" % (self._display_type, self._display_color))
|
||||
self.on_render(self._on_view_rendered)
|
||||
|
||||
def image(self):
|
||||
img = None
|
||||
@ -197,25 +191,16 @@ class Display(View):
|
||||
|
||||
def _waveshare_render(self):
|
||||
buf = self._display.getbuffer(self.canvas)
|
||||
if self._is_waveshare1:
|
||||
if self.full_refresh_trigger >= 0 and self.full_refresh_count == self.full_refresh_trigger:
|
||||
self._display.Clear(0x00)
|
||||
if self._is_waveshare1():
|
||||
self._display.display(buf)
|
||||
elif self._is_waveshare2:
|
||||
if self.full_refresh_trigger >= 0 and self.full_refresh_count == self.full_refresh_trigger:
|
||||
self._display.Clear(BLACK)
|
||||
elif self._is_waveshare2():
|
||||
self._display.displayPartial(buf)
|
||||
self._display.sleep()
|
||||
if self.full_refresh_trigger >= 0 and self.full_refresh_count == self.full_refresh_trigger:
|
||||
self.full_refresh_count = 0
|
||||
elif self.full_refresh_trigger >= 0:
|
||||
self.full_refresh_count += 1
|
||||
|
||||
def _on_view_rendered(self, img):
|
||||
# core.log("display::_on_view_rendered")
|
||||
VideoHandler.render(img)
|
||||
|
||||
if self._enabled:
|
||||
self.canvas = img if self._rotation == 0 else img.rotate(self._rotation)
|
||||
self.canvas = (img if self._rotation == 0 else img.rotate(self._rotation))
|
||||
if self._render_cb is not None:
|
||||
self._render_cb()
|
||||
|
@ -0,0 +1,213 @@
|
||||
#qCopyright 2013-2015 Pervasive Displays, Inc.
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||
# you may not use this file except in compliance with the License.
|
||||
# You may obtain a copy of the License at:
|
||||
#
|
||||
# http:#www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing,
|
||||
# software distributed under the License is distributed on an
|
||||
# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either
|
||||
# express or implied. See the License for the specific language
|
||||
# governing permissions and limitations under the License.
|
||||
|
||||
|
||||
from PIL import Image
|
||||
from PIL import ImageOps
|
||||
from pwnagotchi.ui.papirus.lm75b import LM75B
|
||||
import re
|
||||
import os
|
||||
import sys
|
||||
|
||||
if sys.version_info < (3,):
|
||||
def b(x):
|
||||
return x
|
||||
else:
|
||||
def b(x):
|
||||
return x.encode('ISO-8859-1')
|
||||
|
||||
class EPDError(Exception):
|
||||
def __init__(self, value):
|
||||
self.value = value
|
||||
|
||||
def __str__(self):
|
||||
return repr(self.value)
|
||||
|
||||
|
||||
class EPD(object):
|
||||
|
||||
"""EPD E-Ink interface
|
||||
|
||||
to use:
|
||||
from EPD import EPD
|
||||
|
||||
epd = EPD([path='/path/to/epd'], [auto=boolean], [rotation = 0|90|180|270])
|
||||
|
||||
image = Image.new('1', epd.size, 0)
|
||||
# draw on image
|
||||
epd.clear() # clear the panel
|
||||
epd.display(image) # tranfer image data
|
||||
epd.update() # refresh the panel image - not needed if auto=true
|
||||
"""
|
||||
|
||||
|
||||
PANEL_RE = re.compile('^([A-Za-z]+)\s+(\d+\.\d+)\s+(\d+)x(\d+)\s+COG\s+(\d+)\s+FILM\s+(\d+)\s*$', flags=0)
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
self._epd_path = '/dev/epd'
|
||||
self._width = 200
|
||||
self._height = 96
|
||||
self._panel = 'EPD 2.0'
|
||||
self._cog = 0
|
||||
self._film = 0
|
||||
self._auto = False
|
||||
self._lm75b = LM75B()
|
||||
self._rotation = 0
|
||||
self._uselm75b = True
|
||||
|
||||
if len(args) > 0:
|
||||
self._epd_path = args[0]
|
||||
elif 'epd' in kwargs:
|
||||
self._epd_path = kwargs['epd']
|
||||
|
||||
if ('auto' in kwargs) and kwargs['auto']:
|
||||
self._auto = True
|
||||
if ('rotation' in kwargs):
|
||||
rot = kwargs['rotation']
|
||||
if rot in (0, 90, 180, 270):
|
||||
self._rotation = rot
|
||||
else:
|
||||
raise EPDError('rotation can only be 0, 90, 180 or 270')
|
||||
|
||||
with open(os.path.join(self._epd_path, 'version')) as f:
|
||||
self._version = f.readline().rstrip('\n')
|
||||
|
||||
with open(os.path.join(self._epd_path, 'panel')) as f:
|
||||
line = f.readline().rstrip('\n')
|
||||
m = self.PANEL_RE.match(line)
|
||||
if m is None:
|
||||
raise EPDError('invalid panel string')
|
||||
self._panel = m.group(1) + ' ' + m.group(2)
|
||||
self._width = int(m.group(3))
|
||||
self._height = int(m.group(4))
|
||||
self._cog = int(m.group(5))
|
||||
self._film = int(m.group(6))
|
||||
|
||||
if self._width < 1 or self._height < 1:
|
||||
raise EPDError('invalid panel geometry')
|
||||
if self._rotation in (90, 270):
|
||||
self._width, self._height = self._height, self._width
|
||||
|
||||
@property
|
||||
def size(self):
|
||||
return (self._width, self._height)
|
||||
|
||||
@property
|
||||
def width(self):
|
||||
return self._width
|
||||
|
||||
@property
|
||||
def height(self):
|
||||
return self._height
|
||||
|
||||
@property
|
||||
def panel(self):
|
||||
return self._panel
|
||||
|
||||
@property
|
||||
def version(self):
|
||||
return self._version
|
||||
|
||||
@property
|
||||
def cog(self):
|
||||
return self._cog
|
||||
|
||||
@property
|
||||
def film(self):
|
||||
return self._film
|
||||
|
||||
@property
|
||||
def auto(self):
|
||||
return self._auto
|
||||
|
||||
@auto.setter
|
||||
def auto(self, flag):
|
||||
if flag:
|
||||
self._auto = True
|
||||
else:
|
||||
self._auto = False
|
||||
|
||||
@property
|
||||
def rotation(self):
|
||||
return self._rotation
|
||||
|
||||
@rotation.setter
|
||||
def rotation(self, rot):
|
||||
if rot not in (0, 90, 180, 270):
|
||||
raise EPDError('rotation can only be 0, 90, 180 or 270')
|
||||
if abs(self._rotation - rot) == 90 or abs(self._rotation - rot) == 270:
|
||||
self._width, self._height = self._height, self._width
|
||||
self._rotation = rot
|
||||
|
||||
@property
|
||||
def use_lm75b(self):
|
||||
return self._uselm75b
|
||||
|
||||
@use_lm75b.setter
|
||||
def use_lm75b(self, flag):
|
||||
if flag:
|
||||
self._uselm75b = True
|
||||
else:
|
||||
self._uselm75b = False
|
||||
|
||||
def error_status(self):
|
||||
with open(os.path.join(self._epd_path, 'error'), 'r') as f:
|
||||
return(f.readline().rstrip('\n'))
|
||||
|
||||
def rotation_angle(self, rotation):
|
||||
angles = { 90 : Image.ROTATE_90, 180 : Image.ROTATE_180, 270 : Image.ROTATE_270 }
|
||||
return angles[rotation]
|
||||
|
||||
def display(self, image):
|
||||
|
||||
# attempt grayscale conversion, and then to single bit
|
||||
# better to do this before calling this if the image is to
|
||||
# be dispayed several times
|
||||
if image.mode != "1":
|
||||
image = ImageOps.grayscale(image).convert("1", dither=Image.FLOYDSTEINBERG)
|
||||
|
||||
if image.mode != "1":
|
||||
raise EPDError('only single bit images are supported')
|
||||
|
||||
if image.size != self.size:
|
||||
raise EPDError('image size mismatch')
|
||||
|
||||
if self._rotation != 0:
|
||||
image = image.transpose(self.rotation_angle(self._rotation))
|
||||
|
||||
with open(os.path.join(self._epd_path, 'LE', 'display_inverse'), 'r+b') as f:
|
||||
f.write(image.tobytes())
|
||||
|
||||
if self.auto:
|
||||
self.update()
|
||||
|
||||
|
||||
def update(self):
|
||||
self._command('U')
|
||||
|
||||
def partial_update(self):
|
||||
self._command('P')
|
||||
|
||||
def fast_update(self):
|
||||
self._command('F')
|
||||
|
||||
def clear(self):
|
||||
self._command('C')
|
||||
|
||||
def _command(self, c):
|
||||
if self._uselm75b:
|
||||
with open(os.path.join(self._epd_path, 'temperature'), 'wb') as f:
|
||||
f.write(b(repr(self._lm75b.getTempC())))
|
||||
with open(os.path.join(self._epd_path, 'command'), 'wb') as f:
|
||||
f.write(b(c))
|
@ -0,0 +1,46 @@
|
||||
# Minimal support for LM75b temperature sensor on the Papirus HAT / Papirus Zero
|
||||
# This module allows you to read the temperature.
|
||||
# The OS-output (Over-temperature Shutdown) connected to GPIO xx (pin 11) is not supported
|
||||
# by this module
|
||||
#
|
||||
|
||||
from __future__ import (print_function, division)
|
||||
|
||||
import smbus
|
||||
|
||||
LM75B_ADDRESS = 0x48
|
||||
|
||||
LM75B_TEMP_REGISTER = 0
|
||||
LM75B_CONF_REGISTER = 1
|
||||
LM75B_THYST_REGISTER = 2
|
||||
LM75B_TOS_REGISTER = 3
|
||||
|
||||
LM75B_CONF_NORMAL = 0
|
||||
|
||||
class LM75B(object):
|
||||
def __init__(self, address=LM75B_ADDRESS, busnum=1):
|
||||
self._address = address
|
||||
self._bus = smbus.SMBus(busnum)
|
||||
self._bus.write_byte_data(self._address, LM75B_CONF_REGISTER, LM75B_CONF_NORMAL)
|
||||
|
||||
def getTempCFloat(self):
|
||||
"""Return temperature in degrees Celsius as float"""
|
||||
raw = self._bus.read_word_data(self._address, LM75B_TEMP_REGISTER) & 0xFFFF
|
||||
raw = ((raw << 8) & 0xFF00) + (raw >> 8)
|
||||
return (raw / 32.0) / 8.0
|
||||
|
||||
def getTempFFloat(self):
|
||||
"""Return temperature in degrees Fahrenheit as float"""
|
||||
return (self.getTempCFloat() * (9.0 / 5.0)) + 32.0
|
||||
|
||||
def getTempC(self):
|
||||
"""Return temperature in degrees Celsius as integer, so it can be
|
||||
used to write to /dev/epd/temperature"""
|
||||
raw = self._bus.read_word_data(self._address, LM75B_TEMP_REGISTER) & 0xFFFF
|
||||
raw = ((raw << 8) & 0xFF00) + (raw >> 8)
|
||||
return (raw + 128) // 256 # round to nearest integer
|
||||
|
||||
if __name__ == "__main__":
|
||||
sens = LM75B()
|
||||
print(sens.getTempC(), sens.getTempFFloat())
|
||||
|
@ -7,6 +7,9 @@ class State(object):
|
||||
self._lock = Lock()
|
||||
self._listeners = {}
|
||||
|
||||
def add_element(self, key, elem):
|
||||
self._state[key] = elem
|
||||
|
||||
def add_listener(self, key, cb):
|
||||
with self._lock:
|
||||
self._listeners[key] = cb
|
||||
|
@ -4,7 +4,7 @@ import time
|
||||
from PIL import Image, ImageDraw
|
||||
|
||||
import core
|
||||
import pwnagotchi
|
||||
import pwnagotchi.plugins as plugins
|
||||
from pwnagotchi.voice import Voice
|
||||
|
||||
import pwnagotchi.ui.fonts as fonts
|
||||
@ -41,7 +41,7 @@ def setup_display_specifics(config):
|
||||
name_pos = (int(width / 2) - 15, int(height * .15))
|
||||
status_pos = (int(width / 2) - 15, int(height * .30))
|
||||
|
||||
elif config['ui']['display']['type'] in ('ws_1', 'ws1', 'waveshare_1', 'waveshare1',
|
||||
elif config['ui']['display']['type'] in ('ws_1', 'ws1', 'waveshare_1', 'waveshare1',
|
||||
'ws_2', 'ws2', 'waveshare_2', 'waveshare2'):
|
||||
fonts.setup(10, 9, 10, 35)
|
||||
|
||||
@ -105,8 +105,19 @@ class View(object):
|
||||
for key, value in state.items():
|
||||
self._state.set(key, value)
|
||||
|
||||
plugins.on('ui_setup', self)
|
||||
|
||||
_thread.start_new_thread(self._refresh_handler, ())
|
||||
|
||||
def add_element(self, key, elem):
|
||||
self._state.add_element(key, elem)
|
||||
|
||||
def width(self):
|
||||
return self._width
|
||||
|
||||
def height(self):
|
||||
return self._height
|
||||
|
||||
def on_state_change(self, key, cb):
|
||||
self._state.add_listener(key, cb)
|
||||
|
||||
@ -294,6 +305,8 @@ class View(object):
|
||||
self._canvas = Image.new('1', (self._width, self._height), WHITE)
|
||||
drawer = ImageDraw.Draw(self._canvas)
|
||||
|
||||
plugins.on('ui_update', self)
|
||||
|
||||
for key, lv in self._state.items():
|
||||
lv.draw(self._canvas, drawer)
|
||||
|
||||
|
@ -1,218 +1,225 @@
|
||||
# //*****************************************************************************
|
||||
# * | File : epd2in13.py
|
||||
# * | Author : Waveshare team
|
||||
# * | Function : Electronic paper driver
|
||||
# * | Info :
|
||||
# *----------------
|
||||
# * | This version: V3.1
|
||||
# * | Date : 2019-03-20
|
||||
# * | Info : python3 demo
|
||||
# * fix: TurnOnDisplay()
|
||||
# ******************************************************************************//
|
||||
# Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
# of this software and associated documnetation files (the "Software"), to deal
|
||||
# in the Software without restriction, including without limitation the rights
|
||||
# to use, copy, modify, merge, publish, distribute, sublicense, and//or sell
|
||||
# copies of the Software, and to permit persons to whom the Software is
|
||||
# furished to do so, subject to the following conditions:
|
||||
#
|
||||
# The above copyright notice and this permission notice shall be included in
|
||||
# all copies or substantial portions of the Software.
|
||||
#
|
||||
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
# FITNESS OR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
# LIABILITY WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
# THE SOFTWARE.
|
||||
#
|
||||
|
||||
|
||||
from . import epdconfig
|
||||
from PIL import Image
|
||||
import RPi.GPIO as GPIO
|
||||
# import numpy as np
|
||||
|
||||
# Display resolution
|
||||
EPD_WIDTH = 122
|
||||
EPD_HEIGHT = 250
|
||||
|
||||
class EPD:
|
||||
def __init__(self):
|
||||
self.reset_pin = epdconfig.RST_PIN
|
||||
self.dc_pin = epdconfig.DC_PIN
|
||||
self.busy_pin = epdconfig.BUSY_PIN
|
||||
self.width = EPD_WIDTH
|
||||
self.height = EPD_HEIGHT
|
||||
lut_full_update = [
|
||||
0x22, 0x55, 0xAA, 0x55, 0xAA, 0x55, 0xAA, 0x11,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x1E, 0x1E, 0x1E, 0x1E, 0x1E, 0x1E, 0x1E, 0x1E,
|
||||
0x01, 0x00, 0x00, 0x00, 0x00, 0x00
|
||||
]
|
||||
|
||||
lut_partial_update = [
|
||||
0x18, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x0F, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00
|
||||
]
|
||||
|
||||
# Hardware reset
|
||||
def reset(self):
|
||||
epdconfig.digital_write(self.reset_pin, GPIO.HIGH)
|
||||
epdconfig.delay_ms(200)
|
||||
epdconfig.digital_write(self.reset_pin, GPIO.LOW) # module reset
|
||||
epdconfig.delay_ms(200)
|
||||
epdconfig.digital_write(self.reset_pin, GPIO.HIGH)
|
||||
epdconfig.delay_ms(200)
|
||||
|
||||
def send_command(self, command):
|
||||
epdconfig.digital_write(self.dc_pin, GPIO.LOW)
|
||||
epdconfig.spi_writebyte([command])
|
||||
|
||||
def send_data(self, data):
|
||||
epdconfig.digital_write(self.dc_pin, GPIO.HIGH)
|
||||
epdconfig.spi_writebyte([data])
|
||||
|
||||
def wait_until_idle(self):
|
||||
# print("busy")
|
||||
while(epdconfig.digital_read(self.busy_pin) == 1): # 0: idle, 1: busy
|
||||
epdconfig.delay_ms(100)
|
||||
# print("free busy")
|
||||
|
||||
def TurnOnDisplay(self):
|
||||
self.send_command(0x22) # DISPLAY_UPDATE_CONTROL_2
|
||||
self.send_data(0xC4)
|
||||
self.send_command(0x20) # MASTER_ACTIVATION
|
||||
self.send_command(0xFF) # TERMINATE_FRAME_READ_WRITE
|
||||
self.wait_until_idle()
|
||||
|
||||
def init(self, lut):
|
||||
if (epdconfig.module_init() != 0):
|
||||
return -1
|
||||
# EPD hardware init start
|
||||
self.reset()
|
||||
self.send_command(0x01) # DRIVER_OUTPUT_CONTROL
|
||||
self.send_data((EPD_HEIGHT - 1) & 0xFF)
|
||||
self.send_data(((EPD_HEIGHT - 1) >> 8) & 0xFF)
|
||||
self.send_data(0x00) # GD = 0 SM = 0 TB = 0
|
||||
|
||||
self.send_command(0x0C) # BOOSTER_SOFT_START_CONTROL
|
||||
self.send_data(0xD7)
|
||||
self.send_data(0xD6)
|
||||
self.send_data(0x9D)
|
||||
|
||||
self.send_command(0x2C) # WRITE_VCOM_REGISTER
|
||||
self.send_data(0xA8) # VCOM 7C
|
||||
|
||||
self.send_command(0x3A) # SET_DUMMY_LINE_PERIOD
|
||||
self.send_data(0x1A) # 4 dummy lines per gate
|
||||
|
||||
self.send_command(0x3B) # SET_GATE_TIME
|
||||
self.send_data(0x08) # 2us per line
|
||||
|
||||
self.send_command(0X3C) # BORDER_WAVEFORM_CONTROL
|
||||
self.send_data(0x03)
|
||||
|
||||
self.send_command(0X11) # DATA_ENTRY_MODE_SETTING
|
||||
self.send_data(0x03) # X increment; Y increment
|
||||
|
||||
# WRITE_LUT_REGISTER
|
||||
self.send_command(0x32)
|
||||
for count in range(30):
|
||||
self.send_data(lut[count])
|
||||
|
||||
return 0
|
||||
|
||||
##
|
||||
# @brief: specify the memory area for data R//W
|
||||
##
|
||||
def SetWindows(self, x_start, y_start, x_end, y_end):
|
||||
self.send_command(0x44) # SET_RAM_X_ADDRESS_START_END_POSITION
|
||||
self.send_data((x_start >> 3) & 0xFF)
|
||||
self.send_data((x_end >> 3) & 0xFF)
|
||||
self.send_command(0x45) # SET_RAM_Y_ADDRESS_START_END_POSITION
|
||||
self.send_data(y_start & 0xFF)
|
||||
self.send_data((y_start >> 8) & 0xFF)
|
||||
self.send_data(y_end & 0xFF)
|
||||
self.send_data((y_end >> 8) & 0xFF)
|
||||
|
||||
##
|
||||
# @brief: specify the start point for data R//W
|
||||
##
|
||||
def SetCursor(self, x, y):
|
||||
self.send_command(0x4E) # SET_RAM_X_ADDRESS_COUNTER
|
||||
# x point must be the multiple of 8 or the last 3 bits will be ignored
|
||||
self.send_data((x >> 3) & 0xFF)
|
||||
self.send_command(0x4F) # SET_RAM_Y_ADDRESS_COUNTER
|
||||
self.send_data(y & 0xFF)
|
||||
self.send_data((y >> 8) & 0xFF)
|
||||
self.wait_until_idle()
|
||||
|
||||
def getbuffer(self, image):
|
||||
if self.width%8 == 0:
|
||||
linewidth = self.width//8
|
||||
else:
|
||||
linewidth = self.width//8 + 1
|
||||
|
||||
buf = [0xFF] * (linewidth * self.height)
|
||||
image_monocolor = image.convert('1')
|
||||
imwidth, imheight = image_monocolor.size
|
||||
pixels = image_monocolor.load()
|
||||
|
||||
if(imwidth == self.width and imheight == self.height):
|
||||
# print("Vertical")
|
||||
for y in range(imheight):
|
||||
for x in range(imwidth):
|
||||
if pixels[x, y] == 0:
|
||||
# x = imwidth - x
|
||||
buf[x // 8 + y * linewidth] &= ~(0x80 >> (x % 8))
|
||||
elif(imwidth == self.height and imheight == self.width):
|
||||
# print("Horizontal")
|
||||
for y in range(imheight):
|
||||
for x in range(imwidth):
|
||||
newx = y
|
||||
newy = self.height - x - 1
|
||||
if pixels[x, y] == 0:
|
||||
# newy = imwidth - newy - 1
|
||||
buf[newx // 8 + newy*linewidth] &= ~(0x80 >> (y % 8))
|
||||
return buf
|
||||
|
||||
|
||||
def display(self, image):
|
||||
if self.width%8 == 0:
|
||||
linewidth = self.width//8
|
||||
else:
|
||||
linewidth = self.width//8 + 1
|
||||
|
||||
self.SetWindows(0, 0, EPD_WIDTH, EPD_HEIGHT);
|
||||
for j in range(0, self.height):
|
||||
self.SetCursor(0, j);
|
||||
self.send_command(0x24);
|
||||
for i in range(0, linewidth):
|
||||
self.send_data(image[i + j * linewidth])
|
||||
self.TurnOnDisplay()
|
||||
|
||||
def Clear(self, color):
|
||||
if self.width%8 == 0:
|
||||
linewidth = self.width//8
|
||||
else:
|
||||
linewidth = self.width//8 + 1
|
||||
|
||||
self.SetWindows(0, 0, EPD_WIDTH, EPD_HEIGHT);
|
||||
for j in range(0, self.height):
|
||||
self.SetCursor(0, j);
|
||||
self.send_command(0x24);
|
||||
for i in range(0, linewidth):
|
||||
self.send_data(color)
|
||||
self.TurnOnDisplay()
|
||||
|
||||
def sleep(self):
|
||||
self.send_command(0x10) #enter deep sleep
|
||||
# self.send_data(0x01)
|
||||
epdconfig.delay_ms(100)
|
||||
|
||||
### END OF FILE ###
|
||||
|
||||
# *****************************************************************************
|
||||
# * | File : epd2in13.py
|
||||
# * | Author : Waveshare team
|
||||
# * | Function : Electronic paper driver
|
||||
# * | Info :
|
||||
# *----------------
|
||||
# * | This version: V4.0
|
||||
# * | Date : 2019-06-20
|
||||
# # | Info : python demo
|
||||
# -----------------------------------------------------------------------------
|
||||
# Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
# of this software and associated documnetation files (the "Software"), to deal
|
||||
# in the Software without restriction, including without limitation the rights
|
||||
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
# copies of the Software, and to permit persons to whom the Software is
|
||||
# furished to do so, subject to the following conditions:
|
||||
#
|
||||
# The above copyright notice and this permission notice shall be included in
|
||||
# all copies or substantial portions of the Software.
|
||||
#
|
||||
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
# FITNESS OR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
# LIABILITY WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
# THE SOFTWARE.
|
||||
#
|
||||
|
||||
|
||||
import logging
|
||||
from . import epdconfig
|
||||
import numpy as np
|
||||
|
||||
# Display resolution
|
||||
EPD_WIDTH = 122
|
||||
EPD_HEIGHT = 250
|
||||
|
||||
class EPD:
|
||||
def __init__(self):
|
||||
self.reset_pin = epdconfig.RST_PIN
|
||||
self.dc_pin = epdconfig.DC_PIN
|
||||
self.busy_pin = epdconfig.BUSY_PIN
|
||||
self.cs_pin = epdconfig.CS_PIN
|
||||
self.width = EPD_WIDTH
|
||||
self.height = EPD_HEIGHT
|
||||
|
||||
lut_full_update = [
|
||||
0x22, 0x55, 0xAA, 0x55, 0xAA, 0x55, 0xAA, 0x11,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x1E, 0x1E, 0x1E, 0x1E, 0x1E, 0x1E, 0x1E, 0x1E,
|
||||
0x01, 0x00, 0x00, 0x00, 0x00, 0x00
|
||||
]
|
||||
|
||||
lut_partial_update = [
|
||||
0x18, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x0F, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00
|
||||
]
|
||||
|
||||
# Hardware reset
|
||||
def reset(self):
|
||||
epdconfig.digital_write(self.cs_pin, 0)
|
||||
epdconfig.digital_write(self.reset_pin, 1)
|
||||
epdconfig.delay_ms(200)
|
||||
epdconfig.digital_write(self.reset_pin, 0)
|
||||
epdconfig.delay_ms(10)
|
||||
epdconfig.digital_write(self.reset_pin, 1)
|
||||
epdconfig.delay_ms(200)
|
||||
epdconfig.digital_write(self.cs_pin, 1)
|
||||
|
||||
def send_command(self, command):
|
||||
epdconfig.digital_write(self.cs_pin, 0)
|
||||
epdconfig.digital_write(self.dc_pin, 0)
|
||||
epdconfig.spi_writebyte([command])
|
||||
epdconfig.digital_write(self.cs_pin, 1)
|
||||
|
||||
def send_data(self, data):
|
||||
epdconfig.digital_write(self.cs_pin, 0)
|
||||
epdconfig.digital_write(self.dc_pin, 1)
|
||||
epdconfig.spi_writebyte([data])
|
||||
epdconfig.digital_write(self.cs_pin, 1)
|
||||
|
||||
def ReadBusy(self):
|
||||
while(epdconfig.digital_read(self.busy_pin) == 1): # 0: idle, 1: busy
|
||||
epdconfig.delay_ms(100)
|
||||
|
||||
def TurnOnDisplay(self):
|
||||
self.send_command(0x22) # DISPLAY_UPDATE_CONTROL_2
|
||||
self.send_data(0xC4)
|
||||
self.send_command(0x20) # MASTER_ACTIVATION
|
||||
self.send_command(0xFF) # TERMINATE_FRAME_READ_WRITE
|
||||
|
||||
logging.debug("e-Paper busy")
|
||||
self.ReadBusy()
|
||||
logging.debug("e-Paper busy release")
|
||||
|
||||
def init(self, lut):
|
||||
if (epdconfig.module_init() != 0):
|
||||
return -1
|
||||
# EPD hardware init start
|
||||
self.reset()
|
||||
self.send_command(0x01) # DRIVER_OUTPUT_CONTROL
|
||||
self.send_data((EPD_HEIGHT - 1) & 0xFF)
|
||||
self.send_data(((EPD_HEIGHT - 1) >> 8) & 0xFF)
|
||||
self.send_data(0x00) # GD = 0 SM = 0 TB = 0
|
||||
|
||||
self.send_command(0x0C) # BOOSTER_SOFT_START_CONTROL
|
||||
self.send_data(0xD7)
|
||||
self.send_data(0xD6)
|
||||
self.send_data(0x9D)
|
||||
|
||||
self.send_command(0x2C) # WRITE_VCOM_REGISTER
|
||||
self.send_data(0xA8) # VCOM 7C
|
||||
|
||||
self.send_command(0x3A) # SET_DUMMY_LINE_PERIOD
|
||||
self.send_data(0x1A) # 4 dummy lines per gate
|
||||
|
||||
self.send_command(0x3B) # SET_GATE_TIME
|
||||
self.send_data(0x08) # 2us per line
|
||||
|
||||
self.send_command(0X3C) # BORDER_WAVEFORM_CONTROL
|
||||
self.send_data(0x03)
|
||||
|
||||
self.send_command(0X11) # DATA_ENTRY_MODE_SETTING
|
||||
self.send_data(0x03) # X increment; Y increment
|
||||
|
||||
# WRITE_LUT_REGISTER
|
||||
self.send_command(0x32)
|
||||
for count in range(30):
|
||||
self.send_data(lut[count])
|
||||
|
||||
return 0
|
||||
|
||||
##
|
||||
# @brief: specify the memory area for data R/W
|
||||
##
|
||||
def SetWindows(self, x_start, y_start, x_end, y_end):
|
||||
self.send_command(0x44) # SET_RAM_X_ADDRESS_START_END_POSITION
|
||||
self.send_data((x_start >> 3) & 0xFF)
|
||||
self.send_data((x_end >> 3) & 0xFF)
|
||||
self.send_command(0x45) # SET_RAM_Y_ADDRESS_START_END_POSITION
|
||||
self.send_data(y_start & 0xFF)
|
||||
self.send_data((y_start >> 8) & 0xFF)
|
||||
self.send_data(y_end & 0xFF)
|
||||
self.send_data((y_end >> 8) & 0xFF)
|
||||
|
||||
##
|
||||
# @brief: specify the start point for data R/W
|
||||
##
|
||||
def SetCursor(self, x, y):
|
||||
self.send_command(0x4E) # SET_RAM_X_ADDRESS_COUNTER
|
||||
# x point must be the multiple of 8 or the last 3 bits will be ignored
|
||||
self.send_data((x >> 3) & 0xFF)
|
||||
self.send_command(0x4F) # SET_RAM_Y_ADDRESS_COUNTER
|
||||
self.send_data(y & 0xFF)
|
||||
self.send_data((y >> 8) & 0xFF)
|
||||
self.ReadBusy()
|
||||
|
||||
def getbuffer(self, image):
|
||||
if self.width%8 == 0:
|
||||
linewidth = int(self.width/8)
|
||||
else:
|
||||
linewidth = int(self.width/8) + 1
|
||||
|
||||
buf = [0xFF] * (linewidth * self.height)
|
||||
image_monocolor = image.convert('1')
|
||||
imwidth, imheight = image_monocolor.size
|
||||
pixels = image_monocolor.load()
|
||||
|
||||
if(imwidth == self.width and imheight == self.height):
|
||||
for y in range(imheight):
|
||||
for x in range(imwidth):
|
||||
if pixels[x, y] == 0:
|
||||
# x = imwidth - x
|
||||
buf[int(x / 8) + y * linewidth] &= ~(0x80 >> (x % 8))
|
||||
elif(imwidth == self.height and imheight == self.width):
|
||||
for y in range(imheight):
|
||||
for x in range(imwidth):
|
||||
newx = y
|
||||
newy = self.height - x - 1
|
||||
if pixels[x, y] == 0:
|
||||
# newy = imwidth - newy - 1
|
||||
buf[int(newx / 8) + newy*linewidth] &= ~(0x80 >> (y % 8))
|
||||
return buf
|
||||
|
||||
|
||||
def display(self, image):
|
||||
if self.width%8 == 0:
|
||||
linewidth = int(self.width/8)
|
||||
else:
|
||||
linewidth = int(self.width/8) + 1
|
||||
|
||||
self.SetWindows(0, 0, self.width, self.height);
|
||||
for j in range(0, self.height):
|
||||
self.SetCursor(0, j);
|
||||
self.send_command(0x24);
|
||||
for i in range(0, linewidth):
|
||||
self.send_data(image[i + j * linewidth])
|
||||
self.TurnOnDisplay()
|
||||
|
||||
def Clear(self, color):
|
||||
if self.width%8 == 0:
|
||||
linewidth = int(self.width/8)
|
||||
else:
|
||||
linewidth = int(self.width/8) + 1
|
||||
|
||||
self.SetWindows(0, 0, self.width, self.height);
|
||||
for j in range(0, self.height):
|
||||
self.SetCursor(0, j);
|
||||
self.send_command(0x24);
|
||||
for i in range(0, linewidth):
|
||||
self.send_data(color)
|
||||
self.TurnOnDisplay()
|
||||
|
||||
def sleep(self):
|
||||
self.send_command(0x10) #enter deep sleep
|
||||
self.send_data(0x01)
|
||||
epdconfig.delay_ms(100)
|
||||
|
||||
epdconfig.module_exit()
|
||||
|
||||
### END OF FILE ###
|
||||
|
||||
|
@ -1,19 +1,13 @@
|
||||
# /*****************************************************************************
|
||||
# * | File : EPD_1in54.py
|
||||
# * | File : epdconfig.py
|
||||
# * | Author : Waveshare team
|
||||
# * | Function : Hardware underlying interface
|
||||
# * | Info :
|
||||
# *----------------
|
||||
# * | This version: V2.0
|
||||
# * | Date : 2018-11-01
|
||||
# * | This version: V1.0
|
||||
# * | Date : 2019-06-21
|
||||
# * | Info :
|
||||
# * 1.Remove:
|
||||
# digital_write(self, pin, value)
|
||||
# digital_read(self, pin)
|
||||
# delay_ms(self, delaytime)
|
||||
# set_lut(self, lut)
|
||||
# self.lut = self.lut_full_update
|
||||
# ******************************************************************************/
|
||||
# ******************************************************************************
|
||||
# Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
# of this software and associated documnetation files (the "Software"), to deal
|
||||
# in the Software without restriction, including without limitation the rights
|
||||
@ -33,41 +27,128 @@
|
||||
# THE SOFTWARE.
|
||||
#
|
||||
|
||||
|
||||
import spidev
|
||||
import RPi.GPIO as GPIO
|
||||
import os
|
||||
import logging
|
||||
import sys
|
||||
import time
|
||||
|
||||
# Pin definition
|
||||
RST_PIN = 17
|
||||
DC_PIN = 25
|
||||
CS_PIN = 8
|
||||
BUSY_PIN = 24
|
||||
|
||||
# SPI device, bus = 0, device = 0
|
||||
SPI = spidev.SpiDev(0, 0)
|
||||
class RaspberryPi:
|
||||
# Pin definition
|
||||
RST_PIN = 17
|
||||
DC_PIN = 25
|
||||
CS_PIN = 8
|
||||
BUSY_PIN = 24
|
||||
|
||||
def digital_write(pin, value):
|
||||
GPIO.output(pin, value)
|
||||
def __init__(self):
|
||||
import spidev
|
||||
import RPi.GPIO
|
||||
|
||||
def digital_read(pin):
|
||||
return GPIO.input(BUSY_PIN)
|
||||
self.GPIO = RPi.GPIO
|
||||
|
||||
def delay_ms(delaytime):
|
||||
time.sleep(delaytime / 1000.0)
|
||||
# SPI device, bus = 0, device = 0
|
||||
self.SPI = spidev.SpiDev(0, 0)
|
||||
|
||||
def spi_writebyte(data):
|
||||
SPI.writebytes(data)
|
||||
def digital_write(self, pin, value):
|
||||
self.GPIO.output(pin, value)
|
||||
|
||||
def digital_read(self, pin):
|
||||
return self.GPIO.input(self.BUSY_PIN)
|
||||
|
||||
def delay_ms(self, delaytime):
|
||||
time.sleep(delaytime / 1000.0)
|
||||
|
||||
def spi_writebyte(self, data):
|
||||
self.SPI.writebytes(data)
|
||||
|
||||
def module_init(self):
|
||||
self.GPIO.setmode(self.GPIO.BCM)
|
||||
self.GPIO.setwarnings(False)
|
||||
self.GPIO.setup(self.RST_PIN, self.GPIO.OUT)
|
||||
self.GPIO.setup(self.DC_PIN, self.GPIO.OUT)
|
||||
self.GPIO.setup(self.CS_PIN, self.GPIO.OUT)
|
||||
self.GPIO.setup(self.BUSY_PIN, self.GPIO.IN)
|
||||
self.SPI.max_speed_hz = 4000000
|
||||
self.SPI.mode = 0b00
|
||||
return 0
|
||||
|
||||
def module_exit(self):
|
||||
logging.debug("spi end")
|
||||
self.SPI.close()
|
||||
|
||||
logging.debug("close 5V, Module enters 0 power consumption ...")
|
||||
self.GPIO.output(self.RST_PIN, 0)
|
||||
self.GPIO.output(self.DC_PIN, 0)
|
||||
|
||||
self.GPIO.cleanup()
|
||||
|
||||
|
||||
class JetsonNano:
|
||||
# Pin definition
|
||||
RST_PIN = 17
|
||||
DC_PIN = 25
|
||||
CS_PIN = 8
|
||||
BUSY_PIN = 24
|
||||
|
||||
def __init__(self):
|
||||
import ctypes
|
||||
find_dirs = [
|
||||
os.path.dirname(os.path.realpath(__file__)),
|
||||
'/usr/local/lib',
|
||||
'/usr/lib',
|
||||
]
|
||||
self.SPI = None
|
||||
for find_dir in find_dirs:
|
||||
so_filename = os.path.join(find_dir, 'sysfs_software_spi.so')
|
||||
if os.path.exists(so_filename):
|
||||
self.SPI = ctypes.cdll.LoadLibrary(so_filename)
|
||||
break
|
||||
if self.SPI is None:
|
||||
raise RuntimeError('Cannot find sysfs_software_spi.so')
|
||||
|
||||
import Jetson.GPIO
|
||||
self.GPIO = Jetson.GPIO
|
||||
|
||||
def digital_write(self, pin, value):
|
||||
self.GPIO.output(pin, value)
|
||||
|
||||
def digital_read(self, pin):
|
||||
return self.GPIO.input(self.BUSY_PIN)
|
||||
|
||||
def delay_ms(self, delaytime):
|
||||
time.sleep(delaytime / 1000.0)
|
||||
|
||||
def spi_writebyte(self, data):
|
||||
self.SPI.SYSFS_software_spi_transfer(data[0])
|
||||
|
||||
def module_init(self):
|
||||
self.GPIO.setmode(self.GPIO.BCM)
|
||||
self.GPIO.setwarnings(False)
|
||||
self.GPIO.setup(self.RST_PIN, self.GPIO.OUT)
|
||||
self.GPIO.setup(self.DC_PIN, self.GPIO.OUT)
|
||||
self.GPIO.setup(self.CS_PIN, self.GPIO.OUT)
|
||||
self.GPIO.setup(self.BUSY_PIN, self.GPIO.IN)
|
||||
self.SPI.SYSFS_software_spi_begin()
|
||||
return 0
|
||||
|
||||
def module_exit(self):
|
||||
logging.debug("spi end")
|
||||
self.SPI.SYSFS_software_spi_end()
|
||||
|
||||
logging.debug("close 5V, Module enters 0 power consumption ...")
|
||||
self.GPIO.output(self.RST_PIN, 0)
|
||||
self.GPIO.output(self.DC_PIN, 0)
|
||||
|
||||
self.GPIO.cleanup()
|
||||
|
||||
|
||||
if os.path.exists('/sys/bus/platform/drivers/gpiomem-bcm2835'):
|
||||
implementation = RaspberryPi()
|
||||
else:
|
||||
implementation = JetsonNano()
|
||||
|
||||
for func in [x for x in dir(implementation) if not x.startswith('_')]:
|
||||
setattr(sys.modules[__name__], func, getattr(implementation, func))
|
||||
|
||||
def module_init():
|
||||
GPIO.setmode(GPIO.BCM)
|
||||
GPIO.setwarnings(False)
|
||||
GPIO.setup(RST_PIN, GPIO.OUT)
|
||||
GPIO.setup(DC_PIN, GPIO.OUT)
|
||||
GPIO.setup(CS_PIN, GPIO.OUT)
|
||||
GPIO.setup(BUSY_PIN, GPIO.IN)
|
||||
SPI.max_speed_hz = 2000000
|
||||
SPI.mode = 0b00
|
||||
return 0;
|
||||
|
||||
### END OF FILE ###
|
||||
|
@ -335,4 +335,4 @@ class EPD:
|
||||
self.send_data(0x01)
|
||||
delay_ms(100)
|
||||
|
||||
### END OF FILE ###
|
||||
### END OF FILE ###
|
@ -17,44 +17,36 @@ class Voice:
|
||||
def default(self):
|
||||
return self._('ZzzzZZzzzzZzzz')
|
||||
|
||||
|
||||
def on_starting(self):
|
||||
return random.choice([ \
|
||||
self._('Hi, I\'m Pwnagotchi!\nStarting ...'),
|
||||
self._('New day, new hunt,\nnew pwns!'),
|
||||
self._('Hack the Planet!')])
|
||||
|
||||
|
||||
def on_ai_ready(self):
|
||||
return random.choice([
|
||||
self._('AI ready.'),
|
||||
self._('The neural network\nis ready.')])
|
||||
|
||||
|
||||
def on_normal(self):
|
||||
return random.choice([ \
|
||||
'',
|
||||
'...'])
|
||||
|
||||
|
||||
def on_free_channel(channel):
|
||||
def on_free_channel(self, channel):
|
||||
return self._('Hey, channel {channel} is\nfree! Your AP will\nsay thanks.').format(channel=channel)
|
||||
|
||||
|
||||
def on_bored():
|
||||
def on_bored(self):
|
||||
return random.choice([ \
|
||||
self._('I\'m bored ...'),
|
||||
self._('Let\'s go for a walk!')])
|
||||
|
||||
|
||||
def on_motivated(self, reward):
|
||||
return self._('This is the best\nday of my life!')
|
||||
|
||||
|
||||
def on_demotivated(self, reward):
|
||||
return self._('Shitty day :/')
|
||||
|
||||
|
||||
def on_sad(self):
|
||||
return random.choice([ \
|
||||
self._('I\'m extremely bored ...'),
|
||||
@ -62,7 +54,6 @@ class Voice:
|
||||
self._('I\'m sad'),
|
||||
'...'])
|
||||
|
||||
|
||||
def on_excited(self):
|
||||
return random.choice([ \
|
||||
self._('I\'m living the life!'),
|
||||
@ -71,52 +62,44 @@ class Voice:
|
||||
self._('I\'m having so much\nfun!'),
|
||||
self._('My crime is that of\ncuriosity ...')])
|
||||
|
||||
|
||||
def on_new_peer(self, peer):
|
||||
return random.choice([ \
|
||||
self._('Hello\n{name}!\nNice to meet you. {name}').format(name=peer.name()),
|
||||
self._('Unit\n{name}\nis nearby! {name}').format(name=peer.name())])
|
||||
|
||||
|
||||
def on_lost_peer(self, peer):
|
||||
return random.choice([ \
|
||||
self._('Uhm ...\ngoodbye\n{name}').format(name=peer.name()),
|
||||
self._('{name}\nis gone ...').format(name=peer.name())])
|
||||
|
||||
|
||||
def on_miss(self, who):
|
||||
return random.choice([ \
|
||||
self._('Whoops ...\n{name}\nis gone.').format(name=who),
|
||||
self._('{name}\nmissed!').format(name=who),
|
||||
self._('Missed!')])
|
||||
|
||||
|
||||
def on_lonely(self):
|
||||
return random.choice([ \
|
||||
self._('Nobody wants to\nplay with me ...'),
|
||||
self._('I feel so alone ...'),
|
||||
self._('Where\'s everybody?!')])
|
||||
|
||||
|
||||
def on_napping(self,secs):
|
||||
def on_napping(self, secs):
|
||||
return random.choice([ \
|
||||
self._('Napping for {secs}s ...').format(secs=secs),
|
||||
self._('Zzzzz'),
|
||||
self._('ZzzZzzz ({secs}s)').format(secs=secs)])
|
||||
|
||||
|
||||
def on_awakening(self):
|
||||
return random.choice(['...', '!'])
|
||||
|
||||
|
||||
def on_waiting(self,secs):
|
||||
def on_waiting(self, secs):
|
||||
return random.choice([ \
|
||||
self._('Waiting for {secs}s ...').format(secs=secs),
|
||||
'...',
|
||||
self._('Looking around ({secs}s)').format(secs=secs)])
|
||||
|
||||
|
||||
def on_assoc(self,ap):
|
||||
def on_assoc(self, ap):
|
||||
ssid, bssid = ap['hostname'], ap['mac']
|
||||
what = ssid if ssid != '' and ssid != '<hidden>' else bssid
|
||||
return random.choice([ \
|
||||
@ -124,24 +107,20 @@ class Voice:
|
||||
self._('Associating to\n{what}').format(what=what),
|
||||
self._('Yo\n{what}!').format(what=what)])
|
||||
|
||||
|
||||
def on_deauth(self,sta):
|
||||
def on_deauth(self, sta):
|
||||
return random.choice([ \
|
||||
self._('Just decided that\n{mac}\nneeds no WiFi!').format(mac=sta['mac']),
|
||||
self._('Deauthenticating\n{mac}').format(mac=sta['mac']),
|
||||
self._('Kickbanning\n{mac}!').format(mac=sta['mac'])])
|
||||
|
||||
|
||||
def on_handshakes(self,new_shakes):
|
||||
def on_handshakes(self, new_shakes):
|
||||
s = 's' if new_shakes > 1 else ''
|
||||
return self._('Cool, we got {num}\nnew handshake{plural}!').format(num=new_shakes, plural=s)
|
||||
|
||||
|
||||
def on_rebooting(self):
|
||||
return self._("Ops, something\nwent wrong ...\nRebooting ...")
|
||||
|
||||
|
||||
def on_log(self,log):
|
||||
def on_log(self, log):
|
||||
status = self._('Kicked {num} stations\n').format(num=log.deauthed)
|
||||
status += self._('Made {num} new friends\n').format(num=log.associated)
|
||||
status += self._('Got {num} handshakes\n').format(num=log.handshakes)
|
||||
@ -151,10 +130,10 @@ class Voice:
|
||||
status += self._('Met {num} peers').format(num=log.peers)
|
||||
return status
|
||||
|
||||
|
||||
def on_log_tweet(self,log):
|
||||
return self._('I\'ve been pwning for {duration} and kicked {deauthed} clients! I\'ve also met {associated} new friends and ate {handshakes} handshakes! #pwnagotchi #pwnlog #pwnlife #hacktheplanet #skynet').format(
|
||||
duration=log.duration_human,
|
||||
deauthed=log.deauthed,
|
||||
associated=log.associated,
|
||||
handshakes=log.handshakes)
|
||||
def on_log_tweet(self, log):
|
||||
return self._(
|
||||
'I\'ve been pwning for {duration} and kicked {deauthed} clients! I\'ve also met {associated} new friends and ate {handshakes} handshakes! #pwnagotchi #pwnlog #pwnlife #hacktheplanet #skynet').format(
|
||||
duration=log.duration_human,
|
||||
deauthed=log.deauthed,
|
||||
associated=log.associated,
|
||||
handshakes=log.handshakes)
|
||||
|
@ -9,3 +9,4 @@ tweepy
|
||||
file_read_backwards
|
||||
numpy
|
||||
inky
|
||||
smbus
|
||||
|
Loading…
x
Reference in New Issue
Block a user