Compare commits
187 Commits
Author | SHA1 | Date | |
---|---|---|---|
|
99f6758aae | ||
|
0a33f9c0af | ||
|
d231541403 | ||
|
f1eb3316c6 | ||
|
f3a96b7981 | ||
|
beb2b83f36 | ||
|
24ae443ee9 | ||
|
3f785ee06a | ||
|
cf146a54ee | ||
|
31c1d742e0 | ||
|
c0252c9830 | ||
|
4aa29f1b79 | ||
|
5dc780a88f | ||
|
39a6ae1be5 | ||
|
047f0d5d63 | ||
|
715e696537 | ||
|
22afb563e3 | ||
|
5f4ee26f99 | ||
|
1959bc08ae | ||
|
2c9f54567f | ||
|
63694d57e5 | ||
|
c6c2e0e7ce | ||
|
cb6365b9f2 | ||
|
8b00e0ae10 | ||
|
7954bb5fcf | ||
|
06d8cc63fb | ||
|
c4ae3c15bd | ||
|
3604f483aa | ||
|
d51d3f61ac | ||
|
6bb8fede0c | ||
|
c35d202ffd | ||
|
ffa432587a | ||
|
6f013bb1ef | ||
|
f8f6608968 | ||
|
0c176ca308 | ||
|
ec430a5cba | ||
|
97733cbf43 | ||
|
4445ef6432 | ||
|
a25395a945 | ||
|
0f171c35ce | ||
|
61b957ac77 | ||
|
30e3898f3c | ||
|
094dde0e8c | ||
|
dc5a626bd5 | ||
|
608aad4820 | ||
|
30f9c16778 | ||
|
97cccf5a1d | ||
|
ce0c248cf4 | ||
|
d5ac988498 | ||
|
6d70a24aae | ||
|
032e183ff7 | ||
|
f00c861844 | ||
|
4addefd57d | ||
|
2239406272 | ||
|
0df5e54f91 | ||
|
3e0833698a | ||
|
ab9ddb4513 | ||
|
2beef33251 | ||
|
2b583d7458 | ||
|
da7e21bbdd | ||
|
d9ddae9a41 | ||
|
d306877c7a | ||
|
7d34e631f5 | ||
|
f39c154b23 | ||
|
04b2ee5b44 | ||
|
9a72a8868b | ||
|
d636c2b1f2 | ||
|
84616827ad | ||
|
d16180189e | ||
|
885fddfce8 | ||
|
68d7686a03 | ||
|
d20f6c8a52 | ||
|
5bf9f25a46 | ||
|
b85e4174dc | ||
|
0de2e3d150 | ||
|
412fca5290 | ||
|
3ab2088c79 | ||
|
eda8a18788 | ||
|
ea14cb370e | ||
|
6f2228f439 | ||
|
1d53dc7c4e | ||
|
202cd1f32e | ||
|
ab871f9d2d | ||
|
f2ceec0f8f | ||
|
f734c9cd68 | ||
|
36c3ea5bbc | ||
|
062de3b618 | ||
|
77ada644ed | ||
|
277906a673 | ||
|
a78a4b0b3e | ||
|
3ad426916f | ||
|
23ef17d4c7 | ||
|
7779ebc983 | ||
|
4fa7e9f077 | ||
|
9ca2424df1 | ||
|
8c22b1d6f1 | ||
|
538b547560 | ||
|
414a6b4c7a | ||
|
cdf11df270 | ||
|
ef52cbcc25 | ||
|
b0dab7b589 | ||
|
ace1244d10 | ||
|
84fa293a11 | ||
|
139c9df88c | ||
|
7a721be7dc | ||
|
56079dfd9d | ||
|
90998be24c | ||
|
5cb721f490 | ||
|
c954bf8ffa | ||
|
41ea0e0747 | ||
|
e943cfad70 | ||
|
f576fb609b | ||
|
1e015fe6f6 | ||
|
d526915cce | ||
|
0ac940f636 | ||
|
da6f3608f3 | ||
|
bdfd021820 | ||
|
4a2091e688 | ||
|
16832cd414 | ||
|
020be7185c | ||
|
c8ff69c068 | ||
|
79d252254f | ||
|
64e677f5df | ||
|
bceb3c4c4f | ||
|
df246b255a | ||
|
d6d810497e | ||
|
9df1dbe077 | ||
|
3c97dbf8dc | ||
|
376a74d7ac | ||
|
855b493040 | ||
|
375486f9d0 | ||
|
0263777e4b | ||
|
132666935a | ||
|
74a75f03b8 | ||
|
399e78e675 | ||
|
649091766f | ||
|
999b130224 | ||
|
2a450e64ef | ||
|
6152374024 | ||
|
f9cbef9697 | ||
|
682b373c66 | ||
|
8d0d3df2b0 | ||
|
6d5054387b | ||
|
4018071bba | ||
|
63568f1725 | ||
|
c72cb5b962 | ||
|
0cdb8c3221 | ||
|
ddeefa037d | ||
|
a4e072cf33 | ||
|
677b335403 | ||
|
f762c3ac0d | ||
|
152676f651 | ||
|
0ce1fa9d12 | ||
|
2e8f2aa1b8 | ||
|
f7a23b32c1 | ||
|
4653c5d95d | ||
|
c947d5c43b | ||
|
26bb5d6183 | ||
|
916be1f63e | ||
|
4a4b973f60 | ||
|
d49cefe1e4 | ||
|
c6b3e11e04 | ||
|
e8fa682302 | ||
|
fbd12bf87b | ||
|
40c01db1d1 | ||
|
ca2becd9ce | ||
|
ff6bf5c198 | ||
|
cd5d783c52 | ||
|
99e0a31ea8 | ||
|
5f6cc378f1 | ||
|
d45e8c7ba0 | ||
|
539df810ed | ||
|
bd7c64b2af | ||
|
892fda775d | ||
|
f9c0efc24a | ||
|
400d0e7290 | ||
|
fb2c65ef0a | ||
|
6d88cb17f3 | ||
|
c10f25140c | ||
|
d2966098b0 | ||
|
a3a4854427 | ||
|
e5d623c114 | ||
|
68801adcaf | ||
|
58a085188f | ||
|
f52687642e | ||
|
2d7b6b54fe | ||
|
b3aa5bc2c1 |
@@ -14,7 +14,7 @@
|
||||
[Pwnagotchi](https://pwnagotchi.ai/) is an [A2C](https://hackernoon.com/intuitive-rl-intro-to-advantage-actor-critic-a2c-4ff545978752)-based "AI" leveraging [bettercap](https://www.bettercap.org/) that learns from its surrounding WiFi environment to maximize the crackable WPA key material it captures (either passively, or by performing authentication and association attacks). This material is collected as PCAP files containing any form of handshake supported by [hashcat](https://hashcat.net/hashcat/), 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.
|
||||
|
||||

|
||||

|
||||
|
||||
Instead of merely playing [Super Mario or Atari games](https://becominghuman.ai/getting-mario-back-into-the-gym-setting-up-super-mario-bros-in-openais-gym-8e39a96c1e41?gi=c4b66c3d5ced) like most reinforcement learning-based "AI" *(yawn)*, Pwnagotchi tunes [its parameters](https://github.com/evilsocket/pwnagotchi/blob/master/pwnagotchi/defaults.yml#L73) over time to **get better at pwning WiFi things to** in the environments you expose it to.
|
||||
|
||||
|
@@ -3,6 +3,7 @@ if __name__ == '__main__':
|
||||
import argparse
|
||||
import time
|
||||
import logging
|
||||
import yaml
|
||||
|
||||
import pwnagotchi
|
||||
import pwnagotchi.grid as grid
|
||||
@@ -21,6 +22,9 @@ if __name__ == '__main__':
|
||||
help='If this file exists, configuration will be merged and this will override default values.')
|
||||
|
||||
parser.add_argument('--manual', dest="do_manual", action="store_true", default=False, help="Manual mode.")
|
||||
parser.add_argument('--skip-session', dest="skip_session", action="store_true", default=False,
|
||||
help="Skip last session parsing in manual mode.")
|
||||
|
||||
parser.add_argument('--clear', dest="do_clear", action="store_true", default=False,
|
||||
help="Clear the ePaper display and exit.")
|
||||
|
||||
@@ -31,6 +35,8 @@ if __name__ == '__main__':
|
||||
config = utils.load_config(args)
|
||||
utils.setup_logging(args, config)
|
||||
|
||||
pwnagotchi.set_name(config['main']['name'])
|
||||
|
||||
plugins.load(config)
|
||||
|
||||
display = Display(config=config, state={'name': '%s>' % pwnagotchi.name()})
|
||||
@@ -39,6 +45,8 @@ if __name__ == '__main__':
|
||||
|
||||
logging.info("%s@%s (v%s)" % (pwnagotchi.name(), agent.fingerprint(), pwnagotchi.version))
|
||||
|
||||
logging.debug("effective configuration:\n\n%s\n\n" % yaml.dump(config, default_flow_style=False))
|
||||
|
||||
for _, plugin in plugins.loaded.items():
|
||||
logging.debug("plugin '%s' v%s loaded from %s" % (plugin.__name__, plugin.__version__, plugin.__file__))
|
||||
|
||||
@@ -49,21 +57,20 @@ if __name__ == '__main__':
|
||||
elif args.do_manual:
|
||||
logging.info("entering manual mode ...")
|
||||
|
||||
agent.last_session.parse()
|
||||
|
||||
logging.info(
|
||||
"the last session lasted %s (%d completed epochs, trained for %d), average reward:%s (min:%s max:%s)" % (
|
||||
agent.last_session.duration_human,
|
||||
agent.last_session.epochs,
|
||||
agent.last_session.train_epochs,
|
||||
agent.last_session.avg_reward,
|
||||
agent.last_session.min_reward,
|
||||
agent.last_session.max_reward))
|
||||
agent.last_session.parse(args.skip_session)
|
||||
if not args.skip_session:
|
||||
logging.info(
|
||||
"the last session lasted %s (%d completed epochs, trained for %d), average reward:%s (min:%s max:%s)" % (
|
||||
agent.last_session.duration_human,
|
||||
agent.last_session.epochs,
|
||||
agent.last_session.train_epochs,
|
||||
agent.last_session.avg_reward,
|
||||
agent.last_session.min_reward,
|
||||
agent.last_session.max_reward))
|
||||
|
||||
while True:
|
||||
display.on_manual_mode(agent.last_session)
|
||||
time.sleep(1)
|
||||
|
||||
time.sleep(5)
|
||||
if grid.is_connected():
|
||||
plugins.on('internet_available', agent)
|
||||
|
||||
|
@@ -9,10 +9,12 @@
|
||||
system:
|
||||
boot_options:
|
||||
- "dtoverlay=dwc2"
|
||||
- "dtparam=spi=on"
|
||||
- "dtoverlay=spi1-3cs"
|
||||
- "dtoverlay=i2c_arm=on"
|
||||
- "dtoverlay=i2c1=on"
|
||||
- "dtparam=spi=on"
|
||||
- "dtparam=i2c_arm=on"
|
||||
- "dtparam=i2c1=on"
|
||||
modules:
|
||||
- "i2c-dev"
|
||||
services:
|
||||
enable:
|
||||
- dphys-swapfile.service
|
||||
@@ -31,10 +33,10 @@
|
||||
- ifup@wlan0.service
|
||||
packages:
|
||||
bettercap:
|
||||
url: "https://github.com/bettercap/bettercap/releases/download/v2.25/bettercap_linux_armv6l_2.25.zip"
|
||||
url: "https://github.com/bettercap/bettercap/releases/download/v2.26.1/bettercap_linux_armhf_v2.26.1.zip"
|
||||
ui: "https://github.com/bettercap/ui/releases/download/v1.3.0/ui.zip"
|
||||
pwngrid:
|
||||
url: "https://github.com/evilsocket/pwngrid/releases/download/v1.8.0/pwngrid_linux_armhf_v1.8.0.zip"
|
||||
url: "https://github.com/evilsocket/pwngrid/releases/download/v1.10.1/pwngrid_linux_armhf_v1.10.1.zip"
|
||||
apt:
|
||||
hold:
|
||||
- firmware-atheros
|
||||
@@ -95,26 +97,28 @@
|
||||
- libfuse-dev
|
||||
- bc
|
||||
- fonts-freefont-ttf
|
||||
- fbi
|
||||
|
||||
tasks:
|
||||
|
||||
- name: selected hostname
|
||||
debug:
|
||||
msg: "{{ pwnagotchi.hostname }}"
|
||||
|
||||
- name: build version
|
||||
debug:
|
||||
msg: "{{ pwnagotchi.version }}"
|
||||
|
||||
- name: change hostname
|
||||
hostname:
|
||||
name: "{{pwnagotchi.hostname}}"
|
||||
when: lookup('file', '/etc/hostname') == "raspberrypi"
|
||||
register: hostname
|
||||
|
||||
- name: add hostname to /etc/hosts
|
||||
lineinfile:
|
||||
dest: /etc/hosts
|
||||
regexp: '^127\.0\.0\.1[ \t]+localhost'
|
||||
line: '127.0.0.1 localhost {{pwnagotchi.hostname}} {{pwnagotchi.hostname}}.local'
|
||||
regexp: '^127\.0\.1\.1[ \t]+raspberrypi'
|
||||
line: "127.0.1.1\t{{pwnagotchi.hostname}}"
|
||||
state: present
|
||||
when: hostname.changed
|
||||
|
||||
- name: disable sap plugin for bluetooth.service
|
||||
lineinfile:
|
||||
dest: /lib/systemd/system/bluetooth.service
|
||||
regexp: '^ExecStart=/usr/lib/bluetooth/bluetoothd$'
|
||||
line: 'ExecStart=/usr/lib/bluetooth/bluetoothd --noplugin=sap'
|
||||
state: present
|
||||
|
||||
- name: Add re4son-kernel repo key
|
||||
@@ -161,6 +165,7 @@
|
||||
git:
|
||||
repo: https://github.com/repaper/gratis.git
|
||||
dest: /usr/local/src/gratis
|
||||
register: gratisgit
|
||||
|
||||
- name: build papirus service
|
||||
make:
|
||||
@@ -169,6 +174,7 @@
|
||||
params:
|
||||
EPD_IO: epd_io.h
|
||||
PANEL_VERSION: 'V231_G2'
|
||||
when: gratisgit.changed
|
||||
|
||||
- name: install papirus service
|
||||
make:
|
||||
@@ -177,6 +183,7 @@
|
||||
params:
|
||||
EPD_IO: epd_io.h
|
||||
PANEL_VERSION: 'V231_G2'
|
||||
when: gratisgit.changed
|
||||
|
||||
- name: configure papirus display size
|
||||
lineinfile:
|
||||
@@ -184,6 +191,16 @@
|
||||
regexp: "#EPD_SIZE=2.0"
|
||||
line: "EPD_SIZE=2.0"
|
||||
|
||||
- name: collect python pip package list
|
||||
command: "pip3 list"
|
||||
register: pip_output
|
||||
|
||||
- name: set python pip package facts
|
||||
set_fact:
|
||||
pip_packages: >
|
||||
{{ pip_packages | default({}) | combine( { item.split()[0]: item.split()[1] } ) }}
|
||||
with_items: "{{ pip_output.stdout_lines }}"
|
||||
|
||||
- name: acquire python3 pip target
|
||||
command: "python3 -c 'import sys;print(sys.path.pop())'"
|
||||
register: pip_target
|
||||
@@ -192,26 +209,39 @@
|
||||
git:
|
||||
repo: https://github.com/evilsocket/pwnagotchi.git
|
||||
dest: /usr/local/src/pwnagotchi
|
||||
register: pwnagotchigit
|
||||
|
||||
- name: fetch pwnagotchi version
|
||||
set_fact:
|
||||
pwnagotchi_version: "{{ lookup('file', '/usr/local/src/pwnagotchi/pwnagotchi/__init__.py') | replace('\n', ' ') | regex_replace('.*version.*=.*''([0-9]+\\.[0-9]+\\.[0-9]+[A-Za-z0-9]*)''.*', '\\1') }}"
|
||||
|
||||
- name: pwnagotchi version found
|
||||
debug:
|
||||
msg: "{{ pwnagotchi_version }}"
|
||||
|
||||
- name: build pwnagotchi wheel
|
||||
command: "python3 setup.py sdist bdist_wheel"
|
||||
args:
|
||||
chdir: /usr/local/src/pwnagotchi
|
||||
when: (pwnagotchigit.changed) or (pip_packages['pwnagotchi'] is undefined) or (pip_packages['pwnagotchi'] != pwnagotchi.version)
|
||||
|
||||
- name: install opencv-python
|
||||
pip:
|
||||
name: "https://www.piwheels.hostedpi.com/simple/opencv-python/opencv_python-3.4.3.18-cp37-cp37m-linux_armv6l.whl"
|
||||
extra_args: "--no-deps --no-cache-dir --platform=linux_armv6l --only-binary=:all: --target={{ pip_target.stdout }}"
|
||||
when: (pip_packages['opencv-python'] is undefined) or (pip_packages['opencv-python'] != '3.4.3.18')
|
||||
|
||||
- name: install tensorflow
|
||||
pip:
|
||||
name: "https://www.piwheels.hostedpi.com/simple/tensorflow/tensorflow-1.13.1-cp37-none-linux_armv6l.whl"
|
||||
extra_args: "--no-deps --no-cache-dir --platform=linux_armv6l --only-binary=:all: --target={{ pip_target.stdout }}"
|
||||
when: (pip_packages['tensorflow'] is undefined) or (pip_packages['tensorflow'] != '1.13.1')
|
||||
|
||||
- name: install pwnagotchi wheel and dependencies
|
||||
pip:
|
||||
name: "{{ lookup('fileglob', '/usr/local/src/pwnagotchi/dist/pwnagotchi*.whl') }}"
|
||||
extra_args: "--no-cache-dir"
|
||||
when: (pwnagotchigit.changed) or (pip_packages['pwnagotchi'] is undefined) or (pip_packages['pwnagotchi'] != pwnagotchi.version)
|
||||
|
||||
- name: download and install pwngrid
|
||||
unarchive:
|
||||
@@ -234,11 +264,13 @@
|
||||
git:
|
||||
repo: https://github.com/bettercap/caplets.git
|
||||
dest: /tmp/caplets
|
||||
register: capletsgit
|
||||
|
||||
- name: install bettercap caplets
|
||||
make:
|
||||
chdir: /tmp/caplets
|
||||
target: install
|
||||
when: capletsgit.changed
|
||||
|
||||
- name: download and install bettercap ui
|
||||
unarchive:
|
||||
@@ -247,26 +279,6 @@
|
||||
remote_src: yes
|
||||
mode: 0755
|
||||
|
||||
- name: create cpuusage script
|
||||
copy:
|
||||
dest: /usr/bin/cpuusage
|
||||
mode: 0755
|
||||
content: |
|
||||
#!/usr/bin/env bash
|
||||
while true
|
||||
do
|
||||
top -b -n1 | awk '/Cpu\(s\)/ { printf("%d %", $2 + $4 + 0.5) }'
|
||||
sleep 3
|
||||
done
|
||||
|
||||
- name: create memusage script
|
||||
copy:
|
||||
dest: /usr/bin/memusage
|
||||
mode: 0755
|
||||
content: |
|
||||
#!/usr/bin/env bash
|
||||
free -m | awk '/Mem/ { printf( "%d %", $3 / $2 * 100 + 0.5 ) }'
|
||||
|
||||
- name: create bootblink script
|
||||
copy:
|
||||
dest: /usr/bin/bootblink
|
||||
@@ -292,7 +304,7 @@
|
||||
# blink 10 times to signal ready state
|
||||
/usr/bin/bootblink 10 &
|
||||
# start a detached screen session with bettercap
|
||||
if ifconfig | grep usb0 | grep RUNNING; then
|
||||
if [[ $(ifconfig | grep usb0 | grep RUNNING) ]] || [[ $(cat /sys/class/net/eth0/carrier) ]]; then
|
||||
# if override file exists, go into auto mode
|
||||
if [ -f /root/.pwnagotchi-auto ]; then
|
||||
rm /root/.pwnagotchi-auto
|
||||
@@ -310,18 +322,16 @@
|
||||
mode: 0755
|
||||
content: |
|
||||
#!/usr/bin/env bash
|
||||
# blink 10 times to signal ready state
|
||||
/usr/bin/bootblink 10 &
|
||||
if ifconfig | grep usb0 | grep RUNNING; then
|
||||
/usr/bin/monstart
|
||||
if [[ $(ifconfig | grep usb0 | grep RUNNING) ]] || [[ $(cat /sys/class/net/eth0/carrier) ]]; then
|
||||
# if override file exists, go into auto mode
|
||||
if [ -f /root/.pwnagotchi-auto ]; then
|
||||
rm /root/.pwnagotchi-auto
|
||||
/usr/bin/bettercap -no-colors -caplet pwnagotchi-auto
|
||||
/usr/bin/bettercap -no-colors -caplet pwnagotchi-auto -iface mon0
|
||||
else
|
||||
/usr/bin/bettercap -no-colors -caplet pwnagotchi-manual
|
||||
/usr/bin/bettercap -no-colors -caplet pwnagotchi-manual -iface mon0
|
||||
fi
|
||||
else
|
||||
/usr/bin/bettercap -no-colors -caplet pwnagotchi-auto
|
||||
/usr/bin/bettercap -no-colors -caplet pwnagotchi-auto -iface mon0
|
||||
fi
|
||||
|
||||
- name: create monstart script
|
||||
@@ -434,6 +444,13 @@
|
||||
line: '{{ item }}'
|
||||
with_items: "{{system.boot_options}}"
|
||||
|
||||
- name: adjust /etc/modules
|
||||
lineinfile:
|
||||
dest: /etc/modules
|
||||
insertafter: EOF
|
||||
line: '{{ item }}'
|
||||
with_items: "{{system.modules}}"
|
||||
|
||||
- name: change root partition
|
||||
replace:
|
||||
dest: /boot/cmdline.txt
|
||||
@@ -480,6 +497,7 @@
|
||||
touch /root/.pwnagotchi-auto && systemctl restart pwnagotchi
|
||||
|
||||
You learn more about me at https://pwnagotchi.ai/
|
||||
when: hostname.changed
|
||||
|
||||
- name: clean apt cache
|
||||
apt:
|
||||
@@ -497,7 +515,6 @@
|
||||
Description=pwngrid peer service.
|
||||
Documentation=https://pwnagotchi.ai
|
||||
Wants=network.target
|
||||
After=bettercap.service
|
||||
|
||||
[Service]
|
||||
Type=simple
|
||||
@@ -519,14 +536,12 @@
|
||||
Description=bettercap api.rest service.
|
||||
Documentation=https://bettercap.org
|
||||
Wants=network.target
|
||||
After=network.target
|
||||
After=pwngrid.service
|
||||
|
||||
[Service]
|
||||
Type=simple
|
||||
PermissionsStartOnly=true
|
||||
ExecStartPre=/usr/bin/monstart
|
||||
ExecStart=/usr/bin/bettercap-launcher
|
||||
ExecStopPost=/usr/bin/monstop
|
||||
Restart=always
|
||||
RestartSec=30
|
||||
|
||||
|
@@ -2,13 +2,48 @@ import subprocess
|
||||
import os
|
||||
import logging
|
||||
import time
|
||||
import re
|
||||
import pwnagotchi.ui.view as view
|
||||
import pwnagotchi
|
||||
|
||||
version = '1.0.0'
|
||||
version = '1.1.0RC0'
|
||||
|
||||
_name = None
|
||||
|
||||
|
||||
def set_name(new_name):
|
||||
if new_name is None:
|
||||
return
|
||||
|
||||
new_name = new_name.strip()
|
||||
if new_name == '':
|
||||
return
|
||||
|
||||
if not re.match(r'^[a-zA-Z0-9\-]{2,25}$', new_name):
|
||||
logging.warning("name '%s' is invalid: min length is 2, max length 25, only a-zA-Z0-9- allowed", new_name)
|
||||
return
|
||||
|
||||
current = name()
|
||||
if new_name != current:
|
||||
global _name
|
||||
|
||||
logging.info("setting unit hostname '%s' -> '%s'" % (current, new_name))
|
||||
with open('/etc/hostname', 'wt') as fp:
|
||||
fp.write(new_name)
|
||||
|
||||
with open('/etc/hosts', 'rt') as fp:
|
||||
prev = fp.read()
|
||||
logging.debug("old hosts:\n%s\n" % prev)
|
||||
|
||||
with open('/etc/hosts', 'wt') as fp:
|
||||
patched = prev.replace(current, new_name, -1)
|
||||
logging.debug("new hosts:\n%s\n" % patched)
|
||||
fp.write(patched)
|
||||
|
||||
os.system("hostname '%s'" % new_name)
|
||||
pwnagotchi.reboot()
|
||||
|
||||
|
||||
def name():
|
||||
global _name
|
||||
if _name is None:
|
||||
@@ -23,15 +58,21 @@ def uptime():
|
||||
|
||||
|
||||
def mem_usage():
|
||||
out = subprocess.getoutput("free -m")
|
||||
for line in out.split("\n"):
|
||||
line = line.strip()
|
||||
if line.startswith("Mem:"):
|
||||
parts = list(map(int, line.split()[1:]))
|
||||
tot = parts[0]
|
||||
used = parts[1]
|
||||
free = parts[2]
|
||||
return used / tot
|
||||
with open('/proc/meminfo') as fp:
|
||||
for line in fp:
|
||||
line = line.strip()
|
||||
if line.startswith("MemTotal:"):
|
||||
kb_mem_total = int(line.split()[1])
|
||||
if line.startswith("MemFree:"):
|
||||
kb_mem_free = int(line.split()[1])
|
||||
if line.startswith("MemAvailable:"):
|
||||
kb_mem_available = int(line.split()[1])
|
||||
if line.startswith("Buffers:"):
|
||||
kb_main_buffers = int(line.split()[1])
|
||||
if line.startswith("Cached:"):
|
||||
kb_main_cached = int(line.split()[1])
|
||||
kb_mem_used = kb_mem_total - kb_mem_free - kb_main_cached - kb_main_buffers
|
||||
return round(kb_mem_used / kb_mem_total, 1)
|
||||
|
||||
return 0
|
||||
|
||||
|
@@ -8,6 +8,7 @@ import _thread
|
||||
import pwnagotchi
|
||||
import pwnagotchi.utils as utils
|
||||
import pwnagotchi.plugins as plugins
|
||||
from pwnagotchi.automata import Automata
|
||||
from pwnagotchi.log import LastSession
|
||||
from pwnagotchi.bettercap import Client
|
||||
from pwnagotchi.mesh.utils import AsyncAdvertiser
|
||||
@@ -16,13 +17,14 @@ from pwnagotchi.ai.train import AsyncTrainer
|
||||
RECOVERY_DATA_FILE = '/root/.pwnagotchi-recovery'
|
||||
|
||||
|
||||
class Agent(Client, AsyncAdvertiser, AsyncTrainer):
|
||||
class Agent(Client, Automata, AsyncAdvertiser, AsyncTrainer):
|
||||
def __init__(self, view, config, keypair):
|
||||
Client.__init__(self, config['bettercap']['hostname'],
|
||||
config['bettercap']['scheme'],
|
||||
config['bettercap']['port'],
|
||||
config['bettercap']['username'],
|
||||
config['bettercap']['password'])
|
||||
Automata.__init__(self, config, view)
|
||||
AsyncAdvertiser.__init__(self, config, view, keypair)
|
||||
AsyncTrainer.__init__(self, config)
|
||||
|
||||
@@ -31,6 +33,7 @@ class Agent(Client, AsyncAdvertiser, AsyncTrainer):
|
||||
self._current_channel = 0
|
||||
self._supported_channels = utils.iface_channels(config['main']['iface'])
|
||||
self._view = view
|
||||
self._view.set_agent(self)
|
||||
self._access_points = []
|
||||
self._last_pwnd = None
|
||||
self._history = {}
|
||||
@@ -49,36 +52,6 @@ class Agent(Client, AsyncAdvertiser, AsyncTrainer):
|
||||
def supported_channels(self):
|
||||
return self._supported_channels
|
||||
|
||||
def set_starting(self):
|
||||
self._view.on_starting()
|
||||
|
||||
def set_ready(self):
|
||||
plugins.on('ready', self)
|
||||
|
||||
def set_free_channel(self, channel):
|
||||
self._view.on_free_channel(channel)
|
||||
plugins.on('free_channel', self, channel)
|
||||
|
||||
def set_bored(self):
|
||||
self._view.on_bored()
|
||||
plugins.on('bored', self)
|
||||
|
||||
def set_sad(self):
|
||||
self._view.on_sad()
|
||||
plugins.on('sad', self)
|
||||
|
||||
def set_excited(self):
|
||||
self._view.on_excited()
|
||||
plugins.on('excited', self)
|
||||
|
||||
def set_lonely(self):
|
||||
self._view.on_lonely()
|
||||
plugins.on('lonely', self)
|
||||
|
||||
def set_rebooting(self):
|
||||
self._view.on_rebooting()
|
||||
plugins.on('rebooting', self)
|
||||
|
||||
def setup_events(self):
|
||||
logging.info("connecting to %s ..." % self.url)
|
||||
|
||||
@@ -135,8 +108,18 @@ class Agent(Client, AsyncAdvertiser, AsyncTrainer):
|
||||
|
||||
self.start_advertising()
|
||||
|
||||
def _wait_bettercap(self):
|
||||
while True:
|
||||
try:
|
||||
s = self.session()
|
||||
return
|
||||
except:
|
||||
logging.info("waiting for bettercap API to be available ...")
|
||||
time.sleep(1)
|
||||
|
||||
def start(self):
|
||||
self.start_ai()
|
||||
self._wait_bettercap()
|
||||
self.setup_events()
|
||||
self.set_starting()
|
||||
self.start_monitor_mode()
|
||||
@@ -145,11 +128,6 @@ class Agent(Client, AsyncAdvertiser, AsyncTrainer):
|
||||
self.next_epoch()
|
||||
self.set_ready()
|
||||
|
||||
def wait_for(self, t, sleeping=True):
|
||||
plugins.on('sleep' if sleeping else 'wait', self, t)
|
||||
self._view.wait(t, sleeping)
|
||||
self._epoch.track(sleep=True, inc=t)
|
||||
|
||||
def recon(self):
|
||||
recon_time = self._config['personality']['recon_time']
|
||||
max_inactive = self._config['personality']['max_inactive_scale']
|
||||
@@ -267,6 +245,11 @@ class Agent(Client, AsyncAdvertiser, AsyncTrainer):
|
||||
def _update_peers(self):
|
||||
self._view.set_closest_peer(self._closest_peer, len(self._peers))
|
||||
|
||||
def _reboot(self):
|
||||
self.set_rebooting()
|
||||
self._save_recovery_data()
|
||||
pwnagotchi.reboot()
|
||||
|
||||
def _save_recovery_data(self):
|
||||
logging.warning("writing recovery data to %s ..." % RECOVERY_DATA_FILE)
|
||||
with open(RECOVERY_DATA_FILE, 'w') as fp:
|
||||
@@ -302,19 +285,21 @@ class Agent(Client, AsyncAdvertiser, AsyncTrainer):
|
||||
|
||||
self.run('events.clear')
|
||||
|
||||
logging.debug("event polling started ...")
|
||||
while True:
|
||||
time.sleep(1)
|
||||
|
||||
new_shakes = 0
|
||||
s = self.session()
|
||||
self._update_uptime(s)
|
||||
|
||||
self._update_advertisement(s)
|
||||
self._update_peers()
|
||||
self._update_counters()
|
||||
logging.debug("polling events ...")
|
||||
|
||||
try:
|
||||
s = self.session()
|
||||
self._update_uptime(s)
|
||||
|
||||
self._update_advertisement(s)
|
||||
self._update_peers()
|
||||
self._update_counters()
|
||||
|
||||
for h in [e for e in self.events() if e['tag'] == 'wifi.client.handshake']:
|
||||
filename = h['data']['file']
|
||||
sta_mac = h['data']['station']
|
||||
@@ -340,7 +325,7 @@ class Agent(Client, AsyncAdvertiser, AsyncTrainer):
|
||||
plugins.on('handshake', self, filename, ap, sta)
|
||||
|
||||
except Exception as e:
|
||||
logging.exception("error")
|
||||
logging.error("error: %s" % e)
|
||||
|
||||
finally:
|
||||
self._update_handshakes(new_shakes)
|
||||
@@ -380,21 +365,6 @@ class Agent(Client, AsyncAdvertiser, AsyncTrainer):
|
||||
|
||||
return self._history[who] < self._config['personality']['max_interactions']
|
||||
|
||||
def _on_miss(self, who):
|
||||
logging.info("it looks like %s is not in range anymore :/" % who)
|
||||
self._epoch.track(miss=True)
|
||||
self._view.on_miss(who)
|
||||
|
||||
def _on_error(self, who, e):
|
||||
error = "%s" % e
|
||||
# when we're trying to associate or deauth something that is not in range anymore
|
||||
# (if we are moving), we get the following error from bettercap:
|
||||
# error 400: 50:c7:bf:2e:d3:37 is an unknown BSSID or it is in the association skip list.
|
||||
if 'is an unknown BSSID' in error:
|
||||
self._on_miss(who)
|
||||
else:
|
||||
logging.error("%s" % e)
|
||||
|
||||
def associate(self, ap, throttle=0):
|
||||
if self.is_stale():
|
||||
logging.debug("recon is stale, skipping assoc(%s)" % ap['mac'])
|
||||
@@ -471,44 +441,3 @@ class Agent(Client, AsyncAdvertiser, AsyncTrainer):
|
||||
|
||||
except Exception as e:
|
||||
logging.error("error: %s" % e)
|
||||
|
||||
def is_stale(self):
|
||||
return self._epoch.num_missed > self._config['personality']['max_misses_for_recon']
|
||||
|
||||
def any_activity(self):
|
||||
return self._epoch.any_activity
|
||||
|
||||
def _reboot(self):
|
||||
self.set_rebooting()
|
||||
self._save_recovery_data()
|
||||
pwnagotchi.reboot()
|
||||
|
||||
def next_epoch(self):
|
||||
was_stale = self.is_stale()
|
||||
did_miss = self._epoch.num_missed
|
||||
|
||||
self._epoch.next()
|
||||
|
||||
# after X misses during an epoch, set the status to lonely
|
||||
if was_stale:
|
||||
logging.warning("agent missed %d interactions -> lonely" % did_miss)
|
||||
self.set_lonely()
|
||||
# after X times being bored, the status is set to sad
|
||||
elif self._epoch.inactive_for >= self._config['personality']['sad_num_epochs']:
|
||||
logging.warning("%d epochs with no activity -> sad" % self._epoch.inactive_for)
|
||||
self.set_sad()
|
||||
# after X times being inactive, the status is set to bored
|
||||
elif self._epoch.inactive_for >= self._config['personality']['bored_num_epochs']:
|
||||
logging.warning("%d epochs with no activity -> bored" % self._epoch.inactive_for)
|
||||
self.set_bored()
|
||||
# after X times being active, the status is set to happy / excited
|
||||
elif self._epoch.active_for >= self._config['personality']['excited_num_epochs']:
|
||||
logging.warning("%d epochs with activity -> excited" % self._epoch.active_for)
|
||||
self.set_excited()
|
||||
|
||||
plugins.on('epoch', self, self._epoch.epoch - 1, self._epoch.data())
|
||||
|
||||
if self._epoch.blind_for >= self._config['main']['mon_max_blind_epochs']:
|
||||
logging.critical("%d epochs without visible access points -> rebooting ..." % self._epoch.blind_for)
|
||||
self._reboot()
|
||||
self._epoch.blind_for = 0
|
||||
|
@@ -1,14 +1,13 @@
|
||||
import os
|
||||
import time
|
||||
import warnings
|
||||
import logging
|
||||
|
||||
# https://stackoverflow.com/questions/40426502/is-there-a-way-to-suppress-the-messages-tensorflow-prints/40426709
|
||||
os.environ['TF_CPP_MIN_LOG_LEVEL'] = '3' # or any {'0', '1', '2'}
|
||||
import warnings
|
||||
|
||||
# https://stackoverflow.com/questions/15777951/how-to-suppress-pandas-future-warning
|
||||
warnings.simplefilter(action='ignore', category=FutureWarning)
|
||||
|
||||
import logging
|
||||
|
||||
|
||||
def load(config, agent, epoch, from_disk=True):
|
||||
config = config['ai']
|
||||
@@ -16,27 +15,51 @@ def load(config, agent, epoch, from_disk=True):
|
||||
logging.info("ai disabled")
|
||||
return False
|
||||
|
||||
logging.info("[ai] bootstrapping dependencies ...")
|
||||
try:
|
||||
begin = time.time()
|
||||
|
||||
from stable_baselines import A2C
|
||||
from stable_baselines.common.policies import MlpLstmPolicy
|
||||
from stable_baselines.common.vec_env import DummyVecEnv
|
||||
logging.info("[ai] bootstrapping dependencies ...")
|
||||
|
||||
import pwnagotchi.ai.gym as wrappers
|
||||
start = time.time()
|
||||
from stable_baselines import A2C
|
||||
logging.debug("[ai] A2C imported in %.2fs" % (time.time() - start))
|
||||
|
||||
env = wrappers.Environment(agent, epoch)
|
||||
env = DummyVecEnv([lambda: env])
|
||||
start = time.time()
|
||||
from stable_baselines.common.policies import MlpLstmPolicy
|
||||
logging.debug("[ai] MlpLstmPolicy imported in %.2fs" % (time.time() - start))
|
||||
|
||||
logging.info("[ai] bootstrapping model ...")
|
||||
start = time.time()
|
||||
from stable_baselines.common.vec_env import DummyVecEnv
|
||||
logging.debug("[ai] DummyVecEnv imported in %.2fs" % (time.time() - start))
|
||||
|
||||
a2c = A2C(MlpLstmPolicy, env, **config['params'])
|
||||
start = time.time()
|
||||
import pwnagotchi.ai.gym as wrappers
|
||||
logging.debug("[ai] gym wrapper imported in %.2fs" % (time.time() - start))
|
||||
|
||||
if from_disk and os.path.exists(config['path']):
|
||||
logging.info("[ai] loading %s ..." % config['path'])
|
||||
a2c.load(config['path'], env)
|
||||
else:
|
||||
logging.info("[ai] model created:")
|
||||
for key, value in config['params'].items():
|
||||
logging.info(" %s: %s" % (key, value))
|
||||
env = wrappers.Environment(agent, epoch)
|
||||
env = DummyVecEnv([lambda: env])
|
||||
|
||||
return a2c
|
||||
logging.info("[ai] creating model ...")
|
||||
|
||||
start = time.time()
|
||||
a2c = A2C(MlpLstmPolicy, env, **config['params'])
|
||||
logging.debug("[ai] A2C created in %.2fs" % (time.time() - start))
|
||||
|
||||
if from_disk and os.path.exists(config['path']):
|
||||
logging.info("[ai] loading %s ..." % config['path'])
|
||||
start = time.time()
|
||||
a2c.load(config['path'], env)
|
||||
logging.debug("[ai] A2C loaded in %.2fs" % (time.time() - start))
|
||||
else:
|
||||
logging.info("[ai] model created:")
|
||||
for key, value in config['params'].items():
|
||||
logging.info(" %s: %s" % (key, value))
|
||||
|
||||
logging.debug("[ai] total loading time is %.2fs" % (time.time() - begin))
|
||||
|
||||
return a2c
|
||||
except Exception as e:
|
||||
logging.exception("error while starting AI")
|
||||
|
||||
logging.warning("[ai] AI not loaded!")
|
||||
return False
|
||||
|
@@ -37,6 +37,12 @@ class Epoch(object):
|
||||
self.num_hops = 0
|
||||
# number of seconds sleeping
|
||||
self.num_slept = 0
|
||||
# number of peers seen during this epoch
|
||||
self.num_peers = 0
|
||||
# cumulative bond factor
|
||||
self.tot_bond_factor = 0.0 # cum_bond_factor sounded really bad ...
|
||||
# average bond factor
|
||||
self.avg_bond_factor = 0.0
|
||||
# any activity at all during this epoch?
|
||||
self.any_activity = False
|
||||
# when the current epoch started
|
||||
@@ -74,10 +80,16 @@ class Epoch(object):
|
||||
else:
|
||||
self.blind_for = 0
|
||||
|
||||
bond_unit_scale = self.config['personality']['bond_encounters_factor']
|
||||
|
||||
self.num_peers = len(peers)
|
||||
num_peers = self.num_peers + 1e-10 # avoid division by 0
|
||||
|
||||
self.tot_bond_factor = sum((peer.encounters for peer in peers)) / bond_unit_scale
|
||||
self.avg_bond_factor = self.tot_bond_factor / num_peers
|
||||
|
||||
num_aps = len(aps) + 1e-10
|
||||
num_sta = sum(len(ap['clients']) for ap in aps) + 1e-10
|
||||
num_peers = len(peers) + 1e-10
|
||||
|
||||
aps_per_chan = [0.0] * wifi.NumChannels
|
||||
sta_per_chan = [0.0] * wifi.NumChannels
|
||||
peers_per_chan = [0.0] * wifi.NumChannels
|
||||
@@ -162,6 +174,9 @@ class Epoch(object):
|
||||
'active_for_epochs': self.active_for,
|
||||
'missed_interactions': self.num_missed,
|
||||
'num_hops': self.num_hops,
|
||||
'num_peers': self.num_peers,
|
||||
'tot_bond': self.tot_bond_factor,
|
||||
'avg_bond': self.avg_bond_factor,
|
||||
'num_deauths': self.num_deauths,
|
||||
'num_associations': self.num_assocs,
|
||||
'num_handshakes': self.num_shakes,
|
||||
@@ -173,14 +188,18 @@ class Epoch(object):
|
||||
self._epoch_data['reward'] = self._reward(self.epoch + 1, self._epoch_data)
|
||||
self._epoch_data_ready.set()
|
||||
|
||||
logging.info("[epoch %d] duration=%s slept_for=%s blind=%d inactive=%d active=%d hops=%d missed=%d "
|
||||
"deauths=%d assocs=%d handshakes=%d cpu=%d%% mem=%d%% temperature=%dC reward=%s" % (
|
||||
logging.info("[epoch %d] duration=%s slept_for=%s blind=%d inactive=%d active=%d peers=%d tot_bond=%.2f "
|
||||
"avg_bond=%.2f hops=%d missed=%d deauths=%d assocs=%d handshakes=%d cpu=%d%% mem=%d%% "
|
||||
"temperature=%dC reward=%s" % (
|
||||
self.epoch,
|
||||
utils.secs_to_hhmmss(self.epoch_duration),
|
||||
utils.secs_to_hhmmss(self.num_slept),
|
||||
self.blind_for,
|
||||
self.inactive_for,
|
||||
self.active_for,
|
||||
self.num_peers,
|
||||
self.tot_bond_factor,
|
||||
self.avg_bond_factor,
|
||||
self.num_hops,
|
||||
self.num_missed,
|
||||
self.num_deauths,
|
||||
@@ -195,6 +214,9 @@ class Epoch(object):
|
||||
self.epoch_started = now
|
||||
self.did_deauth = False
|
||||
self.num_deauths = 0
|
||||
self.num_peers = 0
|
||||
self.tot_bond_factor = 0.0
|
||||
self.avg_bond_factor = 0.0
|
||||
self.did_associate = False
|
||||
self.num_assocs = 0
|
||||
self.num_missed = 0
|
||||
|
@@ -8,7 +8,6 @@ import logging
|
||||
|
||||
import pwnagotchi.plugins as plugins
|
||||
import pwnagotchi.ai as ai
|
||||
from pwnagotchi.ai.epoch import Epoch
|
||||
|
||||
|
||||
class Stats(object):
|
||||
@@ -88,7 +87,6 @@ class AsyncTrainer(object):
|
||||
def __init__(self, config):
|
||||
self._config = config
|
||||
self._model = None
|
||||
self._epoch = Epoch(config)
|
||||
self._is_training = False
|
||||
self._training_epochs = 0
|
||||
self._nn_path = self._config['ai']['path']
|
||||
|
127
pwnagotchi/automata.py
Normal file
127
pwnagotchi/automata.py
Normal file
@@ -0,0 +1,127 @@
|
||||
import logging
|
||||
|
||||
import pwnagotchi.plugins as plugins
|
||||
from pwnagotchi.ai.epoch import Epoch
|
||||
|
||||
|
||||
# basic mood system
|
||||
class Automata(object):
|
||||
def __init__(self, config, view):
|
||||
self._config = config
|
||||
self._view = view
|
||||
self._epoch = Epoch(config)
|
||||
|
||||
def _on_miss(self, who):
|
||||
logging.info("it looks like %s is not in range anymore :/" % who)
|
||||
self._epoch.track(miss=True)
|
||||
self._view.on_miss(who)
|
||||
|
||||
def _on_error(self, who, e):
|
||||
error = "%s" % e
|
||||
# when we're trying to associate or deauth something that is not in range anymore
|
||||
# (if we are moving), we get the following error from bettercap:
|
||||
# error 400: 50:c7:bf:2e:d3:37 is an unknown BSSID or it is in the association skip list.
|
||||
if 'is an unknown BSSID' in error:
|
||||
self._on_miss(who)
|
||||
else:
|
||||
logging.error("%s" % e)
|
||||
|
||||
def set_starting(self):
|
||||
self._view.on_starting()
|
||||
|
||||
def set_ready(self):
|
||||
plugins.on('ready', self)
|
||||
|
||||
def in_good_mood(self):
|
||||
return self._has_support_network_for(1.0)
|
||||
|
||||
def _has_support_network_for(self, factor):
|
||||
bond_factor = self._config['personality']['bond_encounters_factor']
|
||||
total_encounters = sum(peer.encounters for _, peer in self._peers.items())
|
||||
support_factor = total_encounters / bond_factor
|
||||
return support_factor >= factor
|
||||
|
||||
# triggered when it's a sad/bad day but you have good friends around ^_^
|
||||
def set_grateful(self):
|
||||
self._view.on_grateful()
|
||||
plugins.on('grateful', self)
|
||||
|
||||
def set_lonely(self):
|
||||
if not self._has_support_network_for(1.0):
|
||||
logging.info("unit is lonely")
|
||||
self._view.on_lonely()
|
||||
plugins.on('lonely', self)
|
||||
else:
|
||||
logging.info("unit is grateful instead of lonely")
|
||||
self.set_grateful()
|
||||
|
||||
def set_bored(self):
|
||||
factor = self._epoch.inactive_for / self._config['personality']['bored_num_epochs']
|
||||
if not self._has_support_network_for(factor):
|
||||
logging.warning("%d epochs with no activity -> bored" % self._epoch.inactive_for)
|
||||
self._view.on_bored()
|
||||
plugins.on('bored', self)
|
||||
else:
|
||||
logging.info("unit is grateful instead of bored")
|
||||
self.set_grateful()
|
||||
|
||||
def set_sad(self):
|
||||
factor = self._epoch.inactive_for / self._config['personality']['sad_num_epochs']
|
||||
if not self._has_support_network_for(factor):
|
||||
logging.warning("%d epochs with no activity -> sad" % self._epoch.inactive_for)
|
||||
self._view.on_sad()
|
||||
plugins.on('sad', self)
|
||||
else:
|
||||
logging.info("unit is grateful instead of sad")
|
||||
self.set_grateful()
|
||||
|
||||
def set_excited(self):
|
||||
logging.warning("%d epochs with activity -> excited" % self._epoch.active_for)
|
||||
self._view.on_excited()
|
||||
plugins.on('excited', self)
|
||||
|
||||
def set_rebooting(self):
|
||||
self._view.on_rebooting()
|
||||
plugins.on('rebooting', self)
|
||||
|
||||
def wait_for(self, t, sleeping=True):
|
||||
plugins.on('sleep' if sleeping else 'wait', self, t)
|
||||
self._view.wait(t, sleeping)
|
||||
self._epoch.track(sleep=True, inc=t)
|
||||
|
||||
def is_stale(self):
|
||||
return self._epoch.num_missed > self._config['personality']['max_misses_for_recon']
|
||||
|
||||
def any_activity(self):
|
||||
return self._epoch.any_activity
|
||||
|
||||
def next_epoch(self):
|
||||
logging.debug("agent.next_epoch()")
|
||||
|
||||
was_stale = self.is_stale()
|
||||
did_miss = self._epoch.num_missed
|
||||
|
||||
self._epoch.next()
|
||||
|
||||
# after X misses during an epoch, set the status to lonely
|
||||
if was_stale:
|
||||
logging.warning("agent missed %d interactions -> lonely" % did_miss)
|
||||
self.set_lonely()
|
||||
# after X times being bored, the status is set to sad
|
||||
elif self._epoch.inactive_for >= self._config['personality']['sad_num_epochs']:
|
||||
self.set_sad()
|
||||
# after X times being inactive, the status is set to bored
|
||||
elif self._epoch.inactive_for >= self._config['personality']['bored_num_epochs']:
|
||||
self.set_bored()
|
||||
# after X times being active, the status is set to happy / excited
|
||||
elif self._epoch.active_for >= self._config['personality']['excited_num_epochs']:
|
||||
self.set_excited()
|
||||
elif self._epoch.active_for >= 5 and self._has_support_network_for(5.0):
|
||||
self.set_grateful()
|
||||
|
||||
plugins.on('epoch', self, self._epoch.epoch - 1, self._epoch.data())
|
||||
|
||||
if self._epoch.blind_for >= self._config['main']['mon_max_blind_epochs']:
|
||||
logging.critical("%d epochs without visible access points -> rebooting ..." % self._epoch.blind_for)
|
||||
self._reboot()
|
||||
self._epoch.blind_for = 0
|
@@ -3,6 +3,20 @@ import requests
|
||||
from requests.auth import HTTPBasicAuth
|
||||
|
||||
|
||||
def decode(r, verbose_errors=True):
|
||||
try:
|
||||
return r.json()
|
||||
except Exception as e:
|
||||
if r.status_code == 200:
|
||||
logging.error("error while decoding json: error='%s' resp='%s'" % (e, r.text))
|
||||
else:
|
||||
err = "error %d: %s" % (r.status_code, r.text.strip())
|
||||
if verbose_errors:
|
||||
logging.info(err)
|
||||
raise Exception(err)
|
||||
return r.text
|
||||
|
||||
|
||||
class Client(object):
|
||||
def __init__(self, hostname='localhost', scheme='http', port=8081, username='user', password='pass'):
|
||||
self.hostname = hostname
|
||||
@@ -13,27 +27,14 @@ class Client(object):
|
||||
self.url = "%s://%s:%d/api" % (scheme, hostname, port)
|
||||
self.auth = HTTPBasicAuth(username, password)
|
||||
|
||||
def _decode(self, r, verbose_errors=True):
|
||||
try:
|
||||
return r.json()
|
||||
except Exception as e:
|
||||
if r.status_code == 200:
|
||||
logging.error("error while decoding json: error='%s' resp='%s'" % (e, r.text))
|
||||
else:
|
||||
err = "error %d: %s" % (r.status_code, r.text.strip())
|
||||
if verbose_errors:
|
||||
logging.info(err)
|
||||
raise Exception(err)
|
||||
return r.text
|
||||
|
||||
def session(self):
|
||||
r = requests.get("%s/session" % self.url, auth=self.auth)
|
||||
return self._decode(r)
|
||||
return decode(r)
|
||||
|
||||
def events(self):
|
||||
r = requests.get("%s/events" % self.url, auth=self.auth)
|
||||
return self._decode(r)
|
||||
return decode(r)
|
||||
|
||||
def run(self, command, verbose_errors=True):
|
||||
r = requests.post("%s/session" % self.url, auth=self.auth, json={'cmd': command})
|
||||
return self._decode(r, verbose_errors=verbose_errors)
|
||||
return decode(r, verbose_errors=verbose_errors)
|
||||
|
@@ -6,72 +6,83 @@
|
||||
#
|
||||
# main algorithm configuration
|
||||
main:
|
||||
# if set this will set the hostname of the unit. min length is 2, max length 25, only a-zA-Z0-9- allowed
|
||||
name: ''
|
||||
# currently implemented: en (default), de, el, fr, it, mk, nl, ru, se, pt-BR, es, pt
|
||||
lang: en
|
||||
# custom plugins path, if null only default plugins with be loaded
|
||||
custom_plugins:
|
||||
# which plugins to load and enable
|
||||
plugins:
|
||||
grid:
|
||||
enabled: true
|
||||
report: false # don't report pwned networks by default!
|
||||
exclude: # do not report the following networks (accepts both ESSIDs and BSSIDs)
|
||||
- YourHomeNetworkHere
|
||||
auto-backup:
|
||||
enabled: false
|
||||
interval: 1 # every day
|
||||
files:
|
||||
- /root/brain.nn
|
||||
- /root/brain.json
|
||||
- /root/.api-report.json
|
||||
- /root/handshakes/
|
||||
- /etc/pwnagotchi/
|
||||
- /etc/hostname
|
||||
- /etc/hosts
|
||||
- /etc/motd
|
||||
- /var/log/pwnagotchi.log
|
||||
commands:
|
||||
- 'tar czf /tmp/backup.tar.gz {files}'
|
||||
- 'scp /tmp/backup.tar.gz pwnagotchi@10.0.0.1:/home/pwnagotchi/backups/backup-$(date +%s).tar.gz'
|
||||
net-pos:
|
||||
enabled: false
|
||||
api_key: 'test'
|
||||
gps:
|
||||
enabled: false
|
||||
speed: 19200
|
||||
device: /dev/ttyUSB0
|
||||
twitter:
|
||||
enabled: false
|
||||
consumer_key: aaa
|
||||
consumer_secret: aaa
|
||||
access_token_key: aaa
|
||||
access_token_secret: aaa
|
||||
onlinehashcrack:
|
||||
enabled: false
|
||||
email: ~
|
||||
wpa-sec:
|
||||
enabled: false
|
||||
api_key: ~
|
||||
wigle:
|
||||
enabled: false
|
||||
api_key: ~
|
||||
screen_refresh:
|
||||
enabled: false
|
||||
refresh_interval: 50
|
||||
quickdic:
|
||||
enabled: false
|
||||
wordlist_folder: /opt/wordlists/
|
||||
AircrackOnly:
|
||||
enabled: false
|
||||
bt-tether:
|
||||
enabled: false # if you want to use this, set ui.display.video.address to 0.0.0.0
|
||||
mac: ~ # mac of your bluetooth device
|
||||
ip: '192.168.44.44' # ip from which your pwnagotchi should be reachable
|
||||
netmask: 24
|
||||
interval: 1 # check every x minutes for device
|
||||
share_internet: false
|
||||
memtemp: # Display memory usage and cpu temperature on screen
|
||||
enabled: false
|
||||
grid:
|
||||
enabled: true
|
||||
report: false # don't report pwned networks by default!
|
||||
exclude: # do not report the following networks (accepts both ESSIDs and BSSIDs)
|
||||
- YourHomeNetworkHere
|
||||
|
||||
auto-update:
|
||||
enabled: false
|
||||
interval: 12 # every 12 hours
|
||||
install: true # if false, it will only warn that updates are available, if true it will install them
|
||||
|
||||
auto-backup:
|
||||
enabled: false
|
||||
interval: 1 # every day
|
||||
files:
|
||||
- /root/brain.nn
|
||||
- /root/brain.json
|
||||
- /root/.api-report.json
|
||||
- /root/handshakes/
|
||||
- /root/peers/
|
||||
- /etc/pwnagotchi/
|
||||
- /var/log/pwnagotchi.log
|
||||
commands:
|
||||
- 'tar czf /root/pwnagotchi-backup.tar.gz {files}'
|
||||
net-pos:
|
||||
enabled: false
|
||||
api_key: 'test'
|
||||
gps:
|
||||
enabled: false
|
||||
speed: 19200
|
||||
device: /dev/ttyUSB0
|
||||
twitter:
|
||||
enabled: false
|
||||
consumer_key: aaa
|
||||
consumer_secret: aaa
|
||||
access_token_key: aaa
|
||||
access_token_secret: aaa
|
||||
onlinehashcrack:
|
||||
enabled: false
|
||||
email: ~
|
||||
wpa-sec:
|
||||
enabled: false
|
||||
api_key: ~
|
||||
api_url: "https://wpa-sec.stanev.org"
|
||||
wigle:
|
||||
enabled: false
|
||||
api_key: ~
|
||||
screen_refresh:
|
||||
enabled: false
|
||||
refresh_interval: 50
|
||||
quickdic:
|
||||
enabled: false
|
||||
wordlist_folder: /opt/wordlists/
|
||||
AircrackOnly:
|
||||
enabled: false
|
||||
bt-tether:
|
||||
enabled: false # if you want to use this, set ui.display.video.address to 0.0.0.0
|
||||
mac: ~ # mac of your bluetooth device
|
||||
ip: '192.168.44.44' # ip from which your pwnagotchi should be reachable
|
||||
netmask: 24
|
||||
interval: 1 # check every x minutes for device
|
||||
share_internet: false
|
||||
memtemp: # Display memory usage, cpu load and cpu temperature on screen
|
||||
enabled: false
|
||||
orientation: horizontal # horizontal/vertical
|
||||
pawgps:
|
||||
enabled: false
|
||||
#The IP Address of your phone with Paw Server running, default (option is empty) is 192.168.44.1
|
||||
ip: ''
|
||||
# monitor interface to use
|
||||
iface: mon0
|
||||
# command to run to bring the mon interface up in case it's not up already
|
||||
@@ -156,9 +167,35 @@ personality:
|
||||
bored_num_epochs: 15
|
||||
# number of inactive epochs that triggers the sad state
|
||||
sad_num_epochs: 25
|
||||
# number of encounters (times met on a channel) with another unit before considering it a friend and bond
|
||||
# also used for cumulative bonding score of nearby units
|
||||
bond_encounters_factor: 20000
|
||||
|
||||
# ui configuration
|
||||
ui:
|
||||
# here you can customize the faces
|
||||
faces:
|
||||
look_r: '( ⚆_⚆)'
|
||||
look_l: '(☉_☉ )'
|
||||
look_r_happy: '( ◕‿◕)'
|
||||
look_l_happy: '(◕‿◕ )'
|
||||
sleep: '(⇀‿‿↼)'
|
||||
sleep2: '(≖‿‿≖)'
|
||||
awake: '(◕‿‿◕)'
|
||||
bored: '(-__-)'
|
||||
intense: '(°▃▃°)'
|
||||
cool: '(⌐■_■)'
|
||||
happy: '(•‿‿•)'
|
||||
excited: '(ᵔ◡◡ᵔ)'
|
||||
grateful: '(^‿‿^)'
|
||||
motivated: '(☼‿‿☼)'
|
||||
demotivated: '(≖__≖)'
|
||||
smart: '(✜‿‿✜)'
|
||||
lonely: '(ب__ب)'
|
||||
sad: '(╥☁╥ )'
|
||||
friend: '(♥‿‿♥)'
|
||||
broken: '(☓‿‿☓)'
|
||||
debug: '(#__#)'
|
||||
# ePaper display can update every 3 secs anyway, set to 0 to only refresh for major data changes
|
||||
# IMPORTANT: The lifespan of an eINK display depends on the cumulative amount of refreshes. If you want to
|
||||
# preserve your display over time, you should set this value to 0.0 so that the display will be refreshed only
|
||||
@@ -167,14 +204,19 @@ ui:
|
||||
display:
|
||||
enabled: true
|
||||
rotation: 180
|
||||
# Possible options inkyphat/inky, papirus/papi, waveshare_1/ws_1 or waveshare_2/ws_2, oledhat
|
||||
# Possible options inkyphat/inky, papirus/papi, waveshare_1/ws_1 or waveshare_2/ws_2, oledhat, lcdhat, waveshare27inch
|
||||
type: 'waveshare_2'
|
||||
# Possible options red/yellow/black (black used for monocromatic displays)
|
||||
color: 'black'
|
||||
video:
|
||||
enabled: true
|
||||
address: '0.0.0.0'
|
||||
origin: '*'
|
||||
port: 8080
|
||||
# command to be executed when a new png frame is available
|
||||
# for instance, to use with framebuffer based displays:
|
||||
# on_frame: 'fbi --noverbose -a -d /dev/fb1 -T 1 /root/pwnagotchi.png > /dev/null 2>&1'
|
||||
on_frame: ''
|
||||
|
||||
|
||||
# bettercap rest api configuration
|
||||
|
@@ -27,7 +27,7 @@ class KeyPair(object):
|
||||
if not os.path.exists(self.priv_path) or not os.path.exists(self.pub_path):
|
||||
self._view.on_keys_generation()
|
||||
logging.info("generating %s ..." % self.priv_path)
|
||||
os.system("/usr/bin/ssh-keygen -t rsa -m PEM -b 4096 -N '' -f '%s'" % self.priv_path)
|
||||
os.system("pwngrid -generate -keys '%s'" % self.path)
|
||||
|
||||
# load keys: they might be corrupted if the unit has been turned off during the generation, in this case
|
||||
# the exception will remove the files and go back at the beginning of this loop.
|
||||
|
Binary file not shown.
@@ -7,7 +7,7 @@ msgid ""
|
||||
msgstr ""
|
||||
"Project-Id-Version: 0.0.1\n"
|
||||
"Report-Msgid-Bugs-To: \n"
|
||||
"POT-Creation-Date: 2019-10-09 17:42+0200\n"
|
||||
"POT-Creation-Date: 2019-10-23 20:56+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"
|
||||
@@ -34,6 +34,9 @@ msgstr "KI bereit."
|
||||
msgid "The neural network is ready."
|
||||
msgstr "Das neurale Netz ist bereit."
|
||||
|
||||
msgid "Generating keys, do not turn off ..."
|
||||
msgstr "Generiere Keys, nicht ausschalten ..."
|
||||
|
||||
#, python-brace-format
|
||||
msgid "Hey, channel {channel} is free! Your AP will say thanks."
|
||||
msgstr "Hey, Channel {channel} ist frei! Dein AP wir des dir danken."
|
||||
@@ -75,11 +78,11 @@ msgid "My crime is that of curiosity ..."
|
||||
msgstr "Mein Verbrechen ist das der Neugier ..."
|
||||
|
||||
#, python-brace-format
|
||||
msgid "Hello {name}! Nice to meet you. {name}"
|
||||
msgid "Hello {name}! Nice to meet you."
|
||||
msgstr "Hallo {name}, nett Dich kennenzulernen."
|
||||
|
||||
#, python-brace-format
|
||||
msgid "Unit {name} is nearby! {name}"
|
||||
msgid "Unit {name} is nearby!"
|
||||
msgstr "Gerät {name} ist in der nähe!!"
|
||||
|
||||
#, python-brace-format
|
||||
@@ -101,6 +104,12 @@ msgstr "{name} verpasst!"
|
||||
msgid "Missed!"
|
||||
msgstr "Verpasst!"
|
||||
|
||||
msgid "Good friends are a blessing!"
|
||||
msgstr "Gute Freunde sind ein Segen!"
|
||||
|
||||
msgid "I love my friends!"
|
||||
msgstr "Ich liebe meine Freunde!"
|
||||
|
||||
msgid "Nobody wants to play with me ..."
|
||||
msgstr "Niemand will mit mir spielen ..."
|
||||
|
||||
@@ -163,6 +172,10 @@ msgstr "Kicke {mac}!"
|
||||
msgid "Cool, we got {num} new handshake{plural}!"
|
||||
msgstr "Cool, wir haben {num} neue Handshake{plural}!"
|
||||
|
||||
#, python-brace-format
|
||||
msgid "You have {count} new message{plural}!"
|
||||
msgstr "Cool, wir haben {num} neue Handshake{plural}!"
|
||||
|
||||
msgid "Ops, something went wrong ... Rebooting ..."
|
||||
msgstr "Ops, da ist etwas schief gelaufen ...Starte neu ..."
|
||||
|
||||
|
Binary file not shown.
@@ -3,12 +3,12 @@
|
||||
# This file is distributed under the same license as the pwnagotchi package.
|
||||
# FIRST AUTHOR <7271496+quantumsheep@users.noreply.github.com>, 2019.
|
||||
#
|
||||
#, fuzzy
|
||||
#,
|
||||
msgid ""
|
||||
msgstr ""
|
||||
"Project-Id-Version: 0.0.1\n"
|
||||
"Report-Msgid-Bugs-To: \n"
|
||||
"POT-Creation-Date: 2019-10-05 14:10+0200\n"
|
||||
"POT-Creation-Date: 2019-10-23 18:37+0200\n"
|
||||
"PO-Revision-Date: 2019-10-03 10:34+0200\n"
|
||||
"Last-Translator: quantumsheep <7271496+quantumsheep@users.noreply.github."
|
||||
"com>\n"
|
||||
@@ -19,16 +19,16 @@ msgstr ""
|
||||
"Content-Transfer-Encoding: 8bit\n"
|
||||
|
||||
msgid "ZzzzZZzzzzZzzz"
|
||||
msgstr ""
|
||||
msgstr "ZzzzZZzzzzZzzz"
|
||||
|
||||
msgid "Hi, I'm Pwnagotchi! Starting ..."
|
||||
msgstr "Bonjour, je suis Pwnagotchi! Démarrage ..."
|
||||
msgstr "Bonjour, je suis Pwnagotchi ! Démarrage..."
|
||||
|
||||
msgid "New day, new hunt, new pwns!"
|
||||
msgstr "Nouveau jour, nouvelle chasse, nouveaux pwns !"
|
||||
|
||||
msgid "Hack the Planet!"
|
||||
msgstr "Hack la planète!"
|
||||
msgstr "Hack la planète !"
|
||||
|
||||
msgid "AI ready."
|
||||
msgstr "L'IA est prête."
|
||||
@@ -36,85 +36,88 @@ msgstr "L'IA est prête."
|
||||
msgid "The neural network is ready."
|
||||
msgstr "Le réseau neuronal est prêt."
|
||||
|
||||
msgid "Generating keys, do not turn off ..."
|
||||
msgstr "Génération des clés, ne pas éteindre..."
|
||||
|
||||
#, python-brace-format
|
||||
msgid "Hey, channel {channel} is free! Your AP will say thanks."
|
||||
msgstr "Hey, le channel {channel} est libre! Ton point d'accès va te remercier."
|
||||
msgstr "Hey, le canal {channel} est libre! Ton point d'accès va te remercier."
|
||||
|
||||
msgid "I'm bored ..."
|
||||
msgstr "Je m'ennuie ..."
|
||||
msgstr "Je m'ennuie..."
|
||||
|
||||
msgid "Let's go for a walk!"
|
||||
msgstr "Allons faire un tour!"
|
||||
msgstr "Allons faire un tour !"
|
||||
|
||||
msgid "This is the best day of my life!"
|
||||
msgstr "C'est le meilleur jour de ma vie!"
|
||||
msgstr "C'est le meilleur jour de ma vie !"
|
||||
|
||||
msgid "Shitty day :/"
|
||||
msgstr "Journée de merde :/"
|
||||
|
||||
msgid "I'm extremely bored ..."
|
||||
msgstr "Je m'ennuie énormément ..."
|
||||
msgstr "Je m'ennuie énormément..."
|
||||
|
||||
msgid "I'm very sad ..."
|
||||
msgstr "Je suis très triste ..."
|
||||
msgstr "Je suis très triste..."
|
||||
|
||||
msgid "I'm sad"
|
||||
msgstr "Je suis triste"
|
||||
|
||||
msgid "I'm living the life!"
|
||||
msgstr "Je vis la vie!"
|
||||
msgstr "Je vis la vie !"
|
||||
|
||||
msgid "I pwn therefore I am."
|
||||
msgstr "Je pwn donc je suis."
|
||||
|
||||
msgid "So many networks!!!"
|
||||
msgstr "Tellement de réseaux!!!"
|
||||
msgstr "Tellement de réseaux !!!"
|
||||
|
||||
msgid "I'm having so much fun!"
|
||||
msgstr "Je m'amuse tellement!"
|
||||
msgstr "Je m'amuse tellement !"
|
||||
|
||||
msgid "My crime is that of curiosity ..."
|
||||
msgstr "Mon crime, c'est la curiosité ..."
|
||||
msgstr "Mon crime, c'est la curiosité..."
|
||||
|
||||
#, python-brace-format
|
||||
msgid "Hello {name}! Nice to meet you. {name}"
|
||||
msgstr "Bonjour {name}! Ravi de te rencontrer. {name}"
|
||||
msgid "Hello {name}! Nice to meet you."
|
||||
msgstr "Bonjour {name} ! Ravi de te rencontrer."
|
||||
|
||||
#, python-brace-format
|
||||
msgid "Unit {name} is nearby! {name}"
|
||||
msgstr "L'unité {name} est proche! {name}"
|
||||
msgid "Unit {name} is nearby!"
|
||||
msgstr "L'unité {name} est proche !"
|
||||
|
||||
#, python-brace-format
|
||||
msgid "Uhm ... goodbye {name}"
|
||||
msgstr "Hum ... au revoir {name}"
|
||||
msgstr "Hum... au revoir {name}"
|
||||
|
||||
#, python-brace-format
|
||||
msgid "{name} is gone ..."
|
||||
msgstr "{name} est parti ..."
|
||||
msgstr "{name} est part ..."
|
||||
|
||||
#, python-brace-format
|
||||
msgid "Whoops ... {name} is gone."
|
||||
msgstr "Oups ... {name} est parti."
|
||||
msgstr "Oups... {name} est parti."
|
||||
|
||||
#, python-brace-format
|
||||
msgid "{name} missed!"
|
||||
msgstr "{name} raté!"
|
||||
msgstr "{name} raté !"
|
||||
|
||||
msgid "Missed!"
|
||||
msgstr "Raté!"
|
||||
msgstr "Raté !"
|
||||
|
||||
msgid "Nobody wants to play with me ..."
|
||||
msgstr "Personne ne veut jouer avec moi ..."
|
||||
msgstr "Personne ne veut jouer avec moi..."
|
||||
|
||||
msgid "I feel so alone ..."
|
||||
msgstr "Je me sens si seul ..."
|
||||
msgstr "Je me sens si seul..."
|
||||
|
||||
msgid "Where's everybody?!"
|
||||
msgstr "Où est tout le monde?!"
|
||||
msgstr "Où est tout le monde ?!"
|
||||
|
||||
#, python-brace-format
|
||||
msgid "Napping for {secs}s ..."
|
||||
msgstr "Fais la sieste pendant {secs}s ..."
|
||||
msgstr "Fais la sieste pendant {secs}s..."
|
||||
|
||||
msgid "Zzzzz"
|
||||
msgstr ""
|
||||
@@ -123,9 +126,15 @@ msgstr ""
|
||||
msgid "ZzzZzzz ({secs}s)"
|
||||
msgstr ""
|
||||
|
||||
msgid "Good night."
|
||||
msgstr "Bonne nuit."
|
||||
|
||||
msgid "Zzz"
|
||||
msgstr "Zzz"
|
||||
|
||||
#, python-brace-format
|
||||
msgid "Waiting for {secs}s ..."
|
||||
msgstr "Attends pendant {secs}s ..."
|
||||
msgstr "Attends pendant {secs}s..."
|
||||
|
||||
#, python-brace-format
|
||||
msgid "Looking around ({secs}s)"
|
||||
@@ -133,7 +142,7 @@ msgstr "Regarde autour ({secs}s)"
|
||||
|
||||
#, python-brace-format
|
||||
msgid "Hey {what} let's be friends!"
|
||||
msgstr "Hey {what}, soyons amis!"
|
||||
msgstr "Hey {what}, soyons amis !"
|
||||
|
||||
#, python-brace-format
|
||||
msgid "Associating to {what}"
|
||||
@@ -141,11 +150,11 @@ msgstr "Association à {what}"
|
||||
|
||||
#, python-brace-format
|
||||
msgid "Yo {what}!"
|
||||
msgstr ""
|
||||
msgstr "Yo {what} !"
|
||||
|
||||
#, python-brace-format
|
||||
msgid "Just decided that {mac} needs no WiFi!"
|
||||
msgstr "Je viens de décider que {mac} n'a pas besoin de WiFi!"
|
||||
msgstr "Je viens de décider que {mac} n'a pas besoin de WiFi !"
|
||||
|
||||
#, python-brace-format
|
||||
msgid "Deauthenticating {mac}"
|
||||
@@ -153,14 +162,18 @@ msgstr "Désauthentification de {mac}"
|
||||
|
||||
#, python-brace-format
|
||||
msgid "Kickbanning {mac}!"
|
||||
msgstr "Je kick et je bannis {mac}!"
|
||||
msgstr "Je kick et je bannis {mac} !"
|
||||
|
||||
#, python-brace-format
|
||||
msgid "Cool, we got {num} new handshake{plural}!"
|
||||
msgstr "Cool, on a {num} nouveaux handshake{plural}!"
|
||||
msgstr "Cool, on a {num} nouveaux handshake{plural} !"
|
||||
|
||||
#, python-brace-format
|
||||
msgid "You have {count} new message{plural}!"
|
||||
msgstr "Tu as {num} nouveaux message{plural} !"
|
||||
|
||||
msgid "Ops, something went wrong ... Rebooting ..."
|
||||
msgstr "Oups, quelque chose s'est mal passé ... Redémarrage ..."
|
||||
msgstr "Oups, quelque chose s'est mal passé... Redémarrage..."
|
||||
|
||||
#, python-brace-format
|
||||
msgid "Kicked {num} stations\n"
|
||||
@@ -187,8 +200,8 @@ msgid ""
|
||||
"{associated} new friends and ate {handshakes} handshakes! #pwnagotchi "
|
||||
"#pwnlog #pwnlife #hacktheplanet #skynet"
|
||||
msgstr ""
|
||||
"J'ai pwn durant {duration} et kick {deauthed} clients! J'ai aussi rencontré "
|
||||
"{associated} nouveaux amis et dévoré {handshakes} handshakes! #pwnagotchi "
|
||||
"J'ai pwn durant {duration} et kick {deauthed} clients ! J'ai aussi rencontré "
|
||||
"{associated} nouveaux amis et dévoré {handshakes} handshakes ! #pwnagotchi "
|
||||
"#pwnlog #pwnlife #hacktheplanet #skynet"
|
||||
|
||||
msgid "hours"
|
||||
|
Binary file not shown.
@@ -1,24 +1,23 @@
|
||||
# Polish voice data for pwnagotchi.
|
||||
# Copyright (C) 2019
|
||||
# This file is distributed under the same license as the PACKAGE package.
|
||||
# FIRST AUTHOR szymex73 <szymex73@gmail.com>, 2019.
|
||||
# This file is distributed under the same license as the pwnagotchi package.
|
||||
# szymex73 <szymex73@gmail.com>, 2019.
|
||||
#
|
||||
#, fuzzy
|
||||
msgid ""
|
||||
msgstr ""
|
||||
"Project-Id-Version: 0.0.1\n"
|
||||
"Project-Id-Version: 0.0.2\n"
|
||||
"Report-Msgid-Bugs-To: \n"
|
||||
"POT-Creation-Date: 2019-10-09 17:42+0200\n"
|
||||
"PO-Revision-Date: 2019-10-09 17:42+0200\n"
|
||||
"Last-Translator: szymex73 <szymex73@gmail.com>\n"
|
||||
"Language-Team: LANGUAGE <LL@li.org>\n"
|
||||
"POT-Creation-Date: 2019-10-21 08:39+0200\n"
|
||||
"PO-Revision-Date: 2019-10-21 10:55+0200\n"
|
||||
"Last-Translator: gkrs <457603+gkrs@users.noreply.github.com>\n"
|
||||
"Language-Team: PL <457603+gkrs@users.noreply.github.com>\n"
|
||||
"Language: polish\n"
|
||||
"MIME-Version: 1.0\n"
|
||||
"Content-Type: text/plain; charset=utf-8\n"
|
||||
"Content-Transfer-Encoding: 8bit\n"
|
||||
|
||||
msgid "ZzzzZZzzzzZzzz"
|
||||
msgstr "ZzzzZZzzzzZzzz"
|
||||
msgstr ""
|
||||
|
||||
msgid "Hi, I'm Pwnagotchi! Starting ..."
|
||||
msgstr "Hej, jestem Pwnagotchi! Uruchamianie ..."
|
||||
@@ -35,9 +34,12 @@ msgstr "SI gotowa."
|
||||
msgid "The neural network is ready."
|
||||
msgstr "Sieć neuronowa jest gotowa."
|
||||
|
||||
msgid "Generating keys, do not turn off ..."
|
||||
msgstr "Generuję klucze, nie wyłączaj ..."
|
||||
|
||||
#, python-brace-format
|
||||
msgid "Hey, channel {channel} is free! Your AP will say thanks."
|
||||
msgstr "Hej, kanał {channel} jest wolny! Twój AP mi podziękuje."
|
||||
msgstr "Hej, kanał {channel} jest wolny! Twój AP będzie Ci wdzięczny."
|
||||
|
||||
msgid "I'm bored ..."
|
||||
msgstr "Nudzi mi się ..."
|
||||
@@ -46,10 +48,10 @@ msgid "Let's go for a walk!"
|
||||
msgstr "Chodźmy na spacer!"
|
||||
|
||||
msgid "This is the best day of my life!"
|
||||
msgstr "To jest najlepszy dzień w moim życiu!"
|
||||
msgstr "To najlepszy dzień mojego życia!"
|
||||
|
||||
msgid "Shitty day :/"
|
||||
msgstr "Ten dzień jest do dupy :/"
|
||||
msgstr "Gówniany dzień :/"
|
||||
|
||||
msgid "I'm extremely bored ..."
|
||||
msgstr "Straaaasznie się nudzę ..."
|
||||
@@ -64,7 +66,7 @@ msgid "I'm living the life!"
|
||||
msgstr "Cieszę się życiem!"
|
||||
|
||||
msgid "I pwn therefore I am."
|
||||
msgstr "Pwnuje więc jestem."
|
||||
msgstr "Pwnuję więc jestem."
|
||||
|
||||
msgid "So many networks!!!"
|
||||
msgstr "Jak dużo sieci!!!"
|
||||
@@ -76,12 +78,12 @@ msgid "My crime is that of curiosity ..."
|
||||
msgstr "Moją zbrodnią jest ciekawość ..."
|
||||
|
||||
#, python-brace-format
|
||||
msgid "Hello {name}! Nice to meet you. {name}"
|
||||
msgstr "Cześć {name}! Miło cię poznać. {name}"
|
||||
msgid "Hello {name}! Nice to meet you."
|
||||
msgstr "Cześć {name}! Miło Cię poznać."
|
||||
|
||||
#, python-brace-format
|
||||
msgid "Unit {name} is nearby! {name}"
|
||||
msgstr "Jednostka {name} jest niedaleko! {name}"
|
||||
msgid "Unit {name} is nearby!"
|
||||
msgstr "Urządzenie {name} jest w pobliżu!"
|
||||
|
||||
#, python-brace-format
|
||||
msgid "Uhm ... goodbye {name}"
|
||||
@@ -97,16 +99,16 @@ msgstr "Ups ... {name} zniknął."
|
||||
|
||||
#, python-brace-format
|
||||
msgid "{name} missed!"
|
||||
msgstr "{name} przeoczył!"
|
||||
msgstr "{name} pudło!"
|
||||
|
||||
msgid "Missed!"
|
||||
msgstr "Spóźniony!"
|
||||
msgstr "Pudło!"
|
||||
|
||||
msgid "Nobody wants to play with me ..."
|
||||
msgstr "Nikt się nie chce ze mną bawić ..."
|
||||
msgstr "Nikt nie chce się ze mną bawić ..."
|
||||
|
||||
msgid "I feel so alone ..."
|
||||
msgstr "Czuję się tak samotnie ..."
|
||||
msgstr "Czuję się taki samotny ..."
|
||||
|
||||
msgid "Where's everybody?!"
|
||||
msgstr "Gdzie są wszyscy?!"
|
||||
@@ -116,17 +118,17 @@ msgid "Napping for {secs}s ..."
|
||||
msgstr "Zdrzemnę się przez {secs}s ..."
|
||||
|
||||
msgid "Zzzzz"
|
||||
msgstr "Zzzzz"
|
||||
msgstr ""
|
||||
|
||||
#, python-brace-format
|
||||
msgid "ZzzZzzz ({secs}s)"
|
||||
msgstr "ZzzZzzz ({secs}s)"
|
||||
msgstr ""
|
||||
|
||||
msgid "Good night."
|
||||
msgstr "Dobranoc."
|
||||
|
||||
msgid "Zzz"
|
||||
msgstr "Zzz"
|
||||
msgstr ""
|
||||
|
||||
#, python-brace-format
|
||||
msgid "Waiting for {secs}s ..."
|
||||
@@ -138,15 +140,15 @@ msgstr "Rozglądam się ({secs}s)"
|
||||
|
||||
#, python-brace-format
|
||||
msgid "Hey {what} let's be friends!"
|
||||
msgstr "Hej {what}, zostańmy przyjaciółmi!"
|
||||
msgstr "Hej {what} zostańmy przyjaciółmi!"
|
||||
|
||||
#, python-brace-format
|
||||
msgid "Associating to {what}"
|
||||
msgstr "Łączenie się z {what}"
|
||||
msgstr "Dołączam do {what}"
|
||||
|
||||
#, python-brace-format
|
||||
msgid "Yo {what}!"
|
||||
msgstr "Ej {what}!"
|
||||
msgstr "Siema {what}!"
|
||||
|
||||
#, python-brace-format
|
||||
msgid "Just decided that {mac} needs no WiFi!"
|
||||
@@ -158,33 +160,33 @@ msgstr "Rozłączam {mac}"
|
||||
|
||||
#, python-brace-format
|
||||
msgid "Kickbanning {mac}!"
|
||||
msgstr "Banowanie {mac}!"
|
||||
msgstr "Banuję {mac}!"
|
||||
|
||||
#, python-brace-format
|
||||
msgid "Cool, we got {num} new handshake{plural}!"
|
||||
msgstr "Super, zdobylismy {num} handshake{plural}!"
|
||||
msgstr "Super, zdobyliśmy {num} nowych handshake'ów!"
|
||||
|
||||
msgid "Ops, something went wrong ... Rebooting ..."
|
||||
msgstr "Ups, coś się stało ... Restartuję ..."
|
||||
msgstr "Ups, coś poszło nie tak ... Restaruję ..."
|
||||
|
||||
#, python-brace-format
|
||||
msgid "Kicked {num} stations\n"
|
||||
msgstr "Wyrzucono {num} stacji\n"
|
||||
msgstr "Wyrzuciłem {num} stacji\n"
|
||||
|
||||
#, python-brace-format
|
||||
msgid "Made {num} new friends\n"
|
||||
msgstr "Zdobyto {num} przyjaciół\n"
|
||||
msgstr "Zdobyłem {num} nowych przyjaciół\n"
|
||||
|
||||
#, python-brace-format
|
||||
msgid "Got {num} handshakes\n"
|
||||
msgstr "Zdobyto {num} handshakow\n"
|
||||
msgstr "Zdobyłem {num} handshake'ów\n"
|
||||
|
||||
msgid "Met 1 peer"
|
||||
msgstr "Spotkano 1 kolegę"
|
||||
msgstr "Spotkałem 1 kolegę"
|
||||
|
||||
#, python-brace-format
|
||||
msgid "Met {num} peers"
|
||||
msgstr "Spotkano {num} kolegów"
|
||||
msgstr "Spotkałem {num} kolegów"
|
||||
|
||||
#, python-brace-format
|
||||
msgid ""
|
||||
@@ -192,8 +194,8 @@ msgid ""
|
||||
"{associated} new friends and ate {handshakes} handshakes! #pwnagotchi "
|
||||
"#pwnlog #pwnlife #hacktheplanet #skynet"
|
||||
msgstr ""
|
||||
"Pwnowalem przez {duration} i wyrzuciłem {deauthed} klientów! Spotkałem także "
|
||||
"{associated} nowych przyjaciół i zjadłem {handshakes} handshaków! #pwnagotchi "
|
||||
"Pwnowałem {duration} i wyrzuciłem {deauthed} klientów! Spotkałem także "
|
||||
"{associated} nowych przyjaciół i zjadłem {handshakes} handshake'ow! #pwnagotchi "
|
||||
"#pwnlog #pwnlife #hacktheplanet #skynet"
|
||||
|
||||
msgid "hours"
|
||||
|
@@ -8,7 +8,7 @@ msgid ""
|
||||
msgstr ""
|
||||
"Project-Id-Version: PACKAGE VERSION\n"
|
||||
"Report-Msgid-Bugs-To: \n"
|
||||
"POT-Creation-Date: 2019-10-09 17:42+0200\n"
|
||||
"POT-Creation-Date: 2019-10-23 20:56+0200\n"
|
||||
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
|
||||
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
|
||||
"Language-Team: LANGUAGE <LL@li.org>\n"
|
||||
@@ -35,6 +35,9 @@ msgstr ""
|
||||
msgid "The neural network is ready."
|
||||
msgstr ""
|
||||
|
||||
msgid "Generating keys, do not turn off ..."
|
||||
msgstr ""
|
||||
|
||||
#, python-brace-format
|
||||
msgid "Hey, channel {channel} is free! Your AP will say thanks."
|
||||
msgstr ""
|
||||
@@ -76,11 +79,11 @@ msgid "My crime is that of curiosity ..."
|
||||
msgstr ""
|
||||
|
||||
#, python-brace-format
|
||||
msgid "Hello {name}! Nice to meet you. {name}"
|
||||
msgid "Hello {name}! Nice to meet you."
|
||||
msgstr ""
|
||||
|
||||
#, python-brace-format
|
||||
msgid "Unit {name} is nearby! {name}"
|
||||
msgid "Unit {name} is nearby!"
|
||||
msgstr ""
|
||||
|
||||
#, python-brace-format
|
||||
@@ -102,6 +105,12 @@ msgstr ""
|
||||
msgid "Missed!"
|
||||
msgstr ""
|
||||
|
||||
msgid "Good friends are a blessing!"
|
||||
msgstr ""
|
||||
|
||||
msgid "I love my friends!"
|
||||
msgstr ""
|
||||
|
||||
msgid "Nobody wants to play with me ..."
|
||||
msgstr ""
|
||||
|
||||
@@ -164,6 +173,10 @@ msgstr ""
|
||||
msgid "Cool, we got {num} new handshake{plural}!"
|
||||
msgstr ""
|
||||
|
||||
#, python-brace-format
|
||||
msgid "You have {count} new message{plural}!"
|
||||
msgstr ""
|
||||
|
||||
msgid "Ops, something went wrong ... Rebooting ..."
|
||||
msgstr ""
|
||||
|
||||
|
@@ -167,28 +167,33 @@ class LastSession(object):
|
||||
self.duration_human = ', '.join(self.duration_human)
|
||||
self.avg_reward /= (self.epochs if self.epochs else 1)
|
||||
|
||||
def parse(self):
|
||||
lines = []
|
||||
def parse(self, skip=False):
|
||||
if skip:
|
||||
logging.debug("skipping parsing of the last session logs ...")
|
||||
else:
|
||||
logging.debug("parsing last session logs ...")
|
||||
|
||||
if os.path.exists(self.path):
|
||||
with FileReadBackwards(self.path, encoding="utf-8") as fp:
|
||||
for line in fp:
|
||||
line = line.strip()
|
||||
if line != "" and line[0] != '[':
|
||||
continue
|
||||
lines.append(line)
|
||||
if LastSession.START_TOKEN in line:
|
||||
break
|
||||
lines.reverse()
|
||||
lines = []
|
||||
|
||||
if len(lines) == 0:
|
||||
lines.append("Initial Session");
|
||||
if os.path.exists(self.path):
|
||||
with FileReadBackwards(self.path, encoding="utf-8") as fp:
|
||||
for line in fp:
|
||||
line = line.strip()
|
||||
if line != "" and line[0] != '[':
|
||||
continue
|
||||
lines.append(line)
|
||||
if LastSession.START_TOKEN in line:
|
||||
break
|
||||
lines.reverse()
|
||||
|
||||
self.last_session = lines
|
||||
self.last_session_id = hashlib.md5(lines[0].encode()).hexdigest()
|
||||
self.last_saved_session_id = self._get_last_saved_session_id()
|
||||
if len(lines) == 0:
|
||||
lines.append("Initial Session");
|
||||
|
||||
self._parse_stats()
|
||||
self.last_session = lines
|
||||
self.last_session_id = hashlib.md5(lines[0].encode()).hexdigest()
|
||||
self.last_saved_session_id = self._get_last_saved_session_id()
|
||||
|
||||
self._parse_stats()
|
||||
self.parsed = True
|
||||
|
||||
def is_new(self):
|
||||
|
@@ -1,17 +1,38 @@
|
||||
import time
|
||||
import logging
|
||||
import datetime
|
||||
|
||||
import pwnagotchi.ui.faces as faces
|
||||
|
||||
|
||||
def parse_rfc3339(dt):
|
||||
if dt == "0001-01-01T00:00:00Z":
|
||||
return datetime.datetime.now()
|
||||
return datetime.datetime.strptime(dt.split('.')[0], "%Y-%m-%dT%H:%M:%S")
|
||||
|
||||
|
||||
class Peer(object):
|
||||
def __init__(self, obj):
|
||||
self.first_seen = time.time()
|
||||
self.last_seen = self.first_seen
|
||||
self.session_id = obj['session_id']
|
||||
self.last_channel = obj['channel']
|
||||
self.rssi = obj['rssi']
|
||||
self.adv = obj['advertisement']
|
||||
now = time.time()
|
||||
just_met = datetime.datetime.now().strftime("%Y-%m-%dT%H:%M:%S")
|
||||
|
||||
try:
|
||||
self.first_met = parse_rfc3339(obj.get('met_at', just_met))
|
||||
self.first_seen = parse_rfc3339(obj.get('detected_at', just_met))
|
||||
self.prev_seen = parse_rfc3339(obj.get('prev_seen_at', just_met))
|
||||
except Exception as e:
|
||||
logging.warning("error while parsing peer timestamps: %s" % e)
|
||||
logging.debug(e, exc_info=True)
|
||||
self.first_met = just_met
|
||||
self.first_seen = just_met
|
||||
self.prev_seen = just_met
|
||||
|
||||
self.last_seen = now # should be seen_at
|
||||
self.encounters = obj.get('encounters', 0)
|
||||
self.session_id = obj.get('session_id', '')
|
||||
self.last_channel = obj.get('channel', 1)
|
||||
self.rssi = obj.get('rssi', 0)
|
||||
self.adv = obj.get('advertisement', {})
|
||||
|
||||
def update(self, new):
|
||||
if self.name() != new.name():
|
||||
@@ -24,36 +45,45 @@ class Peer(object):
|
||||
self.rssi = new.rssi
|
||||
self.session_id = new.session_id
|
||||
self.last_seen = time.time()
|
||||
self.prev_seen = new.prev_seen
|
||||
self.first_met = new.first_met
|
||||
self.encounters = new.encounters
|
||||
|
||||
def inactive_for(self):
|
||||
return time.time() - self.last_seen
|
||||
|
||||
def _adv_field(self, name, default='???'):
|
||||
return self.adv[name] if name in self.adv else default
|
||||
def first_encounter(self):
|
||||
return self.encounters == 1
|
||||
|
||||
def is_good_friend(self, config):
|
||||
return self.encounters >= config['personality']['bond_encounters_factor']
|
||||
|
||||
def face(self):
|
||||
return self._adv_field('face', default=faces.FRIEND)
|
||||
return self.adv.get('face', faces.FRIEND)
|
||||
|
||||
def name(self):
|
||||
return self._adv_field('name')
|
||||
return self.adv.get('name', '???')
|
||||
|
||||
def identity(self):
|
||||
return self._adv_field('identity')
|
||||
return self.adv.get('identity', '???')
|
||||
|
||||
def full_name(self):
|
||||
return "%s@%s" % (self.name(), self.identity())
|
||||
|
||||
def version(self):
|
||||
return self._adv_field('version')
|
||||
return self.adv.get('version', '1.0.0a')
|
||||
|
||||
def pwnd_run(self):
|
||||
return int(self._adv_field('pwnd_run', default=0))
|
||||
return int(self.adv.get('pwnd_run', 0))
|
||||
|
||||
def pwnd_total(self):
|
||||
return int(self._adv_field('pwnd_tot', default=0))
|
||||
return int(self.adv.get('pwnd_tot', 0))
|
||||
|
||||
def uptime(self):
|
||||
return self._adv_field('uptime', default=0)
|
||||
return self.adv.get('uptime', 0)
|
||||
|
||||
def epoch(self):
|
||||
return self._adv_field('epoch', default=0)
|
||||
return self.adv.get('epoch', 0)
|
||||
|
||||
def full_name(self):
|
||||
return '%s@%s' % (self.name(), self.identity())
|
||||
|
@@ -53,11 +53,27 @@ class AsyncAdvertiser(object):
|
||||
self._advertisement['face'] = new
|
||||
grid.set_advertisement_data(self._advertisement)
|
||||
|
||||
def _adv_poller(self):
|
||||
while True:
|
||||
logging.debug("polling pwngrid-peer for peers ...")
|
||||
def cumulative_encounters(self):
|
||||
return sum(peer.encounters for _, peer in self._peers.items())
|
||||
|
||||
def _on_new_peer(self, peer):
|
||||
logging.info("new peer %s detected (%d encounters)" % (peer.full_name(), peer.encounters))
|
||||
self._view.on_new_peer(peer)
|
||||
plugins.on('peer_detected', self, peer)
|
||||
|
||||
def _on_lost_peer(self, peer):
|
||||
logging.info("lost peer %s" % peer.full_name())
|
||||
self._view.on_lost_peer(peer)
|
||||
plugins.on('peer_lost', self, peer)
|
||||
|
||||
def _adv_poller(self):
|
||||
# give the system a few seconds to start the first time so that any expressions
|
||||
# due to nearby units will be rendered properly
|
||||
time.sleep(20)
|
||||
while True:
|
||||
try:
|
||||
logging.debug("polling pwngrid-peer for peers ...")
|
||||
|
||||
grid_peers = grid.peers()
|
||||
new_peers = {}
|
||||
|
||||
@@ -72,24 +88,23 @@ class AsyncAdvertiser(object):
|
||||
to_delete = []
|
||||
for ident, peer in self._peers.items():
|
||||
if ident not in new_peers:
|
||||
self._view.on_lost_peer(peer)
|
||||
plugins.on('peer_lost', self, peer)
|
||||
to_delete.append(ident)
|
||||
|
||||
for ident in to_delete:
|
||||
self._on_lost_peer(self._peers[ident])
|
||||
del self._peers[ident]
|
||||
|
||||
for ident, peer in new_peers.items():
|
||||
# check who's new
|
||||
if ident not in self._peers:
|
||||
self._peers[ident] = peer
|
||||
self._view.on_new_peer(peer)
|
||||
plugins.on('peer_detected', self, peer)
|
||||
self._on_new_peer(peer)
|
||||
# update the rest
|
||||
else:
|
||||
self._peers[ident].update(peer)
|
||||
|
||||
except Exception as e:
|
||||
logging.exception("error while polling pwngrid-peer")
|
||||
logging.warning("error while polling pwngrid-peer: %s" % e)
|
||||
logging.debug(e, exc_info=True)
|
||||
|
||||
time.sleep(1)
|
||||
time.sleep(3)
|
||||
|
@@ -16,11 +16,24 @@ def on(event_name, *args, **kwargs):
|
||||
cb_name = 'on_%s' % event_name
|
||||
for plugin_name, plugin in loaded.items():
|
||||
if cb_name in plugin.__dict__:
|
||||
# print("calling %s %s(%s)" %(cb_name, args, kwargs))
|
||||
try:
|
||||
plugin.__dict__[cb_name](*args, **kwargs)
|
||||
except Exception as e:
|
||||
logging.error("error while running %s.%s : %s" % (plugin_name, cb_name, e))
|
||||
logging.error(e, exc_info=True)
|
||||
|
||||
|
||||
def one(plugin_name, event_name, *args, **kwargs):
|
||||
global loaded
|
||||
if plugin_name in loaded:
|
||||
plugin = loaded[plugin_name]
|
||||
cb_name = 'on_%s' % event_name
|
||||
if cb_name in plugin.__dict__:
|
||||
try:
|
||||
plugin.__dict__[cb_name](*args, **kwargs)
|
||||
except Exception as e:
|
||||
logging.error("error while running %s.%s : %s" % (plugin_name, cb_name, e))
|
||||
logging.error(e, exc_info=True)
|
||||
|
||||
|
||||
def load_from_file(filename):
|
||||
@@ -34,20 +47,25 @@ def load_from_file(filename):
|
||||
def load_from_path(path, enabled=()):
|
||||
global loaded
|
||||
for filename in glob.glob(os.path.join(path, "*.py")):
|
||||
name, plugin = load_from_file(filename)
|
||||
if name in loaded:
|
||||
raise Exception("plugin %s already loaded from %s" % (name, plugin.__file__))
|
||||
elif name not in enabled:
|
||||
# print("plugin %s is not enabled" % name)
|
||||
pass
|
||||
else:
|
||||
loaded[name] = plugin
|
||||
try:
|
||||
name, plugin = load_from_file(filename)
|
||||
if name in loaded:
|
||||
raise Exception("plugin %s already loaded from %s" % (name, plugin.__file__))
|
||||
elif name not in enabled:
|
||||
# print("plugin %s is not enabled" % name)
|
||||
pass
|
||||
else:
|
||||
loaded[name] = plugin
|
||||
except Exception as e:
|
||||
logging.warning("error while loading %s: %s" % (filename, e))
|
||||
logging.debug(e, exc_info=True)
|
||||
|
||||
return loaded
|
||||
|
||||
|
||||
def load(config):
|
||||
enabled = [name for name, options in config['main']['plugins'].items() if 'enabled' in options and options['enabled']]
|
||||
enabled = [name for name, options in config['main']['plugins'].items() if
|
||||
'enabled' in options and options['enabled']]
|
||||
custom_path = config['main']['custom_plugins'] if 'custom_plugins' in config['main'] else None
|
||||
# load default plugins
|
||||
loaded = load_from_path(default_path, enabled=enabled)
|
||||
|
@@ -18,7 +18,7 @@ import os
|
||||
OPTIONS = dict()
|
||||
|
||||
def on_loaded():
|
||||
logging.info("cleancap plugin loaded")
|
||||
logging.info("aircrackonly plugin loaded")
|
||||
|
||||
def on_handshake(agent, filename, access_point, client_station):
|
||||
display = agent._view
|
||||
|
@@ -39,8 +39,11 @@ def on_internet_available(agent):
|
||||
if READY:
|
||||
if STATUS.newer_then_days(OPTIONS['interval']):
|
||||
return
|
||||
|
||||
files_to_backup = " ".join(OPTIONS['files'])
|
||||
|
||||
# Only backup existing files to prevent errors
|
||||
existing_files = list(filter(lambda f: os.path.exists(f), OPTIONS['files']))
|
||||
files_to_backup = " ".join(existing_files)
|
||||
|
||||
try:
|
||||
display = agent.view()
|
||||
|
||||
@@ -57,9 +60,10 @@ def on_internet_available(agent):
|
||||
raise OSError(f"Command failed (rc: {process.returncode})")
|
||||
|
||||
logging.info("AUTO-BACKUP: backup done")
|
||||
display.set('status', 'Backup done!')
|
||||
display.update()
|
||||
STATUS.update()
|
||||
except OSError as os_e:
|
||||
logging.info(f"AUTO-BACKUP: Error: {os_e}")
|
||||
|
||||
display.set('status', 'Backup done!')
|
||||
display.update()
|
||||
display.set('status', 'Backup failed!')
|
||||
display.update()
|
||||
|
206
pwnagotchi/plugins/default/auto-update.py
Normal file
206
pwnagotchi/plugins/default/auto-update.py
Normal file
@@ -0,0 +1,206 @@
|
||||
__author__ = 'evilsocket@gmail.com'
|
||||
__version__ = '1.1.0'
|
||||
__name__ = 'auto-update'
|
||||
__license__ = 'GPL3'
|
||||
__description__ = 'This plugin checks when updates are available and applies them when internet is available.'
|
||||
|
||||
import os
|
||||
import logging
|
||||
import subprocess
|
||||
import requests
|
||||
import platform
|
||||
import shutil
|
||||
import glob
|
||||
import pkg_resources
|
||||
|
||||
import pwnagotchi
|
||||
from pwnagotchi.utils import StatusFile
|
||||
|
||||
OPTIONS = dict()
|
||||
READY = False
|
||||
STATUS = StatusFile('/root/.auto-update')
|
||||
|
||||
|
||||
def on_loaded():
|
||||
global READY
|
||||
if 'interval' not in OPTIONS or ('interval' in OPTIONS and OPTIONS['interval'] is None):
|
||||
logging.error("[update] main.plugins.auto-update.interval is not set")
|
||||
return
|
||||
READY = True
|
||||
logging.info("[update] plugin loaded.")
|
||||
|
||||
|
||||
def check(version, repo, native=True):
|
||||
logging.debug("checking remote version for %s, local is %s" % (repo, version))
|
||||
info = {
|
||||
'repo': repo,
|
||||
'current': version,
|
||||
'available': None,
|
||||
'url': None,
|
||||
'native': native,
|
||||
'arch': platform.machine()
|
||||
}
|
||||
|
||||
resp = requests.get("https://api.github.com/repos/%s/releases/latest" % repo)
|
||||
latest = resp.json()
|
||||
info['available'] = latest_ver = latest['tag_name'].replace('v', '')
|
||||
is_arm = info['arch'].startswith('arm')
|
||||
|
||||
local = pkg_resources.parse_version(info['current'])
|
||||
remote = pkg_resources.parse_version(latest_ver)
|
||||
if remote > local:
|
||||
if not native:
|
||||
info['url'] = "https://github.com/%s/archive/%s.zip" % (repo, latest['tag_name'])
|
||||
else:
|
||||
# check if this release is compatible with arm6
|
||||
for asset in latest['assets']:
|
||||
download_url = asset['browser_download_url']
|
||||
if download_url.endswith('.zip') and (
|
||||
info['arch'] in download_url or (is_arm and 'armhf' in download_url)):
|
||||
info['url'] = download_url
|
||||
break
|
||||
|
||||
return info
|
||||
|
||||
|
||||
def make_path_for(name):
|
||||
path = os.path.join("/tmp/updates/", name)
|
||||
if os.path.exists(path):
|
||||
logging.debug("[update] deleting %s" % path)
|
||||
shutil.rmtree(path, ignore_errors=True, onerror=None)
|
||||
os.makedirs(path)
|
||||
return path
|
||||
|
||||
|
||||
def download_and_unzip(name, path, display, update):
|
||||
target = "%s_%s.zip" % (name, update['available'])
|
||||
target_path = os.path.join(path, target)
|
||||
|
||||
logging.info("[update] downloading %s to %s ..." % (update['url'], target_path))
|
||||
display.update(force=True, new_data={'status': 'Downloading %s %s ...' % (name, update['available'])})
|
||||
|
||||
os.system('wget -q "%s" -O "%s"' % (update['url'], target_path))
|
||||
|
||||
logging.info("[update] extracting %s to %s ..." % (target_path, path))
|
||||
display.update(force=True, new_data={'status': 'Extracting %s %s ...' % (name, update['available'])})
|
||||
|
||||
os.system('unzip "%s" -d "%s"' % (target_path, path))
|
||||
|
||||
|
||||
def verify(name, path, source_path, display, update):
|
||||
display.update(force=True, new_data={'status': 'Verifying %s %s ...' % (name, update['available'])})
|
||||
|
||||
checksums = glob.glob("%s/*.sha256" % path)
|
||||
if len(checksums) == 0:
|
||||
if update['native']:
|
||||
logging.warning("[update] native update without SHA256 checksum file")
|
||||
return False
|
||||
|
||||
else:
|
||||
checksum = checksums[0]
|
||||
|
||||
logging.info("[update] verifying %s for %s ..." % (checksum, source_path))
|
||||
|
||||
with open(checksum, 'rt') as fp:
|
||||
expected = fp.read().split('=')[1].strip().lower()
|
||||
|
||||
real = subprocess.getoutput('sha256sum "%s"' % source_path).split(' ')[0].strip().lower()
|
||||
|
||||
if real != expected:
|
||||
logging.warning("[update] checksum mismatch for %s: expected=%s got=%s" % (source_path, expected, real))
|
||||
return False
|
||||
|
||||
return True
|
||||
|
||||
|
||||
def install(display, update):
|
||||
name = update['repo'].split('/')[1]
|
||||
|
||||
path = make_path_for(name)
|
||||
|
||||
download_and_unzip(name, path, display, update)
|
||||
|
||||
source_path = os.path.join(path, name)
|
||||
if not verify(name, path, source_path, display, update):
|
||||
return False
|
||||
|
||||
logging.info("[update] installing %s ..." % name)
|
||||
display.update(force=True, new_data={'status': 'Installing %s %s ...' % (name, update['available'])})
|
||||
|
||||
if update['native']:
|
||||
dest_path = subprocess.getoutput("which %s" % name)
|
||||
if dest_path == "":
|
||||
logging.warning("[update] can't find path for %s" % name)
|
||||
return False
|
||||
|
||||
logging.info("[update] stopping %s ..." % update['service'])
|
||||
os.system("service %s stop" % update['service'])
|
||||
os.system("mv %s %s" % (source_path, dest_path))
|
||||
logging.info("[update] restarting %s ..." % update['service'])
|
||||
os.system("service %s start" % update['service'])
|
||||
else:
|
||||
if not os.path.exists(source_path):
|
||||
source_path = "%s-%s" % (source_path, update['available'])
|
||||
|
||||
os.system("cd %s && pip3 install ." % source_path)
|
||||
|
||||
return True
|
||||
|
||||
|
||||
def on_internet_available(agent):
|
||||
global STATUS
|
||||
|
||||
logging.debug("[update] internet connectivity is available (ready %s)" % READY)
|
||||
|
||||
if READY:
|
||||
if STATUS.newer_then_hours(OPTIONS['interval']):
|
||||
logging.debug("[update] last check happened less than %d hours ago" % OPTIONS['interval'])
|
||||
return
|
||||
|
||||
logging.info("[update] checking for updates ...")
|
||||
|
||||
display = agent.view()
|
||||
prev_status = display.get('status')
|
||||
|
||||
try:
|
||||
display.update(force=True, new_data={'status': 'Checking for updates ...'})
|
||||
|
||||
to_install = []
|
||||
to_check = [
|
||||
('bettercap/bettercap', subprocess.getoutput('bettercap -version').split(' ')[1].replace('v', ''),
|
||||
True, 'bettercap'),
|
||||
('evilsocket/pwngrid', subprocess.getoutput('pwngrid -version').replace('v', ''), True, 'pwngrid-peer'),
|
||||
('evilsocket/pwnagotchi', pwnagotchi.version, False, 'pwnagotchi')
|
||||
]
|
||||
|
||||
for repo, local_version, is_native, svc_name in to_check:
|
||||
info = check(local_version, repo, is_native)
|
||||
if info['url'] is not None:
|
||||
logging.warning(
|
||||
"update for %s available (local version is '%s'): %s" % (repo, info['current'], info['url']))
|
||||
info['service'] = svc_name
|
||||
to_install.append(info)
|
||||
|
||||
num_updates = len(to_install)
|
||||
num_installed = 0
|
||||
|
||||
if num_updates > 0:
|
||||
if OPTIONS['install']:
|
||||
for update in to_install:
|
||||
if install(display, update):
|
||||
num_installed += 1
|
||||
else:
|
||||
prev_status = '%d new update%c available!' % (num_updates, 's' if num_updates > 1 else '')
|
||||
|
||||
logging.info("[update] done")
|
||||
|
||||
STATUS.update()
|
||||
|
||||
if num_installed > 0:
|
||||
display.update(force=True, new_data={'status': 'Rebooting ...'})
|
||||
pwnagotchi.reboot()
|
||||
|
||||
except Exception as e:
|
||||
logging.error("[update] %s" % e)
|
||||
|
||||
display.update(force=True, new_data={'status': prev_status if prev_status is not None else ''})
|
@@ -141,9 +141,14 @@ class BTNap:
|
||||
"""
|
||||
Set power of devices to on/off
|
||||
"""
|
||||
logging.debug("BT-TETHER: Changing bluetooth device to %s", str(on))
|
||||
|
||||
devs = list(BTNap.find_adapter())
|
||||
devs = dict((BTNap.prop_get(dev, 'Address'), dev) for dev in devs)
|
||||
try:
|
||||
devs = list(BTNap.find_adapter())
|
||||
devs = dict((BTNap.prop_get(dev, 'Address'), dev) for dev in devs)
|
||||
except BTError as bt_err:
|
||||
logging.error(bt_err)
|
||||
return None
|
||||
|
||||
for dev_addr, dev in devs.items():
|
||||
BTNap.prop_set(dev, 'Powered', on)
|
||||
@@ -158,52 +163,62 @@ class BTNap:
|
||||
"""
|
||||
Check if already connected
|
||||
"""
|
||||
logging.debug("BT-TETHER: Checking if device is connected.")
|
||||
|
||||
bt_dev = self.power(True)
|
||||
|
||||
if not bt_dev:
|
||||
return False
|
||||
logging.debug("BT-TETHER: No bluetooth device found.")
|
||||
return None, False
|
||||
|
||||
try:
|
||||
dev_remote = BTNap.find_device(self._mac, bt_dev)
|
||||
return bool(BTNap.prop_get(dev_remote, 'Connected'))
|
||||
return dev_remote, bool(BTNap.prop_get(dev_remote, 'Connected'))
|
||||
except BTError:
|
||||
pass
|
||||
return False
|
||||
logging.debug("BT-TETHER: Device is not connected.")
|
||||
return None, False
|
||||
|
||||
|
||||
def is_paired(self):
|
||||
"""
|
||||
Check if already connected
|
||||
"""
|
||||
logging.debug("BT-TETHER: Checking if device is paired")
|
||||
|
||||
bt_dev = self.power(True)
|
||||
|
||||
if not bt_dev:
|
||||
logging.debug("BT-TETHER: No bluetooth device found.")
|
||||
return False
|
||||
|
||||
try:
|
||||
dev_remote = BTNap.find_device(self._mac, bt_dev)
|
||||
return bool(BTNap.prop_get(dev_remote, 'Paired'))
|
||||
except BTError:
|
||||
pass
|
||||
logging.debug("BT-TETHER: Device is not paired.")
|
||||
return False
|
||||
|
||||
|
||||
def wait_for_device(self, timeout=15):
|
||||
"""
|
||||
Wait for device
|
||||
|
||||
returns device if found None if not
|
||||
"""
|
||||
logging.debug("BT-TETHER: Waiting for device")
|
||||
|
||||
bt_dev = self.power(True)
|
||||
|
||||
if not bt_dev:
|
||||
logging.debug("BT-TETHER: No bluetooth device found.")
|
||||
return None
|
||||
|
||||
try:
|
||||
logging.debug("BT-TETHER: Starting discovery ...")
|
||||
bt_dev.StartDiscovery()
|
||||
except Exception:
|
||||
# can fail with org.bluez.Error.NotReady / org.bluez.Error.Failed
|
||||
# TODO: add loop?
|
||||
pass
|
||||
except Exception as bt_ex:
|
||||
logging.error(bt_ex)
|
||||
raise bt_ex
|
||||
|
||||
dev_remote = None
|
||||
|
||||
@@ -211,74 +226,66 @@ class BTNap:
|
||||
while timeout > -1:
|
||||
try:
|
||||
dev_remote = BTNap.find_device(self._mac, bt_dev)
|
||||
logging.debug('Using remote device (addr: %s): %s',
|
||||
logging.debug("BT-TETHER: Using remote device (addr: %s): %s",
|
||||
BTNap.prop_get(dev_remote, 'Address'), dev_remote.object_path )
|
||||
break
|
||||
except BTError:
|
||||
pass
|
||||
logging.debug("BT-TETHER: Not found yet ...")
|
||||
|
||||
time.sleep(1)
|
||||
timeout -= 1
|
||||
|
||||
try:
|
||||
logging.debug("BT-TETHER: Stoping Discovery ...")
|
||||
bt_dev.StopDiscovery()
|
||||
except Exception:
|
||||
# can fail with org.bluez.Error.NotReady / org.bluez.Error.Failed / org.bluez.Error.NotAuthorized
|
||||
pass
|
||||
except Exception as bt_ex:
|
||||
logging.error(bt_ex)
|
||||
raise bt_ex
|
||||
|
||||
return dev_remote
|
||||
|
||||
|
||||
def connect(self, reconnect=False):
|
||||
"""
|
||||
Connect to device
|
||||
|
||||
return True if connected; False if failed
|
||||
"""
|
||||
|
||||
# check if device is close
|
||||
dev_remote = self.wait_for_device()
|
||||
|
||||
if not dev_remote:
|
||||
return False
|
||||
|
||||
@staticmethod
|
||||
def pair(device):
|
||||
logging.debug('BT-TETHER: Trying to pair ...')
|
||||
try:
|
||||
dev_remote.Pair()
|
||||
device.Pair()
|
||||
logging.info('BT-TETHER: Successful paired with device ;)')
|
||||
time.sleep(10) # wait for bnep0
|
||||
except Exception:
|
||||
# can fail because of AlreadyExists etc.
|
||||
pass
|
||||
|
||||
try:
|
||||
dev_remote.ConnectProfile('nap')
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
net = dbus.Interface(dev_remote, 'org.bluez.Network1')
|
||||
|
||||
try:
|
||||
net.Connect('nap')
|
||||
return True
|
||||
except dbus.exceptions.DBusException as err:
|
||||
if err.get_dbus_name() != 'org.bluez.Error.Failed':
|
||||
raise
|
||||
if err.get_dbus_name() == 'org.bluez.Error.AlreadyExists':
|
||||
logging.debug('BT-TETHER: Already paired ...')
|
||||
return True
|
||||
except Exception:
|
||||
pass
|
||||
return False
|
||||
|
||||
|
||||
@staticmethod
|
||||
def nap(device):
|
||||
logging.debug('BT-TETHER: Trying to nap ...')
|
||||
|
||||
try:
|
||||
logging.debug('BT-TETHER: Connecting to profile ...')
|
||||
device.ConnectProfile('nap')
|
||||
except Exception: # raises exception, but still works
|
||||
pass
|
||||
|
||||
net = dbus.Interface(device, 'org.bluez.Network1')
|
||||
|
||||
try:
|
||||
logging.debug('BT-TETHER: Connecting to nap network ...')
|
||||
net.Connect('nap')
|
||||
return True
|
||||
except dbus.exceptions.DBusException as err:
|
||||
if err.get_dbus_name() == 'org.bluez.Error.AlreadyConnected':
|
||||
return True
|
||||
|
||||
connected = BTNap.prop_get(net, 'Connected')
|
||||
|
||||
if not connected:
|
||||
return False
|
||||
|
||||
if reconnect:
|
||||
net.Disconnect()
|
||||
return self.connect(reconnect=False)
|
||||
|
||||
return True
|
||||
|
||||
|
||||
#################################################
|
||||
#################################################
|
||||
#################################################
|
||||
|
||||
class SystemdUnitWrapper:
|
||||
"""
|
||||
systemd wrapper
|
||||
@@ -445,20 +452,51 @@ def on_ui_update(ui):
|
||||
bt = BTNap(OPTIONS['mac'])
|
||||
|
||||
logging.debug('BT-TETHER: Check if already connected and paired')
|
||||
if bt.is_connected() and bt.is_paired():
|
||||
logging.debug('BT-TETHER: Already connected and paired')
|
||||
ui.set('bluetooth', 'CON')
|
||||
else:
|
||||
logging.debug('BT-TETHER: Try to connect to mac')
|
||||
if bt.connect():
|
||||
logging.info('BT-TETHER: Successfuly connected')
|
||||
else:
|
||||
logging.error('BT-TETHER: Could not connect')
|
||||
dev_remote, connected = bt.is_connected()
|
||||
|
||||
if connected:
|
||||
logging.debug('BT-TETHER: Already connected.')
|
||||
ui.set('bluetooth', 'C')
|
||||
return
|
||||
|
||||
try:
|
||||
logging.info('BT-TETHER: Search device ...')
|
||||
dev_remote = bt.wait_for_device()
|
||||
if dev_remote is None:
|
||||
logging.info('BT-TETHER: Could not find device.')
|
||||
ui.set('bluetooth', 'NF')
|
||||
return
|
||||
except Exception as bt_ex:
|
||||
logging.error(bt_ex)
|
||||
ui.set('bluetooth', 'NF')
|
||||
return
|
||||
|
||||
paired = bt.is_paired()
|
||||
if not paired:
|
||||
if BTNap.pair(dev_remote):
|
||||
logging.info('BT-TETHER: Paired with device.')
|
||||
else:
|
||||
logging.info('BT-TETHER: Pairing failed ...')
|
||||
ui.set('bluetooth', 'PE')
|
||||
return
|
||||
else:
|
||||
logging.debug('BT-TETHER: Already paired.')
|
||||
|
||||
|
||||
btnap_iface = IfaceWrapper('bnep0')
|
||||
logging.debug('BT-TETHER: Check interface')
|
||||
if not btnap_iface.exists():
|
||||
# connected and paired but not napping
|
||||
logging.debug('BT-TETHER: Try to connect to nap ...')
|
||||
if BTNap.nap(dev_remote):
|
||||
logging.info('BT-TETHER: Napping!')
|
||||
ui.set('bluetooth', 'C')
|
||||
time.sleep(5)
|
||||
else:
|
||||
logging.info('BT-TETHER: Napping failed ...')
|
||||
ui.set('bluetooth', 'NF')
|
||||
return
|
||||
|
||||
if btnap_iface.exists():
|
||||
logging.debug('BT-TETHER: Interface found')
|
||||
|
||||
@@ -467,11 +505,11 @@ def on_ui_update(ui):
|
||||
|
||||
logging.debug('BT-TETHER: Try to set ADDR to interface')
|
||||
if not btnap_iface.set_addr(addr):
|
||||
ui.set('bluetooth', 'ERR1')
|
||||
ui.set('bluetooth', 'AE')
|
||||
logging.error("BT-TETHER: Could not set ip of bnep0 to %s", addr)
|
||||
return
|
||||
else:
|
||||
logging.debug('BT-TETHER: Set ADDR to interface')
|
||||
|
||||
logging.debug('BT-TETHER: Set ADDR to interface')
|
||||
|
||||
# change route if sharking
|
||||
if OPTIONS['share_internet']:
|
||||
@@ -485,10 +523,10 @@ def on_ui_update(ui):
|
||||
resolv.seek(0)
|
||||
resolv.write(nameserver + 'nameserver 9.9.9.9\n')
|
||||
|
||||
ui.set('bluetooth', 'CON')
|
||||
ui.set('bluetooth', 'C')
|
||||
else:
|
||||
logging.error('BT-TETHER: bnep0 not found')
|
||||
ui.set('bluetooth', 'ERR2')
|
||||
ui.set('bluetooth', 'BE')
|
||||
|
||||
|
||||
def on_ui_setup(ui):
|
||||
|
@@ -14,6 +14,18 @@ import pwnagotchi.ui.fonts as fonts
|
||||
# Will be set with the options in config.yml config['main']['plugins'][__name__]
|
||||
OPTIONS = dict()
|
||||
|
||||
# called when <host>:<port>/plugins/<pluginname> is opened
|
||||
def on_webhook(response, path):
|
||||
res = "<html><body><a>Hook triggered</a></body></html>"
|
||||
response.send_response(200)
|
||||
response.send_header('Content-type', 'text/html')
|
||||
response.end_headers()
|
||||
|
||||
try:
|
||||
response.wfile.write(bytes(res, "utf-8"))
|
||||
except Exception as ex:
|
||||
logging.error(ex)
|
||||
|
||||
# called when the plugin is loaded
|
||||
def on_loaded():
|
||||
logging.warning("WARNING: plugin %s should be disabled!" % __name__)
|
||||
|
@@ -1,11 +1,13 @@
|
||||
__author__ = 'evilsocket@gmail.com'
|
||||
__version__ = '1.0.0'
|
||||
__version__ = '1.0.1'
|
||||
__name__ = 'grid'
|
||||
__license__ = 'GPL3'
|
||||
__description__ = 'This plugin signals the unit cryptographic identity and list of pwned networks and list of pwned networks to api.pwnagotchi.ai'
|
||||
__description__ = 'This plugin signals the unit cryptographic identity and list of pwned networks and list of pwned ' \
|
||||
'networks to api.pwnagotchi.ai '
|
||||
|
||||
import os
|
||||
import logging
|
||||
import time
|
||||
import glob
|
||||
|
||||
import pwnagotchi.grid as grid
|
||||
@@ -63,27 +65,64 @@ def is_excluded(what):
|
||||
return False
|
||||
|
||||
|
||||
def on_ui_update(ui):
|
||||
new_value = ' %d (%d)' % (UNREAD_MESSAGES, TOTAL_MESSAGES)
|
||||
if not ui.has_element('mailbox') and UNREAD_MESSAGES > 0:
|
||||
if ui.is_inky():
|
||||
pos = (80, 0)
|
||||
else:
|
||||
pos = (100, 0)
|
||||
ui.add_element('mailbox',
|
||||
LabeledValue(color=BLACK, label='MSG', value=new_value,
|
||||
position=pos,
|
||||
label_font=fonts.Bold,
|
||||
text_font=fonts.Medium))
|
||||
ui.set('mailbox', new_value)
|
||||
|
||||
|
||||
def set_reported(reported, net_id):
|
||||
global REPORT
|
||||
reported.append(net_id)
|
||||
REPORT.update(data={'reported': reported})
|
||||
|
||||
|
||||
def check_inbox(agent):
|
||||
global REPORT, UNREAD_MESSAGES, TOTAL_MESSAGES
|
||||
|
||||
logging.debug("checking mailbox ...")
|
||||
|
||||
messages = grid.inbox()
|
||||
TOTAL_MESSAGES = len(messages)
|
||||
UNREAD_MESSAGES = len([m for m in messages if m['seen_at'] is None])
|
||||
|
||||
if UNREAD_MESSAGES:
|
||||
logging.debug("[grid] unread:%d total:%d" % (UNREAD_MESSAGES, TOTAL_MESSAGES))
|
||||
agent.view().on_unread_messages(UNREAD_MESSAGES, TOTAL_MESSAGES)
|
||||
|
||||
|
||||
def check_handshakes(agent):
|
||||
logging.debug("checking pcaps")
|
||||
|
||||
pcap_files = glob.glob(os.path.join(agent.config()['bettercap']['handshakes'], "*.pcap"))
|
||||
num_networks = len(pcap_files)
|
||||
reported = REPORT.data_field_or('reported', default=[])
|
||||
num_reported = len(reported)
|
||||
num_new = num_networks - num_reported
|
||||
|
||||
if num_new > 0:
|
||||
if OPTIONS['report']:
|
||||
logging.info("grid: %d new networks to report" % num_new)
|
||||
logging.debug("OPTIONS: %s" % OPTIONS)
|
||||
logging.debug(" exclude: %s" % OPTIONS['exclude'])
|
||||
|
||||
for pcap_file in pcap_files:
|
||||
net_id = os.path.basename(pcap_file).replace('.pcap', '')
|
||||
if net_id not in reported:
|
||||
if is_excluded(net_id):
|
||||
logging.debug("skipping %s due to exclusion filter" % pcap_file)
|
||||
set_reported(reported, net_id)
|
||||
continue
|
||||
|
||||
essid, bssid = parse_pcap(pcap_file)
|
||||
if bssid:
|
||||
if is_excluded(essid) or is_excluded(bssid):
|
||||
logging.debug("not reporting %s due to exclusion filter" % pcap_file)
|
||||
set_reported(reported, net_id)
|
||||
else:
|
||||
if grid.report_ap(essid, bssid):
|
||||
set_reported(reported, net_id)
|
||||
time.sleep(1.5)
|
||||
else:
|
||||
logging.warning("no bssid found?!")
|
||||
else:
|
||||
logging.debug("grid: reporting disabled")
|
||||
|
||||
|
||||
def on_internet_available(agent):
|
||||
global REPORT, UNREAD_MESSAGES, TOTAL_MESSAGES
|
||||
|
||||
@@ -93,54 +132,17 @@ def on_internet_available(agent):
|
||||
grid.update_data(agent.last_session)
|
||||
except Exception as e:
|
||||
logging.error("error connecting to the pwngrid-peer service: %s" % e)
|
||||
logging.debug(e, exc_info=True)
|
||||
return
|
||||
|
||||
try:
|
||||
logging.debug("checking mailbox ...")
|
||||
|
||||
messages = grid.inbox()
|
||||
TOTAL_MESSAGES = len(messages)
|
||||
UNREAD_MESSAGES = len([m for m in messages if m['seen_at'] is None])
|
||||
|
||||
if TOTAL_MESSAGES:
|
||||
on_ui_update(agent.view())
|
||||
logging.debug(" %d unread messages of %d total" % (UNREAD_MESSAGES, TOTAL_MESSAGES))
|
||||
|
||||
logging.debug("checking pcaps")
|
||||
|
||||
pcap_files = glob.glob(os.path.join(agent.config()['bettercap']['handshakes'], "*.pcap"))
|
||||
num_networks = len(pcap_files)
|
||||
reported = REPORT.data_field_or('reported', default=[])
|
||||
num_reported = len(reported)
|
||||
num_new = num_networks - num_reported
|
||||
|
||||
if num_new > 0:
|
||||
if OPTIONS['report']:
|
||||
logging.info("grid: %d new networks to report" % num_new)
|
||||
logging.debug("OPTIONS: %s" % OPTIONS)
|
||||
logging.debug(" exclude: %s" % OPTIONS['exclude'])
|
||||
|
||||
for pcap_file in pcap_files:
|
||||
net_id = os.path.basename(pcap_file).replace('.pcap', '')
|
||||
if net_id not in reported:
|
||||
if is_excluded(net_id):
|
||||
logging.debug("skipping %s due to exclusion filter" % pcap_file)
|
||||
set_reported(reported, net_id)
|
||||
continue
|
||||
|
||||
essid, bssid = parse_pcap(pcap_file)
|
||||
if bssid:
|
||||
add_as_reported = False
|
||||
if is_excluded(essid) or is_excluded(bssid):
|
||||
logging.debug("not reporting %s due to exclusion filter" % pcap_file)
|
||||
set_reported(reported, net_id)
|
||||
else:
|
||||
if grid.report_ap(essid, bssid):
|
||||
set_reported(reported, net_id)
|
||||
else:
|
||||
logging.warning("no bssid found?!")
|
||||
else:
|
||||
logging.debug("grid: reporting disabled")
|
||||
|
||||
check_inbox(agent)
|
||||
except Exception as e:
|
||||
logging.exception("grid api error")
|
||||
logging.error("[grid] error while checking inbox: %s" % e)
|
||||
logging.debug(e, exc_info=True)
|
||||
|
||||
try:
|
||||
check_handshakes(agent)
|
||||
except Exception as e:
|
||||
logging.error("[grid] error while checking pcaps: %s" % e)
|
||||
logging.debug(e, exc_info=True)
|
||||
|
@@ -1,6 +1,6 @@
|
||||
# tempmem shows memory infos and cpu temperature
|
||||
# memtemp shows memory infos and cpu temperature
|
||||
#
|
||||
# totalmem usedmem freemem cputemp
|
||||
# mem usage, cpu load, cpu temp
|
||||
#
|
||||
###############################################################
|
||||
#
|
||||
@@ -10,71 +10,55 @@
|
||||
# - removed the label so we wont waste screen space
|
||||
# - Updated version to 1.0.1
|
||||
#
|
||||
# 20-10-2019 by spees <speeskonijn@gmail.com>
|
||||
# - Refactored to use the already existing functions
|
||||
# - Now only shows memory usage in percentage
|
||||
# - Added CPU load
|
||||
# - Added horizontal and vertical orientation
|
||||
#
|
||||
###############################################################
|
||||
|
||||
|
||||
__author__ = 'https://github.com/xenDE'
|
||||
__version__ = '1.0.1'
|
||||
__name__ = 'memtemp'
|
||||
__license__ = 'GPL3'
|
||||
__description__ = 'A plugin that will add a memory and temperature indicator'
|
||||
|
||||
import struct
|
||||
__description__ = 'A plugin that will display memory/cpu usage and temperature'
|
||||
|
||||
from pwnagotchi.ui.components import LabeledValue
|
||||
from pwnagotchi.ui.view import BLACK
|
||||
import pwnagotchi.ui.fonts as fonts
|
||||
import pwnagotchi
|
||||
import logging
|
||||
|
||||
import time
|
||||
|
||||
|
||||
class MEMTEMP:
|
||||
|
||||
# set the minimum seconds before refresh the values
|
||||
refresh_wait = 30
|
||||
|
||||
refresh_ts_last = time.time() - refresh_wait
|
||||
|
||||
def __init__(self):
|
||||
# only import when the module is loaded and enabled
|
||||
import os
|
||||
|
||||
def get_temp(self):
|
||||
try:
|
||||
temp = os.popen('/opt/vc/bin/vcgencmd measure_temp').readlines()[0].split('=')[1].replace("\n", '').replace("'","")
|
||||
return 't:' + temp
|
||||
except:
|
||||
return 't:-'
|
||||
# cpu:37.4C
|
||||
|
||||
def get_mem_info(self):
|
||||
try:
|
||||
# includes RAM + Swap Memory:
|
||||
# total, used, free = map(int, os.popen('free -t -m').readlines()[-1].split()[1:])
|
||||
# without Swap, only real memory:
|
||||
total, used, free = map(int, os.popen('free -t -m').readlines()[-3].split()[1:4])
|
||||
return "\nT:"+str(total)+"M U:"+str(used)+"M\nF:"+str(free)+"M"
|
||||
except:
|
||||
return "\nT:- U:-\nF:- "
|
||||
# tm:532 um:82 fm:353
|
||||
|
||||
|
||||
memtemp = None
|
||||
OPTIONS = dict()
|
||||
|
||||
|
||||
def on_loaded():
|
||||
global memtemp
|
||||
memtemp = MEMTEMP()
|
||||
logging.info("memtemp plugin loaded.")
|
||||
|
||||
|
||||
def mem_usage():
|
||||
return int(pwnagotchi.mem_usage() * 100)
|
||||
|
||||
|
||||
def cpu_load():
|
||||
return int(pwnagotchi.cpu_load() * 100)
|
||||
|
||||
|
||||
def on_ui_setup(ui):
|
||||
ui.add_element('memtemp', LabeledValue(color=BLACK, label='', value='\nT:- U:-\nF:- -', position=(ui.width() / 2 + 17, ui.height() / 2),
|
||||
label_font=fonts.Bold, text_font=fonts.Medium))
|
||||
if OPTIONS['orientation'] == "horizontal":
|
||||
ui.add_element('memtemp', LabeledValue(color=BLACK, label='', value='mem cpu temp\n - - -',
|
||||
position=(ui.width() / 2 + 30, ui.height() / 2 + 15),
|
||||
label_font=fonts.Small, text_font=fonts.Small))
|
||||
elif OPTIONS['orientation'] == "vertical":
|
||||
ui.add_element('memtemp', LabeledValue(color=BLACK, label='', value=' mem:-\n cpu:-\ntemp:-',
|
||||
position=(ui.width() / 2 + 55, ui.height() / 2),
|
||||
label_font=fonts.Small, text_font=fonts.Small))
|
||||
|
||||
|
||||
def on_ui_update(ui):
|
||||
if time.time() > memtemp.refresh_ts_last + memtemp.refresh_wait:
|
||||
ui.set('memtemp', "%s %s" % (memtemp.get_mem_info(), memtemp.get_temp()))
|
||||
memtemp.refresh_ts_last = time.time()
|
||||
|
||||
if OPTIONS['orientation'] == "horizontal":
|
||||
ui.set('memtemp', " mem cpu temp\n %s%% %s%% %sc" % (mem_usage(), cpu_load(), pwnagotchi.temperature()))
|
||||
|
||||
elif OPTIONS['orientation'] == "vertical":
|
||||
ui.set('memtemp', " mem:%s%%\n cpu:%s%%\ntemp:%sc" % (mem_usage(), cpu_load(), pwnagotchi.temperature()))
|
||||
|
33
pwnagotchi/plugins/default/paw-gps.py
Normal file
33
pwnagotchi/plugins/default/paw-gps.py
Normal file
@@ -0,0 +1,33 @@
|
||||
__author__ = 'leont'
|
||||
__version__ = '1.0.0'
|
||||
__name__ = 'pawgps'
|
||||
__license__ = 'GPL3'
|
||||
__description__ = 'Saves GPS coordinates whenever an handshake is captured. The GPS data is get from PAW on android '
|
||||
|
||||
'''
|
||||
You need an bluetooth connection to your android phone which is running PAW server with the GPS "hack" from Systemic:
|
||||
https://raw.githubusercontent.com/systemik/pwnagotchi-bt-tether/master/GPS-via-PAW
|
||||
'''
|
||||
|
||||
import logging
|
||||
import json
|
||||
import requests
|
||||
|
||||
OPTIONS = dict()
|
||||
|
||||
|
||||
def on_loaded():
|
||||
logging.info("PAW-GPS loaded")
|
||||
if 'ip' not in OPTIONS or ('ip' in OPTIONS and OPTIONS['ip'] is None):
|
||||
logging.info("PAW-GPS: No IP Address in the config file is defined, it uses the default (192.168.44.1)")
|
||||
|
||||
def on_handshake(agent, filename, access_point, client_station):
|
||||
if 'ip' not in OPTIONS or ('ip' in OPTIONS and OPTIONS['ip'] is None):
|
||||
ip = "192.168.44.1"
|
||||
|
||||
gps = requests.get('http://' + ip + '/gps.xhtml')
|
||||
gps_filename = filename.replace('.pcap', '.gps.json')
|
||||
|
||||
logging.info("saving GPS to %s (%s)" % (gps_filename, gps))
|
||||
with open(gps_filename, 'w+t') as f:
|
||||
f.write(gps.text)
|
@@ -63,7 +63,7 @@ def _transform_wigle_entry(gps_data, pcap_data):
|
||||
dummy.write("WigleWifi-1.4,appRelease=20190201,model=Kismet,release=2019.02.01.{},device=kismet,display=kismet,board=kismet,brand=kismet\n")
|
||||
dummy.write("MAC,SSID,AuthMode,FirstSeen,Channel,RSSI,CurrentLatitude,CurrentLongitude,AltitudeMeters,AccuracyMeters,Type")
|
||||
|
||||
writer = csv.writer(dummy, delimiter=",", quoting=csv.QUOTE_NONE)
|
||||
writer = csv.writer(dummy, delimiter=",", quoting=csv.QUOTE_NONE, escapechar="\\")
|
||||
writer.writerow([
|
||||
pcap_data[WifiInfo.BSSID],
|
||||
pcap_data[WifiInfo.ESSID],
|
||||
@@ -155,7 +155,7 @@ def on_internet_available(agent):
|
||||
continue
|
||||
|
||||
if gps_data['Latitude'] == 0 and gps_data['Longitude'] == 0:
|
||||
logging.warning("WIGLE: Not enough gps-informations for %s. Trying again next time.", gps_file)
|
||||
logging.warning("WIGLE: Not enough gps-information for %s. Trying again next time.", gps_file)
|
||||
SKIP.append(gps_file)
|
||||
continue
|
||||
|
||||
@@ -167,7 +167,7 @@ def on_internet_available(agent):
|
||||
WifiInfo.CHANNEL,
|
||||
WifiInfo.RSSI])
|
||||
except FieldNotFoundError:
|
||||
logging.error("WIGLE: Could not extract all informations. Skip %s", gps_file)
|
||||
logging.error("WIGLE: Could not extract all information. Skip %s", gps_file)
|
||||
SKIP.append(gps_file)
|
||||
continue
|
||||
except Scapy_Exception as sc_e:
|
||||
|
@@ -25,19 +25,23 @@ def on_loaded():
|
||||
logging.error("WPA_SEC: API-KEY isn't set. Can't upload to wpa-sec.stanev.org")
|
||||
return
|
||||
|
||||
if 'api_url' not in OPTIONS or ('api_url' in OPTIONS and OPTIONS['api_url'] is None):
|
||||
logging.error("WPA_SEC: API-URL isn't set. Can't upload, no endpoint configured.")
|
||||
return
|
||||
|
||||
READY = True
|
||||
|
||||
|
||||
def _upload_to_wpasec(path, timeout=30):
|
||||
"""
|
||||
Uploads the file to wpa-sec.stanev.org
|
||||
Uploads the file to https://wpa-sec.stanev.org, or another endpoint.
|
||||
"""
|
||||
with open(path, 'rb') as file_to_upload:
|
||||
cookie = {'key': OPTIONS['api_key']}
|
||||
payload = {'file': file_to_upload}
|
||||
|
||||
try:
|
||||
result = requests.post('https://wpa-sec.stanev.org',
|
||||
result = requests.post(OPTIONS['api_url'],
|
||||
cookies=cookie,
|
||||
files=payload,
|
||||
timeout=timeout)
|
||||
|
@@ -1,112 +1,31 @@
|
||||
import _thread
|
||||
from threading import Lock
|
||||
|
||||
import shutil
|
||||
import os
|
||||
import logging
|
||||
import pwnagotchi, pwnagotchi.plugins as plugins
|
||||
import threading
|
||||
|
||||
import pwnagotchi.plugins as plugins
|
||||
import pwnagotchi.ui.hw as hw
|
||||
import pwnagotchi.ui.web as web
|
||||
from pwnagotchi.ui.view import View
|
||||
|
||||
from http.server import BaseHTTPRequestHandler, HTTPServer
|
||||
|
||||
|
||||
class VideoHandler(BaseHTTPRequestHandler):
|
||||
_lock = Lock()
|
||||
_index = """<html>
|
||||
<head>
|
||||
<title>%s</title>
|
||||
<style>
|
||||
.block {
|
||||
-webkit-appearance: button;
|
||||
-moz-appearance: button;
|
||||
appearance: button;
|
||||
|
||||
display: block;
|
||||
cursor: pointer;
|
||||
text-align: center;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div style="position: absolute; top:0; left:0; width:100%%;">
|
||||
<img src="/ui" id="ui" style="width:100%%"/>
|
||||
<br/>
|
||||
<hr/>
|
||||
<form action="/shutdown" onsubmit="return confirm('This will halt the unit, continue?');">
|
||||
<input type="submit" class="block" value="Shutdown"/>
|
||||
</form>
|
||||
</div>
|
||||
|
||||
<script type="text/javascript">
|
||||
window.onload = function() {
|
||||
var image = document.getElementById("ui");
|
||||
function updateImage() {
|
||||
image.src = image.src.split("?")[0] + "?" + new Date().getTime();
|
||||
}
|
||||
setInterval(updateImage, %d);
|
||||
}
|
||||
</script>
|
||||
</body>
|
||||
</html>"""
|
||||
|
||||
@staticmethod
|
||||
def render(img):
|
||||
with VideoHandler._lock:
|
||||
img.save("/root/pwnagotchi.png", format='PNG')
|
||||
|
||||
def log_message(self, format, *args):
|
||||
return
|
||||
|
||||
def do_GET(self):
|
||||
if self.path == '/':
|
||||
self.send_response(200)
|
||||
self.send_header('Content-type', 'text/html')
|
||||
self.end_headers()
|
||||
try:
|
||||
self.wfile.write(bytes(self._index % (pwnagotchi.name(), 1000), "utf8"))
|
||||
except:
|
||||
pass
|
||||
|
||||
elif self.path.startswith('/shutdown'):
|
||||
pwnagotchi.shutdown()
|
||||
|
||||
elif self.path.startswith('/ui'):
|
||||
with self._lock:
|
||||
self.send_response(200)
|
||||
self.send_header('Content-type', 'image/png')
|
||||
self.end_headers()
|
||||
try:
|
||||
with open("/root/pwnagotchi.png", 'rb') as fp:
|
||||
shutil.copyfileobj(fp, self.wfile)
|
||||
except:
|
||||
pass
|
||||
else:
|
||||
self.send_response(404)
|
||||
|
||||
|
||||
class Display(View):
|
||||
def __init__(self, config, state={}):
|
||||
super(Display, self).__init__(config, hw.display_for(config), state)
|
||||
self._enabled = config['ui']['display']['enabled']
|
||||
self._rotation = config['ui']['display']['rotation']
|
||||
self._video_enabled = config['ui']['display']['video']['enabled']
|
||||
self._video_port = config['ui']['display']['video']['port']
|
||||
self._video_address = config['ui']['display']['video']['address']
|
||||
self._httpd = None
|
||||
config = config['ui']['display']
|
||||
|
||||
self._enabled = config['enabled']
|
||||
self._rotation = config['rotation']
|
||||
self._webui = web.Server(config)
|
||||
|
||||
self.init_display()
|
||||
|
||||
if self._video_enabled:
|
||||
_thread.start_new_thread(self._http_serve, ())
|
||||
|
||||
def _http_serve(self):
|
||||
if self._video_address is not None:
|
||||
self._httpd = HTTPServer((self._video_address, self._video_port), VideoHandler)
|
||||
logging.info("ui available at http://%s:%d/" % (self._video_address, self._video_port))
|
||||
self._httpd.serve_forever()
|
||||
else:
|
||||
logging.info("could not get ip of usb0, video server not starting")
|
||||
self._canvas_next_event = threading.Event()
|
||||
self._canvas_next = None
|
||||
self._render_thread_instance = threading.Thread(
|
||||
target=self._render_thread,
|
||||
daemon=True
|
||||
)
|
||||
self._render_thread_instance.start()
|
||||
|
||||
def is_inky(self):
|
||||
return self._implementation.name == 'inky'
|
||||
@@ -120,13 +39,15 @@ class Display(View):
|
||||
def is_waveshare_v2(self):
|
||||
return self._implementation.name == 'waveshare_2'
|
||||
|
||||
def is_waveshare27inch(self):
|
||||
return self._implementation.name == 'waveshare27inch'
|
||||
|
||||
def is_oledhat(self):
|
||||
return self._implementation.name == 'oledhat'
|
||||
|
||||
def is_lcdhat(self):
|
||||
return self._implementation.name == 'lcdhat'
|
||||
|
||||
|
||||
def is_waveshare_any(self):
|
||||
return self.is_waveshare_v1() or self.is_waveshare_v2()
|
||||
|
||||
@@ -147,9 +68,24 @@ class Display(View):
|
||||
img = self._canvas if self._rotation == 0 else self._canvas.rotate(-self._rotation)
|
||||
return img
|
||||
|
||||
def _render_thread(self):
|
||||
"""Used for non-blocking screen updating."""
|
||||
|
||||
while True:
|
||||
self._canvas_next_event.wait()
|
||||
self._canvas_next_event.clear()
|
||||
self._implementation.render(self._canvas_next)
|
||||
|
||||
def _on_view_rendered(self, img):
|
||||
VideoHandler.render(img)
|
||||
web.update_frame(img)
|
||||
try:
|
||||
if self._config['ui']['display']['video']['on_frame'] != '':
|
||||
os.system(self._config['ui']['display']['video']['on_frame'])
|
||||
except Exception as e:
|
||||
logging.error("%s" % e)
|
||||
|
||||
if self._enabled:
|
||||
self._canvas = (img if self._rotation == 0 else img.rotate(self._rotation))
|
||||
if self._implementation is not None:
|
||||
self._implementation.render(self._canvas)
|
||||
self._canvas_next = self._canvas
|
||||
self._canvas_next_event.set()
|
||||
|
@@ -1,5 +1,7 @@
|
||||
LOOK_R = '( ⚆_⚆)'
|
||||
LOOK_L = '(☉_☉ )'
|
||||
LOOK_R_HAPPY = '( ◕‿◕)'
|
||||
LOOK_L_HAPPY = '(◕‿◕ )'
|
||||
SLEEP = '(⇀‿‿↼)'
|
||||
SLEEP2 = '(≖‿‿≖)'
|
||||
AWAKE = '(◕‿‿◕)'
|
||||
@@ -7,6 +9,7 @@ BORED = '(-__-)'
|
||||
INTENSE = '(°▃▃°)'
|
||||
COOL = '(⌐■_■)'
|
||||
HAPPY = '(•‿‿•)'
|
||||
GRATEFUL = '(^‿‿^)'
|
||||
EXCITED = '(ᵔ◡◡ᵔ)'
|
||||
MOTIVATED = '(☼‿‿☼)'
|
||||
DEMOTIVATED = '(≖__≖)'
|
||||
@@ -16,3 +19,8 @@ SAD = '(╥☁╥ )'
|
||||
FRIEND = '(♥‿‿♥)'
|
||||
BROKEN = '(☓‿‿☓)'
|
||||
DEBUG = '(#__#)'
|
||||
|
||||
|
||||
def load_from_config(config):
|
||||
for face_name, face_value in config.items():
|
||||
globals()[face_name.upper()] = face_value
|
||||
|
@@ -4,6 +4,7 @@ from pwnagotchi.ui.hw.oledhat import OledHat
|
||||
from pwnagotchi.ui.hw.lcdhat import LcdHat
|
||||
from pwnagotchi.ui.hw.waveshare1 import WaveshareV1
|
||||
from pwnagotchi.ui.hw.waveshare2 import WaveshareV2
|
||||
from pwnagotchi.ui.hw.waveshare27inch import Waveshare27inch
|
||||
|
||||
|
||||
def display_for(config):
|
||||
@@ -26,3 +27,6 @@ def display_for(config):
|
||||
|
||||
elif config['ui']['display']['type'] == 'waveshare_2':
|
||||
return WaveshareV2(config)
|
||||
|
||||
elif config['ui']['display']['type'] == 'waveshare27inch':
|
||||
return Waveshare27inch(config)
|
@@ -7,24 +7,25 @@ import numpy as np
|
||||
class ST7789(object):
|
||||
"""class for ST7789 240*240 1.3inch OLED displays."""
|
||||
|
||||
def __init__(self,spi,rst = 27,dc = 25,bl = 24):
|
||||
def __init__(self, spi, rst=27, dc=25, bl=24):
|
||||
self.width = 240
|
||||
self.height = 240
|
||||
#Initialize DC RST pin
|
||||
# Initialize DC RST pin
|
||||
self._dc = dc
|
||||
self._rst = rst
|
||||
self._bl = bl
|
||||
GPIO.setmode(GPIO.BCM)
|
||||
GPIO.setwarnings(False)
|
||||
GPIO.setup(self._dc,GPIO.OUT)
|
||||
GPIO.setup(self._rst,GPIO.OUT)
|
||||
GPIO.setup(self._bl,GPIO.OUT)
|
||||
GPIO.setup(self._dc, GPIO.OUT)
|
||||
GPIO.setup(self._rst, GPIO.OUT)
|
||||
GPIO.setup(self._bl, GPIO.OUT)
|
||||
GPIO.output(self._bl, GPIO.HIGH)
|
||||
#Initialize SPI
|
||||
# Initialize SPI
|
||||
self._spi = spi
|
||||
self._spi.max_speed_hz = 40000000
|
||||
|
||||
""" Write register address and data """
|
||||
|
||||
def command(self, cmd):
|
||||
GPIO.output(self._dc, GPIO.LOW)
|
||||
self._spi.writebytes([cmd])
|
||||
@@ -34,13 +35,13 @@ class ST7789(object):
|
||||
self._spi.writebytes([val])
|
||||
|
||||
def Init(self):
|
||||
"""Initialize dispaly"""
|
||||
"""Initialize dispaly"""
|
||||
self.reset()
|
||||
|
||||
self.command(0x36)
|
||||
self.data(0x70) #self.data(0x00)
|
||||
self.data(0x70) # self.data(0x00)
|
||||
|
||||
self.command(0x3A)
|
||||
self.command(0x3A)
|
||||
self.data(0x05)
|
||||
|
||||
self.command(0xB2)
|
||||
@@ -51,7 +52,7 @@ class ST7789(object):
|
||||
self.data(0x33)
|
||||
|
||||
self.command(0xB7)
|
||||
self.data(0x35)
|
||||
self.data(0x35)
|
||||
|
||||
self.command(0xBB)
|
||||
self.data(0x19)
|
||||
@@ -63,13 +64,13 @@ class ST7789(object):
|
||||
self.data(0x01)
|
||||
|
||||
self.command(0xC3)
|
||||
self.data(0x12)
|
||||
self.data(0x12)
|
||||
|
||||
self.command(0xC4)
|
||||
self.data(0x20)
|
||||
|
||||
self.command(0xC6)
|
||||
self.data(0x0F)
|
||||
self.data(0x0F)
|
||||
|
||||
self.command(0xD0)
|
||||
self.data(0xA4)
|
||||
@@ -106,7 +107,7 @@ class ST7789(object):
|
||||
self.data(0x1F)
|
||||
self.data(0x20)
|
||||
self.data(0x23)
|
||||
|
||||
|
||||
self.command(0x21)
|
||||
|
||||
self.command(0x11)
|
||||
@@ -115,51 +116,51 @@ class ST7789(object):
|
||||
|
||||
def reset(self):
|
||||
"""Reset the display"""
|
||||
GPIO.output(self._rst,GPIO.HIGH)
|
||||
GPIO.output(self._rst, GPIO.HIGH)
|
||||
time.sleep(0.01)
|
||||
GPIO.output(self._rst,GPIO.LOW)
|
||||
GPIO.output(self._rst, GPIO.LOW)
|
||||
time.sleep(0.01)
|
||||
GPIO.output(self._rst,GPIO.HIGH)
|
||||
GPIO.output(self._rst, GPIO.HIGH)
|
||||
time.sleep(0.01)
|
||||
|
||||
|
||||
def SetWindows(self, Xstart, Ystart, Xend, Yend):
|
||||
#set the X coordinates
|
||||
# set the X coordinates
|
||||
self.command(0x2A)
|
||||
self.data(0x00) #Set the horizontal starting point to the high octet
|
||||
self.data(Xstart & 0xff) #Set the horizontal starting point to the low octet
|
||||
self.data(0x00) #Set the horizontal end to the high octet
|
||||
self.data((Xend - 1) & 0xff) #Set the horizontal end to the low octet
|
||||
|
||||
#set the Y coordinates
|
||||
self.data(0x00) # Set the horizontal starting point to the high octet
|
||||
self.data(Xstart & 0xff) # Set the horizontal starting point to the low octet
|
||||
self.data(0x00) # Set the horizontal end to the high octet
|
||||
self.data((Xend - 1) & 0xff) # Set the horizontal end to the low octet
|
||||
|
||||
# set the Y coordinates
|
||||
self.command(0x2B)
|
||||
self.data(0x00)
|
||||
self.data((Ystart & 0xff))
|
||||
self.data(0x00)
|
||||
self.data((Yend - 1) & 0xff )
|
||||
self.data((Yend - 1) & 0xff)
|
||||
|
||||
self.command(0x2C)
|
||||
|
||||
def ShowImage(self,Image,Xstart,Ystart):
|
||||
self.command(0x2C)
|
||||
|
||||
def ShowImage(self, Image, Xstart, Ystart):
|
||||
"""Set buffer to value of Python Imaging Library image."""
|
||||
"""Write display buffer to physical display"""
|
||||
imwidth, imheight = Image.size
|
||||
if imwidth != self.width or imheight != self.height:
|
||||
raise ValueError('Image must be same dimensions as display \
|
||||
({0}x{1}).' .format(self.width, self.height))
|
||||
({0}x{1}).'.format(self.width, self.height))
|
||||
img = np.asarray(Image)
|
||||
pix = np.zeros((self.width,self.height,2), dtype = np.uint8)
|
||||
pix[...,[0]] = np.add(np.bitwise_and(img[...,[0]],0xF8),np.right_shift(img[...,[1]],5))
|
||||
pix[...,[1]] = np.add(np.bitwise_and(np.left_shift(img[...,[1]],3),0xE0),np.right_shift(img[...,[2]],3))
|
||||
pix = np.zeros((self.width, self.height, 2), dtype=np.uint8)
|
||||
pix[..., [0]] = np.add(np.bitwise_and(img[..., [0]], 0xF8), np.right_shift(img[..., [1]], 5))
|
||||
pix[..., [1]] = np.add(np.bitwise_and(np.left_shift(img[..., [1]], 3), 0xE0), np.right_shift(img[..., [2]], 3))
|
||||
pix = pix.flatten().tolist()
|
||||
self.SetWindows ( 0, 0, self.width, self.height)
|
||||
GPIO.output(self._dc,GPIO.HIGH)
|
||||
for i in range(0,len(pix),4096):
|
||||
self._spi.writebytes(pix[i:i+4096])
|
||||
|
||||
self.SetWindows(0, 0, self.width, self.height)
|
||||
GPIO.output(self._dc, GPIO.HIGH)
|
||||
for i in range(0, len(pix), 4096):
|
||||
self._spi.writebytes(pix[i:i + 4096])
|
||||
|
||||
def clear(self):
|
||||
"""Clear contents of image buffer"""
|
||||
_buffer = [0xff]*(self.width * self.height * 2)
|
||||
self.SetWindows ( 0, 0, self.width, self.height)
|
||||
GPIO.output(self._dc,GPIO.HIGH)
|
||||
for i in range(0,len(_buffer),4096):
|
||||
self._spi.writebytes(_buffer[i:i+4096])
|
||||
_buffer = [0xff] * (self.width * self.height * 2)
|
||||
self.SetWindows(0, 0, self.width, self.height)
|
||||
GPIO.output(self._dc, GPIO.HIGH)
|
||||
for i in range(0, len(_buffer), 4096):
|
||||
self._spi.writebytes(_buffer[i:i + 4096])
|
||||
|
@@ -7,70 +7,15 @@
|
||||
# * | Date : 2019-10-18
|
||||
# * | Info :
|
||||
# ******************************************************************************/
|
||||
|
||||
import RPi.GPIO as GPIO
|
||||
import time
|
||||
from smbus import SMBus
|
||||
import spidev
|
||||
|
||||
import ctypes
|
||||
# import spidev
|
||||
|
||||
# Pin definition
|
||||
RST_PIN = 27
|
||||
DC_PIN = 25
|
||||
BL_PIN = 24
|
||||
RST_PIN = 27
|
||||
DC_PIN = 25
|
||||
BL_PIN = 24
|
||||
|
||||
Device_SPI = 1
|
||||
Device_I2C = 0
|
||||
|
||||
Device = Device_SPI
|
||||
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)
|
||||
spi.writebytes([data[0]])
|
||||
|
||||
def i2c_writebyte(reg, value):
|
||||
bus.write_byte_data(address, reg, value)
|
||||
|
||||
# time.sleep(0.01)
|
||||
def module_init():
|
||||
# print("module_init")
|
||||
|
||||
GPIO.setmode(GPIO.BCM)
|
||||
GPIO.setwarnings(False)
|
||||
GPIO.setup(RST_PIN, GPIO.OUT)
|
||||
GPIO.setup(DC_PIN, GPIO.OUT)
|
||||
|
||||
|
||||
# SPI.max_speed_hz = 2000000
|
||||
# SPI.mode = 0b00
|
||||
# i2c_writebyte(0xff,0xff)
|
||||
# spi.SYSFS_software_spi_begin()
|
||||
# spi.SYSFS_software_spi_setDataMode(0);
|
||||
# spi.SYSFS_software_spi_setClockDivider(1);
|
||||
#spi.max_speed_hz = 2000000
|
||||
#spi.mode = 0b00
|
||||
|
||||
GPIO.output(BL_PIN, 1)
|
||||
GPIO.output(DC_PIN, 0)
|
||||
return 0
|
||||
|
||||
def module_exit():
|
||||
spi.SYSFS_software_spi_end()
|
||||
GPIO.output(RST_PIN, 0)
|
||||
GPIO.output(DC_PIN, 0)
|
||||
|
||||
|
||||
|
||||
### END OF FILE ###
|
||||
|
@@ -1,28 +1,21 @@
|
||||
from . import ST7789
|
||||
from . import config
|
||||
|
||||
# Display resolution
|
||||
EPD_WIDTH = 240
|
||||
EPD_HEIGHT = 240
|
||||
|
||||
disp = ST7789.ST7789(config.spi,config.RST_PIN, config.DC_PIN, config.BL_PIN)
|
||||
|
||||
class EPD(object):
|
||||
|
||||
def __init__(self):
|
||||
self.reset_pin = config.RST_PIN
|
||||
self.dc_pin = config.DC_PIN
|
||||
#self.busy_pin = config.BUSY_PIN
|
||||
#self.cs_pin = config.CS_PIN
|
||||
self.width = EPD_WIDTH
|
||||
self.height = EPD_HEIGHT
|
||||
self.width = 240
|
||||
self.height = 240
|
||||
self.st7789 = ST7789.ST7789(config.spi, config.RST_PIN, config.DC_PIN, config.BL_PIN)
|
||||
|
||||
def init(self):
|
||||
disp.Init()
|
||||
self.st7789.Init()
|
||||
|
||||
def Clear(self):
|
||||
disp.clear()
|
||||
def clear(self):
|
||||
self.st7789.clear()
|
||||
|
||||
def display(self, image):
|
||||
rgb_im = image.convert('RGB')
|
||||
disp.ShowImage(rgb_im,0,0)
|
||||
self.st7789.ShowImage(rgb_im, 0, 0)
|
||||
|
@@ -47,11 +47,11 @@ class EPD:
|
||||
# Hardware reset
|
||||
def reset(self):
|
||||
epdconfig.digital_write(self.reset_pin, GPIO.HIGH)
|
||||
epdconfig.delay_ms(200)
|
||||
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)
|
||||
epdconfig.delay_ms(200)
|
||||
|
||||
def send_command(self, command):
|
||||
epdconfig.digital_write(self.dc_pin, GPIO.LOW)
|
||||
@@ -64,8 +64,9 @@ class EPD:
|
||||
epdconfig.digital_write(self.cs_pin, GPIO.LOW)
|
||||
epdconfig.spi_writebyte([data])
|
||||
epdconfig.digital_write(self.cs_pin, GPIO.HIGH)
|
||||
|
||||
|
||||
def ReadBusy(self):
|
||||
epdconfig.delay_ms(20)
|
||||
while(epdconfig.digital_read(self.busy_pin) == 0): # 0: idle, 1: busy
|
||||
epdconfig.delay_ms(100)
|
||||
|
||||
@@ -79,16 +80,16 @@ class EPD:
|
||||
self.send_data(0x17)
|
||||
self.send_data(0x17)
|
||||
self.send_data(0x17)
|
||||
|
||||
|
||||
self.send_command(0x04) # POWER_ON
|
||||
self.ReadBusy()
|
||||
|
||||
|
||||
self.send_command(0x00) # PANEL_SETTING
|
||||
self.send_data(0x8F)
|
||||
|
||||
|
||||
self.send_command(0x50) # VCOM_AND_DATA_INTERVAL_SETTING
|
||||
self.send_data(0xF0)
|
||||
|
||||
|
||||
self.send_command(0x61) # RESOLUTION_SETTING
|
||||
self.send_data(self.width & 0xff)
|
||||
self.send_data(self.height >> 8)
|
||||
@@ -120,7 +121,7 @@ class EPD:
|
||||
for i in range(0, int(self.width * self.height / 8)):
|
||||
self.send_data(imageblack[i])
|
||||
self.send_command(0x92)
|
||||
|
||||
|
||||
self.send_command(0x12) # REFRESH
|
||||
self.ReadBusy()
|
||||
|
||||
@@ -129,26 +130,26 @@ class EPD:
|
||||
for i in range(0, int(self.width * self.height / 8)):
|
||||
self.send_data(imageblack[i])
|
||||
self.send_command(0x92)
|
||||
|
||||
|
||||
self.send_command(0x13)
|
||||
for i in range(0, int(self.width * self.height / 8)):
|
||||
self.send_data(imagecolor[i])
|
||||
self.send_command(0x92)
|
||||
|
||||
|
||||
self.send_command(0x12) # REFRESH
|
||||
self.ReadBusy()
|
||||
|
||||
|
||||
def Clear(self):
|
||||
self.send_command(0x10)
|
||||
for i in range(0, int(self.width * self.height / 8)):
|
||||
self.send_data(0xFF)
|
||||
self.send_command(0x92)
|
||||
|
||||
self.send_command(0x92)
|
||||
|
||||
self.send_command(0x13)
|
||||
for i in range(0, int(self.width * self.height / 8)):
|
||||
self.send_data(0xFF)
|
||||
self.send_command(0x92)
|
||||
|
||||
|
||||
self.send_command(0x12) # REFRESH
|
||||
self.ReadBusy()
|
||||
|
||||
@@ -157,7 +158,7 @@ class EPD:
|
||||
self.ReadBusy()
|
||||
self.send_command(0x07) # DEEP_SLEEP
|
||||
self.send_data(0xA5) # check code
|
||||
|
||||
|
||||
# epdconfig.module_exit()
|
||||
### END OF FILE ###
|
||||
|
||||
|
0
pwnagotchi/ui/hw/libs/waveshare/v27inch/__init__.py
Normal file
0
pwnagotchi/ui/hw/libs/waveshare/v27inch/__init__.py
Normal file
520
pwnagotchi/ui/hw/libs/waveshare/v27inch/epd2in7.py
Normal file
520
pwnagotchi/ui/hw/libs/waveshare/v27inch/epd2in7.py
Normal file
@@ -0,0 +1,520 @@
|
||||
# *****************************************************************************
|
||||
# * | File : epd2in7.py
|
||||
# * | Author : Waveshare team
|
||||
# * | Function : Electronic paper driver
|
||||
# * | Info :
|
||||
# *----------------
|
||||
# * | This version: V4.0
|
||||
# * | Date : 2019-06-20
|
||||
# # | Info : python demo
|
||||
# -----------------------------------------------------------------------------
|
||||
# Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
# of this software and associated documnetation files (the "Software"), to deal
|
||||
# in the Software without restriction, including without limitation the rights
|
||||
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
# copies of the Software, and to permit persons to whom the Software is
|
||||
# furished to do so, subject to the following conditions:
|
||||
#
|
||||
# The above copyright notice and this permission notice shall be included in
|
||||
# all copies or substantial portions of the Software.
|
||||
#
|
||||
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
# FITNESS OR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
# LIABILITY WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
# THE SOFTWARE.
|
||||
#
|
||||
|
||||
import logging
|
||||
from . import epdconfig
|
||||
|
||||
# Display resolution
|
||||
EPD_WIDTH = 176
|
||||
EPD_HEIGHT = 264
|
||||
|
||||
GRAY1 = 0xff #white
|
||||
GRAY2 = 0xC0
|
||||
GRAY3 = 0x80 #gray
|
||||
GRAY4 = 0x00 #Blackest
|
||||
class EPD:
|
||||
def __init__(self):
|
||||
self.reset_pin = epdconfig.RST_PIN
|
||||
self.dc_pin = epdconfig.DC_PIN
|
||||
self.busy_pin = epdconfig.BUSY_PIN
|
||||
self.cs_pin = epdconfig.CS_PIN
|
||||
self.width = EPD_WIDTH
|
||||
self.height = EPD_HEIGHT
|
||||
self.GRAY1 = GRAY1 #white
|
||||
self.GRAY2 = GRAY2
|
||||
self.GRAY3 = GRAY3 #gray
|
||||
self.GRAY4 = GRAY4 #Blackest
|
||||
|
||||
lut_vcom_dc = [0x00, 0x00,
|
||||
0x00, 0x08, 0x00, 0x00, 0x00, 0x02,
|
||||
0x60, 0x28, 0x28, 0x00, 0x00, 0x01,
|
||||
0x00, 0x14, 0x00, 0x00, 0x00, 0x01,
|
||||
0x00, 0x12, 0x12, 0x00, 0x00, 0x01,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00
|
||||
]
|
||||
lut_ww = [
|
||||
0x40, 0x08, 0x00, 0x00, 0x00, 0x02,
|
||||
0x90, 0x28, 0x28, 0x00, 0x00, 0x01,
|
||||
0x40, 0x14, 0x00, 0x00, 0x00, 0x01,
|
||||
0xA0, 0x12, 0x12, 0x00, 0x00, 0x01,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
]
|
||||
lut_bw = [
|
||||
0x40, 0x08, 0x00, 0x00, 0x00, 0x02,
|
||||
0x90, 0x28, 0x28, 0x00, 0x00, 0x01,
|
||||
0x40, 0x14, 0x00, 0x00, 0x00, 0x01,
|
||||
0xA0, 0x12, 0x12, 0x00, 0x00, 0x01,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
]
|
||||
lut_bb = [
|
||||
0x80, 0x08, 0x00, 0x00, 0x00, 0x02,
|
||||
0x90, 0x28, 0x28, 0x00, 0x00, 0x01,
|
||||
0x80, 0x14, 0x00, 0x00, 0x00, 0x01,
|
||||
0x50, 0x12, 0x12, 0x00, 0x00, 0x01,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
]
|
||||
lut_wb = [
|
||||
0x80, 0x08, 0x00, 0x00, 0x00, 0x02,
|
||||
0x90, 0x28, 0x28, 0x00, 0x00, 0x01,
|
||||
0x80, 0x14, 0x00, 0x00, 0x00, 0x01,
|
||||
0x50, 0x12, 0x12, 0x00, 0x00, 0x01,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
]
|
||||
###################full screen update LUT######################
|
||||
#0~3 gray
|
||||
gray_lut_vcom = [
|
||||
0x00, 0x00,
|
||||
0x00, 0x0A, 0x00, 0x00, 0x00, 0x01,
|
||||
0x60, 0x14, 0x14, 0x00, 0x00, 0x01,
|
||||
0x00, 0x14, 0x00, 0x00, 0x00, 0x01,
|
||||
0x00, 0x13, 0x0A, 0x01, 0x00, 0x01,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
]
|
||||
#R21
|
||||
gray_lut_ww =[
|
||||
0x40, 0x0A, 0x00, 0x00, 0x00, 0x01,
|
||||
0x90, 0x14, 0x14, 0x00, 0x00, 0x01,
|
||||
0x10, 0x14, 0x0A, 0x00, 0x00, 0x01,
|
||||
0xA0, 0x13, 0x01, 0x00, 0x00, 0x01,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
]
|
||||
#R22H r
|
||||
gray_lut_bw =[
|
||||
0x40, 0x0A, 0x00, 0x00, 0x00, 0x01,
|
||||
0x90, 0x14, 0x14, 0x00, 0x00, 0x01,
|
||||
0x00, 0x14, 0x0A, 0x00, 0x00, 0x01,
|
||||
0x99, 0x0C, 0x01, 0x03, 0x04, 0x01,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
]
|
||||
#R23H w
|
||||
gray_lut_wb =[
|
||||
0x40, 0x0A, 0x00, 0x00, 0x00, 0x01,
|
||||
0x90, 0x14, 0x14, 0x00, 0x00, 0x01,
|
||||
0x00, 0x14, 0x0A, 0x00, 0x00, 0x01,
|
||||
0x99, 0x0B, 0x04, 0x04, 0x01, 0x01,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
]
|
||||
#R24H b
|
||||
gray_lut_bb =[
|
||||
0x80, 0x0A, 0x00, 0x00, 0x00, 0x01,
|
||||
0x90, 0x14, 0x14, 0x00, 0x00, 0x01,
|
||||
0x20, 0x14, 0x0A, 0x00, 0x00, 0x01,
|
||||
0x50, 0x13, 0x01, 0x00, 0x00, 0x01,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
]
|
||||
|
||||
# Hardware reset
|
||||
def reset(self):
|
||||
epdconfig.digital_write(self.reset_pin, 1)
|
||||
epdconfig.delay_ms(200)
|
||||
epdconfig.digital_write(self.reset_pin, 0)
|
||||
epdconfig.delay_ms(10)
|
||||
epdconfig.digital_write(self.reset_pin, 1)
|
||||
epdconfig.delay_ms(200)
|
||||
|
||||
def send_command(self, command):
|
||||
epdconfig.digital_write(self.dc_pin, 0)
|
||||
epdconfig.digital_write(self.cs_pin, 0)
|
||||
epdconfig.spi_writebyte([command])
|
||||
epdconfig.digital_write(self.cs_pin, 1)
|
||||
|
||||
def send_data(self, data):
|
||||
epdconfig.digital_write(self.dc_pin, 1)
|
||||
epdconfig.digital_write(self.cs_pin, 0)
|
||||
epdconfig.spi_writebyte([data])
|
||||
epdconfig.digital_write(self.cs_pin, 1)
|
||||
|
||||
def ReadBusy(self):
|
||||
logging.debug("e-Paper busy")
|
||||
while(epdconfig.digital_read(self.busy_pin) == 0): # 0: idle, 1: busy
|
||||
epdconfig.delay_ms(200)
|
||||
logging.debug("e-Paper busy release")
|
||||
|
||||
def set_lut(self):
|
||||
self.send_command(0x20) # vcom
|
||||
for count in range(0, 44):
|
||||
self.send_data(self.lut_vcom_dc[count])
|
||||
self.send_command(0x21) # ww --
|
||||
for count in range(0, 42):
|
||||
self.send_data(self.lut_ww[count])
|
||||
self.send_command(0x22) # bw r
|
||||
for count in range(0, 42):
|
||||
self.send_data(self.lut_bw[count])
|
||||
self.send_command(0x23) # wb w
|
||||
for count in range(0, 42):
|
||||
self.send_data(self.lut_bb[count])
|
||||
self.send_command(0x24) # bb b
|
||||
for count in range(0, 42):
|
||||
self.send_data(self.lut_wb[count])
|
||||
|
||||
def gray_SetLut(self):
|
||||
self.send_command(0x20)
|
||||
for count in range(0, 44): #vcom
|
||||
self.send_data(self.gray_lut_vcom[count])
|
||||
|
||||
self.send_command(0x21) #red not use
|
||||
for count in range(0, 42):
|
||||
self.send_data(self.gray_lut_ww[count])
|
||||
|
||||
self.send_command(0x22) #bw r
|
||||
for count in range(0, 42):
|
||||
self.send_data(self.gray_lut_bw[count])
|
||||
|
||||
self.send_command(0x23) #wb w
|
||||
for count in range(0, 42):
|
||||
self.send_data(self.gray_lut_wb[count])
|
||||
|
||||
self.send_command(0x24) #bb b
|
||||
for count in range(0, 42):
|
||||
self.send_data(self.gray_lut_bb[count])
|
||||
|
||||
self.send_command(0x25) #vcom
|
||||
for count in range(0, 42):
|
||||
self.send_data(self.gray_lut_ww[count])
|
||||
|
||||
def init(self):
|
||||
if (epdconfig.module_init() != 0):
|
||||
return -1
|
||||
|
||||
# EPD hardware init start
|
||||
self.reset()
|
||||
|
||||
self.send_command(0x01) # POWER_SETTING
|
||||
self.send_data(0x03) # VDS_EN, VDG_EN
|
||||
self.send_data(0x00) # VCOM_HV, VGHL_LV[1], VGHL_LV[0]
|
||||
self.send_data(0x2b) # VDH
|
||||
self.send_data(0x2b) # VDL
|
||||
self.send_data(0x09) # VDHR
|
||||
|
||||
self.send_command(0x06) # BOOSTER_SOFT_START
|
||||
self.send_data(0x07)
|
||||
self.send_data(0x07)
|
||||
self.send_data(0x17)
|
||||
|
||||
# Power optimization
|
||||
self.send_command(0xF8)
|
||||
self.send_data(0x60)
|
||||
self.send_data(0xA5)
|
||||
|
||||
# Power optimization
|
||||
self.send_command(0xF8)
|
||||
self.send_data(0x89)
|
||||
self.send_data(0xA5)
|
||||
|
||||
# Power optimization
|
||||
self.send_command(0xF8)
|
||||
self.send_data(0x90)
|
||||
self.send_data(0x00)
|
||||
|
||||
# Power optimization
|
||||
self.send_command(0xF8)
|
||||
self.send_data(0x93)
|
||||
self.send_data(0x2A)
|
||||
|
||||
# Power optimization
|
||||
self.send_command(0xF8)
|
||||
self.send_data(0xA0)
|
||||
self.send_data(0xA5)
|
||||
|
||||
# Power optimization
|
||||
self.send_command(0xF8)
|
||||
self.send_data(0xA1)
|
||||
self.send_data(0x00)
|
||||
|
||||
# Power optimization
|
||||
self.send_command(0xF8)
|
||||
self.send_data(0x73)
|
||||
self.send_data(0x41)
|
||||
|
||||
self.send_command(0x16) # PARTIAL_DISPLAY_REFRESH
|
||||
self.send_data(0x00)
|
||||
self.send_command(0x04) # POWER_ON
|
||||
self.ReadBusy()
|
||||
|
||||
self.send_command(0x00) # PANEL_SETTING
|
||||
self.send_data(0xAF) # KW-BF KWR-AF BWROTP 0f
|
||||
|
||||
self.send_command(0x30) # PLL_CONTROL
|
||||
self.send_data(0x3A) # 3A 100HZ 29 150Hz 39 200HZ 31 171HZ
|
||||
|
||||
self.send_command(0x82) # VCM_DC_SETTING_REGISTER
|
||||
self.send_data(0x12)
|
||||
self.set_lut()
|
||||
return 0
|
||||
|
||||
def Init_4Gray(self):
|
||||
if (epdconfig.module_init() != 0):
|
||||
return -1
|
||||
self.reset()
|
||||
|
||||
self.send_command(0x01) #POWER SETTING
|
||||
self.send_data (0x03)
|
||||
self.send_data (0x00)
|
||||
self.send_data (0x2b)
|
||||
self.send_data (0x2b)
|
||||
|
||||
|
||||
self.send_command(0x06) #booster soft start
|
||||
self.send_data (0x07) #A
|
||||
self.send_data (0x07) #B
|
||||
self.send_data (0x17) #C
|
||||
|
||||
self.send_command(0xF8) #boost??
|
||||
self.send_data (0x60)
|
||||
self.send_data (0xA5)
|
||||
|
||||
self.send_command(0xF8) #boost??
|
||||
self.send_data (0x89)
|
||||
self.send_data (0xA5)
|
||||
|
||||
self.send_command(0xF8) #boost??
|
||||
self.send_data (0x90)
|
||||
self.send_data (0x00)
|
||||
|
||||
self.send_command(0xF8) #boost??
|
||||
self.send_data (0x93)
|
||||
self.send_data (0x2A)
|
||||
|
||||
self.send_command(0xF8) #boost??
|
||||
self.send_data (0xa0)
|
||||
self.send_data (0xa5)
|
||||
|
||||
self.send_command(0xF8) #boost??
|
||||
self.send_data (0xa1)
|
||||
self.send_data (0x00)
|
||||
|
||||
self.send_command(0xF8) #boost??
|
||||
self.send_data (0x73)
|
||||
self.send_data (0x41)
|
||||
|
||||
self.send_command(0x16)
|
||||
self.send_data(0x00)
|
||||
|
||||
self.send_command(0x04)
|
||||
self.ReadBusy()
|
||||
|
||||
self.send_command(0x00) #panel setting
|
||||
self.send_data(0xbf) #KW-BF KWR-AF BWROTP 0f
|
||||
|
||||
self.send_command(0x30) #PLL setting
|
||||
self.send_data (0x90) #100hz
|
||||
|
||||
self.send_command(0x61) #resolution setting
|
||||
self.send_data (0x00) #176
|
||||
self.send_data (0xb0)
|
||||
self.send_data (0x01) #264
|
||||
self.send_data (0x08)
|
||||
|
||||
self.send_command(0x82) #vcom_DC setting
|
||||
self.send_data (0x12)
|
||||
|
||||
self.send_command(0X50) #VCOM AND DATA INTERVAL SETTING
|
||||
self.send_data(0x97)
|
||||
|
||||
def getbuffer(self, image):
|
||||
# logging.debug("bufsiz = ",int(self.width/8) * self.height)
|
||||
buf = [0xFF] * (int(self.width/8) * self.height)
|
||||
image_monocolor = image.convert('1')
|
||||
imwidth, imheight = image_monocolor.size
|
||||
pixels = image_monocolor.load()
|
||||
# logging.debug("imwidth = %d, imheight = %d",imwidth,imheight)
|
||||
if(imwidth == self.width and imheight == self.height):
|
||||
logging.debug("Vertical")
|
||||
for y in range(imheight):
|
||||
for x in range(imwidth):
|
||||
# Set the bits for the column of pixels at the current position.
|
||||
if pixels[x, y] == 0:
|
||||
buf[int((x + y * self.width) / 8)] &= ~(0x80 >> (x % 8))
|
||||
elif(imwidth == self.height and imheight == self.width):
|
||||
logging.debug("Horizontal")
|
||||
for y in range(imheight):
|
||||
for x in range(imwidth):
|
||||
newx = y
|
||||
newy = self.height - x - 1
|
||||
if pixels[x, y] == 0:
|
||||
buf[int((newx + newy*self.width) / 8)] &= ~(0x80 >> (y % 8))
|
||||
return buf
|
||||
|
||||
def getbuffer_4Gray(self, image):
|
||||
# logging.debug("bufsiz = ",int(self.width/8) * self.height)
|
||||
buf = [0xFF] * (int(self.width / 4) * self.height)
|
||||
image_monocolor = image.convert('L')
|
||||
imwidth, imheight = image_monocolor.size
|
||||
pixels = image_monocolor.load()
|
||||
i=0
|
||||
# logging.debug("imwidth = %d, imheight = %d",imwidth,imheight)
|
||||
if(imwidth == self.width and imheight == self.height):
|
||||
logging.debug("Vertical")
|
||||
for y in range(imheight):
|
||||
for x in range(imwidth):
|
||||
# Set the bits for the column of pixels at the current position.
|
||||
if(pixels[x, y] == 0xC0):
|
||||
pixels[x, y] = 0x80
|
||||
elif (pixels[x, y] == 0x80):
|
||||
pixels[x, y] = 0x40
|
||||
i= i+1
|
||||
if(i%4 == 0):
|
||||
buf[int((x + (y * self.width))/4)] = ((pixels[x-3, y]&0xc0) | (pixels[x-2, y]&0xc0)>>2 | (pixels[x-1, y]&0xc0)>>4 | (pixels[x, y]&0xc0)>>6)
|
||||
|
||||
elif(imwidth == self.height and imheight == self.width):
|
||||
logging.debug("Horizontal")
|
||||
for x in range(imwidth):
|
||||
for y in range(imheight):
|
||||
newx = y
|
||||
newy = x
|
||||
if(pixels[x, y] == 0xC0):
|
||||
pixels[x, y] = 0x80
|
||||
elif (pixels[x, y] == 0x80):
|
||||
pixels[x, y] = 0x40
|
||||
i= i+1
|
||||
if(i%4 == 0):
|
||||
buf[int((newx + (newy * self.width))/4)] = ((pixels[x, y-3]&0xc0) | (pixels[x, y-2]&0xc0)>>2 | (pixels[x, y-1]&0xc0)>>4 | (pixels[x, y]&0xc0)>>6)
|
||||
return buf
|
||||
|
||||
def display(self, image):
|
||||
self.send_command(0x10)
|
||||
for i in range(0, int(self.width * self.height / 8)):
|
||||
self.send_data(0xFF)
|
||||
self.send_command(0x13)
|
||||
for i in range(0, int(self.width * self.height / 8)):
|
||||
self.send_data(image[i])
|
||||
self.send_command(0x12)
|
||||
self.ReadBusy()
|
||||
|
||||
def display_4Gray(self, image):
|
||||
self.send_command(0x10)
|
||||
for i in range(0, 5808): #5808*4 46464
|
||||
temp3=0
|
||||
for j in range(0, 2):
|
||||
temp1 = image[i*2+j]
|
||||
for k in range(0, 2):
|
||||
temp2 = temp1&0xC0
|
||||
if(temp2 == 0xC0):
|
||||
temp3 |= 0x01#white
|
||||
elif(temp2 == 0x00):
|
||||
temp3 |= 0x00 #black
|
||||
elif(temp2 == 0x80):
|
||||
temp3 |= 0x01 #gray1
|
||||
else: #0x40
|
||||
temp3 |= 0x00 #gray2
|
||||
temp3 <<= 1
|
||||
|
||||
temp1 <<= 2
|
||||
temp2 = temp1&0xC0
|
||||
if(temp2 == 0xC0): #white
|
||||
temp3 |= 0x01
|
||||
elif(temp2 == 0x00): #black
|
||||
temp3 |= 0x00
|
||||
elif(temp2 == 0x80):
|
||||
temp3 |= 0x01 #gray1
|
||||
else : #0x40
|
||||
temp3 |= 0x00 #gray2
|
||||
if(j!=1 or k!=1):
|
||||
temp3 <<= 1
|
||||
temp1 <<= 2
|
||||
self.send_data(temp3)
|
||||
|
||||
self.send_command(0x13)
|
||||
for i in range(0, 5808): #5808*4 46464
|
||||
temp3=0
|
||||
for j in range(0, 2):
|
||||
temp1 = image[i*2+j]
|
||||
for k in range(0, 2):
|
||||
temp2 = temp1&0xC0
|
||||
if(temp2 == 0xC0):
|
||||
temp3 |= 0x01#white
|
||||
elif(temp2 == 0x00):
|
||||
temp3 |= 0x00 #black
|
||||
elif(temp2 == 0x80):
|
||||
temp3 |= 0x00 #gray1
|
||||
else: #0x40
|
||||
temp3 |= 0x01 #gray2
|
||||
temp3 <<= 1
|
||||
|
||||
temp1 <<= 2
|
||||
temp2 = temp1&0xC0
|
||||
if(temp2 == 0xC0): #white
|
||||
temp3 |= 0x01
|
||||
elif(temp2 == 0x00): #black
|
||||
temp3 |= 0x00
|
||||
elif(temp2 == 0x80):
|
||||
temp3 |= 0x00 #gray1
|
||||
else: #0x40
|
||||
temp3 |= 0x01 #gray2
|
||||
if(j!=1 or k!=1):
|
||||
temp3 <<= 1
|
||||
temp1 <<= 2
|
||||
self.send_data(temp3)
|
||||
|
||||
self.gray_SetLut()
|
||||
self.send_command(0x12)
|
||||
epdconfig.delay_ms(200)
|
||||
self.ReadBusy()
|
||||
# pass
|
||||
|
||||
def Clear(self, color):
|
||||
self.send_command(0x10)
|
||||
for i in range(0, int(self.width * self.height / 8)):
|
||||
self.send_data(0xFF)
|
||||
self.send_command(0x13)
|
||||
for i in range(0, int(self.width * self.height / 8)):
|
||||
self.send_data(0xFF)
|
||||
self.send_command(0x12)
|
||||
self.ReadBusy()
|
||||
|
||||
def sleep(self):
|
||||
self.send_command(0X50)
|
||||
self.send_data(0xf7)
|
||||
self.send_command(0X02)
|
||||
self.send_command(0X07)
|
||||
self.send_data(0xA5)
|
||||
|
||||
epdconfig.module_exit()
|
||||
### END OF FILE ###
|
||||
|
154
pwnagotchi/ui/hw/libs/waveshare/v27inch/epdconfig.py
Normal file
154
pwnagotchi/ui/hw/libs/waveshare/v27inch/epdconfig.py
Normal file
@@ -0,0 +1,154 @@
|
||||
# /*****************************************************************************
|
||||
# * | File : epdconfig.py
|
||||
# * | Author : Waveshare team
|
||||
# * | Function : Hardware underlying interface
|
||||
# * | Info :
|
||||
# *----------------
|
||||
# * | This version: V1.0
|
||||
# * | Date : 2019-06-21
|
||||
# * | Info :
|
||||
# ******************************************************************************
|
||||
# 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 os
|
||||
import logging
|
||||
import sys
|
||||
import time
|
||||
|
||||
|
||||
class RaspberryPi:
|
||||
# Pin definition
|
||||
RST_PIN = 17
|
||||
DC_PIN = 25
|
||||
CS_PIN = 8
|
||||
BUSY_PIN = 24
|
||||
|
||||
def __init__(self):
|
||||
import spidev
|
||||
import RPi.GPIO
|
||||
|
||||
self.GPIO = RPi.GPIO
|
||||
|
||||
# SPI device, bus = 0, device = 0
|
||||
self.SPI = spidev.SpiDev(0, 0)
|
||||
|
||||
def digital_write(self, pin, value):
|
||||
self.GPIO.output(pin, value)
|
||||
|
||||
def digital_read(self, pin):
|
||||
return self.GPIO.input(pin)
|
||||
|
||||
def delay_ms(self, delaytime):
|
||||
time.sleep(delaytime / 1000.0)
|
||||
|
||||
def spi_writebyte(self, data):
|
||||
self.SPI.writebytes(data)
|
||||
|
||||
def module_init(self):
|
||||
self.GPIO.setmode(self.GPIO.BCM)
|
||||
self.GPIO.setwarnings(False)
|
||||
self.GPIO.setup(self.RST_PIN, self.GPIO.OUT)
|
||||
self.GPIO.setup(self.DC_PIN, self.GPIO.OUT)
|
||||
self.GPIO.setup(self.CS_PIN, self.GPIO.OUT)
|
||||
self.GPIO.setup(self.BUSY_PIN, self.GPIO.IN)
|
||||
self.SPI.max_speed_hz = 4000000
|
||||
self.SPI.mode = 0b00
|
||||
return 0
|
||||
|
||||
def module_exit(self):
|
||||
logging.debug("spi end")
|
||||
self.SPI.close()
|
||||
|
||||
logging.debug("close 5V, Module enters 0 power consumption ...")
|
||||
self.GPIO.output(self.RST_PIN, 0)
|
||||
self.GPIO.output(self.DC_PIN, 0)
|
||||
|
||||
self.GPIO.cleanup()
|
||||
|
||||
|
||||
class JetsonNano:
|
||||
# Pin definition
|
||||
RST_PIN = 17
|
||||
DC_PIN = 25
|
||||
CS_PIN = 8
|
||||
BUSY_PIN = 24
|
||||
|
||||
def __init__(self):
|
||||
import ctypes
|
||||
find_dirs = [
|
||||
os.path.dirname(os.path.realpath(__file__)),
|
||||
'/usr/local/lib',
|
||||
'/usr/lib',
|
||||
]
|
||||
self.SPI = None
|
||||
for find_dir in find_dirs:
|
||||
so_filename = os.path.join(find_dir, 'sysfs_software_spi.so')
|
||||
if os.path.exists(so_filename):
|
||||
self.SPI = ctypes.cdll.LoadLibrary(so_filename)
|
||||
break
|
||||
if self.SPI is None:
|
||||
raise RuntimeError('Cannot find sysfs_software_spi.so')
|
||||
|
||||
import Jetson.GPIO
|
||||
self.GPIO = Jetson.GPIO
|
||||
|
||||
def digital_write(self, pin, value):
|
||||
self.GPIO.output(pin, value)
|
||||
|
||||
def digital_read(self, pin):
|
||||
return self.GPIO.input(self.BUSY_PIN)
|
||||
|
||||
def delay_ms(self, delaytime):
|
||||
time.sleep(delaytime / 1000.0)
|
||||
|
||||
def spi_writebyte(self, data):
|
||||
self.SPI.SYSFS_software_spi_transfer(data[0])
|
||||
|
||||
def module_init(self):
|
||||
self.GPIO.setmode(self.GPIO.BCM)
|
||||
self.GPIO.setwarnings(False)
|
||||
self.GPIO.setup(self.RST_PIN, self.GPIO.OUT)
|
||||
self.GPIO.setup(self.DC_PIN, self.GPIO.OUT)
|
||||
self.GPIO.setup(self.CS_PIN, self.GPIO.OUT)
|
||||
self.GPIO.setup(self.BUSY_PIN, self.GPIO.IN)
|
||||
self.SPI.SYSFS_software_spi_begin()
|
||||
return 0
|
||||
|
||||
def module_exit(self):
|
||||
logging.debug("spi end")
|
||||
self.SPI.SYSFS_software_spi_end()
|
||||
|
||||
logging.debug("close 5V, Module enters 0 power consumption ...")
|
||||
self.GPIO.output(self.RST_PIN, 0)
|
||||
self.GPIO.output(self.DC_PIN, 0)
|
||||
|
||||
self.GPIO.cleanup()
|
||||
|
||||
|
||||
if os.path.exists('/sys/bus/platform/drivers/gpiomem-bcm2835'):
|
||||
implementation = RaspberryPi()
|
||||
else:
|
||||
implementation = JetsonNano()
|
||||
|
||||
for func in [x for x in dir(implementation) if not x.startswith('_')]:
|
||||
setattr(sys.modules[__name__], func, getattr(implementation, func))
|
||||
|
||||
|
||||
### END OF FILE ###
|
46
pwnagotchi/ui/hw/waveshare27inch.py
Normal file
46
pwnagotchi/ui/hw/waveshare27inch.py
Normal file
@@ -0,0 +1,46 @@
|
||||
import logging
|
||||
|
||||
import pwnagotchi.ui.fonts as fonts
|
||||
from pwnagotchi.ui.hw.base import DisplayImpl
|
||||
|
||||
|
||||
class Waveshare27inch(DisplayImpl):
|
||||
def __init__(self, config):
|
||||
super(Waveshare27inch, self).__init__(config, 'waveshare_2_7inch')
|
||||
self._display = None
|
||||
|
||||
def layout(self):
|
||||
fonts.setup(10, 9, 10, 35)
|
||||
self._layout['width'] = 264
|
||||
self._layout['height'] = 176
|
||||
self._layout['face'] = (66, 27)
|
||||
self._layout['name'] = (5, 73)
|
||||
self._layout['channel'] = (0, 0)
|
||||
self._layout['aps'] = (28, 0)
|
||||
self._layout['uptime'] = (199, 0)
|
||||
self._layout['line1'] = [0, 14, 264, 14]
|
||||
self._layout['line2'] = [0, 162, 264, 162]
|
||||
self._layout['friend_face'] = (0, 146)
|
||||
self._layout['friend_name'] = (40, 146)
|
||||
self._layout['shakes'] = (0, 163)
|
||||
self._layout['mode'] = (239, 163)
|
||||
self._layout['status'] = {
|
||||
'pos': (38, 93),
|
||||
'font': fonts.Medium,
|
||||
'max': 40
|
||||
}
|
||||
return self._layout
|
||||
|
||||
def initialize(self):
|
||||
logging.info("initializing waveshare v1 2.7 inch display")
|
||||
from pwnagotchi.ui.hw.libs.waveshare.v27inch.epd2in7 import EPD
|
||||
self._display = EPD()
|
||||
self._display.init()
|
||||
self._display.Clear(0xFF)
|
||||
|
||||
def render(self, canvas):
|
||||
buf = self._display.getbuffer(canvas)
|
||||
self._display.display(buf)
|
||||
|
||||
def clear(self):
|
||||
self._display.Clear(0xff)
|
@@ -2,6 +2,7 @@ import _thread
|
||||
from threading import Lock
|
||||
import time
|
||||
import logging
|
||||
import random
|
||||
from PIL import ImageDraw
|
||||
|
||||
import pwnagotchi.utils as utils
|
||||
@@ -22,6 +23,10 @@ class View(object):
|
||||
def __init__(self, config, impl, state=None):
|
||||
global ROOT
|
||||
|
||||
# setup faces from the configuration in case the user customized them
|
||||
faces.load_from_config(config['ui']['faces'])
|
||||
|
||||
self._agent = None
|
||||
self._render_cbs = []
|
||||
self._config = config
|
||||
self._canvas = None
|
||||
@@ -85,6 +90,9 @@ class View(object):
|
||||
|
||||
ROOT = self
|
||||
|
||||
def set_agent(self, agent):
|
||||
self._agent = agent
|
||||
|
||||
def has_element(self, key):
|
||||
self._state.has_element(key)
|
||||
|
||||
@@ -120,6 +128,9 @@ class View(object):
|
||||
def set(self, key, value):
|
||||
self._state.set(key, value)
|
||||
|
||||
def get(self, key):
|
||||
return self._state.get(key)
|
||||
|
||||
def on_starting(self):
|
||||
self.set('status', self._voice.on_starting())
|
||||
self.set('face', faces.AWAKE)
|
||||
@@ -132,7 +143,7 @@ class View(object):
|
||||
|
||||
def on_manual_mode(self, last_session):
|
||||
self.set('mode', 'MANU')
|
||||
self.set('face', faces.SAD if last_session.handshakes == 0 else faces.HAPPY)
|
||||
self.set('face', faces.SAD if (last_session.epochs > 3 and last_session.handshakes == 0) else faces.HAPPY)
|
||||
self.set('status', self._voice.on_last_session_data(last_session))
|
||||
self.set('epoch', "%04d" % last_session.epochs)
|
||||
self.set('uptime', last_session.duration)
|
||||
@@ -141,6 +152,7 @@ class View(object):
|
||||
self.set('shakes', '%d (%s)' % (last_session.handshakes, \
|
||||
utils.total_unique_handshakes(self._config['bettercap']['handshakes'])))
|
||||
self.set_closest_peer(last_session.last_peer, last_session.peers)
|
||||
self.update()
|
||||
|
||||
def is_normal(self):
|
||||
return self._state.get('face') not in (
|
||||
@@ -195,9 +207,21 @@ class View(object):
|
||||
self.update()
|
||||
|
||||
def on_new_peer(self, peer):
|
||||
self.set('face', faces.FRIEND)
|
||||
face = ''
|
||||
# first time they met, neutral mood
|
||||
if peer.first_encounter():
|
||||
face = random.choice((faces.AWAKE, faces.COOL))
|
||||
# a good friend, positive expression
|
||||
elif peer.is_good_friend(self._config):
|
||||
face = random.choice((faces.MOTIVATED, faces.FRIEND, faces.HAPPY))
|
||||
# normal friend, neutral-positive
|
||||
else:
|
||||
face = random.choice((faces.EXCITED, faces.HAPPY))
|
||||
|
||||
self.set('face', face)
|
||||
self.set('status', self._voice.on_new_peer(peer))
|
||||
self.update()
|
||||
time.sleep(3)
|
||||
|
||||
def on_lost_peer(self, peer):
|
||||
self.set('face', faces.LONELY)
|
||||
@@ -228,10 +252,11 @@ class View(object):
|
||||
self.set('status', self._voice.on_awakening())
|
||||
else:
|
||||
self.set('status', self._voice.on_waiting(int(secs)))
|
||||
good_mood = self._agent.in_good_mood()
|
||||
if step % 2 == 0:
|
||||
self.set('face', faces.LOOK_R)
|
||||
self.set('face', faces.LOOK_R_HAPPY if good_mood else faces.LOOK_R)
|
||||
else:
|
||||
self.set('face', faces.LOOK_L)
|
||||
self.set('face', faces.LOOK_L_HAPPY if good_mood else faces.LOOK_L)
|
||||
|
||||
time.sleep(part)
|
||||
secs -= part
|
||||
@@ -284,6 +309,11 @@ class View(object):
|
||||
self.set('status', self._voice.on_miss(who))
|
||||
self.update()
|
||||
|
||||
def on_grateful(self):
|
||||
self.set('face', faces.GRATEFUL)
|
||||
self.set('status', self._voice.on_grateful())
|
||||
self.update()
|
||||
|
||||
def on_lonely(self):
|
||||
self.set('face', faces.LONELY)
|
||||
self.set('status', self._voice.on_lonely())
|
||||
@@ -294,6 +324,12 @@ class View(object):
|
||||
self.set('status', self._voice.on_handshakes(new_shakes))
|
||||
self.update()
|
||||
|
||||
def on_unread_messages(self, count, total):
|
||||
self.set('face', faces.EXCITED)
|
||||
self.set('status', self._voice.on_unread_messages(count, total))
|
||||
self.update()
|
||||
time.sleep(5.0)
|
||||
|
||||
def on_rebooting(self):
|
||||
self.set('face', faces.BROKEN)
|
||||
self.set('status', self._voice.on_rebooting())
|
||||
@@ -304,7 +340,10 @@ class View(object):
|
||||
self.set('status', self._voice.custom(text))
|
||||
self.update()
|
||||
|
||||
def update(self, force=False):
|
||||
def update(self, force=False, new_data={}):
|
||||
for key, val in new_data.items():
|
||||
self.set(key, val)
|
||||
|
||||
with self._lock:
|
||||
if self._frozen:
|
||||
return
|
||||
|
204
pwnagotchi/ui/web.py
Normal file
204
pwnagotchi/ui/web.py
Normal file
@@ -0,0 +1,204 @@
|
||||
import re
|
||||
import _thread
|
||||
from http.server import BaseHTTPRequestHandler, HTTPServer
|
||||
from threading import Lock
|
||||
import shutil
|
||||
import logging
|
||||
|
||||
import pwnagotchi
|
||||
from pwnagotchi import plugins
|
||||
|
||||
frame_path = '/root/pwnagotchi.png'
|
||||
frame_format = 'PNG'
|
||||
frame_ctype = 'image/png'
|
||||
frame_lock = Lock()
|
||||
|
||||
|
||||
def update_frame(img):
|
||||
global frame_lock, frame_path, frame_format
|
||||
with frame_lock:
|
||||
img.save(frame_path, format=frame_format)
|
||||
|
||||
|
||||
STYLE = """
|
||||
.block {
|
||||
-webkit-appearance: button;
|
||||
-moz-appearance: button;
|
||||
appearance: button;
|
||||
|
||||
display: block;
|
||||
cursor: pointer;
|
||||
text-align: center;
|
||||
}
|
||||
"""
|
||||
|
||||
SCRIPT = """
|
||||
window.onload = function() {
|
||||
var image = document.getElementById("ui");
|
||||
function updateImage() {
|
||||
image.src = image.src.split("?")[0] + "?" + new Date().getTime();
|
||||
}
|
||||
setInterval(updateImage, %d);
|
||||
}
|
||||
"""
|
||||
|
||||
INDEX = """<html>
|
||||
<head>
|
||||
<title>%s</title>
|
||||
<style>""" + STYLE + """</style>
|
||||
</head>
|
||||
<body>
|
||||
<div style="position: absolute; top:0; left:0; width:100%%;">
|
||||
<img src="/ui" id="ui" style="width:100%%"/>
|
||||
<br/>
|
||||
<hr/>
|
||||
<form method="POST" action="/shutdown" onsubmit="return confirm('This will halt the unit, continue?');">
|
||||
<input type="submit" class="block" value="Shutdown"/>
|
||||
</form>
|
||||
</div>
|
||||
|
||||
<script type="text/javascript">""" + SCRIPT + """</script>
|
||||
</body>
|
||||
</html>"""
|
||||
|
||||
SHUTDOWN = """<html>
|
||||
<head>
|
||||
<title>%s</title>
|
||||
<style>""" + STYLE + """</style>
|
||||
</head>
|
||||
<body>
|
||||
<div style="position: absolute; top:0; left:0; width:100%%;">
|
||||
Shutting down ...
|
||||
</div>
|
||||
</body>
|
||||
</html>"""
|
||||
|
||||
|
||||
class Handler(BaseHTTPRequestHandler):
|
||||
AllowedOrigin = '*'
|
||||
|
||||
# suppress internal logging
|
||||
def log_message(self, format, *args):
|
||||
return
|
||||
|
||||
def _send_cors_headers(self):
|
||||
# misc security
|
||||
self.send_header("X-Frame-Options", "DENY")
|
||||
self.send_header("X-Content-Type-Options", "nosniff")
|
||||
self.send_header("X-XSS-Protection", "1; mode=block")
|
||||
self.send_header("Referrer-Policy", "same-origin")
|
||||
# cors
|
||||
self.send_header("Access-Control-Allow-Origin", Handler.AllowedOrigin)
|
||||
self.send_header('Access-Control-Allow-Credentials', 'true')
|
||||
self.send_header("Access-Control-Allow-Methods", "GET, POST, PUT, DELETE, OPTIONS")
|
||||
self.send_header("Access-Control-Allow-Headers",
|
||||
"Accept, Content-Type, Content-Length, Accept-Encoding, X-CSRF-Token, Authorization")
|
||||
self.send_header("Vary", "Origin")
|
||||
|
||||
# just render some html in a 200 response
|
||||
def _html(self, html):
|
||||
self.send_response(200)
|
||||
self._send_cors_headers()
|
||||
self.send_header('Content-type', 'text/html')
|
||||
self.end_headers()
|
||||
try:
|
||||
self.wfile.write(bytes(html, "utf8"))
|
||||
except:
|
||||
pass
|
||||
|
||||
# serve the main html page
|
||||
def _index(self):
|
||||
self._html(INDEX % (pwnagotchi.name(), 1000))
|
||||
|
||||
# serve a message and shuts down the unit
|
||||
def _shutdown(self):
|
||||
self._html(SHUTDOWN % pwnagotchi.name())
|
||||
pwnagotchi.shutdown()
|
||||
|
||||
# serve the PNG file with the display image
|
||||
def _image(self):
|
||||
global frame_lock, frame_path, frame_ctype
|
||||
|
||||
with frame_lock:
|
||||
self.send_response(200)
|
||||
self._send_cors_headers()
|
||||
self.send_header('Content-type', frame_ctype)
|
||||
self.end_headers()
|
||||
try:
|
||||
with open(frame_path, 'rb') as fp:
|
||||
shutil.copyfileobj(fp, self.wfile)
|
||||
except:
|
||||
pass
|
||||
|
||||
# check the Origin header vs CORS
|
||||
def _is_allowed(self):
|
||||
origin = self.headers.get('origin')
|
||||
if not origin and Handler.AllowedOrigin != '*':
|
||||
logging.warning("request with no Origin header from %s" % self.address_string())
|
||||
return False
|
||||
|
||||
if Handler.AllowedOrigin != '*':
|
||||
if origin != Handler.AllowedOrigin:
|
||||
logging.warning("request with blocked Origin from %s: %s" % (self.address_string(), origin))
|
||||
return False
|
||||
|
||||
return True
|
||||
|
||||
def do_OPTIONS(self):
|
||||
self.send_response(200)
|
||||
self._send_cors_headers()
|
||||
self.end_headers()
|
||||
|
||||
def do_POST(self):
|
||||
if not self._is_allowed():
|
||||
return
|
||||
if self.path.startswith('/shutdown'):
|
||||
self._shutdown()
|
||||
else:
|
||||
self.send_response(404)
|
||||
|
||||
def do_GET(self):
|
||||
if not self._is_allowed():
|
||||
return
|
||||
|
||||
if self.path == '/':
|
||||
self._index()
|
||||
|
||||
elif self.path.startswith('/ui'):
|
||||
self._image()
|
||||
|
||||
elif self.path.startswith('/plugins'):
|
||||
matches = re.match(r'\/plugins\/([^\/]+)(\/.*)?', self.path)
|
||||
if matches:
|
||||
groups = matches.groups()
|
||||
plugin_name = groups[0]
|
||||
right_path = groups[1] if len(groups) == 2 else None
|
||||
plugins.one(plugin_name, 'webhook', right_path)
|
||||
|
||||
else:
|
||||
self.send_response(404)
|
||||
|
||||
|
||||
class Server(object):
|
||||
def __init__(self, config):
|
||||
self._enabled = config['video']['enabled']
|
||||
self._port = config['video']['port']
|
||||
self._address = config['video']['address']
|
||||
self._httpd = None
|
||||
|
||||
if 'origin' in config['video'] and config['video']['origin'] != '*':
|
||||
Handler.AllowedOrigin = config['video']['origin']
|
||||
else:
|
||||
logging.warning("THE WEB UI IS RUNNING WITH ALLOWED ORIGIN SET TO *, READ WHY YOU SHOULD CHANGE IT HERE " +
|
||||
"https://developer.mozilla.org/it/docs/Web/HTTP/CORS")
|
||||
|
||||
if self._enabled:
|
||||
_thread.start_new_thread(self._http_serve, ())
|
||||
|
||||
def _http_serve(self):
|
||||
if self._address is not None:
|
||||
self._httpd = HTTPServer((self._address, self._port), Handler)
|
||||
logging.info("web ui available at http://%s:%d/" % (self._address, self._port))
|
||||
self._httpd.serve_forever()
|
||||
else:
|
||||
logging.info("could not get ip of usb0, video server not starting")
|
@@ -38,6 +38,12 @@ def load_config(args):
|
||||
# https://stackoverflow.com/questions/42392600/oserror-errno-18-invalid-cross-device-link
|
||||
shutil.move("/boot/config.yml", args.user_config)
|
||||
|
||||
# check for an entire pwnagotchi folder on /boot/
|
||||
if os.path.isdir('/boot/pwnagotchi'):
|
||||
print("installing /boot/pwnagotchi to /etc/pwnagotchi ...")
|
||||
shutil.rmtree('/etc/pwnagotchi', ignore_errors=True)
|
||||
shutil.move('/boot/pwnagotchi', '/etc/')
|
||||
|
||||
# if not config is found, copy the defaults
|
||||
if not os.path.exists(args.config):
|
||||
print("copying %s to %s ..." % (ref_defaults_file, args.config))
|
||||
@@ -82,12 +88,16 @@ def load_config(args):
|
||||
elif config['ui']['display']['type'] in ('ws_2', 'ws2', 'waveshare_2', 'waveshare2'):
|
||||
config['ui']['display']['type'] = 'waveshare_2'
|
||||
|
||||
elif config['ui']['display']['type'] in ('ws_27inch', 'ws27inch', 'waveshare_27inch', 'waveshare27inch'):
|
||||
config['ui']['display']['type'] = 'waveshare27inch'
|
||||
|
||||
elif config['ui']['display']['type'] in ('lcdhat'):
|
||||
config['ui']['display']['type'] = 'lcdhat'
|
||||
|
||||
else:
|
||||
print("unsupported display type %s" % config['ui']['display']['type'])
|
||||
exit(1)
|
||||
|
||||
print("Effective Configuration:")
|
||||
print(yaml.dump(config, default_flow_style=False))
|
||||
return config
|
||||
|
||||
|
||||
@@ -106,6 +116,12 @@ def setup_logging(args, config):
|
||||
console_handler.setFormatter(formatter)
|
||||
root.addHandler(console_handler)
|
||||
|
||||
# https://stackoverflow.com/questions/24344045/how-can-i-completely-remove-any-logging-from-requests-module-in-python?noredirect=1&lq=1
|
||||
logging.getLogger("urllib3").propagate = False
|
||||
requests_log = logging.getLogger("requests")
|
||||
requests_log.addHandler(logging.NullHandler())
|
||||
requests_log.propagate = False
|
||||
|
||||
|
||||
def secs_to_hhmmss(secs):
|
||||
mins, secs = divmod(secs, 60)
|
||||
@@ -263,6 +279,9 @@ class StatusFile(object):
|
||||
def newer_then_minutes(self, minutes):
|
||||
return self._updated is not None and ((datetime.now() - self._updated).seconds / 60) < minutes
|
||||
|
||||
def newer_then_hours(self, hours):
|
||||
return self._updated is not None and ((datetime.now() - self._updated).seconds / (60 * 60)) < hours
|
||||
|
||||
def newer_then_days(self, days):
|
||||
return self._updated is not None and (datetime.now() - self._updated).days < days
|
||||
|
||||
|
@@ -70,9 +70,14 @@ class Voice:
|
||||
self._('My crime is that of curiosity ...')])
|
||||
|
||||
def on_new_peer(self, peer):
|
||||
return random.choice([
|
||||
self._('Hello {name}! Nice to meet you.').format(name=peer.name()),
|
||||
self._('Unit {name} is nearby!').format(name=peer.name())])
|
||||
if peer.first_encounter():
|
||||
return random.choice([
|
||||
self._('Hello {name}! Nice to meet you.').format(name=peer.name())])
|
||||
else:
|
||||
return random.choice([
|
||||
self._('Yo {name}! Sup?').format(name=peer.name()),
|
||||
self._('Hey {name} how are you doing?').format(name=peer.name()),
|
||||
self._('Unit {name} is nearby!').format(name=peer.name())])
|
||||
|
||||
def on_lost_peer(self, peer):
|
||||
return random.choice([
|
||||
@@ -85,6 +90,11 @@ class Voice:
|
||||
self._('{name} missed!').format(name=who),
|
||||
self._('Missed!')])
|
||||
|
||||
def on_grateful(self):
|
||||
return random.choice([
|
||||
self._('Good friends are a blessing!'),
|
||||
self._('I love my friends!')])
|
||||
|
||||
def on_lonely(self):
|
||||
return random.choice([
|
||||
self._('Nobody wants to play with me ...'),
|
||||
@@ -129,6 +139,10 @@ class Voice:
|
||||
s = 's' if new_shakes > 1 else ''
|
||||
return self._('Cool, we got {num} new handshake{plural}!').format(num=new_shakes, plural=s)
|
||||
|
||||
def on_unread_messages(self, count, total):
|
||||
s = 's' if count > 1 else ''
|
||||
return self._('You have {count} new message{plural}!').format(count=count, plural=s)
|
||||
|
||||
def on_rebooting(self):
|
||||
return self._("Ops, something went wrong ... Rebooting ...")
|
||||
|
||||
|
@@ -6,7 +6,7 @@ gym==0.14.0
|
||||
stable-baselines==2.7.0
|
||||
tensorflow==1.13.1
|
||||
tensorflow-estimator==1.14.0
|
||||
tweepy==3.6.0
|
||||
tweepy==3.7.0
|
||||
file-read-backwards==2.0.0
|
||||
numpy==1.17.2
|
||||
inky==0.0.5
|
||||
|
@@ -4,18 +4,14 @@
|
||||
UNIT_HOSTNAME=${1:-10.0.0.2}
|
||||
# output backup zip file
|
||||
OUTPUT=${2:-pwnagotchi-backup.zip}
|
||||
# temporary folder
|
||||
TEMP_BACKUP_FOLDER=/tmp/pwnagotchi_backup
|
||||
# what to backup
|
||||
FILES_TO_BACKUP=(
|
||||
/root/brain.nn
|
||||
/root/brain.json
|
||||
/root/.api-report.json
|
||||
/root/handshakes
|
||||
/root/peers
|
||||
/etc/pwnagotchi/
|
||||
/etc/hostname
|
||||
/etc/hosts
|
||||
/etc/motd
|
||||
/var/log/pwnagotchi.log
|
||||
)
|
||||
|
||||
@@ -26,17 +22,24 @@ ping -c 1 $UNIT_HOSTNAME >/dev/null || {
|
||||
|
||||
echo "@ backing up $UNIT_HOSTNAME to $OUTPUT ..."
|
||||
|
||||
rm -rf "$TEMP_BACKUP_FOLDER"
|
||||
ssh pi@$UNIT_HOSTNAME "sudo rm -rf /tmp/backup && sudo rm -rf /tmp/backup.zip" > /dev/null
|
||||
|
||||
for file in "${FILES_TO_BACKUP[@]}"; do
|
||||
dir=$(dirname $file)
|
||||
echo " $file -> $TEMP_BACKUP_FOLDER$dir/"
|
||||
mkdir -p "$TEMP_BACKUP_FOLDER/$dir"
|
||||
scp -Cr root@$UNIT_HOSTNAME:$file "$TEMP_BACKUP_FOLDER$dir/"
|
||||
|
||||
echo "@ copying $file to /tmp/backup$dir"
|
||||
|
||||
ssh pi@$UNIT_HOSTNAME "mkdir -p /tmp/backup$dir" > /dev/null
|
||||
ssh pi@$UNIT_HOSTNAME "sudo cp -r $file /tmp/backup$dir" > /dev/null
|
||||
done
|
||||
|
||||
ZIPFILE="$PWD/$OUTPUT"
|
||||
pushd $PWD
|
||||
cd "$TEMP_BACKUP_FOLDER"
|
||||
zip -r -9 -q "$ZIPFILE" .
|
||||
popd
|
||||
echo "@ pulling from $UNIT_HOSTNAME ..."
|
||||
|
||||
rm -rf /tmp/backup
|
||||
scp -rC pi@$UNIT_HOSTNAME:/tmp/backup /tmp/
|
||||
|
||||
echo "@ compressing ..."
|
||||
|
||||
zip -r -9 -q $OUTPUT /tmp/backup
|
||||
rm -rf /tmp/backup
|
||||
|
||||
|
Reference in New Issue
Block a user