Merge branch 'master' into papirus-support

This commit is contained in:
evilsocket 2019-09-29 21:24:40 +02:00 committed by GitHub
commit 7a6243a4e4
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
25 changed files with 1232 additions and 139 deletions

@ -92,6 +92,34 @@ The UI is available either via display if installed, or via http://pwnagotchi.lo
* **PWND**: Number of handshakes captured in this session and number of unique networks we own at least one handshake of, from the beginning.
* **AUTO**: This indicates that the algorithm is running with AI disabled (or still loading), it disappears once the AI dependencies have been bootrapped and the neural network loaded.
#### Languages
Pwnagotchi is able to speak multiple languages!! Currently supported is:
* **english** (default)
* german
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
# Now make your changes to the file
# sdcard/scripts/rootfs/pwnagotchi/scripts/pwnagotchi/locale/it/LC_MESSAGES/voice.po
./scripts/language.sh compile it
# DONE
```
If you changed the `voice.py`- File, the translations need an update. Do it like this:
```shell
./scripts/language.sh update it
# Now make your changes to the file (changed lines are marked with "fuzzy")
# sdcard/scripts/rootfs/pwnagotchi/scripts/pwnagotchi/locale/it/LC_MESSAGES/voice.po
./scripts/language.sh compile it
# DONE
```
### Random Info
- `hostname` sets the unit name.

@ -195,6 +195,9 @@ function provision_raspbian() {
# slows down boot
systemctl disable apt-daily.timer apt-daily.service apt-daily-upgrade.timer apt-daily-upgrade.service
# unecessary services
systemctl disable triggerhappy bluetooth wpa_supplicant
EOF
sed -i'' 's/^#//g' etc/ld.so.preload
cd "${REPO_DIR}"
@ -213,7 +216,7 @@ usage: $0 [OPTIONS]
-i <file> # Provide the path of an already downloaded raspbian image
-o <file> # Name of the img-file (default: pwnagotchi.img)
-s <size> # Size which should be added to second partition (in Gigabyte) (default: 4)
-v <version> # Version of raspbian (Supported: $SUPPORTED_RASPBIAN_VERSIONS; default: latest)
-v <version> # Version of raspbian (Supported: ${SUPPORTED_RASPBIAN_VERSIONS[*]}; default: latest)
-p # Only run provisioning (assumes the image is already mounted)
-d # Only run dependencies checks
-h # Show this help

71
scripts/language.sh Executable file

@ -0,0 +1,71 @@
#!/bin/bash
set -eu
DEPENDENCIES=( 'xgettext' 'msgfmt' 'msgmerge' )
COMMANDS=( 'add' 'update' 'delete' 'compile' )
REPO_DIR="$(dirname "$(dirname "$(realpath "$0")")")"
LOCALE_DIR="${REPO_DIR}/sdcard/rootfs/root/pwnagotchi/scripts/pwnagotchi/locale"
VOICE_FILE="${REPO_DIR}/sdcard/rootfs/root/pwnagotchi/scripts/pwnagotchi/voice.py"
function usage() {
cat <<EOF
usage: $0 <command> [options]
Commands:
add <language>
delete <language>
compile <language>
update <language>
EOF
}
for REQ in "${DEPENDENCIES[@]}"; do
if ! type "$REQ" >/dev/null 2>&1; then
echo "Dependency check failed for ${REQ}"
exit 1
fi
done
if [[ ! "${COMMANDS[*]}" =~ $1 ]]; then
usage
fi
function add_lang() {
mkdir -p "$LOCALE_DIR/$1/LC_MESSAGES"
cp -n "$LOCALE_DIR/voice.pot" "$LOCALE_DIR/$1/LC_MESSAGES/voice.po"
}
function del_lang() {
# set -eu is present; so not dangerous
rm -rf "$LOCALE_DIR/$1"
}
function comp_lang() {
msgfmt -o "$LOCALE_DIR/$1/LC_MESSAGES/voice.mo" "$LOCALE_DIR/$1/LC_MESSAGES/voice.po"
}
function update_lang() {
xgettext -d voice -o "$LOCALE_DIR/voice.pot" "$VOICE_FILE"
msgmerge --update "$LOCALE_DIR/$1/LC_MESSAGES/voice.po" "$LOCALE_DIR/voice.pot"
}
case "$1" in
add)
add_lang "$2"
;;
delete)
del_lang "$2"
;;
compile)
comp_lang "$2"
;;
update)
update_lang "$2"
;;
esac

@ -7,12 +7,12 @@ USB_IFACE_NET=10.0.0.0/24
# host interface to use for upstream connection
UPSTREAM_IFACE=${2:-enxe4b97aa99867}
ip addr add $USB_IFACE_IP/24 dev $USB_IFACE
ip link set $USB_IFACE up
ip addr add "$USB_IFACE_IP/24" dev "$USB_IFACE"
ip link set "$USB_IFACE" up
iptables -A FORWARD -o $UPSTREAM_IFACE -i $USB_IFACE -s $USB_IFACE_NET -m conntrack --ctstate NEW -j ACCEPT
iptables -A FORWARD -o "$UPSTREAM_IFACE" -i "$USB_IFACE" -s "$USB_IFACE_NET" -m conntrack --ctstate NEW -j ACCEPT
iptables -A FORWARD -m conntrack --ctstate ESTABLISHED,RELATED -j ACCEPT
iptables -t nat -F POSTROUTING
iptables -t nat -A POSTROUTING -o $UPSTREAM_IFACE -j MASQUERADE
iptables -t nat -A POSTROUTING -o "$UPSTREAM_IFACE" -j MASQUERADE
echo 1 > /proc/sys/net/ipv4/ip_forward

@ -1195,3 +1195,8 @@ dtoverlay=dwc2
dtparam=spi=on
dtoverlay=spi1-3cs
# Disable bluetooth
dtoverlay=pi3-disable-bt
# Disable audio
dtparam=audio=off

@ -5,6 +5,9 @@ iface lo inet loopback
allow-hotplug wlan0
iface wlan0 inet static
allow-hotplug eth0
iface eth0 inet dhcp
allow-hotplug usb0
iface usb0 inet static
address 10.0.0.2

@ -10,5 +10,7 @@
# bits.
#
# By default this script does nothing.
# Powersave (Disable HDMI) ~30ma
/opt/vc/bin/tvservice -o
/root/pwnagotchi/scripts/startup.sh &
exit 0

@ -1,5 +1,7 @@
# main algorithm configuration
main:
# currently implemented: en (default), de
lang: en
# monitor interface to use
iface: mon0
# command to run to bring the mon interface up in case it's not up already
@ -93,9 +95,11 @@ ui:
enabled: true
rotation: 180
# Possible options inkyphat/inky, papirus/papi or waveshare/ws
type: 'waveshare'
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'

@ -5,6 +5,7 @@ defscrollback 1024
startup_message off
altscreen on
autodetach on
zombie kr
activity "activity in %n (%t)"
bell_msg "bell in %n (%t)"

@ -5,6 +5,7 @@ defscrollback 1024
startup_message off
altscreen on
autodetach on
zombie kr
activity "activity in %n (%t)"
bell_msg "bell in %n (%t)"

@ -1,6 +1,6 @@
#!/bin/bash
for i in `seq 1 $1`;
for i in $(seq 1 "$1");
do
echo 0 >/sys/class/leds/led0/brightness
sleep 0.3
@ -10,3 +10,8 @@ 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

@ -8,7 +8,7 @@ import core
import pwnagotchi
from pwnagotchi.log import SessionParser
import pwnagotchi.voice as voice
from pwnagotchi.voice import Voice
from pwnagotchi.agent import Agent
from pwnagotchi.ui.display import Display
@ -74,7 +74,7 @@ if args.do_manual:
auth.set_access_token(config['twitter']['access_token_key'], config['twitter']['access_token_secret'])
api = tweepy.API(auth)
tweet = voice.on_log_tweet(log)
tweet = Voice(lang=config['main']['lang']).on_log_tweet(log)
api.update_with_media(filename=picture, status=tweet)
log.save_session_id()

@ -54,7 +54,7 @@ class Stats(object):
def load(self):
with self._lock:
if os.path.exists(self.path):
if os.path.exists(self.path) and os.path.getsize(self.path) > 0:
core.log("[ai] loading %s" % self.path)
with open(self.path, 'rt') as fp:
obj = json.load(fp)

@ -0,0 +1,344 @@
# SOME DESCRIPTIVE TITLE.
# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER
# This file is distributed under the same license as the PACKAGE package.
# FIRST AUTHOR <EMAIL@ADDRESS>, YEAR.
#
#, fuzzy
msgid ""
msgstr ""
"Project-Id-Version: 0.0.1\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2019-09-29 13:34+0200\n"
"PO-Revision-Date: 2019-09-29 14:00+0200\n"
"Last-Translator: dadav <33197631+dadav@users.noreply.github.com>\n"
"Language-Team: DE <33197631+dadav@users.noreply.github.com>\n"
"Language: \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 ""
"Hi, ich bin\n"
"ein Pwnagotchi!\n"
"Starte ..."
#: voice.py:22
msgid ""
"New day, new hunt,\n"
"new pwns!"
msgstr ""
"Neuer Tag, neue Jagd,\n"
"neue Pwns!"
#: voice.py:23
msgid "Hack the Planet!"
msgstr "Hack den Planet!"
#: voice.py:28
msgid "AI ready."
msgstr "KI bereit."
#: voice.py:29
msgid ""
"The neural network\n"
"is ready."
msgstr ""
"Das neurale Netz\n"
"ist bereit."
#: voice.py:39
#, python-brace-format
msgid ""
"Hey, channel {channel} is\n"
"free! Your AP will\n"
"say thanks."
msgstr ""
"Hey, Channel {channel} ist\n"
"frei! Dein AP wird\n"
"es dir danken."
#: voice.py:44
msgid "I'm bored ..."
msgstr "Mir ist langweilig..."
#: voice.py:45
msgid "Let's go for a walk!"
msgstr "Lass uns laufen gehen!"
#: voice.py:49
msgid ""
"This is the best\n"
"day of my life!"
msgstr ""
"Das ist der beste\n"
"Tag meines Lebens."
#: voice.py:53
msgid "Shitty day :/"
msgstr "Scheis Tag :/"
#: voice.py:58
msgid "I'm extremely bored ..."
msgstr "Mir ist sau langweilig..."
#: voice.py:59
msgid "I'm very sad ..."
msgstr "Ich bin sehr traurig..."
#: voice.py:60
msgid "I'm sad"
msgstr "Ich bin traurig"
#: voice.py:66
msgid "I'm living the life!"
msgstr "Ich lebe das Leben!"
#: voice.py:67
msgid "I pwn therefore I am."
msgstr "Ich pwne, also bin ich."
#: voice.py:68
msgid "So many networks!!!"
msgstr "So viele Netwerke!!!"
#: voice.py:69
msgid ""
"I'm having so much\n"
"fun!"
msgstr ""
"Ich habe sooo viel\n"
"Spaß!"
#: voice.py:70
msgid ""
"My crime is that of\n"
"curiosity ..."
msgstr ""
"Mein Verbrechen ist\n"
"das der Neugier ..."
#: voice.py:75
#, python-brace-format
msgid ""
"Hello\n"
"{name}!\n"
"Nice to meet you. {name}"
msgstr ""
"Hallo {name},\n"
"Nett Dich\n"
"kennenzulernen."
#: voice.py:76
#, python-brace-format
msgid ""
"Unit\n"
"{name}\n"
"is nearby! {name}"
msgstr ""
"Gerät {name}\n"
"ist in der\n"
"nähe!!"
#: voice.py:81
#, python-brace-format
msgid ""
"Uhm ...\n"
"goodbye\n"
"{name}"
msgstr ""
"Uhm ...\n"
"tschüß\n"
"{name}"
#: voice.py:82
#, python-brace-format
msgid ""
"{name}\n"
"is gone ..."
msgstr ""
"{name}\n"
"ist weg ..."
#: voice.py:87
#, python-brace-format
msgid ""
"Whoops ...\n"
"{name}\n"
"is gone."
msgstr ""
"Whoops ...\n"
"{name}\n"
"ist weg."
#: voice.py:88
#, python-brace-format
msgid ""
"{name}\n"
"missed!"
msgstr ""
"{name}\n"
"verpasst!"
#: voice.py:89
msgid "Missed!"
msgstr "Verpasst!"
#: voice.py:94
msgid ""
"Nobody wants to\n"
"play with me ..."
msgstr ""
"Niemand will mit\n"
"mir spielen ..."
#: voice.py:95
msgid "I feel so alone ..."
msgstr ""
"Ich fühl mich\n"
"so alleine ..."
#: voice.py:96
msgid "Where's everybody?!"
msgstr "Wo sind denn alle?"
#: voice.py:101
#, python-brace-format
msgid "Napping for {secs}s ..."
msgstr "Schlafe für {secs}s"
#: voice.py:102
msgid "Zzzzz"
msgstr ""
#: voice.py:103
#, python-brace-format
msgid "ZzzZzzz ({secs}s)"
msgstr ""
#: voice.py:112
#, python-brace-format
msgid "Waiting for {secs}s ..."
msgstr "Warte für {secs}s ..."
#: voice.py:114
#, python-brace-format
msgid "Looking around ({secs}s)"
msgstr "Schaue mich um ({secs}s)"
#: voice.py:121
#, python-brace-format
msgid ""
"Hey\n"
"{what}\n"
"let's be friends!"
msgstr ""
"Hey\n"
"{what}\n"
"lass uns Freunde sein!"
#: voice.py:122
#, python-brace-format
msgid ""
"Associating to\n"
"{what}"
msgstr ""
"Verbinde mit\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 ""
"Habe gerade entschieden,\n"
"dass {mac}\n"
"kein WiFi brauch!"
#: voice.py:129
#, python-brace-format
msgid ""
"Deauthenticating\n"
"{mac}"
msgstr ""
"Deauthentifiziere\n"
"{mac}"
#: voice.py:130
#, python-brace-format
msgid ""
"Kickbanning\n"
"{mac}!"
msgstr ""
"Kicke\n"
"{mac}!"
#: voice.py:135
#, python-brace-format
msgid ""
"Cool, we got {num}\n"
"new handshake{plural}!"
msgstr ""
"Cool, wir haben {num}\n"
"neue Handshake{plural}!"
#: voice.py:139
msgid ""
"Ops, something\n"
"went wrong ...\n"
"Rebooting ..."
msgstr ""
"Ops, da ist etwas\n"
"schief gelaufen ...\n"
"Starte neu ..."
#: voice.py:143
#, python-brace-format
msgid "Kicked {num} stations\n"
msgstr "{num} Stationen gekicked\n"
#: voice.py:144
#, python-brace-format
msgid "Made {num} new friends\n"
msgstr "{num} Freunde gefunden\n"
#: voice.py:145
#, python-brace-format
msgid "Got {num} handshakes\n"
msgstr "{num} Handshakes aufgez.\n"
#: voice.py:147
msgid "Met 1 peer"
msgstr "1 Peer getroffen."
#: voice.py:149
#, python-brace-format
msgid "Met {num} peers"
msgstr "{num} Peers getroffen"
#: 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 ""
"Ich war {duration} am Pwnen und habe {deauthed} Clients gekickt! Außerdem habe ich "
"{associated} neue Freunde getroffen und {handshakes} Handshakes gefressen! #pwnagotchi "
"#pwnlog #pwnlife #hacktheplanet #skynet"

@ -0,0 +1,288 @@
# 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.
#
#, 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"
"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 ""
#: voice.py:21
msgid ""
"Hi, I'm Pwnagotchi!\n"
"Starting ..."
msgstr ""
#: voice.py:22
msgid ""
"New day, new hunt,\n"
"new pwns!"
msgstr ""
#: voice.py:23
msgid "Hack the Planet!"
msgstr ""
#: voice.py:28
msgid "AI ready."
msgstr ""
#: voice.py:29
msgid ""
"The neural network\n"
"is ready."
msgstr ""
#: voice.py:39
#, python-brace-format
msgid ""
"Hey, channel {channel} is\n"
"free! Your AP will\n"
"say thanks."
msgstr ""
#: 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 ""
#: 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 ""
#: voice.py:75
#, python-brace-format
msgid ""
"Hello\n"
"{name}!\n"
"Nice to meet you. {name}"
msgstr ""
#: voice.py:76
#, python-brace-format
msgid ""
"Unit\n"
"{name}\n"
"is nearby! {name}"
msgstr ""
#: voice.py:81
#, python-brace-format
msgid ""
"Uhm ...\n"
"goodbye\n"
"{name}"
msgstr ""
#: voice.py:82
#, python-brace-format
msgid ""
"{name}\n"
"is gone ..."
msgstr ""
#: voice.py:87
#, python-brace-format
msgid ""
"Whoops ...\n"
"{name}\n"
"is gone."
msgstr ""
#: voice.py:88
#, python-brace-format
msgid ""
"{name}\n"
"missed!"
msgstr ""
#: voice.py:89
msgid "Missed!"
msgstr ""
#: voice.py:94
msgid ""
"Nobody wants to\n"
"play with me ..."
msgstr ""
#: 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 ""
#: voice.py:102
msgid "Zzzzz"
msgstr ""
#: voice.py:103
#, python-brace-format
msgid "ZzzZzzz ({secs}s)"
msgstr ""
#: voice.py:112
#, python-brace-format
msgid "Waiting for {secs}s ..."
msgstr ""
#: voice.py:114
#, python-brace-format
msgid "Looking around ({secs}s)"
msgstr ""
#: voice.py:121
#, python-brace-format
msgid ""
"Hey\n"
"{what}\n"
"let's be friends!"
msgstr ""
#: voice.py:122
#, python-brace-format
msgid ""
"Associating to\n"
"{what}"
msgstr ""
#: 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 ""
#: voice.py:129
#, python-brace-format
msgid ""
"Deauthenticating\n"
"{mac}"
msgstr ""
#: voice.py:130
#, python-brace-format
msgid ""
"Kickbanning\n"
"{mac}!"
msgstr ""
#: voice.py:135
#, python-brace-format
msgid ""
"Cool, we got {num}\n"
"new handshake{plural}!"
msgstr ""
#: voice.py:139
msgid ""
"Ops, something\n"
"went wrong ...\n"
"Rebooting ..."
msgstr ""
#: voice.py:143
#, python-brace-format
msgid "Kicked {num} stations\n"
msgstr ""
#: voice.py:144
#, python-brace-format
msgid "Made {num} new friends\n"
msgstr ""
#: voice.py:145
#, python-brace-format
msgid "Got {num} handshakes\n"
msgstr ""
#: voice.py:147
msgid "Met 1 peer"
msgstr ""
#: voice.py:149
#, python-brace-format
msgid "Met {num} peers"
msgstr ""
#: 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 ""

@ -80,6 +80,9 @@ 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
self._httpd = None
@ -108,8 +111,11 @@ class Display(View):
def _is_papirus(self):
return self._display_type in ('papirus', 'papi')
def _is_waveshare(self):
return self._display_type in ('waveshare', 'ws')
def _is_waveshare1(self):
return self._display_type in ('waveshare_1', 'ws_1', 'waveshare1', 'ws1')
def _is_waveshare2(self):
return self._display_type in ('waveshare_2', 'ws_2', 'waveshare2', 'ws2')
def _init_display(self):
if self._is_inky():
@ -117,20 +123,32 @@ class Display(View):
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
os.environ['EPD_SIZE'] = '2.0'
self._display = Papirus()
self._display.clear()
self._render_cb = self._papirus_render
elif self._is_waveshare():
from pwnagotchi.ui.waveshare import EPD
elif self._is_waveshare1():
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():
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)
@ -179,7 +197,19 @@ class Display(View):
def _waveshare_render(self):
buf = self._display.getbuffer(self.canvas)
self._display.displayPartial(buf)
if self._is_waveshare1:
if self.full_refresh_trigger >= 0 and self.full_refresh_count == self.full_refresh_trigger:
self._display.Clear(0x00)
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)
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")

@ -5,7 +5,7 @@ from PIL import Image, ImageDraw
import core
import pwnagotchi
from pwnagotchi import voice
from pwnagotchi.voice import Voice
import pwnagotchi.ui.fonts as fonts
import pwnagotchi.ui.faces as faces
@ -31,6 +31,7 @@ def setup_display_specifics(config):
face_pos = (0, int(height / 4))
name_pos = (int(width / 2) - 15, int(height * .15))
status_pos = (int(width / 2) - 15, int(height * .30))
elif config['ui']['display']['type'] in ('papirus', 'papi'):
fonts.setup(10, 8, 10, 23)
@ -39,7 +40,9 @@ def setup_display_specifics(config):
face_pos = (0, int(height / 4))
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', 'waveshare'):
elif config['ui']['display']['type'] in ('ws_1', 'ws1', 'waveshare_1', 'waveshare1',
'ws_2', 'ws2', 'waveshare_2', 'waveshare2'):
fonts.setup(10, 9, 10, 35)
width = 250
@ -57,6 +60,7 @@ class View(object):
self._config = config
self._canvas = None
self._lock = Lock()
self._voice = Voice(lang=config['main']['lang'])
self._width, self._height, \
face_pos, name_pos, status_pos = setup_display_specifics(config)
@ -89,7 +93,7 @@ class View(object):
'name': Text(value='%s>' % 'pwnagotchi', position=name_pos, color=BLACK, font=fonts.Bold),
# 'face2': Bitmap( '/root/pwnagotchi/data/images/face_happy.bmp', (0, 20)),
'status': Text(value=voice.default(), position=status_pos, color=BLACK, font=fonts.Medium),
'status': Text(value=self._voice.default(), position=status_pos, color=BLACK, font=fonts.Medium),
'shakes': LabeledValue(label='PWND ', value='0 (00)', color=BLACK,
position=(0, self._height - int(self._height * .12) + 1), label_font=fonts.Bold,
@ -124,19 +128,19 @@ class View(object):
self._state.set(key, value)
def on_starting(self):
self.set('status', voice.on_starting())
self.set('status', self._voice.on_starting())
self.set('face', faces.AWAKE)
def on_ai_ready(self):
self.set('mode', '')
self.set('face', faces.HAPPY)
self.set('status', voice.on_ai_ready())
self.set('status', self._voice.on_ai_ready())
self.update()
def on_manual_mode(self, log):
self.set('mode', 'MANU')
self.set('face', faces.SAD if log.handshakes == 0 else faces.HAPPY)
self.set('status', voice.on_log(log))
self.set('status', self._voice.on_log(log))
self.set('epoch', "%04d" % log.epochs)
self.set('uptime', log.duration)
self.set('channel', '-')
@ -160,7 +164,7 @@ class View(object):
def on_normal(self):
self.set('face', faces.AWAKE)
self.set('status', voice.on_normal())
self.set('status', self._voice.on_normal())
self.update()
def set_closest_peer(self, peer):
@ -188,17 +192,17 @@ class View(object):
def on_new_peer(self, peer):
self.set('face', faces.FRIEND)
self.set('status', voice.on_new_peer(peer))
self.set('status', self._voice.on_new_peer(peer))
self.update()
def on_lost_peer(self, peer):
self.set('face', faces.LONELY)
self.set('status', voice.on_lost_peer(peer))
self.set('status', self._voice.on_lost_peer(peer))
self.update()
def on_free_channel(self, channel):
self.set('face', faces.SMART)
self.set('status', voice.on_free_channel(channel))
self.set('status', self._voice.on_free_channel(channel))
self.update()
def wait(self, secs, sleeping=True):
@ -214,12 +218,12 @@ class View(object):
if sleeping:
if secs > 1:
self.set('face', faces.SLEEP)
self.set('status', voice.on_napping(secs))
self.set('status', self._voice.on_napping(secs))
else:
self.set('face', faces.SLEEP2)
self.set('status', voice.on_awakening())
self.set('status', self._voice.on_awakening())
else:
self.set('status', voice.on_waiting(secs))
self.set('status', self._voice.on_waiting(secs))
if step % 2 == 0:
self.set('face', faces.LOOK_R)
else:
@ -232,57 +236,57 @@ class View(object):
def on_bored(self):
self.set('face', faces.BORED)
self.set('status', voice.on_bored())
self.set('status', self._voice.on_bored())
self.update()
def on_sad(self):
self.set('face', faces.SAD)
self.set('status', voice.on_sad())
self.set('status', self._voice.on_sad())
self.update()
def on_motivated(self, reward):
self.set('face', faces.MOTIVATED)
self.set('status', voice.on_motivated(reward))
self.set('status', self._voice.on_motivated(reward))
self.update()
def on_demotivated(self, reward):
self.set('face', faces.DEMOTIVATED)
self.set('status', voice.on_demotivated(reward))
self.set('status', self._voice.on_demotivated(reward))
self.update()
def on_excited(self):
self.set('face', faces.EXCITED)
self.set('status', voice.on_excited())
self.set('status', self._voice.on_excited())
self.update()
def on_assoc(self, ap):
self.set('face', faces.INTENSE)
self.set('status', voice.on_assoc(ap))
self.set('status', self._voice.on_assoc(ap))
self.update()
def on_deauth(self, sta):
self.set('face', faces.COOL)
self.set('status', voice.on_deauth(sta))
self.set('status', self._voice.on_deauth(sta))
self.update()
def on_miss(self, who):
self.set('face', faces.SAD)
self.set('status', voice.on_miss(who))
self.set('status', self._voice.on_miss(who))
self.update()
def on_lonely(self):
self.set('face', faces.LONELY)
self.set('status', voice.on_lonely())
self.set('status', self._voice.on_lonely())
self.update()
def on_handshakes(self, new_shakes):
self.set('face', faces.HAPPY)
self.set('status', voice.on_handshakes(new_shakes))
self.set('status', self._voice.on_handshakes(new_shakes))
self.update()
def on_rebooting(self):
self.set('face', faces.BROKEN)
self.set('status', voice.on_rebooting())
self.set('status', self._voice.on_rebooting())
self.update()
def update(self):

@ -0,0 +1,218 @@
# //*****************************************************************************
# * | 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 ###

@ -0,0 +1,73 @@
# /*****************************************************************************
# * | File : EPD_1in54.py
# * | Author : Waveshare team
# * | Function : Hardware underlying interface
# * | Info :
# *----------------
# * | This version: V2.0
# * | Date : 2018-11-01
# * | 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
# 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 spidev
import RPi.GPIO as GPIO
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)
def digital_write(pin, value):
GPIO.output(pin, value)
def digital_read(pin):
return GPIO.input(BUSY_PIN)
def delay_ms(delaytime):
time.sleep(delaytime / 1000.0)
def spi_writebyte(data):
SPI.writebytes(data)
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 ###

@ -1,147 +1,160 @@
import random
import gettext
import os
def default():
return 'ZzzzZZzzzzZzzz'
class Voice:
def __init__(self, lang):
localedir = os.path.join(os.path.abspath(os.path.dirname(__file__)), 'locale')
translation = gettext.translation(
'voice', localedir,
languages=[lang],
fallback=True,
)
translation.install()
self._ = translation.gettext
def default(self):
return self._('ZzzzZZzzzzZzzz')
def on_starting():
return random.choice([ \
'Hi, I\'m Pwnagotchi!\nStarting ...',
'New day, new hunt,\nnew pwns!',
'Hack the Planet!'])
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():
return random.choice([
'AI ready.',
'The neural network\nis ready.'])
def on_ai_ready(self):
return random.choice([
self._('AI ready.'),
self._('The neural network\nis ready.')])
def on_normal():
return random.choice([ \
'',
'...'])
def on_normal(self):
return random.choice([ \
'',
'...'])
def on_free_channel(channel):
return 'Hey, channel %d is\nfree! Your AP will\nsay thanks.' % channel
def on_free_channel(channel):
return self._('Hey, channel {channel} is\nfree! Your AP will\nsay thanks.').format(channel=channel)
def on_bored():
return random.choice([ \
'I\'m bored ...',
'Let\'s go for a walk!'])
def on_bored():
return random.choice([ \
self._('I\'m bored ...'),
self._('Let\'s go for a walk!')])
def on_motivated(reward):
return 'This is the best\nday of my life!'
def on_motivated(self, reward):
return self._('This is the best\nday of my life!')
def on_demotivated(reward):
return 'Shitty day :/'
def on_demotivated(self, reward):
return self._('Shitty day :/')
def on_sad():
return random.choice([ \
'I\'m extremely bored ...',
'I\'m very sad ...',
'I\'m sad',
'...'])
def on_sad(self):
return random.choice([ \
self._('I\'m extremely bored ...'),
self._('I\'m very sad ...'),
self._('I\'m sad'),
'...'])
def on_excited():
return random.choice([ \
'I\'m living the life!',
'I pwn therefore I am.',
'So many networks!!!',
'I\'m having so much\nfun!',
'My crime is that of\ncuriosity ...'])
def on_excited(self):
return random.choice([ \
self._('I\'m living the life!'),
self._('I pwn therefore I am.'),
self._('So many networks!!!'),
self._('I\'m having so much\nfun!'),
self._('My crime is that of\ncuriosity ...')])
def on_new_peer(peer):
return random.choice([ \
'Hello\n%s!\nNice to meet you.' % peer.name(),
'Unit\n%s\nis nearby!' % peer.name()])
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(peer):
return random.choice([ \
'Uhm ...\ngoodbye\n%s' % peer.name(),
'%s\nis gone ...' % 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(who):
return random.choice([ \
'Whoops ...\n%s\nis gone.' % who,
'%s\nmissed!' % who,
'Missed!'])
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():
return random.choice([ \
'Nobody wants to\nplay with me ...',
'I feel so alone ...',
'Where\'s everybody?!'])
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(secs):
return random.choice([ \
'Napping for %ds ...' % secs,
'Zzzzz',
'ZzzZzzz (%ds)' % 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():
return random.choice(['...', '!'])
def on_awakening(self):
return random.choice(['...', '!'])
def on_waiting(secs):
return random.choice([ \
'Waiting for %ds ...' % secs,
'...',
'Looking around (%ds)' % 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(ap):
ssid, bssid = ap['hostname'], ap['mac']
what = ssid if ssid != '' and ssid != '<hidden>' else bssid
return random.choice([ \
'Hey\n%s\nlet\'s be friends!' % what,
'Associating to\n%s' % what,
'Yo\n%s!' % what])
def on_assoc(self,ap):
ssid, bssid = ap['hostname'], ap['mac']
what = ssid if ssid != '' and ssid != '<hidden>' else bssid
return random.choice([ \
self._('Hey\n{what}\nlet\'s be friends!').format(what=what),
self._('Associating to\n{what}').format(what=what),
self._('Yo\n{what}!').format(what=what)])
def on_deauth(sta):
return random.choice([ \
'Just decided that\n%s\nneeds no WiFi!' % sta['mac'],
'Deauthenticating\n%s' % sta['mac'],
'Kickbanning\n%s!' % sta['mac']])
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(new_shakes):
s = 's' if new_shakes > 1 else ''
return 'Cool, we got %d\nnew handshake%s!' % (new_shakes, s)
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():
return "Ops, something\nwent wrong ...\nRebooting ..."
def on_rebooting(self):
return self._("Ops, something\nwent wrong ...\nRebooting ...")
def on_log(log):
status = 'Kicked %d stations\n' % log.deauthed
status += 'Made %d new friends\n' % log.associated
status += 'Got %d handshakes\n' % log.handshakes
if log.peers == 1:
status += 'Met 1 peer'
elif log.peers > 0:
status += 'Met %d peers' % log.peers
return status
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)
if log.peers == 1:
status += self._('Met 1 peer')
elif log.peers > 0:
status += self._('Met {num} peers').format(num=log.peers)
return status
def on_log_tweet(log):
return 'I\'ve been pwning for %s and kicked %d clients! I\'ve also met %d new friends and ate %d handshakes! #pwnagotchi #pwnlog #pwnlife #hacktheplanet #skynet' % ( \
log.duration_human,
log.deauthed,
log.associated,
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)