mirror of
https://github.com/jayofelony/pwnagotchi.git
synced 2025-07-01 18:37:27 -04:00
hello world
This commit is contained in:
139
sdcard/rootfs/root/pwnagotchi/config.yml
Normal file
139
sdcard/rootfs/root/pwnagotchi/config.yml
Normal file
@ -0,0 +1,139 @@
|
||||
# main algorithm configuration
|
||||
main:
|
||||
# monitor interface to use
|
||||
iface: mon0
|
||||
# command to run to bring the mon interface up in case it's not up already
|
||||
mon_start_cmd: /usr/bin/monstart
|
||||
mon_stop_cmd: /usr/bin/monstop
|
||||
mon_max_blind_epochs: 50
|
||||
# log file
|
||||
log: /var/log/pwnagotchi.log
|
||||
# if true, will not restart the wifi module
|
||||
no_restart: false
|
||||
# access points to ignore
|
||||
whitelist:
|
||||
- Casa-2.4
|
||||
- LOTS_OF_MALWARE
|
||||
# if not null, filter access points by this regular expression
|
||||
filter: null
|
||||
# cryptographic key for identity
|
||||
pubkey: /etc/ssh/ssh_host_rsa_key.pub
|
||||
|
||||
ai:
|
||||
# if false, only the default 'personality' will be used
|
||||
enabled: true
|
||||
path: /root/brain.nn
|
||||
# 1.0 - laziness = probability of start training
|
||||
laziness: 0.1
|
||||
# how many epochs to train on
|
||||
epochs_per_episode: 50
|
||||
params:
|
||||
# discount factor
|
||||
gamma: 0.99
|
||||
# the number of steps to run for each environment per update
|
||||
n_steps: 1
|
||||
# value function coefficient for the loss calculation
|
||||
vf_coef: 0.25
|
||||
# entropy coefficient for the loss calculation
|
||||
ent_coef: 0.01
|
||||
# maximum value for the gradient clipping
|
||||
max_grad_norm: 0.5
|
||||
# the learning rate
|
||||
learning_rate: 0.0010
|
||||
# rmsprop decay parameter
|
||||
alpha: 0.99
|
||||
# rmsprop epsilon
|
||||
epsilon: 0.00001
|
||||
# the verbosity level: 0 none, 1 training information, 2 tensorflow debug
|
||||
verbose: 1
|
||||
# type of scheduler for the learning rate update ('linear', 'constant', 'double_linear_con', 'middle_drop' or 'double_middle_drop')
|
||||
lr_schedule: 'constant'
|
||||
# the log location for tensorboard (if None, no logging)
|
||||
tensorboard_log: null
|
||||
|
||||
personality:
|
||||
# advertise our presence
|
||||
advertise: true
|
||||
# perform a deauthentication attack to client stations in order to get full or half handshakes
|
||||
deauth: true
|
||||
# send association frames to APs in order to get the PMKID
|
||||
associate: true
|
||||
# list of channels to recon on, or empty for all channels
|
||||
channels: []
|
||||
# minimum WiFi signal strength in dBm
|
||||
min_rssi: -200
|
||||
# number of seconds for wifi.ap.ttl
|
||||
ap_ttl: 120
|
||||
# number of seconds for wifi.sta.ttl
|
||||
sta_ttl: 300
|
||||
# time in seconds to wait during channel recon
|
||||
recon_time: 30
|
||||
# number of inactive epochs after which recon_time gets multiplied by recon_inactive_multiplier
|
||||
max_inactive_scale: 2
|
||||
# if more than max_inactive_scale epochs are inactive, recon_time *= recon_inactive_multiplier
|
||||
recon_inactive_multiplier: 2
|
||||
# time in seconds to wait during channel hopping if activity has been performed
|
||||
hop_recon_time: 10
|
||||
# time in seconds to wait during channel hopping if no activity has been performed
|
||||
min_recon_time: 5
|
||||
# maximum amount of deauths/associations per BSSID per session
|
||||
max_interactions: 3
|
||||
# maximum amount of misses before considering the data stale and triggering a new recon
|
||||
max_misses_for_recon: 5
|
||||
# number of active epochs that triggers the excited state
|
||||
excited_num_epochs: 10
|
||||
# number of inactive epochs that triggers the bored state
|
||||
bored_num_epochs: 15
|
||||
# number of inactive epochs that triggers the sad state
|
||||
sad_num_epochs: 25
|
||||
|
||||
# ui configuration
|
||||
ui:
|
||||
# ePaper display can update every 3 secs anyway
|
||||
fps: 0.3
|
||||
display:
|
||||
enabled: true
|
||||
rotation: 180
|
||||
video:
|
||||
enabled: true
|
||||
address: '10.0.0.2'
|
||||
port: 8080
|
||||
|
||||
# twitter bot data
|
||||
twitter:
|
||||
enabled: false
|
||||
consumer_key: aaa
|
||||
consumer_secret: aaa
|
||||
access_token_key: aaa
|
||||
access_token_secret: aaa
|
||||
|
||||
# bettercap rest api configuration
|
||||
bettercap:
|
||||
# api scheme://hostname:port username and password
|
||||
scheme: http
|
||||
hostname: localhost
|
||||
port: 8081
|
||||
username: user
|
||||
password: pass
|
||||
# folder where bettercap stores the WPA handshakes, given that
|
||||
# wifi.handshakes.aggregate will be set to false and individual
|
||||
# pcap files will be created in order to minimize the chances
|
||||
# of a single pcap file to get corrupted
|
||||
handshakes: /root/handshakes
|
||||
# events to mute in bettercap's events stream
|
||||
silence:
|
||||
- ble.device.new
|
||||
- ble.device.lost
|
||||
- ble.device.disconnected
|
||||
- ble.device.connected
|
||||
- ble.device.service.discovered
|
||||
- ble.device.characteristic.discovered
|
||||
- wifi.client.new
|
||||
- wifi.client.lost
|
||||
- wifi.client.probe
|
||||
- wifi.ap.new
|
||||
- wifi.ap.lost
|
||||
- mod.started
|
||||
|
||||
|
||||
|
BIN
sdcard/rootfs/root/pwnagotchi/data/images/face_happy.bmp
Normal file
BIN
sdcard/rootfs/root/pwnagotchi/data/images/face_happy.bmp
Normal file
Binary file not shown.
After Width: | Height: | Size: 11 KiB |
48
sdcard/rootfs/root/pwnagotchi/data/screenrc.auto
Normal file
48
sdcard/rootfs/root/pwnagotchi/data/screenrc.auto
Normal file
@ -0,0 +1,48 @@
|
||||
defutf8 on
|
||||
|
||||
shell -${SHELL}
|
||||
defscrollback 1024
|
||||
startup_message off
|
||||
altscreen on
|
||||
autodetach on
|
||||
|
||||
activity "activity in %n (%t)"
|
||||
bell_msg "bell in %n (%t)"
|
||||
|
||||
vbell on
|
||||
vbell_msg "WTF DUDE ??!!"
|
||||
vbellwait 1
|
||||
|
||||
# set terminal emulator to xterm mode
|
||||
term xterm
|
||||
# Make the output buffer large for (fast) xterms.
|
||||
termcapinfo xterm* ti@:te@
|
||||
# tell screen how to set colors
|
||||
termcapinfo xterm 'Co#256:AB=\E[48;5;%dm:AF=\E[38;5;%dm'
|
||||
# allow bold colors
|
||||
attrcolor b ".I"
|
||||
# erase background with current bg color
|
||||
defbce "on"
|
||||
|
||||
# ctrl + { left, right }
|
||||
bindkey ^[[1;5D prev
|
||||
bindkey ^[[1;5C next
|
||||
bindkey ^[[5D prev
|
||||
bindkey ^[[5C next
|
||||
bindkey ^[b prev
|
||||
bindkey ^[f next
|
||||
bindkey ^[[D prev
|
||||
bindkey ^[[C next
|
||||
|
||||
screen -t shell
|
||||
screen -t auto-mode /bin/bash -c "/root/pwnagotchi/scripts/main.py"
|
||||
screen -t bettercap /usr/bin/bettercap -caplet http-ui -eval "!/usr/bin/monstart; set wifi.interface mon0"
|
||||
|
||||
select log
|
||||
|
||||
backtick 1 0 0 cpuusage
|
||||
backtick 2 5 5 memusage
|
||||
|
||||
hardstatus alwayslastline
|
||||
hardstatus string '%{= kG}[ %{G}%H %{g}][%= %{= kw}%?%-Lw%?%{r}(%{W}%n*%f%t%?(%u)%?%{r})%{w}%?%+Lw%?%?%= %{g}][cpu %1`][mem %2`][%{B} %m-%d %{W}%c %{g}]'
|
||||
|
48
sdcard/rootfs/root/pwnagotchi/data/screenrc.manual
Normal file
48
sdcard/rootfs/root/pwnagotchi/data/screenrc.manual
Normal file
@ -0,0 +1,48 @@
|
||||
defutf8 on
|
||||
|
||||
shell -${SHELL}
|
||||
defscrollback 1024
|
||||
startup_message off
|
||||
altscreen on
|
||||
autodetach on
|
||||
|
||||
activity "activity in %n (%t)"
|
||||
bell_msg "bell in %n (%t)"
|
||||
|
||||
vbell on
|
||||
vbell_msg "WTF DUDE ??!!"
|
||||
vbellwait 1
|
||||
|
||||
# set terminal emulator to xterm mode
|
||||
term xterm
|
||||
# Make the output buffer large for (fast) xterms.
|
||||
termcapinfo xterm* ti@:te@
|
||||
# tell screen how to set colors
|
||||
termcapinfo xterm 'Co#256:AB=\E[48;5;%dm:AF=\E[38;5;%dm'
|
||||
# allow bold colors
|
||||
attrcolor b ".I"
|
||||
# erase background with current bg color
|
||||
defbce "on"
|
||||
|
||||
# ctrl + { left, right }
|
||||
bindkey ^[[1;5D prev
|
||||
bindkey ^[[1;5C next
|
||||
bindkey ^[[5D prev
|
||||
bindkey ^[[5C next
|
||||
bindkey ^[b prev
|
||||
bindkey ^[f next
|
||||
bindkey ^[[D prev
|
||||
bindkey ^[[C next
|
||||
|
||||
screen -t shell
|
||||
screen -t manual-mode /bin/bash -c "/root/pwnagotchi/scripts/main.py --manual"
|
||||
screen -t bettercap /usr/bin/bettercap -caplet http-ui -eval "!/usr/bin/monstart; set wifi.interface mon0"
|
||||
|
||||
select log
|
||||
|
||||
backtick 1 0 0 cpuusage
|
||||
backtick 2 5 5 memusage
|
||||
|
||||
hardstatus alwayslastline
|
||||
hardstatus string '%{= kG}[ %{G}%H %{g}][%= %{= kw}%?%-Lw%?%{r}(%{W}%n*%f%t%?(%u)%?%{r})%{w}%?%+Lw%?%?%= %{g}][cpu %1`][mem %2`][%{B} %m-%d %{W}%c %{g}]'
|
||||
|
2
sdcard/rootfs/root/pwnagotchi/scripts/.idea/.gitignore
generated
vendored
Normal file
2
sdcard/rootfs/root/pwnagotchi/scripts/.idea/.gitignore
generated
vendored
Normal file
@ -0,0 +1,2 @@
|
||||
# Default ignored files
|
||||
/workspace.xml
|
9
sdcard/rootfs/root/pwnagotchi/scripts/.idea/dictionaries/evilsocket.xml
generated
Normal file
9
sdcard/rootfs/root/pwnagotchi/scripts/.idea/dictionaries/evilsocket.xml
generated
Normal file
@ -0,0 +1,9 @@
|
||||
<component name="ProjectDictionaryState">
|
||||
<dictionary name="evilsocket">
|
||||
<words>
|
||||
<w>featurize</w>
|
||||
<w>featurizer</w>
|
||||
<w>logits</w>
|
||||
</words>
|
||||
</dictionary>
|
||||
</component>
|
6
sdcard/rootfs/root/pwnagotchi/scripts/.idea/inspectionProfiles/profiles_settings.xml
generated
Normal file
6
sdcard/rootfs/root/pwnagotchi/scripts/.idea/inspectionProfiles/profiles_settings.xml
generated
Normal file
@ -0,0 +1,6 @@
|
||||
<component name="InspectionProjectProfileManager">
|
||||
<settings>
|
||||
<option name="USE_PROJECT_PROFILE" value="false" />
|
||||
<version value="1.0" />
|
||||
</settings>
|
||||
</component>
|
4
sdcard/rootfs/root/pwnagotchi/scripts/.idea/misc.xml
generated
Normal file
4
sdcard/rootfs/root/pwnagotchi/scripts/.idea/misc.xml
generated
Normal file
@ -0,0 +1,4 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project version="4">
|
||||
<component name="ProjectRootManager" version="2" project-jdk-name="Python 3.6" project-jdk-type="Python SDK" />
|
||||
</project>
|
8
sdcard/rootfs/root/pwnagotchi/scripts/.idea/modules.xml
generated
Normal file
8
sdcard/rootfs/root/pwnagotchi/scripts/.idea/modules.xml
generated
Normal file
@ -0,0 +1,8 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project version="4">
|
||||
<component name="ProjectModuleManager">
|
||||
<modules>
|
||||
<module fileurl="file://$PROJECT_DIR$/.idea/scripts.iml" filepath="$PROJECT_DIR$/.idea/scripts.iml" />
|
||||
</modules>
|
||||
</component>
|
||||
</project>
|
11
sdcard/rootfs/root/pwnagotchi/scripts/.idea/scripts.iml
generated
Normal file
11
sdcard/rootfs/root/pwnagotchi/scripts/.idea/scripts.iml
generated
Normal file
@ -0,0 +1,11 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<module type="PYTHON_MODULE" version="4">
|
||||
<component name="NewModuleRootManager">
|
||||
<content url="file://$MODULE_DIR$" />
|
||||
<orderEntry type="inheritedJdk" />
|
||||
<orderEntry type="sourceFolder" forTests="false" />
|
||||
</component>
|
||||
<component name="TestRunnerService">
|
||||
<option name="PROJECT_TEST_RUNNER" value="Unittests" />
|
||||
</component>
|
||||
</module>
|
6
sdcard/rootfs/root/pwnagotchi/scripts/.idea/vcs.xml
generated
Normal file
6
sdcard/rootfs/root/pwnagotchi/scripts/.idea/vcs.xml
generated
Normal file
@ -0,0 +1,6 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project version="4">
|
||||
<component name="VcsDirectoryMappings">
|
||||
<mapping directory="$PROJECT_DIR$/../../../../.." vcs="Git" />
|
||||
</component>
|
||||
</project>
|
40
sdcard/rootfs/root/pwnagotchi/scripts/bettercap/client.py
Normal file
40
sdcard/rootfs/root/pwnagotchi/scripts/bettercap/client.py
Normal file
@ -0,0 +1,40 @@
|
||||
import requests
|
||||
from requests.auth import HTTPBasicAuth
|
||||
|
||||
import core
|
||||
|
||||
|
||||
class Client(object):
|
||||
def __init__(self, hostname='localhost', scheme='http', port=8081, username='user', password='pass'):
|
||||
self.hostname = hostname
|
||||
self.scheme = scheme
|
||||
self.port = port
|
||||
self.username = username
|
||||
self.password = password
|
||||
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:
|
||||
core.log("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:
|
||||
core.log(err)
|
||||
raise Exception(err)
|
||||
return r.text
|
||||
|
||||
def session(self):
|
||||
r = requests.get("%s/session" % self.url, auth=self.auth)
|
||||
return self._decode(r)
|
||||
|
||||
def events(self):
|
||||
r = requests.get("%s/events" % self.url, auth=self.auth)
|
||||
return self._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)
|
12
sdcard/rootfs/root/pwnagotchi/scripts/blink.sh
Executable file
12
sdcard/rootfs/root/pwnagotchi/scripts/blink.sh
Executable file
@ -0,0 +1,12 @@
|
||||
#!/bin/bash
|
||||
|
||||
for i in `seq 1 $1`;
|
||||
do
|
||||
echo 0 >/sys/class/leds/led0/brightness
|
||||
sleep 0.3
|
||||
echo 1 >/sys/class/leds/led0/brightness
|
||||
sleep 0.3
|
||||
done
|
||||
|
||||
echo 0 >/sys/class/leds/led0/brightness
|
||||
sleep 0.3
|
54
sdcard/rootfs/root/pwnagotchi/scripts/core/__init__.py
Normal file
54
sdcard/rootfs/root/pwnagotchi/scripts/core/__init__.py
Normal file
@ -0,0 +1,54 @@
|
||||
import sys
|
||||
import glob
|
||||
import os
|
||||
import time
|
||||
import subprocess
|
||||
from threading import Lock
|
||||
from datetime import datetime
|
||||
|
||||
logfile = None
|
||||
loglock = Lock()
|
||||
|
||||
|
||||
def log(msg):
|
||||
tstamp = str(datetime.now())
|
||||
line = "[%s] %s" % (tstamp, msg.rstrip())
|
||||
print(line)
|
||||
sys.stdout.flush()
|
||||
if logfile is not None:
|
||||
with loglock:
|
||||
with open(logfile, 'a+t') as fp:
|
||||
fp.write("%s\n" % line)
|
||||
|
||||
|
||||
def secs_to_hhmmss(secs):
|
||||
mins, secs = divmod(secs, 60)
|
||||
hours, mins = divmod(mins, 60)
|
||||
return '%02d:%02d:%02d' % (hours, mins, secs)
|
||||
|
||||
|
||||
def total_unique_handshakes(path):
|
||||
expr = os.path.join(path, "*.pcap")
|
||||
return len(glob.glob(expr))
|
||||
|
||||
|
||||
def iface_address(ifname):
|
||||
output = subprocess.getoutput("/usr/sbin/ifconfig %s" % ifname)
|
||||
for line in output.split("\n"):
|
||||
line = line.strip()
|
||||
if line.startswith("inet "):
|
||||
return line.split(' ')[1].strip()
|
||||
return None
|
||||
|
||||
def led(on=True):
|
||||
with open('/sys/class/leds/led0/brightness', 'w+t') as fp:
|
||||
fp.write("%d" % (0 if on is True else 1))
|
||||
|
||||
|
||||
def blink(times=1, delay=0.3):
|
||||
for t in range(0, times):
|
||||
led(True)
|
||||
time.sleep(delay)
|
||||
led(False)
|
||||
time.sleep(delay)
|
||||
led(True)
|
131
sdcard/rootfs/root/pwnagotchi/scripts/main.py
Executable file
131
sdcard/rootfs/root/pwnagotchi/scripts/main.py
Executable file
@ -0,0 +1,131 @@
|
||||
#!/usr/bin/python3
|
||||
import argparse
|
||||
import yaml
|
||||
import time
|
||||
import traceback
|
||||
|
||||
import core
|
||||
import pwnagotchi
|
||||
|
||||
from pwnagotchi.log import SessionParser
|
||||
import pwnagotchi.voice as voice
|
||||
from pwnagotchi.agent import Agent
|
||||
from pwnagotchi.ui.display import Display
|
||||
|
||||
parser = argparse.ArgumentParser()
|
||||
|
||||
parser.add_argument('-C', '--config', action='store', dest='config', default='/root/pwnagotchi/config.yml')
|
||||
|
||||
parser.add_argument('--manual', dest="do_manual", action="store_true", default=False, help="Manual mode.")
|
||||
parser.add_argument('--clear', dest="do_clear", action="store_true", default=False,
|
||||
help="Clear the ePaper display and exit.")
|
||||
|
||||
args = parser.parse_args()
|
||||
|
||||
if args.do_clear:
|
||||
print("clearing the display ...")
|
||||
from pwnagotchi.ui.waveshare import EPD
|
||||
|
||||
epd = EPD()
|
||||
epd.init(epd.FULL_UPDATE)
|
||||
epd.Clear(0xff)
|
||||
quit()
|
||||
|
||||
with open(args.config, 'rt') as fp:
|
||||
config = yaml.safe_load(fp)
|
||||
|
||||
display = Display(config=config, state={'name': '%s>' % pwnagotchi.name()})
|
||||
agent = Agent(view=display, config=config)
|
||||
|
||||
core.log("%s@%s (v%s)" % (pwnagotchi.name(), agent._identity, pwnagotchi.version))
|
||||
# for key, value in config['personality'].items():
|
||||
# core.log(" %s: %s" % (key, value))
|
||||
|
||||
if args.do_manual:
|
||||
core.log("entering manual mode ...")
|
||||
|
||||
log = SessionParser(config['main']['log'])
|
||||
|
||||
core.log("the last session lasted %s (%d completed epochs, trained for %d), average reward:%s (min:%s max:%s)" % (
|
||||
log.duration_human,
|
||||
log.epochs,
|
||||
log.train_epochs,
|
||||
log.avg_reward,
|
||||
log.min_reward,
|
||||
log.max_reward))
|
||||
|
||||
while True:
|
||||
display.on_manual_mode(log)
|
||||
time.sleep(1)
|
||||
if config['twitter']['enabled'] and log.is_new() and Agent.is_connected() and log.handshakes > 0:
|
||||
import tweepy
|
||||
|
||||
core.log("detected a new session and internet connectivity!")
|
||||
|
||||
picture = '/tmp/pwnagotchi.png'
|
||||
|
||||
display.update()
|
||||
display.image().save(picture, 'png')
|
||||
display.set('status', 'Tweeting...')
|
||||
display.update()
|
||||
|
||||
try:
|
||||
auth = tweepy.OAuthHandler(config['twitter']['consumer_key'], config['twitter']['consumer_secret'])
|
||||
auth.set_access_token(config['twitter']['access_token_key'], config['twitter']['access_token_secret'])
|
||||
api = tweepy.API(auth)
|
||||
|
||||
tweet = voice.on_log_tweet(log)
|
||||
api.update_with_media(filename=picture, status=tweet)
|
||||
log.save_session_id()
|
||||
|
||||
core.log("tweeted: %s" % tweet)
|
||||
except Exception as e:
|
||||
core.log("error: %s" % e)
|
||||
|
||||
quit()
|
||||
|
||||
core.logfile = config['main']['log']
|
||||
|
||||
agent.start_ai()
|
||||
agent.setup_events()
|
||||
agent.set_ready()
|
||||
agent.start_monitor_mode()
|
||||
agent.start_event_polling()
|
||||
|
||||
# print initial stats
|
||||
agent.next_epoch()
|
||||
|
||||
while True:
|
||||
try:
|
||||
# recon on all channels
|
||||
agent.recon()
|
||||
# get nearby access points grouped by channel
|
||||
channels = agent.get_access_points_by_channel()
|
||||
# check for free channels to use
|
||||
agent.check_channels(channels)
|
||||
# for each channel
|
||||
for ch, aps in channels:
|
||||
agent.set_channel(ch)
|
||||
|
||||
if not agent.is_stale() and agent.any_activity():
|
||||
core.log("%d access points on channel %d" % (len(aps), ch))
|
||||
|
||||
# for each ap on this channel
|
||||
for ap in aps:
|
||||
# send an association frame in order to get for a PMKID
|
||||
agent.associate(ap)
|
||||
# deauth all client stations in order to get a full handshake
|
||||
for sta in ap['clients']:
|
||||
agent.deauth(ap, sta)
|
||||
|
||||
# An interesting effect of this:
|
||||
#
|
||||
# From Pwnagotchi's perspective, the more new access points
|
||||
# and / or client stations nearby, the longer one epoch of
|
||||
# its relative time will take ... basically, in Pwnagotchi's universe,
|
||||
# WiFi electromagnetic fields affect time like gravitational fields
|
||||
# affect ours ... neat ^_^
|
||||
agent.next_epoch()
|
||||
except Exception as e:
|
||||
core.log("main loop exception: %s" % e)
|
||||
core.log("%s" % traceback.format_exc())
|
48
sdcard/rootfs/root/pwnagotchi/scripts/pwnagotchi/__init__.py
Normal file
48
sdcard/rootfs/root/pwnagotchi/scripts/pwnagotchi/__init__.py
Normal file
@ -0,0 +1,48 @@
|
||||
import subprocess
|
||||
|
||||
version = '1.0.0a'
|
||||
|
||||
_name = None
|
||||
|
||||
|
||||
def name():
|
||||
global _name
|
||||
if _name is None:
|
||||
with open('/etc/hostname', 'rt') as fp:
|
||||
_name = fp.read().strip()
|
||||
return _name
|
||||
|
||||
|
||||
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
|
||||
|
||||
return 0
|
||||
|
||||
|
||||
def cpu_load():
|
||||
with open('/proc/stat', 'rt') as fp:
|
||||
for line in fp:
|
||||
line = line.strip()
|
||||
if line.startswith('cpu '):
|
||||
parts = list(map(int, line.split()[1:]))
|
||||
user_n = parts[0]
|
||||
sys_n = parts[2]
|
||||
idle_n = parts[3]
|
||||
tot = user_n + sys_n + idle_n
|
||||
return (user_n + sys_n) / tot
|
||||
return 0
|
||||
|
||||
|
||||
def temperature(celsius=True):
|
||||
with open('/sys/class/thermal/thermal_zone0/temp', 'rt') as fp:
|
||||
temp = int(fp.read().strip())
|
||||
c = int(temp / 1000)
|
||||
return c if celsius else ((c * (9 / 5)) + 32)
|
510
sdcard/rootfs/root/pwnagotchi/scripts/pwnagotchi/agent.py
Normal file
510
sdcard/rootfs/root/pwnagotchi/scripts/pwnagotchi/agent.py
Normal file
@ -0,0 +1,510 @@
|
||||
import time
|
||||
import json
|
||||
import os
|
||||
import re
|
||||
import socket
|
||||
from datetime import datetime
|
||||
import _thread
|
||||
|
||||
import core
|
||||
|
||||
from bettercap.client import Client
|
||||
from pwnagotchi.mesh.advertise import AsyncAdvertiser
|
||||
from pwnagotchi.ai.train import AsyncTrainer
|
||||
|
||||
RECOVERY_DATA_FILE = '/root/.pwnagotchi-recovery'
|
||||
|
||||
|
||||
class Agent(Client, AsyncAdvertiser, AsyncTrainer):
|
||||
def __init__(self, view, config):
|
||||
Client.__init__(self, config['bettercap']['hostname'],
|
||||
config['bettercap']['scheme'],
|
||||
config['bettercap']['port'],
|
||||
config['bettercap']['username'],
|
||||
config['bettercap']['password'])
|
||||
AsyncAdvertiser.__init__(self, config, view)
|
||||
AsyncTrainer.__init__(self, config)
|
||||
|
||||
self._started_at = time.time()
|
||||
self._filter = None if config['main']['filter'] is None else re.compile(config['main']['filter'])
|
||||
self._current_channel = 0
|
||||
self._view = view
|
||||
self._access_points = []
|
||||
self._last_pwnd = None
|
||||
self._history = {}
|
||||
self._handshakes = {}
|
||||
|
||||
@staticmethod
|
||||
def is_connected():
|
||||
try:
|
||||
socket.create_connection(("www.google.com", 80))
|
||||
return True
|
||||
except OSError:
|
||||
pass
|
||||
return False
|
||||
|
||||
def on_ai_ready(self):
|
||||
self._view.on_ai_ready()
|
||||
|
||||
def set_ready(self):
|
||||
self._view.on_starting()
|
||||
|
||||
def set_free_channel(self, channel):
|
||||
self._view.on_free_channel(channel)
|
||||
|
||||
def set_bored(self):
|
||||
self._view.on_bored()
|
||||
|
||||
def set_sad(self):
|
||||
self._view.on_sad()
|
||||
|
||||
def set_excited(self):
|
||||
self._view.on_excited()
|
||||
|
||||
def set_lonely(self):
|
||||
self._view.on_lonely()
|
||||
|
||||
def set_rebooting(self):
|
||||
self._view.on_rebooting()
|
||||
|
||||
def setup_events(self):
|
||||
core.log("connecting to %s ..." % self.url)
|
||||
|
||||
for tag in self._config['bettercap']['silence']:
|
||||
try:
|
||||
self.run('events.ignore %s' % tag, verbose_errors=False)
|
||||
except Exception as e:
|
||||
pass
|
||||
|
||||
def _reset_wifi_settings(self):
|
||||
mon_iface = self._config['main']['iface']
|
||||
self.run('set wifi.interface %s' % mon_iface)
|
||||
self.run('set wifi.ap.ttl %d' % self._config['personality']['ap_ttl'])
|
||||
self.run('set wifi.sta.ttl %d' % self._config['personality']['sta_ttl'])
|
||||
self.run('set wifi.rssi.min %d' % self._config['personality']['min_rssi'])
|
||||
self.run('set wifi.handshakes.file %s' % self._config['bettercap']['handshakes'])
|
||||
self.run('set wifi.handshakes.aggregate false')
|
||||
|
||||
def start_monitor_mode(self):
|
||||
mon_iface = self._config['main']['iface']
|
||||
mon_start_cmd = self._config['main']['mon_start_cmd']
|
||||
restart = not self._config['main']['no_restart']
|
||||
has_mon = False
|
||||
|
||||
while has_mon is False:
|
||||
s = self.session()
|
||||
for iface in s['interfaces']:
|
||||
if iface['name'] == mon_iface:
|
||||
core.log("found monitor interface: %s" % iface['name'])
|
||||
has_mon = True
|
||||
break
|
||||
|
||||
if has_mon is False:
|
||||
if mon_start_cmd is not None and mon_start_cmd != '':
|
||||
core.log("starting monitor interface ...")
|
||||
self.run('!%s' % mon_start_cmd)
|
||||
else:
|
||||
core.log("waiting for monitor interface %s ..." % mon_iface)
|
||||
time.sleep(1)
|
||||
|
||||
core.log("handshakes will be collected inside %s" % self._config['bettercap']['handshakes'])
|
||||
|
||||
self._reset_wifi_settings()
|
||||
|
||||
wifi_running = self.is_module_running('wifi')
|
||||
if wifi_running and restart:
|
||||
core.log("restarting wifi module ...")
|
||||
self.restart('wifi.recon')
|
||||
self.run('wifi.clear')
|
||||
elif not wifi_running:
|
||||
core.log("starting wifi module ...")
|
||||
self.start('wifi.recon')
|
||||
|
||||
self.start_advertising()
|
||||
|
||||
def wait_for(self, t, sleeping=True):
|
||||
self._view.wait(t, sleeping)
|
||||
self._epoch.track(sleep=True, inc=t)
|
||||
|
||||
def check_channels(self, channels):
|
||||
busy_channels = [ch for ch, aps in channels]
|
||||
# if we're hopping and no filter is configured
|
||||
if self._config['personality']['channels'] == [] and self._config['main']['filter'] is None:
|
||||
# check if any of the non overlapping channels is free
|
||||
for ch in self._epoch.non_overlapping_channels:
|
||||
if ch not in busy_channels:
|
||||
self._epoch.non_overlapping_channels[ch] += 1
|
||||
core.log("channel %d is free from %d epochs" % (ch, self._epoch.non_overlapping_channels[ch]))
|
||||
elif self._epoch.non_overlapping_channels[ch] > 0:
|
||||
self._epoch.non_overlapping_channels[ch] -= 1
|
||||
# report any channel that has been free for at least 3 epochs
|
||||
for ch, num_epochs_free in self._epoch.non_overlapping_channels.items():
|
||||
if num_epochs_free >= 3:
|
||||
core.log("channel %d has been free for %d epochs" % (ch, num_epochs_free))
|
||||
self.set_free_channel(ch)
|
||||
|
||||
def recon(self):
|
||||
recon_time = self._config['personality']['recon_time']
|
||||
max_inactive = self._config['personality']['max_inactive_scale']
|
||||
recon_mul = self._config['personality']['recon_inactive_multiplier']
|
||||
channels = self._config['personality']['channels']
|
||||
|
||||
if self._epoch.inactive_for >= max_inactive:
|
||||
recon_time *= recon_mul
|
||||
|
||||
self._view.set('channel', '*')
|
||||
|
||||
if not channels:
|
||||
self._current_channel = 0
|
||||
# core.log("RECON %ds" % recon_time)
|
||||
self.run('wifi.recon.channel clear')
|
||||
else:
|
||||
# core.log("RECON %ds ON CHANNELS %s" % (recon_time, ','.join(map(str, channels))))
|
||||
try:
|
||||
self.run('wifi.recon.channel %s' % ','.join(map(str, channels)))
|
||||
except Exception as e:
|
||||
core.log("error: %s" % e)
|
||||
|
||||
self.wait_for(recon_time, sleeping=False)
|
||||
|
||||
def _filter_included(self, ap):
|
||||
return self._filter is None or \
|
||||
self._filter.match(ap['hostname']) is not None or \
|
||||
self._filter.match(ap['mac']) is not None
|
||||
|
||||
def set_access_points(self, aps):
|
||||
self._access_points = aps
|
||||
self._epoch.observe(aps, self._advertiser.peers() if self._advertiser is not None else ())
|
||||
return self._access_points
|
||||
|
||||
def get_access_points(self):
|
||||
whitelist = self._config['main']['whitelist']
|
||||
aps = []
|
||||
try:
|
||||
s = self.session()
|
||||
for ap in s['wifi']['aps']:
|
||||
if ap['hostname'] not in whitelist:
|
||||
if self._filter_included(ap):
|
||||
aps.append(ap)
|
||||
except Exception as e:
|
||||
core.log("error: %s" % e)
|
||||
|
||||
aps.sort(key=lambda ap: ap['channel'])
|
||||
return self.set_access_points(aps)
|
||||
|
||||
def get_access_points_by_channel(self):
|
||||
aps = self.get_access_points()
|
||||
channels = self._config['personality']['channels']
|
||||
grouped = {}
|
||||
|
||||
# group by channel
|
||||
for ap in aps:
|
||||
ch = ap['channel']
|
||||
# if we're sticking to a channel, skip anything
|
||||
# which is not on that channel
|
||||
if channels != [] and ch not in channels:
|
||||
continue
|
||||
|
||||
if ch not in grouped:
|
||||
grouped[ch] = [ap]
|
||||
else:
|
||||
grouped[ch].append(ap)
|
||||
|
||||
# sort by more populated channels
|
||||
return sorted(grouped.items(), key=lambda kv: len(kv[1]), reverse=True)
|
||||
|
||||
def _find_ap_sta_in(self, station_mac, ap_mac, session):
|
||||
for ap in session['wifi']['aps']:
|
||||
if ap['mac'] == ap_mac:
|
||||
for sta in ap['clients']:
|
||||
if sta['mac'] == station_mac:
|
||||
return (ap, sta)
|
||||
return (ap, {'mac': station_mac, 'vendor': ''})
|
||||
return None
|
||||
|
||||
def _update_uptime(self, s):
|
||||
secs = time.time() - self._started_at
|
||||
self._view.set('uptime', core.secs_to_hhmmss(secs))
|
||||
self._view.set('epoch', '%04d' % self._epoch.epoch)
|
||||
|
||||
def _update_counters(self):
|
||||
tot_aps = len(self._access_points)
|
||||
tot_stas = sum(len(ap['clients']) for ap in self._access_points)
|
||||
if self._current_channel == 0:
|
||||
self._view.set('aps', '%d' % tot_aps)
|
||||
self._view.set('sta', '%d' % tot_stas)
|
||||
else:
|
||||
aps_on_channel = len([ap for ap in self._access_points if ap['channel'] == self._current_channel])
|
||||
stas_on_channel = sum(
|
||||
[len(ap['clients']) for ap in self._access_points if ap['channel'] == self._current_channel])
|
||||
self._view.set('aps', '%d (%d)' % (aps_on_channel, tot_aps))
|
||||
self._view.set('sta', '%d (%d)' % (stas_on_channel, tot_stas))
|
||||
|
||||
def _update_handshakes(self, new_shakes=0):
|
||||
if new_shakes > 0:
|
||||
self._epoch.track(handshake=True, inc=new_shakes)
|
||||
|
||||
tot = core.total_unique_handshakes(self._config['bettercap']['handshakes'])
|
||||
txt = '%d (%d)' % (len(self._handshakes), tot)
|
||||
|
||||
if self._last_pwnd is not None:
|
||||
txt += ' [%s]' % self._last_pwnd
|
||||
|
||||
self._view.set('shakes', txt)
|
||||
|
||||
if new_shakes > 0:
|
||||
self._view.on_handshakes(new_shakes)
|
||||
|
||||
def _update_advertisement(self, s):
|
||||
run_handshakes = len(self._handshakes)
|
||||
tot_handshakes = core.total_unique_handshakes(self._config['bettercap']['handshakes'])
|
||||
started = s['started_at'].split('.')[0]
|
||||
started = datetime.strptime(started, '%Y-%m-%dT%H:%M:%S')
|
||||
started = time.mktime(started.timetuple())
|
||||
self._advertiser.update({ \
|
||||
'pwnd_run': run_handshakes,
|
||||
'pwnd_tot': tot_handshakes,
|
||||
'uptime': time.time() - started,
|
||||
'epoch': self._epoch.epoch})
|
||||
|
||||
def _update_peers(self):
|
||||
peer = self._advertiser.closest_peer()
|
||||
self._view.set_closest_peer(peer)
|
||||
|
||||
def _save_recovery_data(self):
|
||||
core.log("writing recovery data to %s ..." % RECOVERY_DATA_FILE)
|
||||
with open(RECOVERY_DATA_FILE, 'w') as fp:
|
||||
data = {
|
||||
'started_at': self._started_at,
|
||||
'epoch': self._epoch.epoch,
|
||||
'history': self._history,
|
||||
'handshakes': self._handshakes,
|
||||
'last_pwnd': self._last_pwnd
|
||||
}
|
||||
json.dump(data, fp)
|
||||
|
||||
def _load_recovery_data(self, delete=True, no_exceptions=True):
|
||||
try:
|
||||
with open(RECOVERY_DATA_FILE, 'rt') as fp:
|
||||
data = json.load(fp)
|
||||
core.log("found recovery data: %s" % data)
|
||||
self._started_at = data['started_at']
|
||||
self._epoch.epoch = data['epoch']
|
||||
self._handshakes = data['handshakes']
|
||||
self._history = data['history']
|
||||
self._last_pwnd = data['last_pwnd']
|
||||
|
||||
if delete:
|
||||
core.log("deleting %s" % RECOVERY_DATA_FILE)
|
||||
os.unlink(RECOVERY_DATA_FILE)
|
||||
except:
|
||||
if not no_exceptions:
|
||||
raise
|
||||
|
||||
def _event_poller(self):
|
||||
self._load_recovery_data()
|
||||
|
||||
self.run('events.clear')
|
||||
|
||||
core.log("event polling started ...")
|
||||
while True:
|
||||
time.sleep(1)
|
||||
|
||||
new_shakes = 0
|
||||
s = self.session()
|
||||
self._update_uptime(s)
|
||||
|
||||
if self._advertiser is not None:
|
||||
self._update_advertisement(s)
|
||||
self._update_peers()
|
||||
|
||||
self._update_counters()
|
||||
|
||||
try:
|
||||
for h in [e for e in self.events() if e['tag'] == 'wifi.client.handshake']:
|
||||
sta_mac = h['data']['station']
|
||||
ap_mac = h['data']['ap']
|
||||
key = "%s -> %s" % (sta_mac, ap_mac)
|
||||
|
||||
if key not in self._handshakes:
|
||||
self._handshakes[key] = h
|
||||
new_shakes += 1
|
||||
apsta = self._find_ap_sta_in(sta_mac, ap_mac, s)
|
||||
if apsta is None:
|
||||
core.log("!!! captured new handshake: %s !!!" % key)
|
||||
self._last_pwnd = ap_mac
|
||||
else:
|
||||
(ap, sta) = apsta
|
||||
self._last_pwnd = ap['hostname'] if ap['hostname'] != '' and ap[
|
||||
'hostname'] != '<hidden>' else ap_mac
|
||||
core.log("!!! captured new handshake on channel %d: %s (%s) -> %s [%s (%s)] !!!" % ( \
|
||||
ap['channel'],
|
||||
sta['mac'], sta['vendor'],
|
||||
ap['hostname'], ap['mac'], ap['vendor']))
|
||||
|
||||
except Exception as e:
|
||||
core.log("error: %s" % e)
|
||||
|
||||
finally:
|
||||
self._update_handshakes(new_shakes)
|
||||
|
||||
def start_event_polling(self):
|
||||
_thread.start_new_thread(self._event_poller, ())
|
||||
|
||||
def is_module_running(self, module):
|
||||
s = self.session()
|
||||
for m in s['modules']:
|
||||
if m['name'] == module:
|
||||
return m['running']
|
||||
return False
|
||||
|
||||
def start(self, module):
|
||||
self.run('%s on' % module)
|
||||
|
||||
def restart(self, module):
|
||||
self.run('%s off; %s on' % (module, module))
|
||||
|
||||
def _has_handshake(self, bssid):
|
||||
for key in self._handshakes:
|
||||
if bssid.lower() in key:
|
||||
return True
|
||||
return False
|
||||
|
||||
def _should_interact(self, who):
|
||||
if self._has_handshake(who):
|
||||
return False
|
||||
|
||||
elif who not in self._history:
|
||||
self._history[who] = 1
|
||||
return True
|
||||
|
||||
else:
|
||||
self._history[who] += 1
|
||||
|
||||
return self._history[who] < self._config['personality']['max_interactions']
|
||||
|
||||
def _on_miss(self, who):
|
||||
core.log("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:
|
||||
core.log("error: %s" % e)
|
||||
|
||||
def associate(self, ap, throttle=0):
|
||||
if self.is_stale():
|
||||
core.log("recon is stale, skipping assoc(%s)" % ap['mac'])
|
||||
return
|
||||
|
||||
if self._config['personality']['associate'] and self._should_interact(ap['mac']):
|
||||
self._view.on_assoc(ap)
|
||||
|
||||
try:
|
||||
core.log("sending association frame to %s (%s %s) on channel %d [%d clients]..." % ( \
|
||||
ap['hostname'], ap['mac'], ap['vendor'], ap['channel'], len(ap['clients'])))
|
||||
self.run('wifi.assoc %s' % ap['mac'])
|
||||
self._epoch.track(assoc=True)
|
||||
except Exception as e:
|
||||
self._on_error(ap['mac'], e)
|
||||
|
||||
if throttle > 0:
|
||||
time.sleep(throttle)
|
||||
self._view.on_normal()
|
||||
|
||||
def deauth(self, ap, sta, throttle=0):
|
||||
if self.is_stale():
|
||||
core.log("recon is stale, skipping deauth(%s)" % sta['mac'])
|
||||
return
|
||||
|
||||
if self._config['personality']['deauth'] and self._should_interact(sta['mac']):
|
||||
self._view.on_deauth(sta)
|
||||
|
||||
try:
|
||||
core.log("deauthing %s (%s) from %s (%s %s) on channel %d ..." % (
|
||||
sta['mac'], sta['vendor'], ap['hostname'], ap['mac'], ap['vendor'], ap['channel']))
|
||||
self.run('wifi.deauth %s' % sta['mac'])
|
||||
self._epoch.track(deauth=True)
|
||||
except Exception as e:
|
||||
self._on_error(sta['mac'], e)
|
||||
|
||||
if throttle > 0:
|
||||
time.sleep(throttle)
|
||||
self._view.on_normal()
|
||||
|
||||
def set_channel(self, channel, verbose=True):
|
||||
if self.is_stale():
|
||||
core.log("recon is stale, skipping set_channel(%d)" % channel)
|
||||
return
|
||||
|
||||
# if in the previous loop no client stations has been deauthenticated
|
||||
# and only association frames have been sent, we don't need to wait
|
||||
# very long before switching channel as we don't have to wait for
|
||||
# such client stations to reconnect in order to sniff the handshake.
|
||||
wait = 0
|
||||
if self._epoch.did_deauth:
|
||||
wait = self._config['personality']['hop_recon_time']
|
||||
elif self._epoch.did_associate:
|
||||
wait = self._config['personality']['min_recon_time']
|
||||
|
||||
if channel != self._current_channel:
|
||||
if self._current_channel != 0 and wait > 0:
|
||||
if verbose:
|
||||
core.log("waiting for %ds on channel %d ..." % (wait, self._current_channel))
|
||||
self.wait_for(wait)
|
||||
if verbose and self._epoch.any_activity:
|
||||
core.log("CHANNEL %d" % channel)
|
||||
try:
|
||||
self.run('wifi.recon.channel %d' % channel)
|
||||
self._current_channel = channel
|
||||
self._epoch.track(hop=True)
|
||||
self._view.set('channel', '%d' % channel)
|
||||
except Exception as e:
|
||||
core.log("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()
|
||||
core.log("rebooting the system ...")
|
||||
os.system("/usr/bin/sync")
|
||||
os.system("/usr/sbin/shutdown -r now")
|
||||
|
||||
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:
|
||||
core.log("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']:
|
||||
core.log("%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']:
|
||||
core.log("%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']:
|
||||
core.log("%d epochs with activity -> excited" % self._epoch.active_for)
|
||||
self.set_excited()
|
||||
|
||||
if self._epoch.blind_for >= self._config['main']['mon_max_blind_epochs']:
|
||||
core.log("%d epochs without visible access points -> rebooting ..." % self._epoch.blind_for)
|
||||
self._reboot()
|
||||
self._epoch.blind_for = 0
|
@ -0,0 +1,42 @@
|
||||
import os
|
||||
|
||||
# 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 core
|
||||
|
||||
|
||||
def load(config, agent, epoch, from_disk=True):
|
||||
config = config['ai']
|
||||
if not config['enabled']:
|
||||
core.log("ai disabled")
|
||||
return False
|
||||
|
||||
core.log("[ai] bootstrapping dependencies ...")
|
||||
|
||||
from stable_baselines import A2C
|
||||
from stable_baselines.common.policies import MlpLstmPolicy
|
||||
from stable_baselines.common.vec_env import DummyVecEnv
|
||||
|
||||
import pwnagotchi.ai.gym as wrappers
|
||||
|
||||
env = wrappers.Environment(agent, epoch)
|
||||
env = DummyVecEnv([lambda: env])
|
||||
|
||||
core.log("[ai] bootstrapping model ...")
|
||||
|
||||
a2c = A2C(MlpLstmPolicy, env, **config['params'])
|
||||
|
||||
if from_disk and os.path.exists(config['path']):
|
||||
core.log("[ai] loading %s ..." % config['path'])
|
||||
a2c.load(config['path'], env)
|
||||
else:
|
||||
core.log("[ai] model created:")
|
||||
for key, value in config['params'].items():
|
||||
core.log(" %s: %s" % (key, value))
|
||||
|
||||
return a2c
|
202
sdcard/rootfs/root/pwnagotchi/scripts/pwnagotchi/ai/epoch.py
Normal file
202
sdcard/rootfs/root/pwnagotchi/scripts/pwnagotchi/ai/epoch.py
Normal file
@ -0,0 +1,202 @@
|
||||
import time
|
||||
import threading
|
||||
|
||||
import core
|
||||
import pwnagotchi
|
||||
import pwnagotchi.mesh.wifi as wifi
|
||||
|
||||
from pwnagotchi.ai.reward import RewardFunction
|
||||
|
||||
class Epoch(object):
|
||||
def __init__(self, config):
|
||||
self.epoch = 0
|
||||
self.config = config
|
||||
# how many consecutive epochs with no activity
|
||||
self.inactive_for = 0
|
||||
# how many consecutive epochs with activity
|
||||
self.active_for = 0
|
||||
# number of epochs with no visible access points
|
||||
self.blind_for = 0
|
||||
# did deauth in this epoch in the current channel?
|
||||
self.did_deauth = False
|
||||
# number of deauths in this epoch
|
||||
self.num_deauths = 0
|
||||
# did associate in this epoch in the current channel?
|
||||
self.did_associate = False
|
||||
# number of associations in this epoch
|
||||
self.num_assocs = 0
|
||||
# number of assocs or deauths missed
|
||||
self.num_missed = 0
|
||||
# did get any handshake in this epoch?
|
||||
self.did_handshakes = False
|
||||
# number of handshakes captured in this epoch
|
||||
self.num_shakes = 0
|
||||
# number of channels hops
|
||||
self.num_hops = 0
|
||||
# number of seconds sleeping
|
||||
self.num_slept = 0
|
||||
# any activity at all during this epoch?
|
||||
self.any_activity = False
|
||||
# when the current epoch started
|
||||
self.epoch_started = time.time()
|
||||
# last epoch duration
|
||||
self.epoch_duration = 0
|
||||
# https://www.metageek.com/training/resources/why-channels-1-6-11.html
|
||||
self.non_overlapping_channels = {1: 0, 6: 0, 11: 0}
|
||||
# observation vectors
|
||||
self._observation = {
|
||||
'aps_histogram': [0.0] * wifi.NumChannels,
|
||||
'sta_histogram': [0.0] * wifi.NumChannels,
|
||||
'peers_histogram': [0.0] * wifi.NumChannels
|
||||
}
|
||||
self._observation_ready = threading.Event()
|
||||
self._epoch_data = {}
|
||||
self._epoch_data_ready = threading.Event()
|
||||
self._reward = RewardFunction()
|
||||
|
||||
def wait_for_epoch_data(self, with_observation=True, timeout=None):
|
||||
# if with_observation:
|
||||
# self._observation_ready.wait(timeout)
|
||||
# self._observation_ready.clear()
|
||||
self._epoch_data_ready.wait(timeout)
|
||||
self._epoch_data_ready.clear()
|
||||
return self._epoch_data if with_observation is False else {**self._observation, **self._epoch_data}
|
||||
|
||||
def data(self):
|
||||
return self._epoch_data
|
||||
|
||||
def observe(self, aps, peers):
|
||||
num_aps = len(aps)
|
||||
if num_aps == 0:
|
||||
self.blind_for += 1
|
||||
else:
|
||||
self.blind_for = 0
|
||||
|
||||
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
|
||||
|
||||
for ap in aps:
|
||||
ch_idx = ap['channel'] - 1
|
||||
try:
|
||||
aps_per_chan[ch_idx] += 1.0
|
||||
sta_per_chan[ch_idx] += len(ap['clients'])
|
||||
except IndexError as e:
|
||||
core.log("got data on channel %d, we can store %d channels" % (ap['channel'], wifi.NumChannels))
|
||||
|
||||
for peer in peers:
|
||||
try:
|
||||
peers_per_chan[peer.last_channel - 1] += 1.0
|
||||
except IndexError as e:
|
||||
core.log("got peer data on channel %d, we can store %d channels" % (peer.last_channel, wifi.NumChannels))
|
||||
|
||||
# normalize
|
||||
aps_per_chan = [e / num_aps for e in aps_per_chan]
|
||||
sta_per_chan = [e / num_sta for e in sta_per_chan]
|
||||
peers_per_chan = [e / num_peers for e in peers_per_chan]
|
||||
|
||||
self._observation = {
|
||||
'aps_histogram': aps_per_chan,
|
||||
'sta_histogram': sta_per_chan,
|
||||
'peers_histogram': peers_per_chan
|
||||
}
|
||||
self._observation_ready.set()
|
||||
|
||||
def track(self, deauth=False, assoc=False, handshake=False, hop=False, sleep=False, miss=False, inc=1):
|
||||
if deauth:
|
||||
self.num_deauths += inc
|
||||
self.did_deauth = True
|
||||
self.any_activity = True
|
||||
|
||||
if assoc:
|
||||
self.num_assocs += inc
|
||||
self.did_associate = True
|
||||
self.any_activity = True
|
||||
|
||||
if miss:
|
||||
self.num_missed += inc
|
||||
|
||||
if hop:
|
||||
self.num_hops += inc
|
||||
# these two are used in order to determine the sleep time in seconds
|
||||
# before switching to a new channel ... if nothing happened so far
|
||||
# during this epoch on the current channel, we will sleep less
|
||||
self.did_deauth = False
|
||||
self.did_associate = False
|
||||
|
||||
if handshake:
|
||||
self.num_shakes += inc
|
||||
self.did_handshakes = True
|
||||
|
||||
if sleep:
|
||||
self.num_slept += inc
|
||||
|
||||
def next(self):
|
||||
if self.any_activity is False and self.did_handshakes is False:
|
||||
self.inactive_for += 1
|
||||
self.active_for = 0
|
||||
else:
|
||||
self.active_for += 1
|
||||
self.inactive_for = 0
|
||||
|
||||
now = time.time()
|
||||
cpu = pwnagotchi.cpu_load()
|
||||
mem = pwnagotchi.mem_usage()
|
||||
temp = pwnagotchi.temperature()
|
||||
|
||||
self.epoch_duration = now - self.epoch_started
|
||||
|
||||
# cache the state of this epoch for other threads to read
|
||||
self._epoch_data = {
|
||||
'duration_secs': self.epoch_duration,
|
||||
'slept_for_secs': self.num_slept,
|
||||
'blind_for_epochs': self.blind_for,
|
||||
'inactive_for_epochs': self.inactive_for,
|
||||
'active_for_epochs': self.active_for,
|
||||
'missed_interactions': self.num_missed,
|
||||
'num_hops': self.num_hops,
|
||||
'num_deauths': self.num_deauths,
|
||||
'num_associations': self.num_assocs,
|
||||
'num_handshakes': self.num_shakes,
|
||||
'cpu_load': cpu,
|
||||
'mem_usage': mem,
|
||||
'temperature': temp
|
||||
}
|
||||
|
||||
self._epoch_data['reward'] = self._reward(self.epoch + 1, self._epoch_data)
|
||||
self._epoch_data_ready.set()
|
||||
|
||||
core.log("[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" % (
|
||||
self.epoch,
|
||||
core.secs_to_hhmmss(self.epoch_duration),
|
||||
core.secs_to_hhmmss(self.num_slept),
|
||||
self.blind_for,
|
||||
self.inactive_for,
|
||||
self.active_for,
|
||||
self.num_hops,
|
||||
self.num_missed,
|
||||
self.num_deauths,
|
||||
self.num_assocs,
|
||||
self.num_shakes,
|
||||
cpu * 100,
|
||||
mem * 100,
|
||||
temp,
|
||||
self._epoch_data['reward']))
|
||||
|
||||
self.epoch += 1
|
||||
self.epoch_started = now
|
||||
self.did_deauth = False
|
||||
self.num_deauths = 0
|
||||
self.did_associate = False
|
||||
self.num_assocs = 0
|
||||
self.num_missed = 0
|
||||
self.did_handshakes = False
|
||||
self.num_shakes = 0
|
||||
self.num_hops = 0
|
||||
self.num_slept = 0
|
||||
self.any_activity = False
|
@ -0,0 +1,60 @@
|
||||
import numpy as np
|
||||
|
||||
import pwnagotchi.mesh.wifi as wifi
|
||||
|
||||
MAX_EPOCH_DURATION = 1024
|
||||
|
||||
histogram_size = wifi.NumChannels
|
||||
|
||||
shape = (1,
|
||||
# aps per channel
|
||||
histogram_size +
|
||||
# clients per channel
|
||||
histogram_size +
|
||||
# peers per channel
|
||||
histogram_size +
|
||||
# duration
|
||||
1 +
|
||||
# inactive
|
||||
1 +
|
||||
# active
|
||||
1 +
|
||||
# missed
|
||||
1 +
|
||||
# hops
|
||||
1 +
|
||||
# deauths
|
||||
1 +
|
||||
# assocs
|
||||
1 +
|
||||
# handshakes
|
||||
1)
|
||||
|
||||
|
||||
def featurize(state, step):
|
||||
tot_epochs = step + 1e-10
|
||||
tot_interactions = (state['num_deauths'] + state['num_associations']) + 1e-10
|
||||
return np.concatenate((
|
||||
# aps per channel
|
||||
state['aps_histogram'],
|
||||
# clients per channel
|
||||
state['sta_histogram'],
|
||||
# peers per channel
|
||||
state['peers_histogram'],
|
||||
# duration
|
||||
[np.clip(state['duration_secs'] / MAX_EPOCH_DURATION, 0.0, 1.0)],
|
||||
# inactive
|
||||
[state['inactive_for_epochs'] / tot_epochs],
|
||||
# active
|
||||
[state['active_for_epochs'] / tot_epochs],
|
||||
# missed
|
||||
[state['missed_interactions'] / tot_interactions],
|
||||
# hops
|
||||
[state['num_hops'] / wifi.NumChannels],
|
||||
# deauths
|
||||
[state['num_deauths'] / tot_interactions],
|
||||
# assocs
|
||||
[state['num_associations'] / tot_interactions],
|
||||
# handshakes
|
||||
[state['num_handshakes'] / tot_interactions],
|
||||
))
|
151
sdcard/rootfs/root/pwnagotchi/scripts/pwnagotchi/ai/gym.py
Normal file
151
sdcard/rootfs/root/pwnagotchi/scripts/pwnagotchi/ai/gym.py
Normal file
@ -0,0 +1,151 @@
|
||||
import gym
|
||||
from gym import spaces
|
||||
import numpy as np
|
||||
|
||||
import core
|
||||
import pwnagotchi.ai.featurizer as featurizer
|
||||
import pwnagotchi.ai.reward as reward
|
||||
from pwnagotchi.ai.parameter import Parameter
|
||||
|
||||
|
||||
class Environment(gym.Env):
|
||||
metadata = {'render.modes': ['human']}
|
||||
params = [
|
||||
# Parameter('advertise', trainable=False),
|
||||
# Parameter('deauth', trainable=False),
|
||||
# Parameter('associate', trainable=False),
|
||||
|
||||
Parameter('min_rssi', min_value=-200, max_value=-50),
|
||||
Parameter('ap_ttl', min_value=30, max_value=600),
|
||||
Parameter('sta_ttl', min_value=60, max_value=300),
|
||||
|
||||
Parameter('recon_time', min_value=5, max_value=60),
|
||||
Parameter('max_inactive_scale', min_value=3, max_value=10),
|
||||
Parameter('recon_inactive_multiplier', min_value=1, max_value=3),
|
||||
Parameter('hop_recon_time', min_value=5, max_value=60),
|
||||
Parameter('min_recon_time', min_value=1, max_value=30),
|
||||
Parameter('max_interactions', min_value=1, max_value=25),
|
||||
Parameter('max_misses_for_recon', min_value=3, max_value=10),
|
||||
Parameter('excited_num_epochs', min_value=5, max_value=30),
|
||||
Parameter('bored_num_epochs', min_value=5, max_value=30),
|
||||
Parameter('sad_num_epochs', min_value=5, max_value=30),
|
||||
] + [
|
||||
Parameter('_channel_%d' % ch, min_value=0, max_value=1, meta=ch + 1) for ch in
|
||||
range(featurizer.histogram_size)
|
||||
]
|
||||
|
||||
def __init__(self, agent, epoch):
|
||||
super(Environment, self).__init__()
|
||||
self._agent = agent
|
||||
self._epoch = epoch
|
||||
self._epoch_num = 0
|
||||
self._last_render = None
|
||||
|
||||
self.last = {
|
||||
'reward': 0.0,
|
||||
'observation': None,
|
||||
'policy': None,
|
||||
'params': {},
|
||||
'state': None,
|
||||
'state_v': None
|
||||
}
|
||||
|
||||
self.action_space = spaces.MultiDiscrete([p.space_size() for p in Environment.params if p.trainable])
|
||||
self.observation_space = spaces.Box(low=0, high=1, shape=featurizer.shape, dtype=np.float32)
|
||||
self.reward_range = reward.range
|
||||
|
||||
@staticmethod
|
||||
def policy_size():
|
||||
return len(list(p for p in Environment.params if p.trainable))
|
||||
|
||||
@staticmethod
|
||||
def policy_to_params(policy):
|
||||
num = len(policy)
|
||||
params = {}
|
||||
|
||||
assert len(Environment.params) == num
|
||||
|
||||
channels = []
|
||||
|
||||
for i in range(num):
|
||||
param = Environment.params[i]
|
||||
|
||||
if '_channel' not in param.name:
|
||||
params[param.name] = param.to_param_value(policy[i])
|
||||
else:
|
||||
has_chan = param.to_param_value(policy[i])
|
||||
# print("%s policy:%s bool:%s" % (param.name, policy[i], has_chan))
|
||||
chan = param.meta
|
||||
if has_chan:
|
||||
channels.append(chan)
|
||||
|
||||
params['channels'] = channels
|
||||
|
||||
return params
|
||||
|
||||
def _next_epoch(self):
|
||||
# core.log("[ai] waiting for epoch to finish ...")
|
||||
return self._epoch.wait_for_epoch_data()
|
||||
|
||||
def _apply_policy(self, policy):
|
||||
new_params = Environment.policy_to_params(policy)
|
||||
self.last['policy'] = policy
|
||||
self.last['params'] = new_params
|
||||
self._agent.on_ai_policy(new_params)
|
||||
|
||||
def step(self, policy):
|
||||
# create the parameters from the policy and update
|
||||
# update them in the algorithm
|
||||
self._apply_policy(policy)
|
||||
self._epoch_num += 1
|
||||
|
||||
# wait for the algorithm to run with the new parameters
|
||||
state = self._next_epoch()
|
||||
|
||||
self.last['reward'] = state['reward']
|
||||
self.last['state'] = state
|
||||
self.last['state_v'] = featurizer.featurize(state, self._epoch_num)
|
||||
|
||||
self._agent.on_ai_step()
|
||||
|
||||
return self.last['state_v'], self.last['reward'], not self._agent.is_training(), {}
|
||||
|
||||
def reset(self):
|
||||
# core.log("[ai] resetting environment ...")
|
||||
self._epoch_num = 0
|
||||
state = self._next_epoch()
|
||||
self.last['state'] = state
|
||||
self.last['state_v'] = featurizer.featurize(state, 1)
|
||||
return self.last['state_v']
|
||||
|
||||
def _render_histogram(self, hist):
|
||||
for ch in range(featurizer.histogram_size):
|
||||
if hist[ch]:
|
||||
core.log(" CH %d: %s" % (ch + 1, hist[ch]))
|
||||
|
||||
def render(self, mode='human', close=False, force=False):
|
||||
# when using a vectorialized environment, render gets called twice
|
||||
# avoid rendering the same data
|
||||
if self._last_render == self._epoch_num:
|
||||
return
|
||||
|
||||
if not self._agent.is_training() and not force:
|
||||
return
|
||||
|
||||
self._last_render = self._epoch_num
|
||||
|
||||
core.log("[ai] --- training epoch %d/%d ---" % (self._epoch_num, self._agent.training_epochs()))
|
||||
core.log("[ai] REWARD: %f" % self.last['reward'])
|
||||
|
||||
# core.log("[ai] policy: %s" % ', '.join("%s:%s" % (name, value) for name, value in self.last['params'].items()))
|
||||
|
||||
core.log("[ai] observation:")
|
||||
for name, value in self.last['state'].items():
|
||||
if 'histogram' in name:
|
||||
core.log(" %s" % name.replace('_histogram', ''))
|
||||
self._render_histogram(value)
|
||||
|
||||
# core.log("[ai] outcome:")
|
||||
# for name, value in self.last['state'].items():
|
||||
# if 'histogram' not in name:
|
||||
# core.log(" %s: %s" % (name, value))
|
@ -0,0 +1,30 @@
|
||||
from gym import spaces
|
||||
|
||||
|
||||
class Parameter(object):
|
||||
def __init__(self, name, value=0.0, min_value=0, max_value=2, meta=None, trainable=True):
|
||||
self.name = name
|
||||
self.trainable = trainable
|
||||
self.meta = meta
|
||||
self.value = value
|
||||
self.min_value = min_value
|
||||
self.max_value = max_value + 1
|
||||
|
||||
# gym.space.Discrete is within [0, 1, 2, ..., n-1]
|
||||
if self.min_value < 0:
|
||||
self.scale_factor = abs(self.min_value)
|
||||
elif self.min_value > 0:
|
||||
self.scale_factor = -self.min_value
|
||||
else:
|
||||
self.scale_factor = 0
|
||||
|
||||
def space_size(self):
|
||||
return self.max_value + self.scale_factor
|
||||
|
||||
def space(self):
|
||||
return spaces.Discrete(self.max_value + self.scale_factor)
|
||||
|
||||
def to_param_value(self, policy_v):
|
||||
self.value = policy_v - self.scale_factor
|
||||
assert self.min_value <= self.value <= self.max_value
|
||||
return int(self.value)
|
@ -0,0 +1,21 @@
|
||||
import pwnagotchi.mesh.wifi as wifi
|
||||
|
||||
range = (-.7, 1.02)
|
||||
fuck_zero = 1e-20
|
||||
|
||||
|
||||
class RewardFunction(object):
|
||||
def __call__(self, epoch_n, state):
|
||||
tot_epochs = epoch_n + fuck_zero
|
||||
tot_interactions = max(state['num_deauths'] + state['num_associations'], state['num_handshakes']) + fuck_zero
|
||||
tot_channels = wifi.NumChannels
|
||||
|
||||
h = state['num_handshakes'] / tot_interactions
|
||||
a = .2 * (state['active_for_epochs'] / tot_epochs)
|
||||
c = .1 * (state['num_hops'] / tot_channels)
|
||||
|
||||
b = -.3 * (state['blind_for_epochs'] / tot_epochs)
|
||||
m = -.3 * (state['missed_interactions'] / tot_interactions)
|
||||
i = -.2 * (state['inactive_for_epochs'] / tot_epochs)
|
||||
|
||||
return h + a + c + b + i + m
|
169
sdcard/rootfs/root/pwnagotchi/scripts/pwnagotchi/ai/train.py
Normal file
169
sdcard/rootfs/root/pwnagotchi/scripts/pwnagotchi/ai/train.py
Normal file
@ -0,0 +1,169 @@
|
||||
import _thread
|
||||
import threading
|
||||
import time
|
||||
import random
|
||||
import os
|
||||
import json
|
||||
|
||||
import core
|
||||
|
||||
import pwnagotchi.ai as ai
|
||||
from pwnagotchi.ai.epoch import Epoch
|
||||
|
||||
|
||||
class Stats(object):
|
||||
def __init__(self, path, events_receiver):
|
||||
self._lock = threading.Lock()
|
||||
self._receiver = events_receiver
|
||||
|
||||
self.path = path
|
||||
self.born_at = time.time()
|
||||
# total epochs lived (trained + just eval)
|
||||
self.epochs_lived = 0
|
||||
# total training epochs
|
||||
self.epochs_trained = 0
|
||||
|
||||
self.worst_reward = 0.0
|
||||
self.best_reward = 0.0
|
||||
|
||||
self.load()
|
||||
|
||||
def on_epoch(self, data, training):
|
||||
best_r = False
|
||||
worst_r = False
|
||||
with self._lock:
|
||||
reward = data['reward']
|
||||
if reward < self.worst_reward:
|
||||
self.worst_reward = reward
|
||||
worst_r = True
|
||||
|
||||
elif reward > self.best_reward:
|
||||
best_r = True
|
||||
self.best_reward = reward
|
||||
|
||||
self.epochs_lived += 1
|
||||
if training:
|
||||
self.epochs_trained += 1
|
||||
|
||||
self.save()
|
||||
|
||||
if best_r:
|
||||
self._receiver.on_ai_best_reward(reward)
|
||||
elif worst_r:
|
||||
self._receiver.on_ai_worst_reward(reward)
|
||||
|
||||
def load(self):
|
||||
with self._lock:
|
||||
if os.path.exists(self.path):
|
||||
core.log("[ai] loading %s" % self.path)
|
||||
with open(self.path, 'rt') as fp:
|
||||
obj = json.load(fp)
|
||||
|
||||
self.born_at = obj['born_at']
|
||||
self.epochs_lived, self.epochs_trained = obj['epochs_lived'], obj['epochs_trained']
|
||||
self.best_reward, self.worst_reward = obj['rewards']['best'], obj['rewards']['worst']
|
||||
|
||||
def save(self):
|
||||
with self._lock:
|
||||
core.log("[ai] saving %s" % self.path)
|
||||
with open(self.path, 'wt') as fp:
|
||||
json.dump({
|
||||
'born_at': self.born_at,
|
||||
'epochs_lived': self.epochs_lived,
|
||||
'epochs_trained': self.epochs_trained,
|
||||
'rewards': {
|
||||
'best': self.best_reward,
|
||||
'worst': self.worst_reward
|
||||
}
|
||||
}, fp)
|
||||
|
||||
|
||||
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']
|
||||
self._stats = Stats("%s.json" % os.path.splitext(self._nn_path)[0], self)
|
||||
|
||||
def set_training(self, training, for_epochs=0):
|
||||
self._is_training = training
|
||||
self._training_epochs = for_epochs
|
||||
|
||||
def is_training(self):
|
||||
return self._is_training
|
||||
|
||||
def training_epochs(self):
|
||||
return self._training_epochs
|
||||
|
||||
def start_ai(self):
|
||||
_thread.start_new_thread(self._ai_worker, ())
|
||||
|
||||
def _save_ai(self):
|
||||
core.log("[ai] saving model to %s ..." % self._nn_path)
|
||||
self._model.save(self._nn_path)
|
||||
|
||||
def on_ai_step(self):
|
||||
self._model.env.render()
|
||||
|
||||
if self._is_training:
|
||||
self._save_ai()
|
||||
|
||||
self._stats.on_epoch(self._epoch.data(), self._is_training)
|
||||
|
||||
def on_ai_training_step(self, _locals, _globals):
|
||||
self._model.env.render()
|
||||
|
||||
def on_ai_policy(self, new_params):
|
||||
core.log("[ai] setting new policy:")
|
||||
for name, value in new_params.items():
|
||||
if name in self._config['personality']:
|
||||
curr_value = self._config['personality'][name]
|
||||
if curr_value != value:
|
||||
core.log("[ai] ! %s: %s -> %s" % (name, curr_value, value))
|
||||
self._config['personality'][name] = value
|
||||
else:
|
||||
core.log("[ai] param %s not in personality configuration!" % name)
|
||||
|
||||
self.run('set wifi.ap.ttl %d' % self._config['personality']['ap_ttl'])
|
||||
self.run('set wifi.sta.ttl %d' % self._config['personality']['sta_ttl'])
|
||||
self.run('set wifi.rssi.min %d' % self._config['personality']['min_rssi'])
|
||||
|
||||
def on_ai_best_reward(self, r):
|
||||
core.log("[ai] best reward so far: %s" % r)
|
||||
self._view.on_motivated(r)
|
||||
|
||||
def on_ai_worst_reward(self, r):
|
||||
core.log("[ai] worst reward so far: %s" % r)
|
||||
self._view.on_demotivated(r)
|
||||
|
||||
def _ai_worker(self):
|
||||
self._model = ai.load(self._config, self, self._epoch)
|
||||
|
||||
self.on_ai_ready()
|
||||
|
||||
epochs_per_episode = self._config['ai']['epochs_per_episode']
|
||||
|
||||
obs = None
|
||||
while True:
|
||||
self._model.env.render()
|
||||
# enter in training mode?
|
||||
if random.random() > self._config['ai']['laziness']:
|
||||
core.log("[ai] learning for %d epochs ..." % epochs_per_episode)
|
||||
try:
|
||||
self.set_training(True, epochs_per_episode)
|
||||
self._model.learn(total_timesteps=epochs_per_episode, callback=self.on_ai_training_step)
|
||||
except Exception as e:
|
||||
core.log("[ai] error while training: %s" % e)
|
||||
finally:
|
||||
self.set_training(False)
|
||||
obs = self._model.env.reset()
|
||||
# init the first time
|
||||
elif obs is None:
|
||||
obs = self._model.env.reset()
|
||||
|
||||
# run the inference
|
||||
action, _ = self._model.predict(obs)
|
||||
obs, _, _, _ = self._model.env.step(action)
|
16
sdcard/rootfs/root/pwnagotchi/scripts/pwnagotchi/ai/utils.py
Normal file
16
sdcard/rootfs/root/pwnagotchi/scripts/pwnagotchi/ai/utils.py
Normal file
@ -0,0 +1,16 @@
|
||||
import numpy as np
|
||||
|
||||
|
||||
def normalize(v, min_v, max_v):
|
||||
return (v - min_v) / (max_v - min_v)
|
||||
|
||||
|
||||
def as_batches(x, y, batch_size, shuffle=True):
|
||||
x_size = len(x)
|
||||
assert x_size == len(y)
|
||||
|
||||
indices = np.random.permutation(x_size) if shuffle else None
|
||||
|
||||
for offset in range(0, x_size - batch_size + 1, batch_size):
|
||||
excerpt = indices[offset:offset + batch_size] if shuffle else slice(offset, offset + batch_size)
|
||||
yield x[excerpt], y[excerpt]
|
167
sdcard/rootfs/root/pwnagotchi/scripts/pwnagotchi/log.py
Normal file
167
sdcard/rootfs/root/pwnagotchi/scripts/pwnagotchi/log.py
Normal file
@ -0,0 +1,167 @@
|
||||
import os
|
||||
import hashlib
|
||||
import time
|
||||
import re
|
||||
from datetime import datetime
|
||||
|
||||
from pwnagotchi.mesh.peer import Peer
|
||||
from file_read_backwards import FileReadBackwards
|
||||
|
||||
LAST_SESSION_FILE = '/root/.pwnagotchi-last-session'
|
||||
|
||||
|
||||
class SessionParser(object):
|
||||
EPOCH_TOKEN = '[epoch '
|
||||
EPOCH_PARSER = re.compile(r'^\s*\[epoch (\d+)\] (.+)')
|
||||
EPOCH_DATA_PARSER = re.compile(r'([a-z_]+)=([^\s]+)')
|
||||
TRAINING_TOKEN = ' training epoch '
|
||||
START_TOKEN = 'connecting to http'
|
||||
DEAUTH_TOKEN = 'deauthing '
|
||||
ASSOC_TOKEN = 'sending association frame to '
|
||||
HANDSHAKE_TOKEN = '!!! captured new handshake '
|
||||
PEER_TOKEN = 'detected unit '
|
||||
|
||||
def _get_last_saved_session_id(self):
|
||||
saved = ''
|
||||
try:
|
||||
with open(LAST_SESSION_FILE, 'rt') as fp:
|
||||
saved = fp.read().strip()
|
||||
except:
|
||||
saved = ''
|
||||
return saved
|
||||
|
||||
def save_session_id(self):
|
||||
with open(LAST_SESSION_FILE, 'w+t') as fp:
|
||||
fp.write(self.last_session_id)
|
||||
self.last_saved_session_id = self.last_session_id
|
||||
|
||||
def _parse_datetime(self, dt):
|
||||
dt = datetime.strptime(dt.split('.')[0], '%Y-%m-%d %H:%M:%S')
|
||||
return time.mktime(dt.timetuple())
|
||||
|
||||
def _parse_stats(self):
|
||||
self.duration = ''
|
||||
self.duration_human = ''
|
||||
self.deauthed = 0
|
||||
self.associated = 0
|
||||
self.handshakes = 0
|
||||
self.epochs = 0
|
||||
self.train_epochs = 0
|
||||
self.peers = 0
|
||||
self.last_peer = None
|
||||
self.min_reward = 1000
|
||||
self.max_reward = -1000
|
||||
self.avg_reward = 0
|
||||
|
||||
started_at = None
|
||||
stopped_at = None
|
||||
cache = {}
|
||||
|
||||
for line in self.last_session:
|
||||
parts = line.split(']')
|
||||
if len(parts) < 2:
|
||||
continue
|
||||
line_timestamp = parts[0].strip('[')
|
||||
line = ']'.join(parts[1:])
|
||||
stopped_at = self._parse_datetime(line_timestamp)
|
||||
if started_at is None:
|
||||
started_at = stopped_at
|
||||
|
||||
if SessionParser.DEAUTH_TOKEN in line and line not in cache:
|
||||
self.deauthed += 1
|
||||
cache[line] = 1
|
||||
|
||||
elif SessionParser.ASSOC_TOKEN in line and line not in cache:
|
||||
self.associated += 1
|
||||
cache[line] = 1
|
||||
|
||||
elif SessionParser.HANDSHAKE_TOKEN in line and line not in cache:
|
||||
self.handshakes += 1
|
||||
cache[line] = 1
|
||||
|
||||
elif SessionParser.TRAINING_TOKEN in line:
|
||||
self.train_epochs += 1
|
||||
|
||||
elif SessionParser.EPOCH_TOKEN in line:
|
||||
self.epochs += 1
|
||||
m = SessionParser.EPOCH_PARSER.findall(line)
|
||||
if m:
|
||||
epoch_num, epoch_data = m[0]
|
||||
m = SessionParser.EPOCH_DATA_PARSER.findall(epoch_data)
|
||||
for key, value in m:
|
||||
if key == 'reward':
|
||||
reward = float(value)
|
||||
self.avg_reward += reward
|
||||
if reward < self.min_reward:
|
||||
self.min_reward = reward
|
||||
|
||||
elif reward > self.max_reward:
|
||||
self.max_reward = reward
|
||||
|
||||
elif SessionParser.PEER_TOKEN in line:
|
||||
m = self._peer_parser.findall(line)
|
||||
if m:
|
||||
name, pubkey, rssi, sid, pwnd_tot, uptime = m[0]
|
||||
if pubkey not in cache:
|
||||
self.last_peer = Peer(sid, 1, int(rssi),
|
||||
{'name': name,
|
||||
'identity': pubkey,
|
||||
'pwnd_tot': int(pwnd_tot)})
|
||||
self.peers += 1
|
||||
cache[pubkey] = self.last_peer
|
||||
else:
|
||||
cache[pubkey].adv['pwnd_tot'] = pwnd_tot
|
||||
|
||||
if started_at is not None:
|
||||
self.duration = stopped_at - started_at
|
||||
mins, secs = divmod(self.duration, 60)
|
||||
hours, mins = divmod(mins, 60)
|
||||
else:
|
||||
hours = mins = secs = 0
|
||||
|
||||
self.duration = '%02d:%02d:%02d' % (hours, mins, secs)
|
||||
self.duration_human = []
|
||||
if hours > 0:
|
||||
self.duration_human.append('%d hours' % hours)
|
||||
if mins > 0:
|
||||
self.duration_human.append('%d minutes' % mins)
|
||||
if secs > 0:
|
||||
self.duration_human.append('%d seconds' % secs)
|
||||
|
||||
self.duration_human = ', '.join(self.duration_human)
|
||||
self.avg_reward /= self.epochs
|
||||
|
||||
def __init__(self, path='/var/log/pwnagotchi.log'):
|
||||
self.path = path
|
||||
self.last_session = None
|
||||
self.last_session_id = ''
|
||||
self.last_saved_session_id = ''
|
||||
self.duration = ''
|
||||
self.duration_human = ''
|
||||
self.deauthed = 0
|
||||
self.associated = 0
|
||||
self.handshakes = 0
|
||||
self.peers = 0
|
||||
self.last_peer = None
|
||||
self._peer_parser = re.compile(
|
||||
'detected unit (.+)@(.+) \(v.+\) on channel \d+ \(([\d\-]+) dBm\) \[sid:(.+) pwnd_tot:(\d+) uptime:(\d+)\]')
|
||||
|
||||
lines = []
|
||||
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 SessionParser.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()
|
||||
|
||||
self._parse_stats()
|
||||
|
||||
def is_new(self):
|
||||
return self.last_session_id != self.last_saved_session_id
|
@ -0,0 +1,14 @@
|
||||
import os
|
||||
|
||||
from Crypto.PublicKey import RSA
|
||||
import hashlib
|
||||
|
||||
def new_session_id():
|
||||
return ':'.join(['%02x' % b for b in os.urandom(6)])
|
||||
|
||||
|
||||
def get_identity(config):
|
||||
pubkey = None
|
||||
with open(config['main']['pubkey']) as fp:
|
||||
pubkey = RSA.importKey(fp.read())
|
||||
return pubkey, hashlib.sha1(pubkey.exportKey('DER')).hexdigest()
|
@ -0,0 +1,220 @@
|
||||
import time
|
||||
import json
|
||||
import _thread
|
||||
import threading
|
||||
from scapy.all import Dot11, Dot11FCS, Dot11Elt, RadioTap, sendp, sniff
|
||||
|
||||
import core
|
||||
import pwnagotchi
|
||||
import pwnagotchi.ui.faces as faces
|
||||
|
||||
from pwnagotchi.mesh import get_identity
|
||||
import pwnagotchi.mesh.wifi as wifi
|
||||
from pwnagotchi.mesh import new_session_id
|
||||
from pwnagotchi.mesh.peer import Peer
|
||||
|
||||
|
||||
def _dummy_peer_cb(peer):
|
||||
pass
|
||||
|
||||
|
||||
class Advertiser(object):
|
||||
MAX_STALE_TIME = 300
|
||||
|
||||
def __init__(self, iface, name, version, identity, period=0.3, data={}):
|
||||
self._iface = iface
|
||||
self._period = period
|
||||
self._running = False
|
||||
self._stopped = threading.Event()
|
||||
self._peers_lock = threading.Lock()
|
||||
self._adv_lock = threading.Lock()
|
||||
self._new_peer_cb = _dummy_peer_cb
|
||||
self._lost_peer_cb = _dummy_peer_cb
|
||||
self._peers = {}
|
||||
self._frame = None
|
||||
self._me = Peer(new_session_id(), 0, 0, {
|
||||
'name': name,
|
||||
'version': version,
|
||||
'identity': identity,
|
||||
'face': faces.FRIEND,
|
||||
'pwnd_run': 0,
|
||||
'pwnd_tot': 0,
|
||||
'uptime': 0,
|
||||
'epoch': 0,
|
||||
'data': data
|
||||
})
|
||||
self.update()
|
||||
|
||||
def update(self, values={}):
|
||||
with self._adv_lock:
|
||||
for field, value in values.items():
|
||||
self._me.adv[field] = value
|
||||
self._frame = wifi.encapsulate(payload=json.dumps(self._me.adv), addr_from=self._me.session_id)
|
||||
|
||||
def on_peer(self, new_cb, lost_cb):
|
||||
self._new_peer_cb = new_cb
|
||||
self._lost_peer_cb = lost_cb
|
||||
|
||||
def on_face_change(self, old, new):
|
||||
# core.log("face change: %s -> %s" % (old, new))
|
||||
self.update({'face': new})
|
||||
|
||||
def start(self):
|
||||
self._running = True
|
||||
_thread.start_new_thread(self._sender, ())
|
||||
_thread.start_new_thread(self._listener, ())
|
||||
_thread.start_new_thread(self._pruner, ())
|
||||
|
||||
def num_peers(self):
|
||||
with self._peers_lock:
|
||||
return len(self._peers)
|
||||
|
||||
def peers(self):
|
||||
with self._peers_lock:
|
||||
return list(self._peers.values())
|
||||
|
||||
def closest_peer(self):
|
||||
closest = None
|
||||
with self._peers_lock:
|
||||
for ident, peer in self._peers.items():
|
||||
if closest is None or peer.is_closer(closest):
|
||||
closest = peer
|
||||
return closest
|
||||
|
||||
def stop(self):
|
||||
self._running = False
|
||||
self._stopped.set()
|
||||
|
||||
def _sender(self):
|
||||
core.log("started advertiser thread (period:%s sid:%s) ..." % (str(self._period), self._me.session_id))
|
||||
while self._running:
|
||||
try:
|
||||
sendp(self._frame, iface=self._iface, verbose=False, count=5, inter=self._period)
|
||||
except Exception as e:
|
||||
core.log("error: %s" % e)
|
||||
time.sleep(self._period)
|
||||
|
||||
def _on_advertisement(self, src_session_id, channel, rssi, adv):
|
||||
ident = adv['identity']
|
||||
with self._peers_lock:
|
||||
if ident not in self._peers:
|
||||
peer = Peer(src_session_id, channel, rssi, adv)
|
||||
core.log("detected unit %s (v%s) on channel %d (%s dBm) [sid:%s pwnd_tot:%d uptime:%d]" % ( \
|
||||
peer.full_name(),
|
||||
peer.version(),
|
||||
channel,
|
||||
rssi,
|
||||
src_session_id,
|
||||
peer.pwnd_total(),
|
||||
peer.uptime()))
|
||||
|
||||
self._peers[ident] = peer
|
||||
self._new_peer_cb(peer)
|
||||
else:
|
||||
self._peers[ident].update(src_session_id, channel, rssi, adv)
|
||||
|
||||
def _parse_identity(self, radio, dot11, dot11elt):
|
||||
payload = b''
|
||||
while dot11elt:
|
||||
payload += dot11elt.info
|
||||
dot11elt = dot11elt.payload.getlayer(Dot11Elt)
|
||||
|
||||
if payload != b'':
|
||||
adv = json.loads(payload)
|
||||
self._on_advertisement( \
|
||||
dot11.addr3,
|
||||
wifi.freq_to_channel(radio.Channel),
|
||||
radio.dBm_AntSignal,
|
||||
adv)
|
||||
|
||||
def _is_broadcasted_advertisement(self, dot11):
|
||||
# dst bcast + protocol signature + not ours
|
||||
return dot11 is not None and \
|
||||
dot11.addr1 == wifi.BroadcastAddress and \
|
||||
dot11.addr2 == wifi.SignatureAddress and \
|
||||
dot11.addr3 != self._me.session_id
|
||||
|
||||
def _is_frame_for_us(self, dot11):
|
||||
# dst is us + protocol signature + not ours (why would we send a frame to ourself anyway?)
|
||||
return dot11 is not None and \
|
||||
dot11.addr1 == self._me.session_id and \
|
||||
dot11.addr2 == wifi.SignatureAddress and \
|
||||
dot11.addr3 != self._me.session_id
|
||||
|
||||
def _on_packet(self, p):
|
||||
# https://github.com/secdev/scapy/issues/1590
|
||||
if p.haslayer(Dot11):
|
||||
dot11 = p[Dot11]
|
||||
elif p.haslayer(Dot11FCS):
|
||||
dot11 = p[Dot11FCS]
|
||||
else:
|
||||
dot11 = None
|
||||
|
||||
if self._is_broadcasted_advertisement(dot11):
|
||||
try:
|
||||
dot11elt = p.getlayer(Dot11Elt)
|
||||
if dot11elt.ID == wifi.Dot11ElemID_Identity:
|
||||
self._parse_identity(p[RadioTap], dot11, dot11elt)
|
||||
|
||||
else:
|
||||
raise Exception("unknown frame id %d" % dot11elt.ID)
|
||||
|
||||
except Exception as e:
|
||||
core.log("error decoding packet from %s: %s" % (dot11.addr3, e))
|
||||
|
||||
def _listener(self):
|
||||
# core.log("started advertisements listener ...")
|
||||
expr = "type mgt subtype beacon and ether src %s" % wifi.SignatureAddress
|
||||
sniff(iface=self._iface, filter=expr, prn=self._on_packet, store=0, stop_filter=lambda x: self._stopped.isSet())
|
||||
|
||||
def _pruner(self):
|
||||
while self._running:
|
||||
time.sleep(10)
|
||||
with self._peers_lock:
|
||||
stale = []
|
||||
for ident, peer in self._peers.items():
|
||||
inactive_for = peer.inactive_for()
|
||||
if inactive_for >= Advertiser.MAX_STALE_TIME:
|
||||
core.log("peer %s lost (inactive for %ds)" % (peer.full_name(), inactive_for))
|
||||
self._lost_peer_cb(peer)
|
||||
stale.append(ident)
|
||||
|
||||
for ident in stale:
|
||||
del self._peers[ident]
|
||||
|
||||
|
||||
class AsyncAdvertiser(object):
|
||||
def __init__(self, config, view):
|
||||
self._config = config
|
||||
self._view = view
|
||||
self._public_key, self._identity = get_identity(config)
|
||||
self._advertiser = None
|
||||
|
||||
def start_advertising(self):
|
||||
_thread.start_new_thread(self._adv_worker, ())
|
||||
|
||||
def _adv_worker(self):
|
||||
# this will take some time due to scapy being slow to be imported ...
|
||||
from pwnagotchi.mesh.advertise import Advertiser
|
||||
|
||||
self._advertiser = Advertiser(
|
||||
self._config['main']['iface'],
|
||||
pwnagotchi.name(),
|
||||
pwnagotchi.version,
|
||||
self._identity,
|
||||
period=0.3,
|
||||
data=self._config['personality'])
|
||||
|
||||
self._advertiser.on_peer(self._on_new_unit, self._on_lost_unit)
|
||||
|
||||
if self._config['personality']['advertise']:
|
||||
self._advertiser.start()
|
||||
self._view.on_state_change('face', self._advertiser.on_face_change)
|
||||
else:
|
||||
core.log("advertising is disabled")
|
||||
|
||||
def _on_new_unit(self, peer):
|
||||
self._view.on_new_peer(peer)
|
||||
|
||||
def _on_lost_unit(self, peer):
|
||||
self._view.on_lost_peer(peer)
|
@ -0,0 +1,66 @@
|
||||
import time
|
||||
|
||||
import pwnagotchi.mesh.wifi as wifi
|
||||
import pwnagotchi.ui.faces as faces
|
||||
import core
|
||||
|
||||
|
||||
class Peer(object):
|
||||
def __init__(self, sid, channel, rssi, adv):
|
||||
self.first_seen = time.time()
|
||||
self.last_seen = self.first_seen
|
||||
self.session_id = sid
|
||||
self.last_channel = channel
|
||||
self.presence = [0] * wifi.NumChannels
|
||||
self.adv = adv
|
||||
self.rssi = rssi
|
||||
self.presence[channel - 1] = 1
|
||||
|
||||
def update(self, sid, channel, rssi, adv):
|
||||
if self.name() != adv['name']:
|
||||
core.log("peer %s changed name: %s -> %s" % (self.full_name(), self.name(), adv['name']))
|
||||
|
||||
if self.session_id != sid:
|
||||
core.log("peer %s changed session id: %s -> %s" % (self.full_name(), self.session_id, sid))
|
||||
|
||||
self.presence[channel - 1] += 1
|
||||
self.adv = adv
|
||||
self.rssi = rssi
|
||||
self.session_id = sid
|
||||
self.last_seen = time.time()
|
||||
|
||||
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 face(self):
|
||||
return self._adv_field('face', default=faces.FRIEND)
|
||||
|
||||
def name(self):
|
||||
return self._adv_field('name')
|
||||
|
||||
def identity(self):
|
||||
return self._adv_field('identity')
|
||||
|
||||
def version(self):
|
||||
return self._adv_field('version')
|
||||
|
||||
def pwnd_run(self):
|
||||
return int(self._adv_field('pwnd_run', default=0))
|
||||
|
||||
def pwnd_total(self):
|
||||
return int(self._adv_field('pwnd_tot', default=0))
|
||||
|
||||
def uptime(self):
|
||||
return self._adv_field('uptime', default=0)
|
||||
|
||||
def epoch(self):
|
||||
return self._adv_field('epoch', default=0)
|
||||
|
||||
def full_name(self):
|
||||
return '%s@%s' % (self.name(), self.identity())
|
||||
|
||||
def is_closer(self, other):
|
||||
return self.rssi > other.rssi
|
@ -0,0 +1,37 @@
|
||||
SignatureAddress = 'de:ad:be:ef:de:ad'
|
||||
BroadcastAddress = 'ff:ff:ff:ff:ff:ff'
|
||||
Dot11ElemID_Identity = 222
|
||||
NumChannels = 14
|
||||
|
||||
def freq_to_channel(freq):
|
||||
if freq <= 2472:
|
||||
return int(((freq - 2412) / 5) + 1)
|
||||
elif freq == 2484:
|
||||
return int(14)
|
||||
elif 5035 <= freq <= 5865:
|
||||
return int(((freq - 5035) / 5) + 7)
|
||||
else:
|
||||
return 0
|
||||
|
||||
|
||||
def encapsulate(payload, addr_from, addr_to=BroadcastAddress):
|
||||
from scapy.all import Dot11, Dot11Beacon, Dot11Elt, RadioTap
|
||||
|
||||
radio = RadioTap()
|
||||
dot11 = Dot11(type=0, subtype=8, addr1=addr_to, addr2=SignatureAddress, addr3=addr_from)
|
||||
beacon = Dot11Beacon(cap='ESS')
|
||||
frame = radio / dot11 / beacon
|
||||
|
||||
data_size = len(payload)
|
||||
data_left = data_size
|
||||
data_off = 0
|
||||
chunk_size = 255
|
||||
|
||||
while data_left > 0:
|
||||
sz = min(chunk_size, data_left)
|
||||
chunk = payload[data_off: data_off + sz]
|
||||
frame /= Dot11Elt(ID=Dot11ElemID_Identity, info=chunk, len=sz)
|
||||
data_off += sz
|
||||
data_left -= sz
|
||||
|
||||
return frame
|
@ -0,0 +1 @@
|
||||
|
@ -0,0 +1,66 @@
|
||||
from PIL import Image
|
||||
|
||||
|
||||
class Widget(object):
|
||||
def __init__(self, xy, color=0):
|
||||
self.xy = xy
|
||||
self.color = color
|
||||
|
||||
def draw(self, canvas, drawer):
|
||||
raise Exception("not implemented")
|
||||
|
||||
|
||||
class Bitmap(Widget):
|
||||
def __init__(self, path, xy, color=0):
|
||||
super().__init__(xy, color)
|
||||
self.image = Image.open(path)
|
||||
|
||||
def draw(self, canvas, drawer):
|
||||
canvas.paste(self.image, self.xy)
|
||||
|
||||
|
||||
class Line(Widget):
|
||||
def __init__(self, xy, color=0, width=1):
|
||||
super().__init__(xy, color)
|
||||
self.width = width
|
||||
|
||||
def draw(self, canvas, drawer):
|
||||
drawer.line(self.xy, fill=self.color, width=self.width)
|
||||
|
||||
|
||||
class Rect(Widget):
|
||||
def draw(self, canvas, drawer):
|
||||
drawer.rectangle(self.xy, outline=self.color)
|
||||
|
||||
|
||||
class FilledRect(Widget):
|
||||
def draw(self, canvas, drawer):
|
||||
drawer.rectangle(self.xy, fill=self.color)
|
||||
|
||||
|
||||
class Text(Widget):
|
||||
def __init__(self, value="", position=(0, 0), font=None, color=0):
|
||||
super().__init__(position, color)
|
||||
self.value = value
|
||||
self.font = font
|
||||
|
||||
def draw(self, canvas, drawer):
|
||||
if self.value is not None:
|
||||
drawer.text(self.xy, self.value, font=self.font, fill=self.color)
|
||||
|
||||
|
||||
class LabeledValue(Widget):
|
||||
def __init__(self, label, value="", position=(0, 0), label_font=None, text_font=None, color=0):
|
||||
super().__init__(position, color)
|
||||
self.label = label
|
||||
self.value = value
|
||||
self.label_font = label_font
|
||||
self.text_font = text_font
|
||||
|
||||
def draw(self, canvas, drawer):
|
||||
if self.label is None:
|
||||
drawer.text(self.xy, self.value, font=self.label_font, fill=self.color)
|
||||
else:
|
||||
pos = self.xy
|
||||
drawer.text(pos, self.label, font=self.label_font, fill=self.color)
|
||||
drawer.text((pos[0] + 5 + 5 * len(self.label), pos[1]), self.value, font=self.text_font, fill=self.color)
|
123
sdcard/rootfs/root/pwnagotchi/scripts/pwnagotchi/ui/display.py
Normal file
123
sdcard/rootfs/root/pwnagotchi/scripts/pwnagotchi/ui/display.py
Normal file
@ -0,0 +1,123 @@
|
||||
import _thread
|
||||
from threading import Lock
|
||||
|
||||
import io
|
||||
import core
|
||||
import pwnagotchi
|
||||
|
||||
from pwnagotchi.ui.view import WHITE, View
|
||||
|
||||
from http.server import BaseHTTPRequestHandler, HTTPServer
|
||||
|
||||
|
||||
class VideoHandler(BaseHTTPRequestHandler):
|
||||
_lock = Lock()
|
||||
_buffer = None
|
||||
_index = """<html>
|
||||
<head>
|
||||
<title>%s</title>
|
||||
</head>
|
||||
<body>
|
||||
<img src="/ui" id="ui"/>
|
||||
|
||||
<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:
|
||||
writer = io.BytesIO()
|
||||
img.save(writer, format='PNG')
|
||||
VideoHandler._buffer = writer.getvalue()
|
||||
|
||||
def log_message(self, format, *args):
|
||||
return
|
||||
|
||||
def _w(self, data):
|
||||
try:
|
||||
self.wfile.write(data)
|
||||
except:
|
||||
pass
|
||||
|
||||
def do_GET(self):
|
||||
if self._buffer is None:
|
||||
self.send_response(404)
|
||||
|
||||
elif self.path == '/':
|
||||
self.send_response(200)
|
||||
self.send_header('Content-type', 'text/html')
|
||||
self.end_headers()
|
||||
self._w(bytes(self._index % (pwnagotchi.name(), 1000), "utf8"))
|
||||
|
||||
elif self.path.startswith('/ui'):
|
||||
with self._lock:
|
||||
self.send_response(200)
|
||||
self.send_header('Content-type', 'image/png')
|
||||
self.send_header('Content-length', '%d' % len(self._buffer))
|
||||
self.end_headers()
|
||||
self._w(self._buffer)
|
||||
else:
|
||||
self.send_response(404)
|
||||
|
||||
|
||||
class Display(View):
|
||||
def __init__(self, config, state={}):
|
||||
super(Display, self).__init__(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._display = None
|
||||
self._httpd = None
|
||||
self.canvas = None
|
||||
|
||||
if self._enabled:
|
||||
self._init_display()
|
||||
else:
|
||||
self.on_render(self._on_view_rendered)
|
||||
core.log("display module is disabled")
|
||||
|
||||
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)
|
||||
core.log("ui available at http://%s:%d/" % (self._video_address, self._video_port))
|
||||
self._httpd.serve_forever()
|
||||
else:
|
||||
core.log("could not get ip of usb0, video server not starting")
|
||||
|
||||
def _init_display(self):
|
||||
from pwnagotchi.ui.waveshare import EPD
|
||||
# core.log("display module started")
|
||||
self._display = EPD()
|
||||
self._display.init(self._display.FULL_UPDATE)
|
||||
self._display.Clear(WHITE)
|
||||
self._display.init(self._display.PART_UPDATE)
|
||||
self.on_render(self._on_view_rendered)
|
||||
|
||||
def image(self):
|
||||
img = None
|
||||
if self.canvas is not None:
|
||||
img = self.canvas if self._rotation == 0 else self.canvas.rotate(-self._rotation)
|
||||
return img
|
||||
|
||||
def _on_view_rendered(self, img):
|
||||
# core.log("display::_on_view_rendered")
|
||||
VideoHandler.render(img)
|
||||
|
||||
if self._enabled:
|
||||
self.canvas = img if self._rotation == 0 else img.rotate(self._rotation)
|
||||
buf = self._display.getbuffer(self.canvas)
|
||||
self._display.displayPartial(buf)
|
17
sdcard/rootfs/root/pwnagotchi/scripts/pwnagotchi/ui/faces.py
Normal file
17
sdcard/rootfs/root/pwnagotchi/scripts/pwnagotchi/ui/faces.py
Normal file
@ -0,0 +1,17 @@
|
||||
LOOK_R = '(⌐■_■)'
|
||||
LOOK_L = '(■_■-)'
|
||||
SLEEP = '(=‿‿=)'
|
||||
SLEEP2 = '(◕‿‿-)'
|
||||
AWAKE = '(◕‿‿◕)'
|
||||
BORED = '(-‿‿-)'
|
||||
INTENSE = '(°‿‿°)'
|
||||
COOL = '(⌐0_0)'
|
||||
HAPPY = '(^‿‿^)'
|
||||
EXCITED = '(*‿‿*)'
|
||||
MOTIVATED = '(*‿‿*)'
|
||||
DEMOTIVATED = '(╥__╥)'
|
||||
SMART = '(^‿‿^)'
|
||||
LONELY = '(╥__╥)'
|
||||
SAD = '(╥__╥)'
|
||||
FRIEND = '(♥‿‿♥)'
|
||||
BROKEN = '(x‿‿x)'
|
@ -0,0 +1,8 @@
|
||||
from PIL import ImageFont
|
||||
|
||||
PATH = '/usr/share/fonts/truetype/dejavu/DejaVuSansMono'
|
||||
|
||||
Bold = ImageFont.truetype("%s-Bold.ttf" % PATH, 10)
|
||||
BoldSmall = ImageFont.truetype("%s-Bold.ttf" % PATH, 9)
|
||||
Medium = ImageFont.truetype("%s.ttf" % PATH, 10)
|
||||
Huge = ImageFont.truetype("%s-Bold.ttf" % PATH, 35)
|
28
sdcard/rootfs/root/pwnagotchi/scripts/pwnagotchi/ui/state.py
Normal file
28
sdcard/rootfs/root/pwnagotchi/scripts/pwnagotchi/ui/state.py
Normal file
@ -0,0 +1,28 @@
|
||||
from threading import Lock
|
||||
|
||||
|
||||
class State(object):
|
||||
def __init__(self, state={}):
|
||||
self._state = state
|
||||
self._lock = Lock()
|
||||
self._listeners = {}
|
||||
|
||||
def add_listener(self, key, cb):
|
||||
with self._lock:
|
||||
self._listeners[key] = cb
|
||||
|
||||
def items(self):
|
||||
with self._lock:
|
||||
return self._state.items()
|
||||
|
||||
def get(self, key):
|
||||
with self._lock:
|
||||
return self._state[key].value if key in self._state else None
|
||||
|
||||
def set(self, key, value):
|
||||
with self._lock:
|
||||
if key in self._state:
|
||||
prev = self._state[key].value
|
||||
self._state[key].value = value
|
||||
if key in self._listeners and self._listeners[key] is not None and prev != value:
|
||||
self._listeners[key](prev, value)
|
303
sdcard/rootfs/root/pwnagotchi/scripts/pwnagotchi/ui/view.py
Normal file
303
sdcard/rootfs/root/pwnagotchi/scripts/pwnagotchi/ui/view.py
Normal file
@ -0,0 +1,303 @@
|
||||
import _thread
|
||||
from threading import Lock
|
||||
import time
|
||||
from PIL import Image, ImageDraw
|
||||
|
||||
import core
|
||||
import pwnagotchi
|
||||
from pwnagotchi import voice
|
||||
|
||||
import pwnagotchi.ui.fonts as fonts
|
||||
import pwnagotchi.ui.faces as faces
|
||||
from pwnagotchi.ui.components import *
|
||||
from pwnagotchi.ui.state import State
|
||||
|
||||
WHITE = 0xff
|
||||
BLACK = 0x00
|
||||
WIDTH = 122
|
||||
HEIGHT = 250
|
||||
|
||||
|
||||
class View(object):
|
||||
def __init__(self, config, state={}):
|
||||
self._render_cbs = []
|
||||
self._config = config
|
||||
self._canvas = None
|
||||
self._lock = Lock()
|
||||
self._state = State(state={
|
||||
'channel': LabeledValue(color=BLACK, label='CH', value='00', position=(0, 0), label_font=fonts.Bold,
|
||||
text_font=fonts.Medium),
|
||||
'aps': LabeledValue(color=BLACK, label='APS', value='0 (00)', position=(30, 0), label_font=fonts.Bold,
|
||||
text_font=fonts.Medium),
|
||||
#'epoch': LabeledValue(color=BLACK, label='E', value='0000', position=(145, 0), label_font=fonts.Bold,
|
||||
# text_font=fonts.Medium),
|
||||
'uptime': LabeledValue(color=BLACK, label='UP', value='00:00:00', position=(185, 0), label_font=fonts.Bold,
|
||||
text_font=fonts.Medium),
|
||||
|
||||
# 'square': Rect([1, 11, 124, 111]),
|
||||
'line1': Line([0, 13, 250, 13], color=BLACK),
|
||||
'line2': Line([0, 109, 250, 109], color=BLACK),
|
||||
|
||||
# 'histogram': Histogram([4, 94], color = BLACK),
|
||||
|
||||
'face': Text(value=faces.SLEEP, position=(0, 40), color=BLACK, font=fonts.Huge),
|
||||
|
||||
'friend_face': Text(value=None, position=(0, 90), font=fonts.Bold, color=BLACK),
|
||||
'friend_name': Text(value=None, position=(40, 93), font=fonts.BoldSmall, color=BLACK),
|
||||
|
||||
'name': Text(value='%s>' % 'pwnagotchi', position=(125, 20), color=BLACK, font=fonts.Bold),
|
||||
# 'face2': Bitmap( '/root/pwnagotchi/data/images/face_happy.bmp', (0, 20)),
|
||||
'status': Text(value=voice.default(), position=(125, 35), color=BLACK, font=fonts.Medium),
|
||||
|
||||
'shakes': LabeledValue(label='PWND ', value='0 (00)', color=BLACK, position=(0, 110), label_font=fonts.Bold,
|
||||
text_font=fonts.Medium),
|
||||
'mode': Text(value='AUTO', position=(225, 110), font=fonts.Bold, color=BLACK),
|
||||
})
|
||||
|
||||
for key, value in state.items():
|
||||
self._state.set(key, value)
|
||||
|
||||
_thread.start_new_thread(self._refresh_handler, ())
|
||||
|
||||
def on_state_change(self, key, cb):
|
||||
self._state.add_listener(key, cb)
|
||||
|
||||
def on_render(self, cb):
|
||||
if cb not in self._render_cbs:
|
||||
self._render_cbs.append(cb)
|
||||
|
||||
def _refresh_handler(self):
|
||||
delay = 1.0 / self._config['ui']['fps']
|
||||
# core.log("view refresh handler started with period of %.2fs" % delay)
|
||||
|
||||
while True:
|
||||
name = self._state.get('name')
|
||||
self.set('name', name.rstrip('█').strip() if '█' in name else (name + ' █'))
|
||||
self.update()
|
||||
time.sleep(delay)
|
||||
|
||||
def set(self, key, value):
|
||||
self._state.set(key, value)
|
||||
|
||||
def on_starting(self):
|
||||
self.set('status', voice.on_starting())
|
||||
self.set('face', faces.AWAKE)
|
||||
|
||||
def on_ai_ready(self):
|
||||
self.set('mode', '')
|
||||
self.set('face', faces.HAPPY)
|
||||
self.set('status', voice.on_ai_ready())
|
||||
self.update()
|
||||
|
||||
def on_manual_mode(self, log):
|
||||
self.set('mode', 'MANU')
|
||||
self.set('face', faces.SAD if log.handshakes == 0 else faces.HAPPY)
|
||||
self.set('status', voice.on_log(log))
|
||||
self.set('epoch', "%04d" % log.epochs)
|
||||
self.set('uptime', log.duration)
|
||||
self.set('channel', '-')
|
||||
self.set('aps', "%d" % log.associated)
|
||||
self.set('shakes', '%d (%s)' % (log.handshakes, \
|
||||
core.total_unique_handshakes(self._config['bettercap']['handshakes'])))
|
||||
self.set_closest_peer(log.last_peer)
|
||||
|
||||
def is_normal(self):
|
||||
return self._state.get('face') not in (
|
||||
faces.INTENSE,
|
||||
faces.COOL,
|
||||
faces.BORED,
|
||||
faces.HAPPY,
|
||||
faces.EXCITED,
|
||||
faces.MOTIVATED,
|
||||
faces.DEMOTIVATED,
|
||||
faces.SMART,
|
||||
faces.SAD,
|
||||
faces.LONELY)
|
||||
|
||||
def on_normal(self):
|
||||
self.set('face', faces.AWAKE)
|
||||
self.set('status', voice.on_normal())
|
||||
self.update()
|
||||
|
||||
def set_closest_peer(self, peer):
|
||||
if peer is None:
|
||||
self.set('friend_face', None)
|
||||
self.set('friend_name', None)
|
||||
else:
|
||||
# ref. https://www.metageek.com/training/resources/understanding-rssi-2.html
|
||||
if peer.rssi >= -67:
|
||||
num_bars = 4
|
||||
elif peer.rssi >= -70:
|
||||
num_bars = 3
|
||||
elif peer.rssi >= -80:
|
||||
num_bars = 2
|
||||
else:
|
||||
num_bars = 1
|
||||
|
||||
name = '▌' * num_bars
|
||||
name += '│' * (4 - num_bars)
|
||||
name += ' %s %d (%d)' % (peer.name(), peer.pwnd_run(), peer.pwnd_total())
|
||||
|
||||
self.set('friend_face', peer.face())
|
||||
self.set('friend_name', name)
|
||||
self.update()
|
||||
|
||||
def on_new_peer(self, peer):
|
||||
self.set('face', faces.FRIEND)
|
||||
self.set('status', voice.on_new_peer(peer))
|
||||
self.update()
|
||||
|
||||
def on_lost_peer(self, peer):
|
||||
self.set('face', faces.LONELY)
|
||||
self.set('status', voice.on_lost_peer(peer))
|
||||
self.update()
|
||||
|
||||
def on_free_channel(self, channel):
|
||||
self.set('face', faces.SMART)
|
||||
self.set('status', voice.on_free_channel(channel))
|
||||
self.update()
|
||||
|
||||
def wait(self, secs, sleeping=True):
|
||||
was_normal = self.is_normal()
|
||||
part = secs / 10.0
|
||||
|
||||
for step in range(0, 10):
|
||||
# if we weren't in a normal state before goin
|
||||
# to sleep, keep that face and status on for
|
||||
# a while, otherwise the sleep animation will
|
||||
# always override any minor state change before it
|
||||
if was_normal or step > 5:
|
||||
if sleeping:
|
||||
if secs > 1:
|
||||
self.set('face', faces.SLEEP)
|
||||
self.set('status', voice.on_napping(secs))
|
||||
else:
|
||||
self.set('face', faces.SLEEP2)
|
||||
self.set('status', voice.on_awakening())
|
||||
else:
|
||||
self.set('status', voice.on_waiting(secs))
|
||||
if step % 2 == 0:
|
||||
self.set('face', faces.LOOK_R)
|
||||
else:
|
||||
self.set('face', faces.LOOK_L)
|
||||
|
||||
time.sleep(part)
|
||||
secs -= part
|
||||
|
||||
self.on_normal()
|
||||
|
||||
def on_bored(self):
|
||||
self.set('face', faces.BORED)
|
||||
self.set('status', voice.on_bored())
|
||||
self.update()
|
||||
|
||||
def on_sad(self):
|
||||
self.set('face', faces.SAD)
|
||||
self.set('status', voice.on_sad())
|
||||
self.update()
|
||||
|
||||
def on_motivated(self, reward):
|
||||
self.set('face', faces.MOTIVATED)
|
||||
self.set('status', voice.on_motivated(reward))
|
||||
self.update()
|
||||
|
||||
def on_demotivated(self, reward):
|
||||
self.set('face', faces.DEMOTIVATED)
|
||||
self.set('status', voice.on_demotivated(reward))
|
||||
self.update()
|
||||
|
||||
def on_excited(self):
|
||||
self.set('face', faces.EXCITED)
|
||||
self.set('status', voice.on_excited())
|
||||
self.update()
|
||||
|
||||
def on_assoc(self, ap):
|
||||
self.set('face', faces.INTENSE)
|
||||
self.set('status', voice.on_assoc(ap))
|
||||
self.update()
|
||||
|
||||
def on_deauth(self, sta):
|
||||
self.set('face', faces.COOL)
|
||||
self.set('status', voice.on_deauth(sta))
|
||||
self.update()
|
||||
|
||||
def on_miss(self, who):
|
||||
self.set('face', faces.SAD)
|
||||
self.set('status', voice.on_miss(who))
|
||||
self.update()
|
||||
|
||||
def on_lonely(self):
|
||||
self.set('face', faces.LONELY)
|
||||
self.set('status', voice.on_lonely())
|
||||
self.update()
|
||||
|
||||
def on_handshakes(self, new_shakes):
|
||||
self.set('face', faces.HAPPY)
|
||||
self.set('status', voice.on_handshakes(new_shakes))
|
||||
self.update()
|
||||
|
||||
def on_rebooting(self):
|
||||
self.set('face', faces.BROKEN)
|
||||
self.set('status', voice.on_rebooting())
|
||||
self.update()
|
||||
|
||||
def update(self):
|
||||
"""
|
||||
ncalls tottime percall cumtime percall filename:lineno(function)
|
||||
|
||||
19 0.001 0.000 0.007 0.000 Image.py:1137(copy)
|
||||
19 0.001 0.000 0.069 0.004 Image.py:1894(rotate)
|
||||
19 0.001 0.000 0.068 0.004 Image.py:2388(transpose)
|
||||
1 0.000 0.000 0.000 0.000 Image.py:2432(ImagePointHandler)
|
||||
1 0.000 0.000 0.000 0.000 Image.py:2437(ImageTransformHandler)
|
||||
19 0.001 0.000 0.001 0.000 Image.py:2455(_check_size)
|
||||
19 0.002 0.000 0.010 0.001 Image.py:2473(new)
|
||||
1 0.002 0.002 0.127 0.127 Image.py:30(<module>)
|
||||
1 0.000 0.000 0.001 0.001 Image.py:3103(_apply_env_variables)
|
||||
1 0.000 0.000 0.000 0.000 Image.py:3139(Exif)
|
||||
1 0.000 0.000 0.000 0.000 Image.py:495(_E)
|
||||
1 0.000 0.000 0.000 0.000 Image.py:536(Image)
|
||||
76 0.003 0.000 0.003 0.000 Image.py:552(__init__)
|
||||
19 0.000 0.000 0.000 0.000 Image.py:572(size)
|
||||
57 0.004 0.000 0.006 0.000 Image.py:576(_new)
|
||||
76 0.001 0.000 0.002 0.000 Image.py:595(__exit__)
|
||||
76 0.001 0.000 0.003 0.000 Image.py:633(__del__)
|
||||
1 0.000 0.000 0.000 0.000 Image.py:71(DecompressionBombWarning)
|
||||
1 0.000 0.000 0.000 0.000 Image.py:75(DecompressionBombError)
|
||||
1 0.000 0.000 0.000 0.000 Image.py:79(_imaging_not_installed)
|
||||
95 0.002 0.000 0.014 0.000 Image.py:842(load)
|
||||
19 0.001 0.000 0.008 0.000 Image.py:892(convert)
|
||||
1 0.000 0.000 0.000 0.000 ImageColor.py:20(<module>)
|
||||
297 0.012 0.000 0.042 0.000 ImageDraw.py:101(_getink)
|
||||
38 0.001 0.000 0.026 0.001 ImageDraw.py:153(line)
|
||||
295 0.005 0.000 0.007 0.000 ImageDraw.py:252(_multiline_check)
|
||||
8 0.000 0.000 0.001 0.000 ImageDraw.py:258(_multiline_split)
|
||||
267/247 0.033 0.000 1.741 0.007 ImageDraw.py:263(text)
|
||||
8 0.003 0.000 0.237 0.030 ImageDraw.py:282(multiline_text)
|
||||
28 0.001 0.000 0.064 0.002 ImageDraw.py:328(textsize)
|
||||
1 0.000 0.000 0.008 0.008 ImageDraw.py:33(<module>)
|
||||
19 0.002 0.000 0.006 0.000 ImageDraw.py:355(Draw)
|
||||
1 0.000 0.000 0.000 0.000 ImageDraw.py:47(ImageDraw)
|
||||
19 0.002 0.000 0.004 0.000 ImageDraw.py:48(__init__)
|
||||
1 0.000 0.000 0.000 0.000 ImageFont.py:123(FreeTypeFont)
|
||||
3 0.000 0.000 0.002 0.001 ImageFont.py:126(__init__)
|
||||
28 0.001 0.000 0.062 0.002 ImageFont.py:185(getsize)
|
||||
1 0.000 0.000 0.011 0.011 ImageFont.py:28(<module>)
|
||||
259 0.020 0.000 1.435 0.006 ImageFont.py:337(getmask2)
|
||||
1 0.000 0.000 0.000 0.000 ImageFont.py:37(_imagingft_not_installed)
|
||||
1 0.000 0.000 0.000 0.000 ImageFont.py:474(TransposedFont)
|
||||
3 0.000 0.000 0.003 0.001 ImageFont.py:517(truetype)
|
||||
3 0.000 0.000 0.002 0.001 ImageFont.py:542(freetype)
|
||||
1 0.000 0.000 0.000 0.000 ImageFont.py:65(ImageFont)
|
||||
1 0.000 0.000 0.000 0.000 ImageMode.py:17(<module>)
|
||||
1 0.000 0.000 0.000 0.000 ImageMode.py:20(ModeDescriptor)
|
||||
"""
|
||||
with self._lock:
|
||||
self._canvas = Image.new('1', (HEIGHT, WIDTH), WHITE)
|
||||
drawer = ImageDraw.Draw(self._canvas)
|
||||
|
||||
for key, lv in self._state.items():
|
||||
lv.draw(self._canvas, drawer)
|
||||
|
||||
for cb in self._render_cbs:
|
||||
cb(self._canvas)
|
338
sdcard/rootfs/root/pwnagotchi/scripts/pwnagotchi/ui/waveshare.py
Normal file
338
sdcard/rootfs/root/pwnagotchi/scripts/pwnagotchi/ui/waveshare.py
Normal file
@ -0,0 +1,338 @@
|
||||
# //*****************************************************************************
|
||||
# * | File : epd2in13.py
|
||||
# * | Author : Waveshare team
|
||||
# * | Function : Electronic paper driver
|
||||
# * | Info :
|
||||
# *----------------
|
||||
# * | This version: V3.0
|
||||
# * | Date : 2018-11-01
|
||||
# * | Info : python2 demo
|
||||
# * 1.Remove:
|
||||
# digital_write(self, pin, value)
|
||||
# digital_read(self, pin)
|
||||
# delay_ms(self, delaytime)
|
||||
# set_lut(self, lut)
|
||||
# self.lut = self.lut_full_update
|
||||
# * 2.Change:
|
||||
# display_frame -> TurnOnDisplay
|
||||
# set_memory_area -> SetWindow
|
||||
# set_memory_pointer -> SetCursor
|
||||
# * 3.How to use
|
||||
# epd = epd2in13.EPD()
|
||||
# epd.init(epd.lut_full_update)
|
||||
# image = Image.new('1', (epd2in13.EPD_WIDTH, epd2in13.EPD_HEIGHT), 255)
|
||||
# ...
|
||||
# drawing ......
|
||||
# ...
|
||||
# epd.display(getbuffer(image))
|
||||
# ******************************************************************************//
|
||||
# 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 time
|
||||
import spidev
|
||||
import RPi.GPIO as GPIO
|
||||
from PIL import Image
|
||||
|
||||
# Pin definition
|
||||
RST_PIN = 17
|
||||
DC_PIN = 25
|
||||
CS_PIN = 8
|
||||
BUSY_PIN = 24
|
||||
|
||||
# SPI device, bus = 0, device = 0
|
||||
SPI = spidev.SpiDev(0, 0)
|
||||
|
||||
|
||||
def digital_write(pin, value):
|
||||
GPIO.output(pin, value)
|
||||
|
||||
|
||||
def digital_read(pin):
|
||||
return GPIO.input(BUSY_PIN)
|
||||
|
||||
|
||||
def delay_ms(delaytime):
|
||||
time.sleep(delaytime / 1000.0)
|
||||
|
||||
|
||||
def spi_writebyte(data):
|
||||
SPI.writebytes(data)
|
||||
|
||||
|
||||
def module_init():
|
||||
GPIO.setmode(GPIO.BCM)
|
||||
GPIO.setwarnings(False)
|
||||
GPIO.setup(RST_PIN, GPIO.OUT)
|
||||
GPIO.setup(DC_PIN, GPIO.OUT)
|
||||
GPIO.setup(CS_PIN, GPIO.OUT)
|
||||
GPIO.setup(BUSY_PIN, GPIO.IN)
|
||||
SPI.max_speed_hz = 2000000
|
||||
SPI.mode = 0b00
|
||||
return 0;
|
||||
|
||||
|
||||
# Display resolution
|
||||
EPD_WIDTH = 122
|
||||
EPD_HEIGHT = 250
|
||||
|
||||
|
||||
class EPD:
|
||||
def __init__(self):
|
||||
self.reset_pin = RST_PIN
|
||||
self.dc_pin = DC_PIN
|
||||
self.busy_pin = BUSY_PIN
|
||||
self.width = EPD_WIDTH
|
||||
self.height = EPD_HEIGHT
|
||||
|
||||
FULL_UPDATE = 0
|
||||
PART_UPDATE = 1
|
||||
lut_full_update = [
|
||||
0x80, 0x60, 0x40, 0x00, 0x00, 0x00, 0x00, # LUT0: BB: VS 0 ~7
|
||||
0x10, 0x60, 0x20, 0x00, 0x00, 0x00, 0x00, # LUT1: BW: VS 0 ~7
|
||||
0x80, 0x60, 0x40, 0x00, 0x00, 0x00, 0x00, # LUT2: WB: VS 0 ~7
|
||||
0x10, 0x60, 0x20, 0x00, 0x00, 0x00, 0x00, # LUT3: WW: VS 0 ~7
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, # LUT4: VCOM: VS 0 ~7
|
||||
|
||||
0x03, 0x03, 0x00, 0x00, 0x02, # TP0 A~D RP0
|
||||
0x09, 0x09, 0x00, 0x00, 0x02, # TP1 A~D RP1
|
||||
0x03, 0x03, 0x00, 0x00, 0x02, # TP2 A~D RP2
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, # TP3 A~D RP3
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, # TP4 A~D RP4
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, # TP5 A~D RP5
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, # TP6 A~D RP6
|
||||
|
||||
0x15, 0x41, 0xA8, 0x32, 0x30, 0x0A,
|
||||
]
|
||||
|
||||
lut_partial_update = [ # 20 bytes
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, # LUT0: BB: VS 0 ~7
|
||||
0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, # LUT1: BW: VS 0 ~7
|
||||
0x40, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, # LUT2: WB: VS 0 ~7
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, # LUT3: WW: VS 0 ~7
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, # LUT4: VCOM: VS 0 ~7
|
||||
|
||||
0x0A, 0x00, 0x00, 0x00, 0x00, # TP0 A~D RP0
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, # TP1 A~D RP1
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, # TP2 A~D RP2
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, # TP3 A~D RP3
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, # TP4 A~D RP4
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, # TP5 A~D RP5
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, # TP6 A~D RP6
|
||||
|
||||
0x15, 0x41, 0xA8, 0x32, 0x30, 0x0A,
|
||||
]
|
||||
|
||||
# Hardware reset
|
||||
def reset(self):
|
||||
digital_write(self.reset_pin, GPIO.HIGH)
|
||||
delay_ms(200)
|
||||
digital_write(self.reset_pin, GPIO.LOW) # module reset
|
||||
delay_ms(200)
|
||||
digital_write(self.reset_pin, GPIO.HIGH)
|
||||
delay_ms(200)
|
||||
|
||||
def send_command(self, command):
|
||||
digital_write(self.dc_pin, GPIO.LOW)
|
||||
spi_writebyte([command])
|
||||
|
||||
def send_data(self, data):
|
||||
digital_write(self.dc_pin, GPIO.HIGH)
|
||||
spi_writebyte([data])
|
||||
|
||||
def wait_until_idle(self):
|
||||
while (digital_read(self.busy_pin) == 1): # 0: idle, 1: busy
|
||||
delay_ms(100)
|
||||
|
||||
def TurnOnDisplay(self):
|
||||
self.send_command(0x22)
|
||||
self.send_data(0xC7)
|
||||
self.send_command(0x20)
|
||||
self.wait_until_idle()
|
||||
|
||||
def init(self, update):
|
||||
if (module_init() != 0):
|
||||
return -1
|
||||
# EPD hardware init start
|
||||
self.reset()
|
||||
if (update == self.FULL_UPDATE):
|
||||
self.wait_until_idle()
|
||||
self.send_command(0x12) # soft reset
|
||||
self.wait_until_idle()
|
||||
|
||||
self.send_command(0x74) # set analog block control
|
||||
self.send_data(0x54)
|
||||
self.send_command(0x7E) # set digital block control
|
||||
self.send_data(0x3B)
|
||||
|
||||
self.send_command(0x01) # Driver output control
|
||||
self.send_data(0xF9)
|
||||
self.send_data(0x00)
|
||||
self.send_data(0x00)
|
||||
|
||||
self.send_command(0x11) # data entry mode
|
||||
self.send_data(0x01)
|
||||
|
||||
self.send_command(0x44) # set Ram-X address start//end position
|
||||
self.send_data(0x00)
|
||||
self.send_data(0x0F) # 0x0C-->(15+1)*8=128
|
||||
|
||||
self.send_command(0x45) # set Ram-Y address start//end position
|
||||
self.send_data(0xF9) # 0xF9-->(249+1)=250
|
||||
self.send_data(0x00)
|
||||
self.send_data(0x00)
|
||||
self.send_data(0x00)
|
||||
|
||||
self.send_command(0x3C) # BorderWavefrom
|
||||
self.send_data(0x03)
|
||||
|
||||
self.send_command(0x2C) # VCOM Voltage
|
||||
self.send_data(0x55) #
|
||||
|
||||
self.send_command(0x03)
|
||||
self.send_data(self.lut_full_update[70])
|
||||
|
||||
self.send_command(0x04) #
|
||||
self.send_data(self.lut_full_update[71])
|
||||
self.send_data(self.lut_full_update[72])
|
||||
self.send_data(self.lut_full_update[73])
|
||||
|
||||
self.send_command(0x3A) # Dummy Line
|
||||
self.send_data(self.lut_full_update[74])
|
||||
self.send_command(0x3B) # Gate time
|
||||
self.send_data(self.lut_full_update[75])
|
||||
|
||||
self.send_command(0x32)
|
||||
for count in range(70):
|
||||
self.send_data(self.lut_full_update[count])
|
||||
|
||||
self.send_command(0x4E) # set RAM x address count to 0
|
||||
self.send_data(0x00)
|
||||
self.send_command(0x4F) # set RAM y address count to 0X127
|
||||
self.send_data(0xF9)
|
||||
self.send_data(0x00)
|
||||
self.wait_until_idle()
|
||||
else:
|
||||
self.send_command(0x2C) # VCOM Voltage
|
||||
self.send_data(0x26)
|
||||
|
||||
self.wait_until_idle()
|
||||
|
||||
self.send_command(0x32)
|
||||
for count in range(70):
|
||||
self.send_data(self.lut_partial_update[count])
|
||||
|
||||
self.send_command(0x37)
|
||||
self.send_data(0x00)
|
||||
self.send_data(0x00)
|
||||
self.send_data(0x00)
|
||||
self.send_data(0x00)
|
||||
self.send_data(0x40)
|
||||
self.send_data(0x00)
|
||||
self.send_data(0x00)
|
||||
|
||||
self.send_command(0x22)
|
||||
self.send_data(0xC0)
|
||||
self.send_command(0x20)
|
||||
self.wait_until_idle()
|
||||
|
||||
self.send_command(0x3C) # BorderWavefrom
|
||||
self.send_data(0x01)
|
||||
return 0
|
||||
|
||||
def getbuffer(self, image):
|
||||
if self.width % 8 == 0:
|
||||
linewidth = self.width // 8
|
||||
else:
|
||||
linewidth = self.width // 8 + 1
|
||||
|
||||
buf = [0xFF] * (linewidth * self.height)
|
||||
image_monocolor = image.convert('1')
|
||||
imwidth, imheight = image_monocolor.size
|
||||
pixels = image_monocolor.load()
|
||||
|
||||
if (imwidth == self.width and imheight == self.height):
|
||||
# print("Vertical")
|
||||
for y in range(imheight):
|
||||
for x in range(imwidth):
|
||||
if pixels[x, y] == 0:
|
||||
x = imwidth - x
|
||||
buf[x // 8 + y * linewidth] &= ~(0x80 >> (x % 8))
|
||||
elif (imwidth == self.height and imheight == self.width):
|
||||
# print("Horizontal")
|
||||
for y in range(imheight):
|
||||
for x in range(imwidth):
|
||||
newx = y
|
||||
newy = self.height - x - 1
|
||||
if pixels[x, y] == 0:
|
||||
newy = imwidth - newy - 1
|
||||
buf[newx // 8 + newy * linewidth] &= ~(0x80 >> (y % 8))
|
||||
return buf
|
||||
|
||||
def display(self, image):
|
||||
if self.width % 8 == 0:
|
||||
linewidth = self.width // 8
|
||||
else:
|
||||
linewidth = self.width // 8 + 1
|
||||
|
||||
self.send_command(0x24)
|
||||
for j in range(0, self.height):
|
||||
for i in range(0, linewidth):
|
||||
self.send_data(image[i + j * linewidth])
|
||||
self.TurnOnDisplay()
|
||||
|
||||
def displayPartial(self, image):
|
||||
if self.width % 8 == 0:
|
||||
linewidth = self.width // 8
|
||||
else:
|
||||
linewidth = self.width // 8 + 1
|
||||
|
||||
self.send_command(0x24)
|
||||
for j in range(0, self.height):
|
||||
for i in range(0, linewidth):
|
||||
self.send_data(image[i + j * linewidth])
|
||||
self.send_command(0x26)
|
||||
for j in range(0, self.height):
|
||||
for i in range(0, linewidth):
|
||||
self.send_data(~image[i + j * linewidth])
|
||||
self.TurnOnDisplay()
|
||||
|
||||
def Clear(self, color):
|
||||
if self.width % 8 == 0:
|
||||
linewidth = self.width // 8
|
||||
else:
|
||||
linewidth = self.width // 8 + 1
|
||||
# print(linewidth)
|
||||
|
||||
self.send_command(0x24)
|
||||
for j in range(0, self.height):
|
||||
for i in range(0, linewidth):
|
||||
self.send_data(color)
|
||||
self.TurnOnDisplay()
|
||||
|
||||
def sleep(self):
|
||||
self.send_command(0x22) # POWER OFF
|
||||
self.send_data(0xC3)
|
||||
self.send_command(0x20)
|
||||
|
||||
self.send_command(0x10) # enter deep sleep
|
||||
self.send_data(0x01)
|
||||
delay_ms(100)
|
||||
|
||||
### END OF FILE ###
|
147
sdcard/rootfs/root/pwnagotchi/scripts/pwnagotchi/voice.py
Normal file
147
sdcard/rootfs/root/pwnagotchi/scripts/pwnagotchi/voice.py
Normal file
@ -0,0 +1,147 @@
|
||||
import random
|
||||
|
||||
|
||||
def default():
|
||||
return 'ZzzzZZzzzzZzzz'
|
||||
|
||||
|
||||
def on_starting():
|
||||
return random.choice([ \
|
||||
'Hi, I\'m Pwnagotchi!\nStarting ...',
|
||||
'New day, new hunt,\nnew pwns!',
|
||||
'Hack the Planet!'])
|
||||
|
||||
|
||||
def on_ai_ready():
|
||||
return random.choice([
|
||||
'AI ready.',
|
||||
'The neural network\nis ready.'])
|
||||
|
||||
|
||||
def on_normal():
|
||||
return random.choice([ \
|
||||
'',
|
||||
'...'])
|
||||
|
||||
|
||||
def on_free_channel(channel):
|
||||
return 'Hey, channel %d is\nfree! Your AP will\nsay thanks.' % channel
|
||||
|
||||
|
||||
def on_bored():
|
||||
return random.choice([ \
|
||||
'I\'m bored ...',
|
||||
'Let\'s go for a walk!'])
|
||||
|
||||
|
||||
def on_motivated(reward):
|
||||
return 'This is best day\nof my life!'
|
||||
|
||||
|
||||
def on_demotivated(reward):
|
||||
return 'Shitty day :/'
|
||||
|
||||
|
||||
def on_sad():
|
||||
return random.choice([ \
|
||||
'I\'m extremely bored ...',
|
||||
'I\'m very sad ...',
|
||||
'I\'m sad',
|
||||
'...'])
|
||||
|
||||
|
||||
def on_excited():
|
||||
return random.choice([ \
|
||||
'I\'m living the life!',
|
||||
'I pwn therefore I am.',
|
||||
'So many networks!!!',
|
||||
'I\'m having so much\nfun!',
|
||||
'My crime is that of\ncuriosity ...'])
|
||||
|
||||
|
||||
def on_new_peer(peer):
|
||||
return random.choice([ \
|
||||
'Hello\n%s!\nNice to meet you.' % peer.name(),
|
||||
'Unit\n%s\nis nearby!' % peer.name()])
|
||||
|
||||
|
||||
def on_lost_peer(peer):
|
||||
return random.choice([ \
|
||||
'Uhm ...\ngoodbye\n%s' % peer.name(),
|
||||
'%s\nis gone ...' % peer.name()])
|
||||
|
||||
|
||||
def on_miss(who):
|
||||
return random.choice([ \
|
||||
'Whops ...\n%s\nis gone.' % who,
|
||||
'%s\nmissed!' % who,
|
||||
'Missed!'])
|
||||
|
||||
|
||||
def on_lonely():
|
||||
return random.choice([ \
|
||||
'Nobody wants to\nplay with me ...',
|
||||
'I feel so alone ...',
|
||||
'Where\'s everybody?!'])
|
||||
|
||||
|
||||
def on_napping(secs):
|
||||
return random.choice([ \
|
||||
'Napping for %ds ...' % secs,
|
||||
'Zzzzz',
|
||||
'ZzzZzzz (%ds)' % secs])
|
||||
|
||||
|
||||
def on_awakening():
|
||||
return random.choice(['...', '!'])
|
||||
|
||||
|
||||
def on_waiting(secs):
|
||||
return random.choice([ \
|
||||
'Waiting for %ds ...' % secs,
|
||||
'...',
|
||||
'Looking around (%ds)' % secs])
|
||||
|
||||
|
||||
def on_assoc(ap):
|
||||
ssid, bssid = ap['hostname'], ap['mac']
|
||||
what = ssid if ssid != '' and ssid != '<hidden>' else bssid
|
||||
return random.choice([ \
|
||||
'Hey\n%s\nlet\'s be friends!' % what,
|
||||
'Associating to\n%s' % what,
|
||||
'Yo\n%s!' % what])
|
||||
|
||||
|
||||
def on_deauth(sta):
|
||||
return random.choice([ \
|
||||
'Just decided that\n%s\nneeds no WiFi!' % sta['mac'],
|
||||
'Deauthenticating\n%s' % sta['mac'],
|
||||
'Kickbanning\n%s!' % sta['mac']])
|
||||
|
||||
|
||||
def on_handshakes(new_shakes):
|
||||
s = 's' if new_shakes > 1 else ''
|
||||
return 'Cool, we got %d\nnew handshake%s!' % (new_shakes, s)
|
||||
|
||||
|
||||
def on_rebooting():
|
||||
return "Ops, something\nwent wrong ...\nRebooting ..."
|
||||
|
||||
|
||||
def on_log(log):
|
||||
status = 'Kicked %d stations\n' % log.deauthed
|
||||
status += 'Made %d new friends\n' % log.associated
|
||||
status += 'Got %d handshakes\n' % log.handshakes
|
||||
if log.peers == 1:
|
||||
status += 'Met 1 peer'
|
||||
elif log.peers > 0:
|
||||
status += 'Met %d peers' % log.peers
|
||||
return status
|
||||
|
||||
|
||||
def on_log_tweet(log):
|
||||
return 'I\'ve been pwning for %s and kicked %d clients! I\'ve also met %d new friends and ate %d handshakes! #pwnagotchi #pwnlog #pwnlife #hacktheplanet #skynet' % ( \
|
||||
log.duration_human,
|
||||
log.deauthed,
|
||||
log.associated,
|
||||
log.handshakes)
|
9
sdcard/rootfs/root/pwnagotchi/scripts/requirements.txt
Normal file
9
sdcard/rootfs/root/pwnagotchi/scripts/requirements.txt
Normal file
@ -0,0 +1,9 @@
|
||||
Crypto
|
||||
requests
|
||||
pyyaml
|
||||
scapy
|
||||
gym
|
||||
stable-baselines
|
||||
tweepy
|
||||
file_read_backwards
|
||||
numpy
|
13
sdcard/rootfs/root/pwnagotchi/scripts/startup.sh
Executable file
13
sdcard/rootfs/root/pwnagotchi/scripts/startup.sh
Executable file
@ -0,0 +1,13 @@
|
||||
#!/bin/bash
|
||||
|
||||
# blink 10 times to signal ready state
|
||||
/root/pwnagotchi/scripts/blink.sh 10 &
|
||||
|
||||
# start a detached screen session with bettercap
|
||||
if ifconfig | grep usb0 | grep RUNNING; then
|
||||
sudo -H -u root /usr/bin/screen -dmS pwnagotchi -c /root/pwnagotchi/data/screenrc.manual
|
||||
else
|
||||
sudo -H -u root /usr/bin/screen -dmS pwnagotchi -c /root/pwnagotchi/data/screenrc.auto
|
||||
fi
|
||||
|
||||
|
176
sdcard/rootfs/root/pwnagotchi/scripts/test_nn.py
Executable file
176
sdcard/rootfs/root/pwnagotchi/scripts/test_nn.py
Executable file
@ -0,0 +1,176 @@
|
||||
#!/usr/bin/python3
|
||||
import os
|
||||
|
||||
# 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 time
|
||||
import random
|
||||
|
||||
print("loading dependencies ...")
|
||||
|
||||
start = time.time()
|
||||
|
||||
from stable_baselines.common.policies import MlpPolicy
|
||||
from stable_baselines.common.vec_env import DummyVecEnv
|
||||
from stable_baselines import A2C
|
||||
|
||||
quit()
|
||||
|
||||
import pwnagotchi.mesh.wifi as wifi
|
||||
import pwnagotchi.ai.gym as wrappers
|
||||
|
||||
print("deps loaded in %ds" % (time.time() - start))
|
||||
print()
|
||||
|
||||
|
||||
class EpochMock(object):
|
||||
def __init__(self):
|
||||
self.epoch = 0
|
||||
|
||||
def wait_for_epoch_data(self, timeout=None):
|
||||
duration = random.randint(5, 60)
|
||||
slept = random.randint(0, duration)
|
||||
|
||||
tot_epochs = self.epoch + 1
|
||||
|
||||
num_active = random.randint(0, tot_epochs)
|
||||
num_inactive = tot_epochs - num_active
|
||||
|
||||
tot_interactions = random.randint(0, 100)
|
||||
missed = random.randint(0, int(tot_interactions / 10))
|
||||
num_deauth = random.randint(0, tot_interactions - missed)
|
||||
num_assocs = tot_interactions - num_deauth
|
||||
|
||||
# time.sleep(duration)
|
||||
|
||||
data = {
|
||||
'aps_histogram': [random.random() for c in range(0, wifi.NumChannels)],
|
||||
'sta_histogram': [random.random() for c in range(0, wifi.NumChannels)],
|
||||
'peers_histogram': [random.random() for c in range(0, wifi.NumChannels)],
|
||||
|
||||
'duration_secs': duration,
|
||||
'slept_for_secs': slept,
|
||||
'blind_for_epochs': random.randint(0, 5),
|
||||
'inactive_for_epochs': num_inactive,
|
||||
'active_for_epochs': num_active,
|
||||
'missed_interactions': missed,
|
||||
'num_hops': random.randint(1, wifi.NumChannels),
|
||||
'num_deauths': num_deauth,
|
||||
'num_associations': num_assocs,
|
||||
'num_handshakes': random.randint(0, tot_interactions),
|
||||
'cpu_load': .5 + random.random(),
|
||||
'mem_usage': .5 + random.random(),
|
||||
'temperature': random.randint(40, 60)
|
||||
}
|
||||
|
||||
self.epoch += 1
|
||||
return data
|
||||
|
||||
|
||||
epoch_mock = EpochMock()
|
||||
env = wrappers.Environment(epoch_mock)
|
||||
env = DummyVecEnv([lambda: env])
|
||||
|
||||
print("learning from random data ...")
|
||||
|
||||
model = A2C(MlpPolicy, env, verbose=1)
|
||||
model.learn(total_timesteps=10)
|
||||
|
||||
model.save("test.nn")
|
||||
|
||||
print("running ...")
|
||||
obs = env.reset()
|
||||
for i in range(1000):
|
||||
env.render()
|
||||
action, _states = model.predict(obs)
|
||||
obs, rewards, dones, info = env.step(action)
|
||||
env.render()
|
||||
|
||||
"""
|
||||
memory = Memory()
|
||||
|
||||
state = env.reset()
|
||||
|
||||
for i in range(0, 10):
|
||||
env.render()
|
||||
policy = env.action_space.sample()
|
||||
|
||||
next_state, reward, done, info = env.step(policy)
|
||||
|
||||
if done:
|
||||
next_state = np.zeros(state.shape)
|
||||
memory.add((state, policy, reward, next_state))
|
||||
|
||||
env.reset()
|
||||
|
||||
state, reward, done, info = env.step(env.action_space.sample())
|
||||
else:
|
||||
memory.add((state, policy, reward, next_state))
|
||||
state = next_state
|
||||
"""
|
||||
|
||||
"""
|
||||
import numpy as np
|
||||
|
||||
import pwnagotchi.ai.nn as nn
|
||||
|
||||
|
||||
def on_epoch(epoch, epoch_time, train_accu, valid_accu):
|
||||
print("epoch:%d duration:%f t_accu:%f v_accu:%f" % (epoch, epoch_time, train_accu, valid_accu))
|
||||
if valid_accu >= 0.98:
|
||||
return True
|
||||
|
||||
|
||||
x = []
|
||||
y = []
|
||||
|
||||
with open('nn-data.csv', 'rt') as fp:
|
||||
for line in fp:
|
||||
line = line.strip()
|
||||
if line != "":
|
||||
v = np.asarray(list(map(float, line.split(','))))
|
||||
x.append(v[1:])
|
||||
y.append(v[0])
|
||||
|
||||
x = np.asarray(x)
|
||||
y = np.asarray(y)
|
||||
|
||||
num_inputs = len(x[0])
|
||||
num_outputs = 2
|
||||
valid_perc = 0.1
|
||||
tot_samples = len(x)
|
||||
valid_samples = int(tot_samples * valid_perc)
|
||||
train_samples = tot_samples - valid_samples
|
||||
|
||||
print("loaded %d samples (inputs:%d train:%d validation:%d)" % (tot_samples, num_inputs, train_samples, valid_samples))
|
||||
|
||||
x_train = x[:train_samples]
|
||||
y_train = y[:train_samples]
|
||||
x_val = x[train_samples:]
|
||||
y_val = y[train_samples:]
|
||||
|
||||
print("training ...")
|
||||
|
||||
net = nn.ANN(layers=(
|
||||
nn.Dense(num_inputs, 150),
|
||||
nn.ReLU(),
|
||||
nn.Dense(150, 150),
|
||||
nn.ReLU(),
|
||||
nn.Dense(150, 150),
|
||||
nn.ReLU(),
|
||||
nn.Dense(150, num_outputs),
|
||||
nn.ReLU(),
|
||||
))
|
||||
|
||||
net.train(x_train, y_train, x_val, y_val, 1000, epoch_cbs=(on_epoch,))
|
||||
|
||||
net.save("test-nn.pkl")
|
||||
# def train(self, x_train, y_train, x_val, y_val, epochs, batch_size=32, epoch_cbs=()):
|
||||
# if cb(epoch, epoch_time, train_accu, valid_accu) is True:
|
||||
"""
|
Reference in New Issue
Block a user