Merge pull request #2 from evilsocket/master

Update
This commit is contained in:
Periklis Fregkos 2019-09-30 04:57:26 +03:00 committed by GitHub
commit 34a83720c3
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
30 changed files with 1533 additions and 280 deletions

23
.travis.yml Normal file
View File

@ -0,0 +1,23 @@
dist: bionic
language: generic
env:
global:
- LANG=C
- LC_ALL=C
before_cache:
- mountpoint -q $TRAVIS_BUILD_DIR/tmp/mnt && sudo umount -R $TRAVIS_BUILD_DIR/tmp/mnt
- sudo find $TRAVIS_BUILD_DIR/tmp/ -name '*.img' -delete
cache:
apt: true
directories:
- tmp/
before_script:
- sudo apt-get -y update
- sudo apt-get -y install qemu-user-static binfmt-support qemu
- sudo update-binfmts --display
- unset GOROOT
script:
- sudo ./scripts/create_sibling.sh -n pwnagotchi -o pwnagotchi.img -s 4
- zip -s 2g pwnagotchi.zip pwnagotchi.img
# TODO: deploy!

View File

@ -1,5 +1,13 @@
# Pwnagotchi # Pwnagotchi
<p align="center">
<p align="center">
<a href="https://github.com/evilsocket/pwnagotchi/releases/latest"><img alt="Release" src="https://img.shields.io/github/release/evilsocket/pwnagotchi.svg?style=flat-square"></a>
<a href="https://github.com/evilsocket/pwnagotchi/blob/master/LICENSE.md"><img alt="Software License" src="https://img.shields.io/badge/license-GPL3-brightgreen.svg?style=flat-square"></a>
<a href="https://travis-ci.org/evilsocket/pwnagotchi"><img alt="Travis" src="https://img.shields.io/travis/evilsocket/pwnagotchi/master.svg?style=flat-square"></a>
</p>
</p>
[Pwnagotchi](https://twitter.com/pwnagotchi) is an "AI" that learns from the WiFi environment and instruments bettercap in order to maximize the WPA key material (any form of handshake that is crackable, including [PMKIDs](https://www.evilsocket.net/2019/02/13/Pwning-WiFi-networks-with-bettercap-and-the-PMKID-client-less-attack/), full and half WPA handshakes) captured. [Pwnagotchi](https://twitter.com/pwnagotchi) is an "AI" that learns from the WiFi environment and instruments bettercap in order to maximize the WPA key material (any form of handshake that is crackable, including [PMKIDs](https://www.evilsocket.net/2019/02/13/Pwning-WiFi-networks-with-bettercap-and-the-PMKID-client-less-attack/), full and half WPA handshakes) captured.
![handshake](https://i.imgur.com/pdA4vCZ.png) ![handshake](https://i.imgur.com/pdA4vCZ.png)
@ -14,9 +22,9 @@ Multiple units can talk to each other, advertising their own presence using a pa
Depending on the status of the unit, several states and states transitions are configurable and represented on the display as different moods, expressions and sentences. Depending on the status of the unit, several states and states transitions are configurable and represented on the display as different moods, expressions and sentences.
If instead you are a boring person, you can disable the AI and have the algorithm run just with the preconfigured default parameters and enjoy a very portable bettercap + webui dedicated hardware. If instead you just want to use your own parameters and save battery and CPU cycles, you can disable the AI in `config.yml` and enjoy an automated deauther, WPA handshake sniffer and portable bettercap + webui dedicated hardware.
**NOTE:** The software **requires bettercap v2.25**. **NOTE:** The software **requires at least bettercap v2.25**.
![units](https://i.imgur.com/MStjXZF.png) ![units](https://i.imgur.com/MStjXZF.png)
@ -28,12 +36,22 @@ For hackers to learn reinforcement learning, WiFi networking and have an excuse
**THIS IS STILL ALPHA STAGE SOFTWARE, IF YOU DECIDE TO TRY TO USE IT, YOU ARE ON YOUR OWN, NO SUPPORT WILL BE PROVIDED, NEITHER FOR INSTALLATION OR FOR BUGS** **THIS IS STILL ALPHA STAGE SOFTWARE, IF YOU DECIDE TO TRY TO USE IT, YOU ARE ON YOUR OWN, NO SUPPORT WILL BE PROVIDED, NEITHER FOR INSTALLATION OR FOR BUGS**
However, there's [a Slack channel](https://join.slack.com/t/pwnagotchi/shared_invite/enQtNzc4NzY3MDE2OTAzLTg5NmNmNDJiMDM3ZWFkMWUwN2Y5NDk0Y2JlZWZjODlhMmRhNDZiOGMwYjJhM2UzNzA3YjA5NjJmZGY5NGI5NmI).
### Hardware ### Hardware
- Raspberry Pi Zero W - Raspberry Pi Zero W
- [Waveshare eInk Display](https://www.waveshare.com/2.13inch-e-paper-hat.htm) (optional if you connect to usb0 and point your browser to the web ui, see config.yml)
- A decent power bank (with 1500 mAh you get ~2 hours with AI on) - A decent power bank (with 1500 mAh you get ~2 hours with AI on)
#### Display (optional)
The display is optional if you connect to `usb0` (by using the data port on the unit) and point your browser to the web ui (see config.yml).
The supported models are:
- [Waveshare eInk Display (both V1 and V2)](https://www.waveshare.com/2.13inch-e-paper-hat.htm)
- [Pimoroni Inky pHAT](https://shop.pimoroni.com/products/inky-phat)
- [PaPiRus eInk Screen](https://uk.pi-supply.com/products/papirus-zero-epaper-screen-phat-pi-zero)
### Software ### Software
- Raspbian + [nexmon patches](https://re4son-kernel.com/re4son-pi-kernel/) for monitor mode, or any Linux with a monitor mode enabled interface (if you tune config.yml). - Raspbian + [nexmon patches](https://re4son-kernel.com/re4son-pi-kernel/) for monitor mode, or any Linux with a monitor mode enabled interface (if you tune config.yml).
@ -52,6 +70,7 @@ usage: ./scripts/create_sibling.sh [OPTIONS]
-i <file> # Provide the path of an already downloaded raspbian image -i <file> # Provide the path of an already downloaded raspbian image
-o <file> # Name of the img-file (default: pwnagotchi.img) -o <file> # Name of the img-file (default: pwnagotchi.img)
-s <size> # Size which should be added to second partition (in Gigabyte) (default: 4) -s <size> # Size which should be added to second partition (in Gigabyte) (default: 4)
-v <version> # Version of raspbian (Supported: latest; default: latest)
-p # Only run provisioning (assumes the image is already mounted) -p # Only run provisioning (assumes the image is already mounted)
-d # Only run dependencies checks -d # Only run dependencies checks
-h # Show this help -h # Show this help
@ -63,7 +82,7 @@ If you connect to the unit via `usb0` (thus using the data port), you might want
### UI ### UI
The UI is available either via display if installed, or via http://10.0.0.2:8080/ if you connect to the unit via `usb0` and set a static address on the network interface. 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).
![ui](https://i.imgur.com/XgIrcur.png) ![ui](https://i.imgur.com/XgIrcur.png)
@ -73,6 +92,34 @@ The UI is available either via display if installed, or via http://10.0.0.2:8080
* **PWND**: Number of handshakes captured in this session and number of unique networks we own at least one handshake of, from the beginning. * **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. * **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 are:
* **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/rootfs/root/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/rootfs/root/pwnagotchi/scripts/pwnagotchi/locale/it/LC_MESSAGES/voice.po
./scripts/language.sh compile it
# DONE
```
### Random Info ### Random Info
- `hostname` sets the unit name. - `hostname` sets the unit name.

View File

@ -1,4 +1,4 @@
#!/bin/bash #!/usr/bin/env bash
# based on: https://wiki.debian.org/RaspberryPi/qemu-user-static # based on: https://wiki.debian.org/RaspberryPi/qemu-user-static
## and https://z4ziggy.wordpress.com/2015/05/04/from-bochs-to-chroot/ ## and https://z4ziggy.wordpress.com/2015/05/04/from-bochs-to-chroot/
@ -16,6 +16,9 @@ PWNI_SIZE="4"
OPT_PROVISION_ONLY=0 OPT_PROVISION_ONLY=0
OPT_CHECK_DEPS_ONLY=0 OPT_CHECK_DEPS_ONLY=0
OPT_IMAGE_PROVIDED=0 OPT_IMAGE_PROVIDED=0
OPT_RASPBIAN_VERSION='latest'
SUPPORTED_RASPBIAN_VERSIONS=( 'latest' 'buster' 'stretch' )
if [[ "$EUID" -ne 0 ]]; then if [[ "$EUID" -ne 0 ]]; then
echo "Run this script as root!" echo "Run this script as root!"
@ -44,9 +47,23 @@ function check_dependencies() {
} }
function get_raspbian() { function get_raspbian() {
VERSION="$1"
case "$VERSION" in
latest)
URL="https://downloads.raspberrypi.org/raspbian_lite_latest"
;;
buster)
URL="https://downloads.raspberrypi.org/raspbian/images/raspbian-2019-07-12/2019-07-10-raspbian-buster.zip"
;;
stretch)
URL="https://downloads.raspberrypi.org/raspbian/images/raspbian-2019-04-09/2019-04-08-raspbian-stretch.zip"
;;
esac
echo "[+] Downloading raspbian.zip" echo "[+] Downloading raspbian.zip"
mkdir -p "${TMP_DIR}" mkdir -p "${TMP_DIR}"
wget --show-progress -qcO "${TMP_DIR}/raspbian.zip" "https://downloads.raspberrypi.org/raspbian_lite_latest" wget --show-progress -qcO "${TMP_DIR}/raspbian.zip" "$URL"
echo "[+] Unpacking raspbian.zip to raspbian.img" echo "[+] Unpacking raspbian.zip to raspbian.img"
gunzip -c "${TMP_DIR}/raspbian.zip" > "${TMP_DIR}/raspbian.img" gunzip -c "${TMP_DIR}/raspbian.zip" > "${TMP_DIR}/raspbian.img"
} }
@ -68,7 +85,7 @@ function setup_raspbian(){
parted -s "$LOOP_PATH" rm 2 parted -s "$LOOP_PATH" rm 2
parted -s "$LOOP_PATH" mkpart primary "$PART2_START" 100% parted -s "$LOOP_PATH" mkpart primary "$PART2_START" 100%
echo "[+] Check FS" echo "[+] Check FS"
e2fsck -f "${LOOP_PATH}p2" e2fsck -y -f "${LOOP_PATH}p2"
echo "[+] Resize FS" echo "[+] Resize FS"
resize2fs "${LOOP_PATH}p2" resize2fs "${LOOP_PATH}p2"
echo "[+] Device is ${LOOP_PATH}" echo "[+] Device is ${LOOP_PATH}"
@ -82,9 +99,8 @@ function setup_raspbian(){
mount --bind /sys "${MNT_DIR}/sys/" mount --bind /sys "${MNT_DIR}/sys/"
mount --bind /proc "${MNT_DIR}/proc/" mount --bind /proc "${MNT_DIR}/proc/"
mount --bind /dev/pts "${MNT_DIR}/dev/pts" mount --bind /dev/pts "${MNT_DIR}/dev/pts"
mount --bind /etc/ssl/certs "${MNT_DIR}/etc/ssl/certs"
mount --bind /etc/ca-certificates "${MNT_DIR}/etc/ca-certificates"
cp /usr/bin/qemu-arm-static "${MNT_DIR}/usr/bin" cp /usr/bin/qemu-arm-static "${MNT_DIR}/usr/bin"
cp /etc/resolv.conf "${MNT_DIR}/etc/resolv.conf"
} }
function provision_raspbian() { function provision_raspbian() {
@ -94,12 +110,17 @@ function provision_raspbian() {
LANG=C chroot . bin/bash -x <<EOF LANG=C chroot . bin/bash -x <<EOF
set -eu set -eu
export PATH="/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin" export PATH="/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin"
uname -a
apt-get -y update apt-get -y update
apt-get -y upgrade apt-get -y upgrade
apt-get -y install git vim screen build-essential golang python3-pip apt-get -y install git vim screen build-essential golang python3-pip gawk
apt-get -y install libpcap-dev libusb-1.0-0-dev libnetfilter-queue-dev apt-get -y install libpcap-dev libusb-1.0-0-dev libnetfilter-queue-dev
apt-get -y install dphys-swapfile libopenmpi-dev libatlas-base-dev apt-get -y install dphys-swapfile libopenmpi-dev libatlas-base-dev
apt-get -y install libjasper-dev libqtgui4 libqt4-test apt-get -y install libjasper-dev libqtgui4 libqt4-test libopenjp2-7
apt-get -y install tcpdump libilmbase23 libopenexr23 libgstreamer1.0-0
apt-get -y install libavcodec58 libavformat58 libswscale5
# setup dphys-swapfile # setup dphys-swapfile
echo "CONF_SWAPSIZE=1024" >/etc/dphys-swapfile echo "CONF_SWAPSIZE=1024" >/etc/dphys-swapfile
@ -110,19 +131,32 @@ function provision_raspbian() {
git clone https://github.com/evilsocket/pwnagotchi.git git clone https://github.com/evilsocket/pwnagotchi.git
rsync -aP pwnagotchi/sdcard/boot/* /boot/ rsync -aP pwnagotchi/sdcard/boot/* /boot/
rsync -aP pwnagotchi/sdcard/rootfs/* / rsync -aP pwnagotchi/sdcard/rootfs/* /
rm -rf /tmp/pwnagotchi
# configure pwnagotchi # configure pwnagotchi
echo -e "$PWNI_NAME" > /etc/hostname echo -e "$PWNI_NAME" > /etc/hostname
sed -i "s@^127\.0\.0\.1 .*@127.0.0.1 localhost "$PWNI_NAME" "$PWNI_NAME".local@g" /etc/hosts sed -i "s@^127\.0\.0\.1 .*@127.0.0.1 localhost "$PWNI_NAME" "$PWNI_NAME".local@g" /etc/hosts
sed -i "s@pwnagotchi@$PWNI_NAME@g" /etc/motd sed -i "s@alpha@$PWNI_NAME@g" /etc/motd
chmod +x /etc/rc.local chmod +x /etc/rc.local
# need armv6l version of tensorflow and opencv-python, not armv7l
# PIP_OPTS="--upgrade --only-binary :all: --abi cp37m --platform linux_armv6l --target /usr/lib/python3.7/site-packages/"
# pip3 install \$PIP_OPTS opencv-python
# Should work for tensorflow too, but BUG: Hash mismatch; therefore:
wget -P /root/ -c https://www.piwheels.org/simple/tensorflow/tensorflow-1.13.1-cp37-none-linux_armv6l.whl
wget -P /root/ -c https://www.piwheels.org/simple/opencv-python/opencv_python-3.4.3.18-cp37-cp37m-linux_armv6l.whl
# we need to install these on first raspberry start...
sed -i '/startup\.sh/i pip3 install --no-deps --force-reinstall --upgrade /root/tensorflow-1.13.1-cp37-none-linux_armv6l.whl /root/opencv_python-3.4.3.18-cp37-cp37m-linux_armv6l.whl && rm /root/tensorflow-1.13.1-cp37-none-linux_armv6l.whl /root/opencv_python-3.4.3.18-cp37-cp37m-linux_armv6l.whl && sed -i "/tensorflow/d" /etc/rc.local' /etc/rc.local
# newer version is broken
pip3 install gast==0.2.2
</root/pwnagotchi/scripts/requirements.txt xargs -I{} --max-args=1 --max-procs="$(nproc)"\ </root/pwnagotchi/scripts/requirements.txt xargs -I{} --max-args=1 --max-procs="$(nproc)"\
pip3 install --trusted-host www.piwheels.org {} >/dev/null 2>&1 pip3 install --progress-bar off {}
# waveshare # waveshare
pip3 install --trusted-host www.piwheels.org spidev RPi.GPIO pip3 install spidev RPi.GPIO
# install bettercap # install bettercap
export GOPATH=/root/go export GOPATH=/root/go
@ -134,33 +168,36 @@ function provision_raspbian() {
git clone https://github.com/bettercap/caplets.git git clone https://github.com/bettercap/caplets.git
cd caplets cd caplets
make install make install
rm -rf /tmp/caplets
# monstart + monstop cd /root # fixes getcwd error that was bugging me
cat <<"STOP" > /usr/bin/monstop
#!/bin/bash
interface=mon0
ifconfig \${interface} down
sleep 1
iw dev \${interface} del
STOP
cat <<"STOP" > /usr/bin/monstart
interface=mon0
echo "Bring up monitor mode interface \${interface}"
iw phy phy0 interface add \${interface} type monitor
ifconfig \${interface} up
if [ \$? -eq 0 ]; then
echo "started monitor interface on \${interface}"
fi
STOP
chmod +x /usr/bin/{monstart,monstop}
# Re4son-Kernel # Re4son-Kernel
echo "deb http://http.re4son-kernel.com/re4son/ kali-pi main" > /etc/apt/sources.list.d/re4son.list echo "deb http://http.re4son-kernel.com/re4son/ kali-pi main" > /etc/apt/sources.list.d/re4son.list
wget -O - https://re4son-kernel.com/keys/http/archive-key.asc | apt-key add - wget -O - https://re4son-kernel.com/keys/http/archive-key.asc | apt-key add -
apt update apt update
apt install -y kalipi-kernel kalipi-bootloader kalipi-re4son-firmware kalipi-kernel-headers libraspberrypi0 libraspberrypi-dev libraspberrypi-doc libraspberrypi-bin apt install -y kalipi-kernel kalipi-bootloader kalipi-re4son-firmware kalipi-kernel-headers libraspberrypi0 libraspberrypi-dev libraspberrypi-doc libraspberrypi-bin
# Fix PARTUUID
PUUID_ROOT="\$(blkid "\$(df / --output=source | tail -1)" | grep -Po 'PARTUUID="\K[^"]+')"
PUUID_BOOT="\$(blkid "\$(df /boot --output=source | tail -1)" | grep -Po 'PARTUUID="\K[^"]+')"
# sed regex info: search for line containing / followed by whitespace or /boot (second sed)
# in this line, search for PARTUUID= followed by letters, numbers or "-"
# replace that match with the new PARTUUID
sed -i "/\/[ ]\+/s/PARTUUID=[A-Za-z0-9-]\+/PARTUUID=\$PUUID_ROOT/g" /etc/fstab
sed -i "/\/boot/s/PARTUUID=[A-Za-z0-9-]\+/PARTUUID=\$PUUID_BOOT/g" /etc/fstab
sed -i "s/root=[^ ]\+/root=PARTUUID=\${PUUID_ROOT}/g" /boot/cmdline.txt
# delete keys
find /etc/ssh/ -name "ssh_host_*key*" -delete
# 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 EOF
sed -i'' 's/^#//g' etc/ld.so.preload sed -i'' 's/^#//g' etc/ld.so.preload
cd "${REPO_DIR}" cd "${REPO_DIR}"
@ -179,6 +216,7 @@ usage: $0 [OPTIONS]
-i <file> # Provide the path of an already downloaded raspbian image -i <file> # Provide the path of an already downloaded raspbian image
-o <file> # Name of the img-file (default: pwnagotchi.img) -o <file> # Name of the img-file (default: pwnagotchi.img)
-s <size> # Size which should be added to second partition (in Gigabyte) (default: 4) -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)
-p # Only run provisioning (assumes the image is already mounted) -p # Only run provisioning (assumes the image is already mounted)
-d # Only run dependencies checks -d # Only run dependencies checks
-h # Show this help -h # Show this help
@ -188,7 +226,7 @@ EOF
exit 0 exit 0
} }
while getopts ":n:i:o:s:dph" o; do while getopts ":n:i:o:s:v:dph" o; do
case "${o}" in case "${o}" in
n) n)
PWNI_NAME="${OPTARG}" PWNI_NAME="${OPTARG}"
@ -209,6 +247,13 @@ while getopts ":n:i:o:s:dph" o; do
d) d)
OPT_CHECK_DEPS_ONLY=1 OPT_CHECK_DEPS_ONLY=1
;; ;;
v)
if [[ "${SUPPORTED_RASPBIAN_VERSIONS[*]}" =~ ${OPTARG} ]]; then
OPT_RASPBIAN_VERSION="${OPTARG}"
else
usage
fi
;;
h) h)
usage usage
;; ;;
@ -232,7 +277,7 @@ check_dependencies
if [[ "$OPT_IMAGE_PROVIDED" -eq 1 ]]; then if [[ "$OPT_IMAGE_PROVIDED" -eq 1 ]]; then
provide_raspbian provide_raspbian
else else
get_raspbian get_raspbian "$OPT_RASPBIAN_VERSION"
fi fi
setup_raspbian setup_raspbian

71
scripts/language.sh Executable file
View 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

View File

@ -1,4 +1,4 @@
#!/bin/bash #!/usr/bin/env bash
# name of the ethernet gadget interface on the host # name of the ethernet gadget interface on the host
USB_IFACE=${1:-enp0s20f0u1} USB_IFACE=${1:-enp0s20f0u1}
@ -7,12 +7,12 @@ USB_IFACE_NET=10.0.0.0/24
# host interface to use for upstream connection # host interface to use for upstream connection
UPSTREAM_IFACE=${2:-enxe4b97aa99867} UPSTREAM_IFACE=${2:-enxe4b97aa99867}
ip addr add $USB_IFACE_IP/24 dev $USB_IFACE ip addr add "$USB_IFACE_IP/24" dev "$USB_IFACE"
ip link set $USB_IFACE up 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 -A FORWARD -m conntrack --ctstate ESTABLISHED,RELATED -j ACCEPT
iptables -t nat -F POSTROUTING 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 echo 1 > /proc/sys/net/ipv4/ip_forward

View File

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

View File

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

View File

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

View File

@ -1,5 +1,7 @@
# main algorithm configuration # main algorithm configuration
main: main:
# currently implemented: en (default), de
lang: en
# monitor interface to use # monitor interface to use
iface: mon0 iface: mon0
# command to run to bring the mon interface up in case it's not up already # command to run to bring the mon interface up in case it's not up already
@ -11,9 +13,7 @@ main:
# if true, will not restart the wifi module # if true, will not restart the wifi module
no_restart: false no_restart: false
# access points to ignore # access points to ignore
whitelist: whitelist: []
- Casa-2.4
- LOTS_OF_MALWARE
# if not null, filter access points by this regular expression # if not null, filter access points by this regular expression
filter: null filter: null
# cryptographic key for identity # cryptographic key for identity
@ -94,6 +94,12 @@ ui:
display: display:
enabled: true enabled: true
rotation: 180 rotation: 180
# Possible options inkyphat/inky, papirus/papi, waveshare_1/ws_1 or waveshare_2/ws_2
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: video:
enabled: true enabled: true
address: '10.0.0.2' address: '10.0.0.2'

View File

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

View File

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

View File

@ -1,6 +1,6 @@
#!/bin/bash #!/usr/bin/env bash
for i in `seq 1 $1`; for i in $(seq 1 "$1");
do do
echo 0 >/sys/class/leds/led0/brightness echo 0 >/sys/class/leds/led0/brightness
sleep 0.3 sleep 0.3
@ -10,3 +10,8 @@ done
echo 0 >/sys/class/leds/led0/brightness echo 0 >/sys/class/leds/led0/brightness
sleep 0.3 sleep 0.3
# Powersave options
# Disable power LED ~30ma
echo none >/sys/class/leds/led0/trigger
echo 1 >/sys/class/leds/led0/brightness

View File

@ -8,7 +8,7 @@ import core
import pwnagotchi import pwnagotchi
from pwnagotchi.log import SessionParser from pwnagotchi.log import SessionParser
import pwnagotchi.voice as voice from pwnagotchi.voice import Voice
from pwnagotchi.agent import Agent from pwnagotchi.agent import Agent
from pwnagotchi.ui.display import Display from pwnagotchi.ui.display import Display
@ -62,7 +62,7 @@ if args.do_manual:
core.log("detected a new session and internet connectivity!") core.log("detected a new session and internet connectivity!")
picture = '/tmp/pwnagotchi.png' picture = '/dev/shm/pwnagotchi.png'
display.update() display.update()
display.image().save(picture, 'png') display.image().save(picture, 'png')
@ -74,7 +74,7 @@ if args.do_manual:
auth.set_access_token(config['twitter']['access_token_key'], config['twitter']['access_token_secret']) auth.set_access_token(config['twitter']['access_token_key'], config['twitter']['access_token_secret'])
api = tweepy.API(auth) 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) api.update_with_media(filename=picture, status=tweet)
log.save_session_id() log.save_session_id()

View File

@ -54,7 +54,7 @@ class Stats(object):
def load(self): def load(self):
with self._lock: 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) core.log("[ai] loading %s" % self.path)
with open(self.path, 'rt') as fp: with open(self.path, 'rt') as fp:
obj = json.load(fp) obj = json.load(fp)

View File

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

View File

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

View File

@ -2,6 +2,7 @@ import os
import hashlib import hashlib
import time import time
import re import re
import os
from datetime import datetime from datetime import datetime
from pwnagotchi.mesh.peer import Peer from pwnagotchi.mesh.peer import Peer
@ -129,7 +130,7 @@ class SessionParser(object):
self.duration_human.append('%d seconds' % secs) self.duration_human.append('%d seconds' % secs)
self.duration_human = ', '.join(self.duration_human) self.duration_human = ', '.join(self.duration_human)
self.avg_reward /= self.epochs self.avg_reward /= (self.epochs if self.epochs else 1)
def __init__(self, path='/var/log/pwnagotchi.log'): def __init__(self, path='/var/log/pwnagotchi.log'):
self.path = path self.path = path
@ -147,6 +148,8 @@ class SessionParser(object):
'detected unit (.+)@(.+) \(v.+\) on channel \d+ \(([\d\-]+) dBm\) \[sid:(.+) pwnd_tot:(\d+) uptime:(\d+)\]') 'detected unit (.+)@(.+) \(v.+\) on channel \d+ \(([\d\-]+) dBm\) \[sid:(.+) pwnd_tot:(\d+) uptime:(\d+)\]')
lines = [] lines = []
if os.path.exists(self.path):
with FileReadBackwards(self.path, encoding="utf-8") as fp: with FileReadBackwards(self.path, encoding="utf-8") as fp:
for line in fp: for line in fp:
line = line.strip() line = line.strip()

View File

@ -3,6 +3,7 @@ from threading import Lock
import io import io
import core import core
import os
import pwnagotchi import pwnagotchi
from pwnagotchi.ui.view import WHITE, View from pwnagotchi.ui.view import WHITE, View
@ -77,6 +78,12 @@ class Display(View):
self._video_enabled = config['ui']['display']['video']['enabled'] self._video_enabled = config['ui']['display']['video']['enabled']
self._video_port = config['ui']['display']['video']['port'] self._video_port = config['ui']['display']['video']['port']
self._video_address = config['ui']['display']['video']['address'] 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._display = None
self._httpd = None self._httpd = None
self.canvas = None self.canvas = None
@ -98,26 +105,117 @@ class Display(View):
else: else:
core.log("could not get ip of usb0, video server not starting") core.log("could not get ip of usb0, video server not starting")
def _is_inky(self):
return self._display_type in ('inkyphat', 'inky')
def _is_papirus(self):
return self._display_type in ('papirus', 'papi')
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): def _init_display(self):
from pwnagotchi.ui.waveshare import EPD if self._is_inky():
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
os.environ['EPD_SIZE'] = '2.0'
self._display = Papirus()
self._display.clear()
self._render_cb = self._papirus_render
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") # core.log("display module started")
self._display = EPD() self._display = EPD()
self._display.init(self._display.FULL_UPDATE) self._display.init(self._display.FULL_UPDATE)
self._display.Clear(WHITE) self._display.Clear(WHITE)
self._display.init(self._display.PART_UPDATE) 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) self.on_render(self._on_view_rendered)
core.log("display type '%s' initialized (color:%s)" % (self._display_type, self._display_color))
def image(self): def image(self):
img = None img = None
if self.canvas is not None: if self.canvas is not None:
img = self.canvas if self._rotation == 0 else self.canvas.rotate(-self._rotation) img = self.canvas if self._rotation == 0 else self.canvas.rotate(-self._rotation)
return img return img
def _inky_render(self):
if self._display_color != 'mono':
display_colors = 3
else:
display_colors = 2
imgbuf = self.canvas.convert('RGB').convert('P', palette=1, colors=display_colors)
if self._display_color == 'red':
imgbuf.putpalette([
255, 255, 255, # index 0 is white
0, 0, 0, # index 1 is black
255, 0, 0 # index 2 is red
])
elif self._display_color == 'yellow':
imgbuf.putpalette([
255, 255, 255, # index 0 is white
0, 0, 0, # index 1 is black
255, 255, 0 # index 2 is yellow
])
else:
imgbuf.putpalette([
255, 255, 255, # index 0 is white
0, 0, 0 # index 1 is black
])
self._display.set_image(imgbuf)
self._display.show()
def _papirus_render(self):
self._display.display(self.canvas)
self._display.partial_update()
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)
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): def _on_view_rendered(self, img):
# core.log("display::_on_view_rendered") # core.log("display::_on_view_rendered")
VideoHandler.render(img) VideoHandler.render(img)
if self._enabled: 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)
buf = self._display.getbuffer(self.canvas) if self._render_cb is not None:
self._display.displayPartial(buf) self._render_cb()

View File

@ -3,6 +3,14 @@ from PIL import ImageFont
PATH = '/usr/share/fonts/truetype/dejavu/DejaVuSansMono' PATH = '/usr/share/fonts/truetype/dejavu/DejaVuSansMono'
Bold = ImageFont.truetype("%s-Bold.ttf" % PATH, 10) Bold = ImageFont.truetype("%s-Bold.ttf" % PATH, 10)
BoldSmall = ImageFont.truetype("%s-Bold.ttf" % PATH, 9) BoldSmall = ImageFont.truetype("%s-Bold.ttf" % PATH, 8)
Medium = ImageFont.truetype("%s.ttf" % PATH, 10) Medium = ImageFont.truetype("%s.ttf" % PATH, 10)
Huge = ImageFont.truetype("%s-Bold.ttf" % PATH, 35) Huge = ImageFont.truetype("%s-Bold.ttf" % PATH, 25)
def setup(bold, bold_small, medium, huge):
global PATH, Bold, BoldSmall, Medium, Huge
Bold = ImageFont.truetype("%s-Bold.ttf" % PATH, bold)
BoldSmall = ImageFont.truetype("%s-Bold.ttf" % PATH, bold_small)
Medium = ImageFont.truetype("%s.ttf" % PATH, medium)
Huge = ImageFont.truetype("%s-Bold.ttf" % PATH, huge)

View File

@ -5,7 +5,7 @@ from PIL import Image, ImageDraw
import core import core
import pwnagotchi import pwnagotchi
from pwnagotchi import voice from pwnagotchi.voice import Voice
import pwnagotchi.ui.fonts as fonts import pwnagotchi.ui.fonts as fonts
import pwnagotchi.ui.faces as faces import pwnagotchi.ui.faces as faces
@ -14,8 +14,44 @@ from pwnagotchi.ui.state import State
WHITE = 0xff WHITE = 0xff
BLACK = 0x00 BLACK = 0x00
WIDTH = 122
HEIGHT = 250
def setup_display_specifics(config):
width = 0
height = 0
face_pos = (0, 0)
name_pos = (0, 0)
status_pos = (0, 0)
if config['ui']['display']['type'] in ('inky', 'inkyphat'):
fonts.setup(10, 8, 10, 25)
width = 212
height = 104
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)
width = 200
height = 96
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_1', 'ws1', 'waveshare_1', 'waveshare1',
'ws_2', 'ws2', 'waveshare_2', 'waveshare2'):
fonts.setup(10, 9, 10, 35)
width = 250
height = 122
face_pos = (0, 40)
name_pos = (125, 20)
status_pos = (125, 35)
return width, height, face_pos, name_pos, status_pos
class View(object): class View(object):
@ -24,34 +60,46 @@ class View(object):
self._config = config self._config = config
self._canvas = None self._canvas = None
self._lock = Lock() self._lock = Lock()
self._voice = Voice(lang=config['main']['lang'])
self._width, self._height, \
face_pos, name_pos, status_pos = setup_display_specifics(config)
self._state = State(state={ self._state = State(state={
'channel': LabeledValue(color=BLACK, label='CH', value='00', position=(0, 0), label_font=fonts.Bold, 'channel': LabeledValue(color=BLACK, label='CH', value='00', position=(0, 0), label_font=fonts.Bold,
text_font=fonts.Medium), text_font=fonts.Medium),
'aps': LabeledValue(color=BLACK, label='APS', value='0 (00)', position=(30, 0), label_font=fonts.Bold, 'aps': LabeledValue(color=BLACK, label='APS', value='0 (00)', position=(30, 0), label_font=fonts.Bold,
text_font=fonts.Medium), text_font=fonts.Medium),
# 'epoch': LabeledValue(color=BLACK, label='E', value='0000', position=(145, 0), label_font=fonts.Bold, # 'epoch': LabeledValue(color=BLACK, label='E', value='0000', position=(145, 0), label_font=fonts.Bold,
# text_font=fonts.Medium), # text_font=fonts.Medium),
'uptime': LabeledValue(color=BLACK, label='UP', value='00:00:00', position=(185, 0), label_font=fonts.Bold,
'uptime': LabeledValue(color=BLACK, label='UP', value='00:00:00', position=(self._width - 65, 0),
label_font=fonts.Bold,
text_font=fonts.Medium), text_font=fonts.Medium),
# 'square': Rect([1, 11, 124, 111]), # 'square': Rect([1, 11, 124, 111]),
'line1': Line([0, 13, 250, 13], color=BLACK), 'line1': Line([0, int(self._height * .12), self._width, int(self._height * .12)], color=BLACK),
'line2': Line([0, 109, 250, 109], color=BLACK), 'line2': Line(
[0, self._height - int(self._height * .12), self._width, self._height - int(self._height * .12)],
color=BLACK),
# 'histogram': Histogram([4, 94], color = BLACK), # 'histogram': Histogram([4, 94], color = BLACK),
'face': Text(value=faces.SLEEP, position=(0, 40), color=BLACK, font=fonts.Huge), 'face': Text(value=faces.SLEEP, position=face_pos, color=BLACK, font=fonts.Huge),
'friend_face': Text(value=None, position=(0, 90), font=fonts.Bold, color=BLACK), 'friend_face': Text(value=None, position=(0, 90), font=fonts.Bold, color=BLACK),
'friend_name': Text(value=None, position=(40, 93), font=fonts.BoldSmall, color=BLACK), 'friend_name': Text(value=None, position=(40, 93), font=fonts.BoldSmall, color=BLACK),
'name': Text(value='%s>' % 'pwnagotchi', position=(125, 20), color=BLACK, font=fonts.Bold), 'name': Text(value='%s>' % 'pwnagotchi', position=name_pos, color=BLACK, font=fonts.Bold),
# 'face2': Bitmap( '/root/pwnagotchi/data/images/face_happy.bmp', (0, 20)), # 'face2': Bitmap( '/root/pwnagotchi/data/images/face_happy.bmp', (0, 20)),
'status': Text(value=voice.default(), position=(125, 35), 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, 110), label_font=fonts.Bold, 'shakes': LabeledValue(label='PWND ', value='0 (00)', color=BLACK,
position=(0, self._height - int(self._height * .12) + 1), label_font=fonts.Bold,
text_font=fonts.Medium), text_font=fonts.Medium),
'mode': Text(value='AUTO', position=(225, 110), font=fonts.Bold, color=BLACK), 'mode': Text(value='AUTO', position=(self._width - 25, self._height - int(self._height * .12) + 1),
font=fonts.Bold, color=BLACK),
}) })
for key, value in state.items(): for key, value in state.items():
@ -80,19 +128,19 @@ class View(object):
self._state.set(key, value) self._state.set(key, value)
def on_starting(self): def on_starting(self):
self.set('status', voice.on_starting()) self.set('status', self._voice.on_starting())
self.set('face', faces.AWAKE) self.set('face', faces.AWAKE)
def on_ai_ready(self): def on_ai_ready(self):
self.set('mode', '') self.set('mode', '')
self.set('face', faces.HAPPY) self.set('face', faces.HAPPY)
self.set('status', voice.on_ai_ready()) self.set('status', self._voice.on_ai_ready())
self.update() self.update()
def on_manual_mode(self, log): def on_manual_mode(self, log):
self.set('mode', 'MANU') self.set('mode', 'MANU')
self.set('face', faces.SAD if log.handshakes == 0 else faces.HAPPY) 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('epoch', "%04d" % log.epochs)
self.set('uptime', log.duration) self.set('uptime', log.duration)
self.set('channel', '-') self.set('channel', '-')
@ -116,7 +164,7 @@ class View(object):
def on_normal(self): def on_normal(self):
self.set('face', faces.AWAKE) self.set('face', faces.AWAKE)
self.set('status', voice.on_normal()) self.set('status', self._voice.on_normal())
self.update() self.update()
def set_closest_peer(self, peer): def set_closest_peer(self, peer):
@ -144,17 +192,17 @@ class View(object):
def on_new_peer(self, peer): def on_new_peer(self, peer):
self.set('face', faces.FRIEND) self.set('face', faces.FRIEND)
self.set('status', voice.on_new_peer(peer)) self.set('status', self._voice.on_new_peer(peer))
self.update() self.update()
def on_lost_peer(self, peer): def on_lost_peer(self, peer):
self.set('face', faces.LONELY) self.set('face', faces.LONELY)
self.set('status', voice.on_lost_peer(peer)) self.set('status', self._voice.on_lost_peer(peer))
self.update() self.update()
def on_free_channel(self, channel): def on_free_channel(self, channel):
self.set('face', faces.SMART) self.set('face', faces.SMART)
self.set('status', voice.on_free_channel(channel)) self.set('status', self._voice.on_free_channel(channel))
self.update() self.update()
def wait(self, secs, sleeping=True): def wait(self, secs, sleeping=True):
@ -170,12 +218,12 @@ class View(object):
if sleeping: if sleeping:
if secs > 1: if secs > 1:
self.set('face', faces.SLEEP) self.set('face', faces.SLEEP)
self.set('status', voice.on_napping(secs)) self.set('status', self._voice.on_napping(secs))
else: else:
self.set('face', faces.SLEEP2) self.set('face', faces.SLEEP2)
self.set('status', voice.on_awakening()) self.set('status', self._voice.on_awakening())
else: else:
self.set('status', voice.on_waiting(secs)) self.set('status', self._voice.on_waiting(secs))
if step % 2 == 0: if step % 2 == 0:
self.set('face', faces.LOOK_R) self.set('face', faces.LOOK_R)
else: else:
@ -188,112 +236,62 @@ class View(object):
def on_bored(self): def on_bored(self):
self.set('face', faces.BORED) self.set('face', faces.BORED)
self.set('status', voice.on_bored()) self.set('status', self._voice.on_bored())
self.update() self.update()
def on_sad(self): def on_sad(self):
self.set('face', faces.SAD) self.set('face', faces.SAD)
self.set('status', voice.on_sad()) self.set('status', self._voice.on_sad())
self.update() self.update()
def on_motivated(self, reward): def on_motivated(self, reward):
self.set('face', faces.MOTIVATED) self.set('face', faces.MOTIVATED)
self.set('status', voice.on_motivated(reward)) self.set('status', self._voice.on_motivated(reward))
self.update() self.update()
def on_demotivated(self, reward): def on_demotivated(self, reward):
self.set('face', faces.DEMOTIVATED) self.set('face', faces.DEMOTIVATED)
self.set('status', voice.on_demotivated(reward)) self.set('status', self._voice.on_demotivated(reward))
self.update() self.update()
def on_excited(self): def on_excited(self):
self.set('face', faces.EXCITED) self.set('face', faces.EXCITED)
self.set('status', voice.on_excited()) self.set('status', self._voice.on_excited())
self.update() self.update()
def on_assoc(self, ap): def on_assoc(self, ap):
self.set('face', faces.INTENSE) self.set('face', faces.INTENSE)
self.set('status', voice.on_assoc(ap)) self.set('status', self._voice.on_assoc(ap))
self.update() self.update()
def on_deauth(self, sta): def on_deauth(self, sta):
self.set('face', faces.COOL) self.set('face', faces.COOL)
self.set('status', voice.on_deauth(sta)) self.set('status', self._voice.on_deauth(sta))
self.update() self.update()
def on_miss(self, who): def on_miss(self, who):
self.set('face', faces.SAD) self.set('face', faces.SAD)
self.set('status', voice.on_miss(who)) self.set('status', self._voice.on_miss(who))
self.update() self.update()
def on_lonely(self): def on_lonely(self):
self.set('face', faces.LONELY) self.set('face', faces.LONELY)
self.set('status', voice.on_lonely()) self.set('status', self._voice.on_lonely())
self.update() self.update()
def on_handshakes(self, new_shakes): def on_handshakes(self, new_shakes):
self.set('face', faces.HAPPY) self.set('face', faces.HAPPY)
self.set('status', voice.on_handshakes(new_shakes)) self.set('status', self._voice.on_handshakes(new_shakes))
self.update() self.update()
def on_rebooting(self): def on_rebooting(self):
self.set('face', faces.BROKEN) self.set('face', faces.BROKEN)
self.set('status', voice.on_rebooting()) self.set('status', self._voice.on_rebooting())
self.update() self.update()
def update(self): def update(self):
"""
ncalls tottime percall cumtime percall filename:lineno(function)
19 0.001 0.000 0.007 0.000 Image.py:1137(copy)
19 0.001 0.000 0.069 0.004 Image.py:1894(rotate)
19 0.001 0.000 0.068 0.004 Image.py:2388(transpose)
1 0.000 0.000 0.000 0.000 Image.py:2432(ImagePointHandler)
1 0.000 0.000 0.000 0.000 Image.py:2437(ImageTransformHandler)
19 0.001 0.000 0.001 0.000 Image.py:2455(_check_size)
19 0.002 0.000 0.010 0.001 Image.py:2473(new)
1 0.002 0.002 0.127 0.127 Image.py:30(<module>)
1 0.000 0.000 0.001 0.001 Image.py:3103(_apply_env_variables)
1 0.000 0.000 0.000 0.000 Image.py:3139(Exif)
1 0.000 0.000 0.000 0.000 Image.py:495(_E)
1 0.000 0.000 0.000 0.000 Image.py:536(Image)
76 0.003 0.000 0.003 0.000 Image.py:552(__init__)
19 0.000 0.000 0.000 0.000 Image.py:572(size)
57 0.004 0.000 0.006 0.000 Image.py:576(_new)
76 0.001 0.000 0.002 0.000 Image.py:595(__exit__)
76 0.001 0.000 0.003 0.000 Image.py:633(__del__)
1 0.000 0.000 0.000 0.000 Image.py:71(DecompressionBombWarning)
1 0.000 0.000 0.000 0.000 Image.py:75(DecompressionBombError)
1 0.000 0.000 0.000 0.000 Image.py:79(_imaging_not_installed)
95 0.002 0.000 0.014 0.000 Image.py:842(load)
19 0.001 0.000 0.008 0.000 Image.py:892(convert)
1 0.000 0.000 0.000 0.000 ImageColor.py:20(<module>)
297 0.012 0.000 0.042 0.000 ImageDraw.py:101(_getink)
38 0.001 0.000 0.026 0.001 ImageDraw.py:153(line)
295 0.005 0.000 0.007 0.000 ImageDraw.py:252(_multiline_check)
8 0.000 0.000 0.001 0.000 ImageDraw.py:258(_multiline_split)
267/247 0.033 0.000 1.741 0.007 ImageDraw.py:263(text)
8 0.003 0.000 0.237 0.030 ImageDraw.py:282(multiline_text)
28 0.001 0.000 0.064 0.002 ImageDraw.py:328(textsize)
1 0.000 0.000 0.008 0.008 ImageDraw.py:33(<module>)
19 0.002 0.000 0.006 0.000 ImageDraw.py:355(Draw)
1 0.000 0.000 0.000 0.000 ImageDraw.py:47(ImageDraw)
19 0.002 0.000 0.004 0.000 ImageDraw.py:48(__init__)
1 0.000 0.000 0.000 0.000 ImageFont.py:123(FreeTypeFont)
3 0.000 0.000 0.002 0.001 ImageFont.py:126(__init__)
28 0.001 0.000 0.062 0.002 ImageFont.py:185(getsize)
1 0.000 0.000 0.011 0.011 ImageFont.py:28(<module>)
259 0.020 0.000 1.435 0.006 ImageFont.py:337(getmask2)
1 0.000 0.000 0.000 0.000 ImageFont.py:37(_imagingft_not_installed)
1 0.000 0.000 0.000 0.000 ImageFont.py:474(TransposedFont)
3 0.000 0.000 0.003 0.001 ImageFont.py:517(truetype)
3 0.000 0.000 0.002 0.001 ImageFont.py:542(freetype)
1 0.000 0.000 0.000 0.000 ImageFont.py:65(ImageFont)
1 0.000 0.000 0.000 0.000 ImageMode.py:17(<module>)
1 0.000 0.000 0.000 0.000 ImageMode.py:20(ModeDescriptor)
"""
with self._lock: with self._lock:
self._canvas = Image.new('1', (HEIGHT, WIDTH), WHITE) self._canvas = Image.new('1', (self._width, self._height), WHITE)
drawer = ImageDraw.Draw(self._canvas) drawer = ImageDraw.Draw(self._canvas)
for key, lv in self._state.items(): for key, lv in self._state.items():

View File

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

View 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 ###

View File

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

View File

@ -8,3 +8,4 @@ tensorflow
tweepy tweepy
file_read_backwards file_read_backwards
numpy numpy
inky

View File

@ -1,4 +1,4 @@
#!/bin/bash #!/usr/bin/env bash
# blink 10 times to signal ready state # blink 10 times to signal ready state
/root/pwnagotchi/scripts/blink.sh 10 & /root/pwnagotchi/scripts/blink.sh 10 &