Merge pull request from evilsocket/master

Update
This commit is contained in:
Periklis Fregkos 2019-10-03 07:21:18 +03:00 committed by GitHub
commit d3afab73ff
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
42 changed files with 2647 additions and 480 deletions

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!

@ -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"

@ -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

@ -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())

@ -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)

@ -267,7 +267,7 @@ msgid ""
"{mac}\n"
"needs no WiFi!"
msgstr ""
"Habe gerade entschieden,\n"
"Ich denke,\n"
"dass {mac}\n"
"kein WiFi brauch!"

@ -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 "

@ -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"

@ -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"

@ -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