187 Commits

Author SHA1 Message Date
Simone Margaritelli
99f6758aae releasing v1.1.0RC0 2019-10-26 14:14:43 +02:00
Simone Margaritelli
0a33f9c0af new: bump bettercap version to 2.26.1 2019-10-26 10:31:02 +02:00
evilsocket
d231541403 Merge pull request #399 from leon-th/patch-2
Added plugin PAW GPS
2019-10-26 10:29:56 +02:00
evilsocket
f1eb3316c6 Merge pull request #401 from SpiderDead/master
Updated libraries to V4.0 for the 2.7" display
2019-10-26 10:29:08 +02:00
evilsocket
f3a96b7981 Merge pull request #402 from PhyberApex/patch-1
Minor text fixes
2019-10-26 10:27:19 +02:00
PhyberApex
beb2b83f36 Minor text fixes
There is no plural of information.
2019-10-26 02:12:37 +02:00
SpiderDead
24ae443ee9 Updated libraries to V4.0 for the 2.7" display
Signed-off-by: Mike van der Vrugt <mimij68@live.nl>
2019-10-25 23:20:01 +02:00
Simone Margaritelli
3f785ee06a misc: small fix or general refactoring i did not bother commenting 2019-10-25 19:38:23 +02:00
Simone Margaritelli
cf146a54ee misc: small fix or general refactoring i did not bother commenting 2019-10-25 19:36:23 +02:00
Simone Margaritelli
31c1d742e0 fix: webui /shutdown is now on POST 2019-10-25 19:34:31 +02:00
Simone Margaritelli
c0252c9830 fix: safer call to webhook 2019-10-25 19:32:11 +02:00
Simone Margaritelli
4aa29f1b79 misc: small fix or general refactoring i did not bother commenting 2019-10-25 19:15:55 +02:00
Leon T
5dc780a88f Edited defaut.yml to add the pawgps plugin 2019-10-25 17:02:15 +02:00
Leon T
39a6ae1be5 Created the paw-gps plugin v1.0.0
This plugin connects to PAW Server with the GPS hack and saves the GPS location when a handshake is detected
2019-10-25 16:57:05 +02:00
Simone Margaritelli
047f0d5d63 misc: bump bettercap to 2.26 2019-10-25 16:43:14 +02:00
Simone Margaritelli
715e696537 fix: rebooting after setting hostname 2019-10-25 16:37:45 +02:00
Simone Margaritelli
22afb563e3 misc: small fix or general refactoring i did not bother commenting 2019-10-25 16:01:53 +02:00
Simone Margaritelli
5f4ee26f99 new: pwnagotchi folder can now be in /boot/ in which case will be moved to /etc/pwnagotchi 2019-10-25 15:29:10 +02:00
Simone Margaritelli
1959bc08ae Merge branch 'master' of github.com:evilsocket/pwnagotchi 2019-10-25 13:21:17 +02:00
Simone Margaritelli
2c9f54567f misc: small fix or general refactoring i did not bother commenting 2019-10-25 13:21:08 +02:00
evilsocket
63694d57e5 Merge pull request #395 from deveth0/patch-1
Add missing lcdhat
2019-10-25 12:36:08 +02:00
Alex Muthmann
c6c2e0e7ce Update utils.py 2019-10-25 12:25:42 +02:00
Alex Muthmann
cb6365b9f2 Add missing lcdhat 2019-10-25 12:18:13 +02:00
Simone Margaritelli
8b00e0ae10 misc: small fix or general refactoring i did not bother commenting 2019-10-25 12:17:44 +02:00
Simone Margaritelli
7954bb5fcf misc: small fix or general refactoring i did not bother commenting 2019-10-25 12:15:56 +02:00
Simone Margaritelli
06d8cc63fb misc: added debug logs for AI loading times 2019-10-25 11:40:58 +02:00
evilsocket
c4ae3c15bd Merge pull request #392 from deveth0/Display-failed-Backup
Show information on failed backup on display
2019-10-25 11:21:21 +02:00
evilsocket
3604f483aa Merge pull request #393 from deveth0/391_verify_backupfiles
#391: Verify if the configured files exist
2019-10-25 11:21:01 +02:00
Alex Muthmann
d51d3f61ac #391: Verify if the configured files exist 2019-10-25 10:55:45 +02:00
Alex Muthmann
6bb8fede0c Show information on failed backup on display 2019-10-25 10:33:12 +02:00
evilsocket
c35d202ffd Merge pull request #385 from caquino/master
parse /proc/meminfo to gather memory usage
2019-10-25 10:21:46 +02:00
evilsocket
ffa432587a Merge pull request #389 from dadav/fix/bt-tet
Fix lot of bt-tether problems
2019-10-25 10:21:23 +02:00
dadav
6f013bb1ef Fix lot of bt-tether problems 2019-10-25 01:06:29 +02:00
Cassiano Aquino
f8f6608968 one decimal place 2019-10-24 20:35:30 +01:00
Cassiano Aquino
0c176ca308 parse /proc/meminfo to gather memory usage 2019-10-24 20:29:09 +01:00
Simone Margaritelli
ec430a5cba misc: small fix or general refactoring i did not bother commenting 2019-10-24 19:54:55 +02:00
Simone Margaritelli
97733cbf43 fix: hostname validation when provided by config 2019-10-24 18:12:10 +02:00
Simone Margaritelli
4445ef6432 misc: small fix or general refactoring i did not bother commenting 2019-10-24 17:53:02 +02:00
Simone Margaritelli
a25395a945 misc: small fix or general refactoring i did not bother commenting 2019-10-24 17:52:43 +02:00
Simone Margaritelli
0f171c35ce new: unit's name can be now set via main.name configuration parameter 2019-10-24 17:48:37 +02:00
Simone Margaritelli
61b957ac77 fix: prevent user contributed plugins to crash the main process while loading 2019-10-24 17:23:33 +02:00
Simone Margaritelli
30e3898f3c misc: small fix or general refactoring i did not bother commenting 2019-10-24 15:55:00 +02:00
Simone Margaritelli
094dde0e8c fix: 'effective configuration' is a debug log now 2019-10-24 15:47:21 +02:00
Simone Margaritelli
dc5a626bd5 misc: small fix or general refactoring i did not bother commenting 2019-10-24 15:34:52 +02:00
Simone Margaritelli
608aad4820 new: observing and reporting total number of peers met per each epoch 2019-10-24 15:18:04 +02:00
Simone Margaritelli
30f9c16778 new: bumped pwngrid required version to 1.10.1 2019-10-24 14:00:22 +02:00
Simone Margaritelli
97cccf5a1d new: face expression while looking around will change dependig if the unit is with friends or alone 2019-10-24 13:42:43 +02:00
Simone Margaritelli
ce0c248cf4 misc: small fix or general refactoring i did not bother commenting 2019-10-24 13:22:05 +02:00
Simone Margaritelli
d5ac988498 misc: small fix or general refactoring i did not bother commenting 2019-10-24 13:20:55 +02:00
Simone Margaritelli
6d70a24aae misc: small fix or general refactoring i did not bother commenting 2019-10-24 13:09:54 +02:00
Simone Margaritelli
032e183ff7 new: face expression when a new unit is detected depends on the units bond level 2019-10-24 13:02:50 +02:00
Simone Margaritelli
f00c861844 misc: small fix or general refactoring i did not bother commenting 2019-10-24 12:53:52 +02:00
Simone Margaritelli
4addefd57d misc: small fix or general refactoring i did not bother commenting 2019-10-24 12:48:04 +02:00
Simone Margaritelli
2239406272 misc: small fix or general refactoring i did not bother commenting 2019-10-24 12:33:03 +02:00
Simone Margaritelli
0df5e54f91 misc: small fix or general refactoring i did not bother commenting 2019-10-24 12:29:39 +02:00
Simone Margaritelli
3e0833698a fix: forcing view update when calling manual mode 2019-10-24 12:26:18 +02:00
Simone Margaritelli
ab9ddb4513 fix: throttled manual mode grid connections to avoid rate limits 2019-10-24 12:24:38 +02:00
Simone Margaritelli
2beef33251 fix: checking inbox correctly from the grid plugin 2019-10-24 12:21:48 +02:00
evilsocket
2b583d7458 Merge pull request #373 from dadav/fix/lang
Fix/lang
2019-10-24 10:53:29 +02:00
evilsocket
da7e21bbdd Merge pull request #375 from dadav/fix/wigle-bug
[Fix] Wigle plugin - add escape char
2019-10-24 10:53:19 +02:00
evilsocket
d9ddae9a41 Merge pull request #378 from spees/patch-1
Small fix
2019-10-24 10:53:06 +02:00
spees
d306877c7a Small fix
Last change broke the launchers.
2019-10-24 10:26:58 +02:00
dadav
7d34e631f5 add escape char 2019-10-23 23:27:25 +02:00
dadav
f39c154b23 fix en and de 2019-10-23 20:59:22 +02:00
dadav
04b2ee5b44 fix en and de 2019-10-23 20:57:26 +02:00
Simone Margaritelli
9a72a8868b misc: small fix or general refactoring i did not bother commenting 2019-10-23 20:02:42 +02:00
Simone Margaritelli
d636c2b1f2 misc: small fix or general refactoring i did not bother commenting 2019-10-23 19:53:23 +02:00
Simone Margaritelli
84616827ad misc: small fix or general refactoring i did not bother commenting 2019-10-23 19:46:26 +02:00
Simone Margaritelli
d16180189e misc: small fix or general refactoring i did not bother commenting 2019-10-23 19:41:14 +02:00
Simone Margaritelli
885fddfce8 misc: small fix or general refactoring i did not bother commenting 2019-10-23 19:35:51 +02:00
Simone Margaritelli
68d7686a03 misc: small fix or general refactoring i did not bother commenting 2019-10-23 19:32:24 +02:00
Simone Margaritelli
d20f6c8a52 misc: small fix or general refactoring i did not bother commenting 2019-10-23 19:28:05 +02:00
Simone Margaritelli
5bf9f25a46 Merge branch 'master' of github.com:evilsocket/pwnagotchi 2019-10-23 19:24:16 +02:00
Simone Margaritelli
b85e4174dc misc: small fix or general refactoring i did not bother commenting 2019-10-23 19:21:42 +02:00
evilsocket
0de2e3d150 Merge pull request #369 from zenzen666/aircrackonly-minor-correction
Aircrackonly minor correction
2019-10-23 19:13:45 +02:00
evilsocket
412fca5290 Merge pull request #370 from XavierForks/fix/language-fr
Correct some typos and update
2019-10-23 19:13:33 +02:00
Simone Margaritelli
3ab2088c79 misc: small fix or general refactoring i did not bother commenting 2019-10-23 19:09:36 +02:00
Simone Margaritelli
eda8a18788 misc: small fix or general refactoring i did not bother commenting 2019-10-23 19:08:43 +02:00
Xavier Stouder
ea14cb370e fix: forgot some exclamation mark fix
Signed-off-by: Xavier Stouder <xavier@stouder.io>
2019-10-23 19:04:28 +02:00
Xavier Stouder
6f2228f439 fix: add translation
Signed-off-by: Xavier Stouder <xavier@stouder.io>
2019-10-23 18:58:58 +02:00
Xavier Stouder
1d53dc7c4e fix: fix space before !
Signed-off-by: Xavier Stouder <xavier@stouder.io>
2019-10-23 18:58:23 +02:00
Xavier Stouder
202cd1f32e fix: translate a word
Signed-off-by: Xavier Stouder <xavier@stouder.io>
2019-10-23 18:54:12 +02:00
Xavier Stouder
ab871f9d2d fix: fix type of ellpsis in french
Signed-off-by: Xavier Stouder <xavier@stouder.io>
2019-10-23 18:52:40 +02:00
Xavier Stouder
f2ceec0f8f fix: update french translation
Signed-off-by: Xavier Stouder <xavier@stouder.io>
2019-10-23 18:47:47 +02:00
Simone Margaritelli
f734c9cd68 new: bumped pwngrid required version to 1.10.0 2019-10-23 18:21:23 +02:00
Simone Margaritelli
36c3ea5bbc new: new grateful status that can override sad/bored/lonely if units with a strong bond are nearby 2019-10-23 18:19:43 +02:00
Zenzen San
062de3b618 minor logging correction
`on loaded` the plugin was showing a different name: `cleancap plugin loaded`
I've changed it with the name of the plugin to be consistent and easy to find what going on from pwnagotchi.log
2019-10-23 15:59:11 +00:00
Zenzen San
77ada644ed Merge pull request #2 from evilsocket/master
just rebase from origin
2019-10-23 15:56:38 +00:00
Simone Margaritelli
277906a673 fix: fixed bogus support for waveshare lcd displays (fixes #364) 2019-10-23 15:37:12 +02:00
Simone Margaritelli
a78a4b0b3e fix: fixes a race condition in the launcher scripts and enables MANU if eth0 is up (closes #365) 2019-10-23 15:26:53 +02:00
Simone Margaritelli
3ad426916f small refactoring to facilitate integration of the bond equation 2019-10-23 15:20:16 +02:00
Simone Margaritelli
23ef17d4c7 fix: using normal status to signal unread messages in order to avoid BT overlap bug 2019-10-23 15:14:55 +02:00
evilsocket
7779ebc983 Merge pull request #362 from python273/add-non-blocking-screen-updating
Add non blocking display updating
2019-10-23 12:52:02 +02:00
python273
4fa7e9f077 Add slight delay for waveshare v1 ReadBusy
Signed-off-by: python273 <iam@python273.pw>
2019-10-22 20:59:15 +03:00
python273
9ca2424df1 Add non-blocking screen updating
Signed-off-by: python273 <iam@python273.pw>
2019-10-22 20:53:15 +03:00
Simone Margaritelli
8c22b1d6f1 fix: if bettercap starts and no active interfaces are found, if mon0 is not explicitly passed it will fail with a 'No active interfces' error 2019-10-22 12:14:56 +02:00
Simone Margaritelli
538b547560 fix: on rpi4 sometimes systemd fails to monstart 2019-10-22 12:09:41 +02:00
Simone Margaritelli
414a6b4c7a misc: small fix or general refactoring i did not bother commenting 2019-10-22 11:35:35 +02:00
evilsocket
cdf11df270 Merge pull request #354 from caquino/caquino/papirus
papirus, fbi and idempotency changes
2019-10-22 11:11:01 +02:00
evilsocket
ef52cbcc25 Merge pull request #356 from SpiderDead/master
Changed the overall look of the layout on the 2.7"
2019-10-21 23:54:13 +02:00
SpiderDead
b0dab7b589 Changed the overall look of the layout on the 2.7"
Signed-off-by: Mike van der Vrugt <mimij68@live.nl>
2019-10-21 23:45:35 +02:00
Cassiano Aquino
ace1244d10 disable sap plugin for bluetooth service 2019-10-21 18:41:36 +01:00
Cassiano Aquino
84fa293a11 change quotes to allow tab expansion 2019-10-21 17:58:00 +01:00
Simone Margaritelli
139c9df88c fix: fixed auto-backup plugin to only create local backups 2019-10-21 18:51:22 +02:00
Cassiano Aquino
7a721be7dc papirus, fbi and idempotency changes 2019-10-21 16:38:53 +01:00
Simone Margaritelli
56079dfd9d fix: waiting for bettercap's API to start 2019-10-21 16:26:07 +02:00
Simone Margaritelli
90998be24c fix: handling exceptions when bettercap is not running yet 2019-10-21 16:22:38 +02:00
Simone Margaritelli
5cb721f490 fix: correct services dependencies 2019-10-21 16:10:09 +02:00
Simone Margaritelli
c954bf8ffa fix: refactored backup.sh script to not require root login 2019-10-21 15:41:45 +02:00
Simone Margaritelli
41ea0e0747 new: new ui.display.video.on_frame configuration to use fbi on framebuffer based screens 2019-10-21 14:01:21 +02:00
Simone Margaritelli
e943cfad70 misc: small fix or general refactoring i did not bother commenting 2019-10-21 12:27:16 +02:00
Simone Margaritelli
f576fb609b Merge branch 'master' of github.com:evilsocket/pwnagotchi 2019-10-21 11:34:00 +02:00
Simone Margaritelli
1e015fe6f6 misc: replaced ssh-keygen with pwngrid -generate 2019-10-21 11:33:48 +02:00
evilsocket
d526915cce Merge pull request #352 from gkrs/master
Updated polish language pack. New phrase, corrections and cleanup.
2019-10-21 11:29:02 +02:00
gkrs
0ac940f636 Updated polish language pack. New phrase, corrections and cleanup. 2019-10-21 10:57:16 +02:00
evilsocket
da6f3608f3 Merge pull request #350 from friedphish/patch-1
Make wpa-sec URL configurable, part 1.
2019-10-21 10:20:25 +02:00
evilsocket
bdfd021820 Merge pull request #351 from friedphish/patch-2
Update wpa-sec.py
2019-10-21 10:20:15 +02:00
friedphish
4a2091e688 Update wpa-sec.py
Part 2 of closing https://github.com/evilsocket/pwnagotchi/issues/347.
2019-10-21 09:53:32 +02:00
friedphish
16832cd414 Update defaults.yml 2019-10-21 09:48:55 +02:00
evilsocket
020be7185c Merge pull request #299 from dadav/feature/web-hook-for-plugins
Implement webhook for plugins
2019-10-20 22:11:04 +02:00
evilsocket
c8ff69c068 Merge pull request #348 from SpiderDead/master
Added support for Waveshare 2.7inch e-Paper Display
2019-10-20 22:10:39 +02:00
evilsocket
79d252254f Merge pull request #345 from python273/patch-3
Fix Origin header check bypass
2019-10-20 21:42:08 +02:00
Simone Margaritelli
64e677f5df new: implemented auto-update plugin (closes #343) 2019-10-20 21:39:14 +02:00
Simone Margaritelli
bceb3c4c4f misc: small fix or general refactoring i did not bother commenting 2019-10-20 21:36:37 +02:00
Simone Margaritelli
df246b255a misc: small fix or general refactoring i did not bother commenting 2019-10-20 21:35:36 +02:00
SpiderDead
d6d810497e Added libraries for the 2.7" display
Signed-off-by: Mike van der Vrugt <mimij68@live.nl>
2019-10-20 21:28:29 +02:00
SpiderDead
9df1dbe077 Added configuration file for waveshare27inch
Signed-off-by: Mike van der Vrugt <mimij68@live.nl>
2019-10-20 21:27:44 +02:00
SpiderDead
3c97dbf8dc Added waveshare27inch as a known display
Signed-off-by: Mike van der Vrugt <mimij68@live.nl>
2019-10-20 21:27:10 +02:00
SpiderDead
376a74d7ac Added documentation for waveshare27inch
Signed-off-by: Mike van der Vrugt <mimij68@live.nl>
2019-10-20 21:26:32 +02:00
Simone Margaritelli
855b493040 misc: small fix or general refactoring i did not bother commenting 2019-10-20 21:25:27 +02:00
Simone Margaritelli
375486f9d0 misc: small fix or general refactoring i did not bother commenting 2019-10-20 21:20:55 +02:00
Simone Margaritelli
0263777e4b misc: small fix or general refactoring i did not bother commenting 2019-10-20 21:18:43 +02:00
Simone Margaritelli
132666935a misc: small fix or general refactoring i did not bother commenting 2019-10-20 20:59:59 +02:00
Simone Margaritelli
74a75f03b8 misc: small fix or general refactoring i did not bother commenting 2019-10-20 20:57:15 +02:00
Simone Margaritelli
399e78e675 misc: small fix or general refactoring i did not bother commenting 2019-10-20 20:52:22 +02:00
Simone Margaritelli
649091766f misc: small fix or general refactoring i did not bother commenting 2019-10-20 20:47:52 +02:00
Simone Margaritelli
999b130224 misc: small fix or general refactoring i did not bother commenting 2019-10-20 20:46:18 +02:00
Simone Margaritelli
2a450e64ef misc: small fix or general refactoring i did not bother commenting 2019-10-20 20:32:18 +02:00
Simone Margaritelli
6152374024 misc: small fix or general refactoring i did not bother commenting 2019-10-20 19:56:31 +02:00
Simone Margaritelli
f9cbef9697 misc: small fix or general refactoring i did not bother commenting 2019-10-20 19:53:02 +02:00
Simone Margaritelli
682b373c66 misc: small fix or general refactoring i did not bother commenting 2019-10-20 19:51:30 +02:00
Simone Margaritelli
8d0d3df2b0 misc: small fix or general refactoring i did not bother commenting 2019-10-20 19:50:05 +02:00
Simone Margaritelli
6d5054387b misc: small fix or general refactoring i did not bother commenting 2019-10-20 19:48:52 +02:00
Simone Margaritelli
4018071bba misc: small fix or general refactoring i did not bother commenting 2019-10-20 19:47:08 +02:00
Simone Margaritelli
63568f1725 misc: small fix or general refactoring i did not bother commenting 2019-10-20 19:45:45 +02:00
Simone Margaritelli
c72cb5b962 misc: small fix or general refactoring i did not bother commenting 2019-10-20 19:42:15 +02:00
Simone Margaritelli
0cdb8c3221 misc: small fix or general refactoring i did not bother commenting 2019-10-20 19:29:11 +02:00
Simone Margaritelli
ddeefa037d misc: small fix or general refactoring i did not bother commenting 2019-10-20 19:27:31 +02:00
Simone Margaritelli
a4e072cf33 misc: small fix or general refactoring i did not bother commenting 2019-10-20 19:22:04 +02:00
Simone Margaritelli
677b335403 misc: small fix or general refactoring i did not bother commenting 2019-10-20 19:18:50 +02:00
Kirill
f762c3ac0d Fix headers.get('origin') == None check 2019-10-20 20:03:17 +03:00
Simone Margaritelli
152676f651 fix: don't show sad face in manual mode for very short sessions 2019-10-20 18:51:33 +02:00
Simone Margaritelli
0ce1fa9d12 misc: small fix or general refactoring i did not bother commenting 2019-10-20 18:49:45 +02:00
Simone Margaritelli
2e8f2aa1b8 misc: small fix or general refactoring i did not bother commenting 2019-10-20 18:48:05 +02:00
Simone Margaritelli
f7a23b32c1 misc: small fix or general refactoring i did not bother commenting 2019-10-20 18:45:56 +02:00
Kirill
4653c5d95d Fix Origin header check bypass 2019-10-20 19:45:43 +03:00
Simone Margaritelli
c947d5c43b misc: small fix or general refactoring i did not bother commenting 2019-10-20 18:41:57 +02:00
Simone Margaritelli
26bb5d6183 misc: small fix or general refactoring i did not bother commenting 2019-10-20 18:39:01 +02:00
Simone Margaritelli
916be1f63e misc: small fix or general refactoring i did not bother commenting 2019-10-20 18:36:06 +02:00
Simone Margaritelli
4a4b973f60 misc: small fix or general refactoring i did not bother commenting 2019-10-20 18:33:43 +02:00
Simone Margaritelli
d49cefe1e4 misc: small fix or general refactoring i did not bother commenting 2019-10-20 18:32:24 +02:00
Simone Margaritelli
c6b3e11e04 new: new --skip-session for manual mode to skip session parsing 2019-10-20 18:29:36 +02:00
Simone Margaritelli
e8fa682302 misc: small fix or general refactoring i did not bother commenting 2019-10-20 18:20:07 +02:00
Simone Margaritelli
fbd12bf87b misc: small fix or general refactoring i did not bother commenting 2019-10-20 18:11:18 +02:00
Simone Margaritelli
40c01db1d1 misc: small fix or general refactoring i did not bother commenting 2019-10-20 18:08:14 +02:00
Simone Margaritelli
ca2becd9ce working on auto-update plugin 2019-10-20 18:05:53 +02:00
Simone Margaritelli
ff6bf5c198 started working on #343 2019-10-20 17:36:34 +02:00
Simone Margaritelli
cd5d783c52 new: users can now customize the faces via config.yml (ui.faces) 2019-10-20 17:13:05 +02:00
Simone Margaritelli
99e0a31ea8 misc: changed readme image 2019-10-20 16:31:34 +02:00
dadav
5f6cc378f1 Implement webhook 2019-10-20 16:17:46 +02:00
Simone Margaritelli
d45e8c7ba0 new: secured the web ui with CORS 2019-10-20 16:09:27 +02:00
Simone Margaritelli
539df810ed misc: small fix or general refactoring i did not bother commenting 2019-10-20 15:42:08 +02:00
Simone Margaritelli
bd7c64b2af misc: refactored web ui code 2019-10-20 15:41:06 +02:00
Simone Margaritelli
892fda775d fix: throttling report requests a bit to avoid rate limits on the API 2019-10-20 14:55:34 +02:00
Simone Margaritelli
f9c0efc24a new: bump pwngrid to 1.9.0 2019-10-20 14:55:15 +02:00
Simone Margaritelli
400d0e7290 fix: removed unused variable in the grid plugin 2019-10-20 14:32:15 +02:00
Simone Margaritelli
fb2c65ef0a fix: made grid api errors due to rate limiting and stuff way less dramatic 2019-10-20 14:31:45 +02:00
Simone Margaritelli
6d88cb17f3 minor refactoring 2019-10-20 14:28:34 +02:00
Simone Margaritelli
c10f25140c Merge branch 'master' of github.com:evilsocket/pwnagotchi 2019-10-20 14:26:54 +02:00
Simone Margaritelli
d2966098b0 lol dadav 2019-10-20 14:24:40 +02:00
evilsocket
a3a4854427 Merge pull request #338 from spees/master
refactored memtemp so it actually works
2019-10-20 14:17:55 +02:00
evilsocket
e5d623c114 Merge pull request #336 from dadav/fix/tweepy_version
increase version
2019-10-20 13:52:18 +02:00
dadav
68801adcaf increase version 2019-10-20 12:10:01 +02:00
spees
58a085188f refactored to use the already existing functions
Signed-off-by: spees <speeskonijn@gmail.com>
2019-10-19 22:25:12 +02:00
spees
f52687642e refactored so it actually works
Signed-off-by: spees <speeskonijn@gmail.com>
2019-10-19 21:32:55 +02:00
Simone Margaritelli
2d7b6b54fe releasing v1.0.1 2019-10-19 18:56:03 +02:00
Simone Margaritelli
b3aa5bc2c1 fix: bumped pwngrid to 1.8.1 2019-10-19 18:43:16 +02:00
50 changed files with 2341 additions and 857 deletions

View File

@@ -14,7 +14,7 @@
[Pwnagotchi](https://pwnagotchi.ai/) is an [A2C](https://hackernoon.com/intuitive-rl-intro-to-advantage-actor-critic-a2c-4ff545978752)-based "AI" leveraging [bettercap](https://www.bettercap.org/) that learns from its surrounding WiFi environment to maximize the crackable WPA key material it captures (either passively, or by performing authentication and association attacks). This material is collected as PCAP files containing any form of handshake supported by [hashcat](https://hashcat.net/hashcat/), including [PMKIDs](https://www.evilsocket.net/2019/02/13/Pwning-WiFi-networks-with-bettercap-and-the-PMKID-client-less-attack/), [Pwnagotchi](https://pwnagotchi.ai/) is an [A2C](https://hackernoon.com/intuitive-rl-intro-to-advantage-actor-critic-a2c-4ff545978752)-based "AI" leveraging [bettercap](https://www.bettercap.org/) that learns from its surrounding WiFi environment to maximize the crackable WPA key material it captures (either passively, or by performing authentication and association attacks). This material is collected as PCAP files containing any form of handshake supported by [hashcat](https://hashcat.net/hashcat/), including [PMKIDs](https://www.evilsocket.net/2019/02/13/Pwning-WiFi-networks-with-bettercap-and-the-PMKID-client-less-attack/),
full and half WPA handshakes. full and half WPA handshakes.
![ui](https://i.imgur.com/c7xh4hN.png) ![ui](https://i.imgur.com/X68GXrn.png)
Instead of merely playing [Super Mario or Atari games](https://becominghuman.ai/getting-mario-back-into-the-gym-setting-up-super-mario-bros-in-openais-gym-8e39a96c1e41?gi=c4b66c3d5ced) like most reinforcement learning-based "AI" *(yawn)*, Pwnagotchi tunes [its parameters](https://github.com/evilsocket/pwnagotchi/blob/master/pwnagotchi/defaults.yml#L73) over time to **get better at pwning WiFi things to** in the environments you expose it to. Instead of merely playing [Super Mario or Atari games](https://becominghuman.ai/getting-mario-back-into-the-gym-setting-up-super-mario-bros-in-openais-gym-8e39a96c1e41?gi=c4b66c3d5ced) like most reinforcement learning-based "AI" *(yawn)*, Pwnagotchi tunes [its parameters](https://github.com/evilsocket/pwnagotchi/blob/master/pwnagotchi/defaults.yml#L73) over time to **get better at pwning WiFi things to** in the environments you expose it to.

View File

@@ -3,6 +3,7 @@ if __name__ == '__main__':
import argparse import argparse
import time import time
import logging import logging
import yaml
import pwnagotchi import pwnagotchi
import pwnagotchi.grid as grid import pwnagotchi.grid as grid
@@ -21,6 +22,9 @@ if __name__ == '__main__':
help='If this file exists, configuration will be merged and this will override default values.') help='If this file exists, configuration will be merged and this will override default values.')
parser.add_argument('--manual', dest="do_manual", action="store_true", default=False, help="Manual mode.") parser.add_argument('--manual', dest="do_manual", action="store_true", default=False, help="Manual mode.")
parser.add_argument('--skip-session', dest="skip_session", action="store_true", default=False,
help="Skip last session parsing in manual mode.")
parser.add_argument('--clear', dest="do_clear", action="store_true", default=False, parser.add_argument('--clear', dest="do_clear", action="store_true", default=False,
help="Clear the ePaper display and exit.") help="Clear the ePaper display and exit.")
@@ -31,6 +35,8 @@ if __name__ == '__main__':
config = utils.load_config(args) config = utils.load_config(args)
utils.setup_logging(args, config) utils.setup_logging(args, config)
pwnagotchi.set_name(config['main']['name'])
plugins.load(config) plugins.load(config)
display = Display(config=config, state={'name': '%s>' % pwnagotchi.name()}) display = Display(config=config, state={'name': '%s>' % pwnagotchi.name()})
@@ -39,6 +45,8 @@ if __name__ == '__main__':
logging.info("%s@%s (v%s)" % (pwnagotchi.name(), agent.fingerprint(), pwnagotchi.version)) logging.info("%s@%s (v%s)" % (pwnagotchi.name(), agent.fingerprint(), pwnagotchi.version))
logging.debug("effective configuration:\n\n%s\n\n" % yaml.dump(config, default_flow_style=False))
for _, plugin in plugins.loaded.items(): for _, plugin in plugins.loaded.items():
logging.debug("plugin '%s' v%s loaded from %s" % (plugin.__name__, plugin.__version__, plugin.__file__)) logging.debug("plugin '%s' v%s loaded from %s" % (plugin.__name__, plugin.__version__, plugin.__file__))
@@ -49,8 +57,8 @@ if __name__ == '__main__':
elif args.do_manual: elif args.do_manual:
logging.info("entering manual mode ...") logging.info("entering manual mode ...")
agent.last_session.parse() agent.last_session.parse(args.skip_session)
if not args.skip_session:
logging.info( logging.info(
"the last session lasted %s (%d completed epochs, trained for %d), average reward:%s (min:%s max:%s)" % ( "the last session lasted %s (%d completed epochs, trained for %d), average reward:%s (min:%s max:%s)" % (
agent.last_session.duration_human, agent.last_session.duration_human,
@@ -62,8 +70,7 @@ if __name__ == '__main__':
while True: while True:
display.on_manual_mode(agent.last_session) display.on_manual_mode(agent.last_session)
time.sleep(1) time.sleep(5)
if grid.is_connected(): if grid.is_connected():
plugins.on('internet_available', agent) plugins.on('internet_available', agent)

View File

@@ -9,10 +9,12 @@
system: system:
boot_options: boot_options:
- "dtoverlay=dwc2" - "dtoverlay=dwc2"
- "dtparam=spi=on"
- "dtoverlay=spi1-3cs" - "dtoverlay=spi1-3cs"
- "dtoverlay=i2c_arm=on" - "dtparam=spi=on"
- "dtoverlay=i2c1=on" - "dtparam=i2c_arm=on"
- "dtparam=i2c1=on"
modules:
- "i2c-dev"
services: services:
enable: enable:
- dphys-swapfile.service - dphys-swapfile.service
@@ -31,10 +33,10 @@
- ifup@wlan0.service - ifup@wlan0.service
packages: packages:
bettercap: bettercap:
url: "https://github.com/bettercap/bettercap/releases/download/v2.25/bettercap_linux_armv6l_2.25.zip" url: "https://github.com/bettercap/bettercap/releases/download/v2.26.1/bettercap_linux_armhf_v2.26.1.zip"
ui: "https://github.com/bettercap/ui/releases/download/v1.3.0/ui.zip" ui: "https://github.com/bettercap/ui/releases/download/v1.3.0/ui.zip"
pwngrid: pwngrid:
url: "https://github.com/evilsocket/pwngrid/releases/download/v1.8.0/pwngrid_linux_armhf_v1.8.0.zip" url: "https://github.com/evilsocket/pwngrid/releases/download/v1.10.1/pwngrid_linux_armhf_v1.10.1.zip"
apt: apt:
hold: hold:
- firmware-atheros - firmware-atheros
@@ -95,26 +97,28 @@
- libfuse-dev - libfuse-dev
- bc - bc
- fonts-freefont-ttf - fonts-freefont-ttf
- fbi
tasks: tasks:
- name: selected hostname
debug:
msg: "{{ pwnagotchi.hostname }}"
- name: build version
debug:
msg: "{{ pwnagotchi.version }}"
- name: change hostname - name: change hostname
hostname: hostname:
name: "{{pwnagotchi.hostname}}" name: "{{pwnagotchi.hostname}}"
when: lookup('file', '/etc/hostname') == "raspberrypi"
register: hostname
- name: add hostname to /etc/hosts - name: add hostname to /etc/hosts
lineinfile: lineinfile:
dest: /etc/hosts dest: /etc/hosts
regexp: '^127\.0\.0\.1[ \t]+localhost' regexp: '^127\.0\.1\.1[ \t]+raspberrypi'
line: '127.0.0.1 localhost {{pwnagotchi.hostname}} {{pwnagotchi.hostname}}.local' line: "127.0.1.1\t{{pwnagotchi.hostname}}"
state: present
when: hostname.changed
- name: disable sap plugin for bluetooth.service
lineinfile:
dest: /lib/systemd/system/bluetooth.service
regexp: '^ExecStart=/usr/lib/bluetooth/bluetoothd$'
line: 'ExecStart=/usr/lib/bluetooth/bluetoothd --noplugin=sap'
state: present state: present
- name: Add re4son-kernel repo key - name: Add re4son-kernel repo key
@@ -161,6 +165,7 @@
git: git:
repo: https://github.com/repaper/gratis.git repo: https://github.com/repaper/gratis.git
dest: /usr/local/src/gratis dest: /usr/local/src/gratis
register: gratisgit
- name: build papirus service - name: build papirus service
make: make:
@@ -169,6 +174,7 @@
params: params:
EPD_IO: epd_io.h EPD_IO: epd_io.h
PANEL_VERSION: 'V231_G2' PANEL_VERSION: 'V231_G2'
when: gratisgit.changed
- name: install papirus service - name: install papirus service
make: make:
@@ -177,6 +183,7 @@
params: params:
EPD_IO: epd_io.h EPD_IO: epd_io.h
PANEL_VERSION: 'V231_G2' PANEL_VERSION: 'V231_G2'
when: gratisgit.changed
- name: configure papirus display size - name: configure papirus display size
lineinfile: lineinfile:
@@ -184,6 +191,16 @@
regexp: "#EPD_SIZE=2.0" regexp: "#EPD_SIZE=2.0"
line: "EPD_SIZE=2.0" line: "EPD_SIZE=2.0"
- name: collect python pip package list
command: "pip3 list"
register: pip_output
- name: set python pip package facts
set_fact:
pip_packages: >
{{ pip_packages | default({}) | combine( { item.split()[0]: item.split()[1] } ) }}
with_items: "{{ pip_output.stdout_lines }}"
- name: acquire python3 pip target - name: acquire python3 pip target
command: "python3 -c 'import sys;print(sys.path.pop())'" command: "python3 -c 'import sys;print(sys.path.pop())'"
register: pip_target register: pip_target
@@ -192,26 +209,39 @@
git: git:
repo: https://github.com/evilsocket/pwnagotchi.git repo: https://github.com/evilsocket/pwnagotchi.git
dest: /usr/local/src/pwnagotchi dest: /usr/local/src/pwnagotchi
register: pwnagotchigit
- name: fetch pwnagotchi version
set_fact:
pwnagotchi_version: "{{ lookup('file', '/usr/local/src/pwnagotchi/pwnagotchi/__init__.py') | replace('\n', ' ') | regex_replace('.*version.*=.*''([0-9]+\\.[0-9]+\\.[0-9]+[A-Za-z0-9]*)''.*', '\\1') }}"
- name: pwnagotchi version found
debug:
msg: "{{ pwnagotchi_version }}"
- name: build pwnagotchi wheel - name: build pwnagotchi wheel
command: "python3 setup.py sdist bdist_wheel" command: "python3 setup.py sdist bdist_wheel"
args: args:
chdir: /usr/local/src/pwnagotchi chdir: /usr/local/src/pwnagotchi
when: (pwnagotchigit.changed) or (pip_packages['pwnagotchi'] is undefined) or (pip_packages['pwnagotchi'] != pwnagotchi.version)
- name: install opencv-python - name: install opencv-python
pip: pip:
name: "https://www.piwheels.hostedpi.com/simple/opencv-python/opencv_python-3.4.3.18-cp37-cp37m-linux_armv6l.whl" name: "https://www.piwheels.hostedpi.com/simple/opencv-python/opencv_python-3.4.3.18-cp37-cp37m-linux_armv6l.whl"
extra_args: "--no-deps --no-cache-dir --platform=linux_armv6l --only-binary=:all: --target={{ pip_target.stdout }}" extra_args: "--no-deps --no-cache-dir --platform=linux_armv6l --only-binary=:all: --target={{ pip_target.stdout }}"
when: (pip_packages['opencv-python'] is undefined) or (pip_packages['opencv-python'] != '3.4.3.18')
- name: install tensorflow - name: install tensorflow
pip: pip:
name: "https://www.piwheels.hostedpi.com/simple/tensorflow/tensorflow-1.13.1-cp37-none-linux_armv6l.whl" name: "https://www.piwheels.hostedpi.com/simple/tensorflow/tensorflow-1.13.1-cp37-none-linux_armv6l.whl"
extra_args: "--no-deps --no-cache-dir --platform=linux_armv6l --only-binary=:all: --target={{ pip_target.stdout }}" extra_args: "--no-deps --no-cache-dir --platform=linux_armv6l --only-binary=:all: --target={{ pip_target.stdout }}"
when: (pip_packages['tensorflow'] is undefined) or (pip_packages['tensorflow'] != '1.13.1')
- name: install pwnagotchi wheel and dependencies - name: install pwnagotchi wheel and dependencies
pip: pip:
name: "{{ lookup('fileglob', '/usr/local/src/pwnagotchi/dist/pwnagotchi*.whl') }}" name: "{{ lookup('fileglob', '/usr/local/src/pwnagotchi/dist/pwnagotchi*.whl') }}"
extra_args: "--no-cache-dir" extra_args: "--no-cache-dir"
when: (pwnagotchigit.changed) or (pip_packages['pwnagotchi'] is undefined) or (pip_packages['pwnagotchi'] != pwnagotchi.version)
- name: download and install pwngrid - name: download and install pwngrid
unarchive: unarchive:
@@ -234,11 +264,13 @@
git: git:
repo: https://github.com/bettercap/caplets.git repo: https://github.com/bettercap/caplets.git
dest: /tmp/caplets dest: /tmp/caplets
register: capletsgit
- name: install bettercap caplets - name: install bettercap caplets
make: make:
chdir: /tmp/caplets chdir: /tmp/caplets
target: install target: install
when: capletsgit.changed
- name: download and install bettercap ui - name: download and install bettercap ui
unarchive: unarchive:
@@ -247,26 +279,6 @@
remote_src: yes remote_src: yes
mode: 0755 mode: 0755
- name: create cpuusage script
copy:
dest: /usr/bin/cpuusage
mode: 0755
content: |
#!/usr/bin/env bash
while true
do
top -b -n1 | awk '/Cpu\(s\)/ { printf("%d %", $2 + $4 + 0.5) }'
sleep 3
done
- name: create memusage script
copy:
dest: /usr/bin/memusage
mode: 0755
content: |
#!/usr/bin/env bash
free -m | awk '/Mem/ { printf( "%d %", $3 / $2 * 100 + 0.5 ) }'
- name: create bootblink script - name: create bootblink script
copy: copy:
dest: /usr/bin/bootblink dest: /usr/bin/bootblink
@@ -292,7 +304,7 @@
# blink 10 times to signal ready state # blink 10 times to signal ready state
/usr/bin/bootblink 10 & /usr/bin/bootblink 10 &
# start a detached screen session with bettercap # start a detached screen session with bettercap
if ifconfig | grep usb0 | grep RUNNING; then if [[ $(ifconfig | grep usb0 | grep RUNNING) ]] || [[ $(cat /sys/class/net/eth0/carrier) ]]; then
# if override file exists, go into auto mode # if override file exists, go into auto mode
if [ -f /root/.pwnagotchi-auto ]; then if [ -f /root/.pwnagotchi-auto ]; then
rm /root/.pwnagotchi-auto rm /root/.pwnagotchi-auto
@@ -310,18 +322,16 @@
mode: 0755 mode: 0755
content: | content: |
#!/usr/bin/env bash #!/usr/bin/env bash
# blink 10 times to signal ready state /usr/bin/monstart
/usr/bin/bootblink 10 & if [[ $(ifconfig | grep usb0 | grep RUNNING) ]] || [[ $(cat /sys/class/net/eth0/carrier) ]]; then
if ifconfig | grep usb0 | grep RUNNING; then
# if override file exists, go into auto mode # if override file exists, go into auto mode
if [ -f /root/.pwnagotchi-auto ]; then if [ -f /root/.pwnagotchi-auto ]; then
rm /root/.pwnagotchi-auto /usr/bin/bettercap -no-colors -caplet pwnagotchi-auto -iface mon0
/usr/bin/bettercap -no-colors -caplet pwnagotchi-auto
else else
/usr/bin/bettercap -no-colors -caplet pwnagotchi-manual /usr/bin/bettercap -no-colors -caplet pwnagotchi-manual -iface mon0
fi fi
else else
/usr/bin/bettercap -no-colors -caplet pwnagotchi-auto /usr/bin/bettercap -no-colors -caplet pwnagotchi-auto -iface mon0
fi fi
- name: create monstart script - name: create monstart script
@@ -434,6 +444,13 @@
line: '{{ item }}' line: '{{ item }}'
with_items: "{{system.boot_options}}" with_items: "{{system.boot_options}}"
- name: adjust /etc/modules
lineinfile:
dest: /etc/modules
insertafter: EOF
line: '{{ item }}'
with_items: "{{system.modules}}"
- name: change root partition - name: change root partition
replace: replace:
dest: /boot/cmdline.txt dest: /boot/cmdline.txt
@@ -480,6 +497,7 @@
touch /root/.pwnagotchi-auto && systemctl restart pwnagotchi touch /root/.pwnagotchi-auto && systemctl restart pwnagotchi
You learn more about me at https://pwnagotchi.ai/ You learn more about me at https://pwnagotchi.ai/
when: hostname.changed
- name: clean apt cache - name: clean apt cache
apt: apt:
@@ -497,7 +515,6 @@
Description=pwngrid peer service. Description=pwngrid peer service.
Documentation=https://pwnagotchi.ai Documentation=https://pwnagotchi.ai
Wants=network.target Wants=network.target
After=bettercap.service
[Service] [Service]
Type=simple Type=simple
@@ -519,14 +536,12 @@
Description=bettercap api.rest service. Description=bettercap api.rest service.
Documentation=https://bettercap.org Documentation=https://bettercap.org
Wants=network.target Wants=network.target
After=network.target After=pwngrid.service
[Service] [Service]
Type=simple Type=simple
PermissionsStartOnly=true PermissionsStartOnly=true
ExecStartPre=/usr/bin/monstart
ExecStart=/usr/bin/bettercap-launcher ExecStart=/usr/bin/bettercap-launcher
ExecStopPost=/usr/bin/monstop
Restart=always Restart=always
RestartSec=30 RestartSec=30

View File

@@ -2,13 +2,48 @@ import subprocess
import os import os
import logging import logging
import time import time
import re
import pwnagotchi.ui.view as view import pwnagotchi.ui.view as view
import pwnagotchi
version = '1.0.0' version = '1.1.0RC0'
_name = None _name = None
def set_name(new_name):
if new_name is None:
return
new_name = new_name.strip()
if new_name == '':
return
if not re.match(r'^[a-zA-Z0-9\-]{2,25}$', new_name):
logging.warning("name '%s' is invalid: min length is 2, max length 25, only a-zA-Z0-9- allowed", new_name)
return
current = name()
if new_name != current:
global _name
logging.info("setting unit hostname '%s' -> '%s'" % (current, new_name))
with open('/etc/hostname', 'wt') as fp:
fp.write(new_name)
with open('/etc/hosts', 'rt') as fp:
prev = fp.read()
logging.debug("old hosts:\n%s\n" % prev)
with open('/etc/hosts', 'wt') as fp:
patched = prev.replace(current, new_name, -1)
logging.debug("new hosts:\n%s\n" % patched)
fp.write(patched)
os.system("hostname '%s'" % new_name)
pwnagotchi.reboot()
def name(): def name():
global _name global _name
if _name is None: if _name is None:
@@ -23,15 +58,21 @@ def uptime():
def mem_usage(): def mem_usage():
out = subprocess.getoutput("free -m") with open('/proc/meminfo') as fp:
for line in out.split("\n"): for line in fp:
line = line.strip() line = line.strip()
if line.startswith("Mem:"): if line.startswith("MemTotal:"):
parts = list(map(int, line.split()[1:])) kb_mem_total = int(line.split()[1])
tot = parts[0] if line.startswith("MemFree:"):
used = parts[1] kb_mem_free = int(line.split()[1])
free = parts[2] if line.startswith("MemAvailable:"):
return used / tot kb_mem_available = int(line.split()[1])
if line.startswith("Buffers:"):
kb_main_buffers = int(line.split()[1])
if line.startswith("Cached:"):
kb_main_cached = int(line.split()[1])
kb_mem_used = kb_mem_total - kb_mem_free - kb_main_cached - kb_main_buffers
return round(kb_mem_used / kb_mem_total, 1)
return 0 return 0

View File

@@ -8,6 +8,7 @@ import _thread
import pwnagotchi import pwnagotchi
import pwnagotchi.utils as utils import pwnagotchi.utils as utils
import pwnagotchi.plugins as plugins import pwnagotchi.plugins as plugins
from pwnagotchi.automata import Automata
from pwnagotchi.log import LastSession from pwnagotchi.log import LastSession
from pwnagotchi.bettercap import Client from pwnagotchi.bettercap import Client
from pwnagotchi.mesh.utils import AsyncAdvertiser from pwnagotchi.mesh.utils import AsyncAdvertiser
@@ -16,13 +17,14 @@ from pwnagotchi.ai.train import AsyncTrainer
RECOVERY_DATA_FILE = '/root/.pwnagotchi-recovery' RECOVERY_DATA_FILE = '/root/.pwnagotchi-recovery'
class Agent(Client, AsyncAdvertiser, AsyncTrainer): class Agent(Client, Automata, AsyncAdvertiser, AsyncTrainer):
def __init__(self, view, config, keypair): def __init__(self, view, config, keypair):
Client.__init__(self, config['bettercap']['hostname'], Client.__init__(self, config['bettercap']['hostname'],
config['bettercap']['scheme'], config['bettercap']['scheme'],
config['bettercap']['port'], config['bettercap']['port'],
config['bettercap']['username'], config['bettercap']['username'],
config['bettercap']['password']) config['bettercap']['password'])
Automata.__init__(self, config, view)
AsyncAdvertiser.__init__(self, config, view, keypair) AsyncAdvertiser.__init__(self, config, view, keypair)
AsyncTrainer.__init__(self, config) AsyncTrainer.__init__(self, config)
@@ -31,6 +33,7 @@ class Agent(Client, AsyncAdvertiser, AsyncTrainer):
self._current_channel = 0 self._current_channel = 0
self._supported_channels = utils.iface_channels(config['main']['iface']) self._supported_channels = utils.iface_channels(config['main']['iface'])
self._view = view self._view = view
self._view.set_agent(self)
self._access_points = [] self._access_points = []
self._last_pwnd = None self._last_pwnd = None
self._history = {} self._history = {}
@@ -49,36 +52,6 @@ class Agent(Client, AsyncAdvertiser, AsyncTrainer):
def supported_channels(self): def supported_channels(self):
return self._supported_channels return self._supported_channels
def set_starting(self):
self._view.on_starting()
def set_ready(self):
plugins.on('ready', self)
def set_free_channel(self, channel):
self._view.on_free_channel(channel)
plugins.on('free_channel', self, channel)
def set_bored(self):
self._view.on_bored()
plugins.on('bored', self)
def set_sad(self):
self._view.on_sad()
plugins.on('sad', self)
def set_excited(self):
self._view.on_excited()
plugins.on('excited', self)
def set_lonely(self):
self._view.on_lonely()
plugins.on('lonely', self)
def set_rebooting(self):
self._view.on_rebooting()
plugins.on('rebooting', self)
def setup_events(self): def setup_events(self):
logging.info("connecting to %s ..." % self.url) logging.info("connecting to %s ..." % self.url)
@@ -135,8 +108,18 @@ class Agent(Client, AsyncAdvertiser, AsyncTrainer):
self.start_advertising() self.start_advertising()
def _wait_bettercap(self):
while True:
try:
s = self.session()
return
except:
logging.info("waiting for bettercap API to be available ...")
time.sleep(1)
def start(self): def start(self):
self.start_ai() self.start_ai()
self._wait_bettercap()
self.setup_events() self.setup_events()
self.set_starting() self.set_starting()
self.start_monitor_mode() self.start_monitor_mode()
@@ -145,11 +128,6 @@ class Agent(Client, AsyncAdvertiser, AsyncTrainer):
self.next_epoch() self.next_epoch()
self.set_ready() self.set_ready()
def wait_for(self, t, sleeping=True):
plugins.on('sleep' if sleeping else 'wait', self, t)
self._view.wait(t, sleeping)
self._epoch.track(sleep=True, inc=t)
def recon(self): def recon(self):
recon_time = self._config['personality']['recon_time'] recon_time = self._config['personality']['recon_time']
max_inactive = self._config['personality']['max_inactive_scale'] max_inactive = self._config['personality']['max_inactive_scale']
@@ -267,6 +245,11 @@ class Agent(Client, AsyncAdvertiser, AsyncTrainer):
def _update_peers(self): def _update_peers(self):
self._view.set_closest_peer(self._closest_peer, len(self._peers)) self._view.set_closest_peer(self._closest_peer, len(self._peers))
def _reboot(self):
self.set_rebooting()
self._save_recovery_data()
pwnagotchi.reboot()
def _save_recovery_data(self): def _save_recovery_data(self):
logging.warning("writing recovery data to %s ..." % RECOVERY_DATA_FILE) logging.warning("writing recovery data to %s ..." % RECOVERY_DATA_FILE)
with open(RECOVERY_DATA_FILE, 'w') as fp: with open(RECOVERY_DATA_FILE, 'w') as fp:
@@ -302,11 +285,14 @@ class Agent(Client, AsyncAdvertiser, AsyncTrainer):
self.run('events.clear') self.run('events.clear')
logging.debug("event polling started ...")
while True: while True:
time.sleep(1) time.sleep(1)
new_shakes = 0 new_shakes = 0
logging.debug("polling events ...")
try:
s = self.session() s = self.session()
self._update_uptime(s) self._update_uptime(s)
@@ -314,7 +300,6 @@ class Agent(Client, AsyncAdvertiser, AsyncTrainer):
self._update_peers() self._update_peers()
self._update_counters() self._update_counters()
try:
for h in [e for e in self.events() if e['tag'] == 'wifi.client.handshake']: for h in [e for e in self.events() if e['tag'] == 'wifi.client.handshake']:
filename = h['data']['file'] filename = h['data']['file']
sta_mac = h['data']['station'] sta_mac = h['data']['station']
@@ -340,7 +325,7 @@ class Agent(Client, AsyncAdvertiser, AsyncTrainer):
plugins.on('handshake', self, filename, ap, sta) plugins.on('handshake', self, filename, ap, sta)
except Exception as e: except Exception as e:
logging.exception("error") logging.error("error: %s" % e)
finally: finally:
self._update_handshakes(new_shakes) self._update_handshakes(new_shakes)
@@ -380,21 +365,6 @@ class Agent(Client, AsyncAdvertiser, AsyncTrainer):
return self._history[who] < self._config['personality']['max_interactions'] return self._history[who] < self._config['personality']['max_interactions']
def _on_miss(self, who):
logging.info("it looks like %s is not in range anymore :/" % who)
self._epoch.track(miss=True)
self._view.on_miss(who)
def _on_error(self, who, e):
error = "%s" % e
# when we're trying to associate or deauth something that is not in range anymore
# (if we are moving), we get the following error from bettercap:
# error 400: 50:c7:bf:2e:d3:37 is an unknown BSSID or it is in the association skip list.
if 'is an unknown BSSID' in error:
self._on_miss(who)
else:
logging.error("%s" % e)
def associate(self, ap, throttle=0): def associate(self, ap, throttle=0):
if self.is_stale(): if self.is_stale():
logging.debug("recon is stale, skipping assoc(%s)" % ap['mac']) logging.debug("recon is stale, skipping assoc(%s)" % ap['mac'])
@@ -471,44 +441,3 @@ class Agent(Client, AsyncAdvertiser, AsyncTrainer):
except Exception as e: except Exception as e:
logging.error("error: %s" % e) logging.error("error: %s" % e)
def is_stale(self):
return self._epoch.num_missed > self._config['personality']['max_misses_for_recon']
def any_activity(self):
return self._epoch.any_activity
def _reboot(self):
self.set_rebooting()
self._save_recovery_data()
pwnagotchi.reboot()
def next_epoch(self):
was_stale = self.is_stale()
did_miss = self._epoch.num_missed
self._epoch.next()
# after X misses during an epoch, set the status to lonely
if was_stale:
logging.warning("agent missed %d interactions -> lonely" % did_miss)
self.set_lonely()
# after X times being bored, the status is set to sad
elif self._epoch.inactive_for >= self._config['personality']['sad_num_epochs']:
logging.warning("%d epochs with no activity -> sad" % self._epoch.inactive_for)
self.set_sad()
# after X times being inactive, the status is set to bored
elif self._epoch.inactive_for >= self._config['personality']['bored_num_epochs']:
logging.warning("%d epochs with no activity -> bored" % self._epoch.inactive_for)
self.set_bored()
# after X times being active, the status is set to happy / excited
elif self._epoch.active_for >= self._config['personality']['excited_num_epochs']:
logging.warning("%d epochs with activity -> excited" % self._epoch.active_for)
self.set_excited()
plugins.on('epoch', self, self._epoch.epoch - 1, self._epoch.data())
if self._epoch.blind_for >= self._config['main']['mon_max_blind_epochs']:
logging.critical("%d epochs without visible access points -> rebooting ..." % self._epoch.blind_for)
self._reboot()
self._epoch.blind_for = 0

View File

@@ -1,14 +1,13 @@
import os import os
import time
import warnings
import logging
# https://stackoverflow.com/questions/40426502/is-there-a-way-to-suppress-the-messages-tensorflow-prints/40426709 # 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'} 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 # https://stackoverflow.com/questions/15777951/how-to-suppress-pandas-future-warning
warnings.simplefilter(action='ignore', category=FutureWarning) warnings.simplefilter(action='ignore', category=FutureWarning)
import logging
def load(config, agent, epoch, from_disk=True): def load(config, agent, epoch, from_disk=True):
config = config['ai'] config = config['ai']
@@ -16,27 +15,51 @@ def load(config, agent, epoch, from_disk=True):
logging.info("ai disabled") logging.info("ai disabled")
return False return False
try:
begin = time.time()
logging.info("[ai] bootstrapping dependencies ...") logging.info("[ai] bootstrapping dependencies ...")
start = time.time()
from stable_baselines import A2C from stable_baselines import A2C
from stable_baselines.common.policies import MlpLstmPolicy logging.debug("[ai] A2C imported in %.2fs" % (time.time() - start))
from stable_baselines.common.vec_env import DummyVecEnv
start = time.time()
from stable_baselines.common.policies import MlpLstmPolicy
logging.debug("[ai] MlpLstmPolicy imported in %.2fs" % (time.time() - start))
start = time.time()
from stable_baselines.common.vec_env import DummyVecEnv
logging.debug("[ai] DummyVecEnv imported in %.2fs" % (time.time() - start))
start = time.time()
import pwnagotchi.ai.gym as wrappers import pwnagotchi.ai.gym as wrappers
logging.debug("[ai] gym wrapper imported in %.2fs" % (time.time() - start))
env = wrappers.Environment(agent, epoch) env = wrappers.Environment(agent, epoch)
env = DummyVecEnv([lambda: env]) env = DummyVecEnv([lambda: env])
logging.info("[ai] bootstrapping model ...") logging.info("[ai] creating model ...")
start = time.time()
a2c = A2C(MlpLstmPolicy, env, **config['params']) a2c = A2C(MlpLstmPolicy, env, **config['params'])
logging.debug("[ai] A2C created in %.2fs" % (time.time() - start))
if from_disk and os.path.exists(config['path']): if from_disk and os.path.exists(config['path']):
logging.info("[ai] loading %s ..." % config['path']) logging.info("[ai] loading %s ..." % config['path'])
start = time.time()
a2c.load(config['path'], env) a2c.load(config['path'], env)
logging.debug("[ai] A2C loaded in %.2fs" % (time.time() - start))
else: else:
logging.info("[ai] model created:") logging.info("[ai] model created:")
for key, value in config['params'].items(): for key, value in config['params'].items():
logging.info(" %s: %s" % (key, value)) logging.info(" %s: %s" % (key, value))
logging.debug("[ai] total loading time is %.2fs" % (time.time() - begin))
return a2c return a2c
except Exception as e:
logging.exception("error while starting AI")
logging.warning("[ai] AI not loaded!")
return False

View File

@@ -37,6 +37,12 @@ class Epoch(object):
self.num_hops = 0 self.num_hops = 0
# number of seconds sleeping # number of seconds sleeping
self.num_slept = 0 self.num_slept = 0
# number of peers seen during this epoch
self.num_peers = 0
# cumulative bond factor
self.tot_bond_factor = 0.0 # cum_bond_factor sounded really bad ...
# average bond factor
self.avg_bond_factor = 0.0
# any activity at all during this epoch? # any activity at all during this epoch?
self.any_activity = False self.any_activity = False
# when the current epoch started # when the current epoch started
@@ -74,10 +80,16 @@ class Epoch(object):
else: else:
self.blind_for = 0 self.blind_for = 0
bond_unit_scale = self.config['personality']['bond_encounters_factor']
self.num_peers = len(peers)
num_peers = self.num_peers + 1e-10 # avoid division by 0
self.tot_bond_factor = sum((peer.encounters for peer in peers)) / bond_unit_scale
self.avg_bond_factor = self.tot_bond_factor / num_peers
num_aps = len(aps) + 1e-10 num_aps = len(aps) + 1e-10
num_sta = sum(len(ap['clients']) for ap in 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 aps_per_chan = [0.0] * wifi.NumChannels
sta_per_chan = [0.0] * wifi.NumChannels sta_per_chan = [0.0] * wifi.NumChannels
peers_per_chan = [0.0] * wifi.NumChannels peers_per_chan = [0.0] * wifi.NumChannels
@@ -162,6 +174,9 @@ class Epoch(object):
'active_for_epochs': self.active_for, 'active_for_epochs': self.active_for,
'missed_interactions': self.num_missed, 'missed_interactions': self.num_missed,
'num_hops': self.num_hops, 'num_hops': self.num_hops,
'num_peers': self.num_peers,
'tot_bond': self.tot_bond_factor,
'avg_bond': self.avg_bond_factor,
'num_deauths': self.num_deauths, 'num_deauths': self.num_deauths,
'num_associations': self.num_assocs, 'num_associations': self.num_assocs,
'num_handshakes': self.num_shakes, 'num_handshakes': self.num_shakes,
@@ -173,14 +188,18 @@ class Epoch(object):
self._epoch_data['reward'] = self._reward(self.epoch + 1, self._epoch_data) self._epoch_data['reward'] = self._reward(self.epoch + 1, self._epoch_data)
self._epoch_data_ready.set() self._epoch_data_ready.set()
logging.info("[epoch %d] duration=%s slept_for=%s blind=%d inactive=%d active=%d hops=%d missed=%d " logging.info("[epoch %d] duration=%s slept_for=%s blind=%d inactive=%d active=%d peers=%d tot_bond=%.2f "
"deauths=%d assocs=%d handshakes=%d cpu=%d%% mem=%d%% temperature=%dC reward=%s" % ( "avg_bond=%.2f hops=%d missed=%d deauths=%d assocs=%d handshakes=%d cpu=%d%% mem=%d%% "
"temperature=%dC reward=%s" % (
self.epoch, self.epoch,
utils.secs_to_hhmmss(self.epoch_duration), utils.secs_to_hhmmss(self.epoch_duration),
utils.secs_to_hhmmss(self.num_slept), utils.secs_to_hhmmss(self.num_slept),
self.blind_for, self.blind_for,
self.inactive_for, self.inactive_for,
self.active_for, self.active_for,
self.num_peers,
self.tot_bond_factor,
self.avg_bond_factor,
self.num_hops, self.num_hops,
self.num_missed, self.num_missed,
self.num_deauths, self.num_deauths,
@@ -195,6 +214,9 @@ class Epoch(object):
self.epoch_started = now self.epoch_started = now
self.did_deauth = False self.did_deauth = False
self.num_deauths = 0 self.num_deauths = 0
self.num_peers = 0
self.tot_bond_factor = 0.0
self.avg_bond_factor = 0.0
self.did_associate = False self.did_associate = False
self.num_assocs = 0 self.num_assocs = 0
self.num_missed = 0 self.num_missed = 0

View File

@@ -8,7 +8,6 @@ import logging
import pwnagotchi.plugins as plugins import pwnagotchi.plugins as plugins
import pwnagotchi.ai as ai import pwnagotchi.ai as ai
from pwnagotchi.ai.epoch import Epoch
class Stats(object): class Stats(object):
@@ -88,7 +87,6 @@ class AsyncTrainer(object):
def __init__(self, config): def __init__(self, config):
self._config = config self._config = config
self._model = None self._model = None
self._epoch = Epoch(config)
self._is_training = False self._is_training = False
self._training_epochs = 0 self._training_epochs = 0
self._nn_path = self._config['ai']['path'] self._nn_path = self._config['ai']['path']

127
pwnagotchi/automata.py Normal file
View File

@@ -0,0 +1,127 @@
import logging
import pwnagotchi.plugins as plugins
from pwnagotchi.ai.epoch import Epoch
# basic mood system
class Automata(object):
def __init__(self, config, view):
self._config = config
self._view = view
self._epoch = Epoch(config)
def _on_miss(self, who):
logging.info("it looks like %s is not in range anymore :/" % who)
self._epoch.track(miss=True)
self._view.on_miss(who)
def _on_error(self, who, e):
error = "%s" % e
# when we're trying to associate or deauth something that is not in range anymore
# (if we are moving), we get the following error from bettercap:
# error 400: 50:c7:bf:2e:d3:37 is an unknown BSSID or it is in the association skip list.
if 'is an unknown BSSID' in error:
self._on_miss(who)
else:
logging.error("%s" % e)
def set_starting(self):
self._view.on_starting()
def set_ready(self):
plugins.on('ready', self)
def in_good_mood(self):
return self._has_support_network_for(1.0)
def _has_support_network_for(self, factor):
bond_factor = self._config['personality']['bond_encounters_factor']
total_encounters = sum(peer.encounters for _, peer in self._peers.items())
support_factor = total_encounters / bond_factor
return support_factor >= factor
# triggered when it's a sad/bad day but you have good friends around ^_^
def set_grateful(self):
self._view.on_grateful()
plugins.on('grateful', self)
def set_lonely(self):
if not self._has_support_network_for(1.0):
logging.info("unit is lonely")
self._view.on_lonely()
plugins.on('lonely', self)
else:
logging.info("unit is grateful instead of lonely")
self.set_grateful()
def set_bored(self):
factor = self._epoch.inactive_for / self._config['personality']['bored_num_epochs']
if not self._has_support_network_for(factor):
logging.warning("%d epochs with no activity -> bored" % self._epoch.inactive_for)
self._view.on_bored()
plugins.on('bored', self)
else:
logging.info("unit is grateful instead of bored")
self.set_grateful()
def set_sad(self):
factor = self._epoch.inactive_for / self._config['personality']['sad_num_epochs']
if not self._has_support_network_for(factor):
logging.warning("%d epochs with no activity -> sad" % self._epoch.inactive_for)
self._view.on_sad()
plugins.on('sad', self)
else:
logging.info("unit is grateful instead of sad")
self.set_grateful()
def set_excited(self):
logging.warning("%d epochs with activity -> excited" % self._epoch.active_for)
self._view.on_excited()
plugins.on('excited', self)
def set_rebooting(self):
self._view.on_rebooting()
plugins.on('rebooting', self)
def wait_for(self, t, sleeping=True):
plugins.on('sleep' if sleeping else 'wait', self, t)
self._view.wait(t, sleeping)
self._epoch.track(sleep=True, inc=t)
def is_stale(self):
return self._epoch.num_missed > self._config['personality']['max_misses_for_recon']
def any_activity(self):
return self._epoch.any_activity
def next_epoch(self):
logging.debug("agent.next_epoch()")
was_stale = self.is_stale()
did_miss = self._epoch.num_missed
self._epoch.next()
# after X misses during an epoch, set the status to lonely
if was_stale:
logging.warning("agent missed %d interactions -> lonely" % did_miss)
self.set_lonely()
# after X times being bored, the status is set to sad
elif self._epoch.inactive_for >= self._config['personality']['sad_num_epochs']:
self.set_sad()
# after X times being inactive, the status is set to bored
elif self._epoch.inactive_for >= self._config['personality']['bored_num_epochs']:
self.set_bored()
# after X times being active, the status is set to happy / excited
elif self._epoch.active_for >= self._config['personality']['excited_num_epochs']:
self.set_excited()
elif self._epoch.active_for >= 5 and self._has_support_network_for(5.0):
self.set_grateful()
plugins.on('epoch', self, self._epoch.epoch - 1, self._epoch.data())
if self._epoch.blind_for >= self._config['main']['mon_max_blind_epochs']:
logging.critical("%d epochs without visible access points -> rebooting ..." % self._epoch.blind_for)
self._reboot()
self._epoch.blind_for = 0

View File

@@ -3,17 +3,7 @@ import requests
from requests.auth import HTTPBasicAuth from requests.auth import HTTPBasicAuth
class Client(object): def decode(r, verbose_errors=True):
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: try:
return r.json() return r.json()
except Exception as e: except Exception as e:
@@ -26,14 +16,25 @@ class Client(object):
raise Exception(err) raise Exception(err)
return r.text return r.text
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 session(self): def session(self):
r = requests.get("%s/session" % self.url, auth=self.auth) r = requests.get("%s/session" % self.url, auth=self.auth)
return self._decode(r) return decode(r)
def events(self): def events(self):
r = requests.get("%s/events" % self.url, auth=self.auth) r = requests.get("%s/events" % self.url, auth=self.auth)
return self._decode(r) return decode(r)
def run(self, command, verbose_errors=True): def run(self, command, verbose_errors=True):
r = requests.post("%s/session" % self.url, auth=self.auth, json={'cmd': command}) r = requests.post("%s/session" % self.url, auth=self.auth, json={'cmd': command})
return self._decode(r, verbose_errors=verbose_errors) return decode(r, verbose_errors=verbose_errors)

View File

@@ -6,6 +6,8 @@
# #
# main algorithm configuration # main algorithm configuration
main: main:
# if set this will set the hostname of the unit. min length is 2, max length 25, only a-zA-Z0-9- allowed
name: ''
# currently implemented: en (default), de, el, fr, it, mk, nl, ru, se, pt-BR, es, pt # currently implemented: en (default), de, el, fr, it, mk, nl, ru, se, pt-BR, es, pt
lang: en lang: en
# custom plugins path, if null only default plugins with be loaded # custom plugins path, if null only default plugins with be loaded
@@ -17,6 +19,12 @@ main:
report: false # don't report pwned networks by default! report: false # don't report pwned networks by default!
exclude: # do not report the following networks (accepts both ESSIDs and BSSIDs) exclude: # do not report the following networks (accepts both ESSIDs and BSSIDs)
- YourHomeNetworkHere - YourHomeNetworkHere
auto-update:
enabled: false
interval: 12 # every 12 hours
install: true # if false, it will only warn that updates are available, if true it will install them
auto-backup: auto-backup:
enabled: false enabled: false
interval: 1 # every day interval: 1 # every day
@@ -25,14 +33,11 @@ main:
- /root/brain.json - /root/brain.json
- /root/.api-report.json - /root/.api-report.json
- /root/handshakes/ - /root/handshakes/
- /root/peers/
- /etc/pwnagotchi/ - /etc/pwnagotchi/
- /etc/hostname
- /etc/hosts
- /etc/motd
- /var/log/pwnagotchi.log - /var/log/pwnagotchi.log
commands: commands:
- 'tar czf /tmp/backup.tar.gz {files}' - 'tar czf /root/pwnagotchi-backup.tar.gz {files}'
- 'scp /tmp/backup.tar.gz pwnagotchi@10.0.0.1:/home/pwnagotchi/backups/backup-$(date +%s).tar.gz'
net-pos: net-pos:
enabled: false enabled: false
api_key: 'test' api_key: 'test'
@@ -52,6 +57,7 @@ main:
wpa-sec: wpa-sec:
enabled: false enabled: false
api_key: ~ api_key: ~
api_url: "https://wpa-sec.stanev.org"
wigle: wigle:
enabled: false enabled: false
api_key: ~ api_key: ~
@@ -70,8 +76,13 @@ main:
netmask: 24 netmask: 24
interval: 1 # check every x minutes for device interval: 1 # check every x minutes for device
share_internet: false share_internet: false
memtemp: # Display memory usage and cpu temperature on screen memtemp: # Display memory usage, cpu load and cpu temperature on screen
enabled: false enabled: false
orientation: horizontal # horizontal/vertical
pawgps:
enabled: false
#The IP Address of your phone with Paw Server running, default (option is empty) is 192.168.44.1
ip: ''
# monitor interface to use # monitor interface to use
iface: mon0 iface: mon0
# command to run to bring the mon interface up in case it's not up already # command to run to bring the mon interface up in case it's not up already
@@ -156,9 +167,35 @@ personality:
bored_num_epochs: 15 bored_num_epochs: 15
# number of inactive epochs that triggers the sad state # number of inactive epochs that triggers the sad state
sad_num_epochs: 25 sad_num_epochs: 25
# number of encounters (times met on a channel) with another unit before considering it a friend and bond
# also used for cumulative bonding score of nearby units
bond_encounters_factor: 20000
# ui configuration # ui configuration
ui: ui:
# here you can customize the faces
faces:
look_r: '( ⚆_⚆)'
look_l: '(☉_☉ )'
look_r_happy: '( ◕‿◕)'
look_l_happy: '(◕‿◕ )'
sleep: '(⇀‿‿↼)'
sleep2: '(≖‿‿≖)'
awake: '(◕‿‿◕)'
bored: '(-__-)'
intense: '(°▃▃°)'
cool: '(⌐■_■)'
happy: '(•‿‿•)'
excited: '(ᵔ◡◡ᵔ)'
grateful: '(^‿‿^)'
motivated: '(☼‿‿☼)'
demotivated: '(≖__≖)'
smart: '(✜‿‿✜)'
lonely: '(ب__ب)'
sad: '(╥☁╥ )'
friend: '(♥‿‿♥)'
broken: '(☓‿‿☓)'
debug: '(#__#)'
# ePaper display can update every 3 secs anyway, set to 0 to only refresh for major data changes # ePaper display can update every 3 secs anyway, set to 0 to only refresh for major data changes
# IMPORTANT: The lifespan of an eINK display depends on the cumulative amount of refreshes. If you want to # IMPORTANT: The lifespan of an eINK display depends on the cumulative amount of refreshes. If you want to
# preserve your display over time, you should set this value to 0.0 so that the display will be refreshed only # preserve your display over time, you should set this value to 0.0 so that the display will be refreshed only
@@ -167,14 +204,19 @@ ui:
display: display:
enabled: true enabled: true
rotation: 180 rotation: 180
# Possible options inkyphat/inky, papirus/papi, waveshare_1/ws_1 or waveshare_2/ws_2, oledhat # Possible options inkyphat/inky, papirus/papi, waveshare_1/ws_1 or waveshare_2/ws_2, oledhat, lcdhat, waveshare27inch
type: 'waveshare_2' type: 'waveshare_2'
# Possible options red/yellow/black (black used for monocromatic displays) # Possible options red/yellow/black (black used for monocromatic displays)
color: 'black' color: 'black'
video: video:
enabled: true enabled: true
address: '0.0.0.0' address: '0.0.0.0'
origin: '*'
port: 8080 port: 8080
# command to be executed when a new png frame is available
# for instance, to use with framebuffer based displays:
# on_frame: 'fbi --noverbose -a -d /dev/fb1 -T 1 /root/pwnagotchi.png > /dev/null 2>&1'
on_frame: ''
# bettercap rest api configuration # bettercap rest api configuration

View File

@@ -27,7 +27,7 @@ class KeyPair(object):
if not os.path.exists(self.priv_path) or not os.path.exists(self.pub_path): if not os.path.exists(self.priv_path) or not os.path.exists(self.pub_path):
self._view.on_keys_generation() self._view.on_keys_generation()
logging.info("generating %s ..." % self.priv_path) logging.info("generating %s ..." % self.priv_path)
os.system("/usr/bin/ssh-keygen -t rsa -m PEM -b 4096 -N '' -f '%s'" % self.priv_path) os.system("pwngrid -generate -keys '%s'" % self.path)
# load keys: they might be corrupted if the unit has been turned off during the generation, in this case # load keys: they might be corrupted if the unit has been turned off during the generation, in this case
# the exception will remove the files and go back at the beginning of this loop. # the exception will remove the files and go back at the beginning of this loop.

View File

@@ -7,7 +7,7 @@ msgid ""
msgstr "" msgstr ""
"Project-Id-Version: 0.0.1\n" "Project-Id-Version: 0.0.1\n"
"Report-Msgid-Bugs-To: \n" "Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2019-10-09 17:42+0200\n" "POT-Creation-Date: 2019-10-23 20:56+0200\n"
"PO-Revision-Date: 2019-09-29 14:00+0200\n" "PO-Revision-Date: 2019-09-29 14:00+0200\n"
"Last-Translator: dadav <33197631+dadav@users.noreply.github.com>\n" "Last-Translator: dadav <33197631+dadav@users.noreply.github.com>\n"
"Language-Team: DE <33197631+dadav@users.noreply.github.com>\n" "Language-Team: DE <33197631+dadav@users.noreply.github.com>\n"
@@ -34,6 +34,9 @@ msgstr "KI bereit."
msgid "The neural network is ready." msgid "The neural network is ready."
msgstr "Das neurale Netz ist bereit." msgstr "Das neurale Netz ist bereit."
msgid "Generating keys, do not turn off ..."
msgstr "Generiere Keys, nicht ausschalten ..."
#, python-brace-format #, python-brace-format
msgid "Hey, channel {channel} is free! Your AP will say thanks." msgid "Hey, channel {channel} is free! Your AP will say thanks."
msgstr "Hey, Channel {channel} ist frei! Dein AP wir des dir danken." msgstr "Hey, Channel {channel} ist frei! Dein AP wir des dir danken."
@@ -75,11 +78,11 @@ msgid "My crime is that of curiosity ..."
msgstr "Mein Verbrechen ist das der Neugier ..." msgstr "Mein Verbrechen ist das der Neugier ..."
#, python-brace-format #, python-brace-format
msgid "Hello {name}! Nice to meet you. {name}" msgid "Hello {name}! Nice to meet you."
msgstr "Hallo {name}, nett Dich kennenzulernen." msgstr "Hallo {name}, nett Dich kennenzulernen."
#, python-brace-format #, python-brace-format
msgid "Unit {name} is nearby! {name}" msgid "Unit {name} is nearby!"
msgstr "Gerät {name} ist in der nähe!!" msgstr "Gerät {name} ist in der nähe!!"
#, python-brace-format #, python-brace-format
@@ -101,6 +104,12 @@ msgstr "{name} verpasst!"
msgid "Missed!" msgid "Missed!"
msgstr "Verpasst!" msgstr "Verpasst!"
msgid "Good friends are a blessing!"
msgstr "Gute Freunde sind ein Segen!"
msgid "I love my friends!"
msgstr "Ich liebe meine Freunde!"
msgid "Nobody wants to play with me ..." msgid "Nobody wants to play with me ..."
msgstr "Niemand will mit mir spielen ..." msgstr "Niemand will mit mir spielen ..."
@@ -163,6 +172,10 @@ msgstr "Kicke {mac}!"
msgid "Cool, we got {num} new handshake{plural}!" msgid "Cool, we got {num} new handshake{plural}!"
msgstr "Cool, wir haben {num} neue Handshake{plural}!" msgstr "Cool, wir haben {num} neue Handshake{plural}!"
#, python-brace-format
msgid "You have {count} new message{plural}!"
msgstr "Cool, wir haben {num} neue Handshake{plural}!"
msgid "Ops, something went wrong ... Rebooting ..." msgid "Ops, something went wrong ... Rebooting ..."
msgstr "Ops, da ist etwas schief gelaufen ...Starte neu ..." msgstr "Ops, da ist etwas schief gelaufen ...Starte neu ..."

View File

@@ -3,12 +3,12 @@
# This file is distributed under the same license as the pwnagotchi package. # This file is distributed under the same license as the pwnagotchi package.
# FIRST AUTHOR <7271496+quantumsheep@users.noreply.github.com>, 2019. # FIRST AUTHOR <7271496+quantumsheep@users.noreply.github.com>, 2019.
# #
#, fuzzy #,
msgid "" msgid ""
msgstr "" msgstr ""
"Project-Id-Version: 0.0.1\n" "Project-Id-Version: 0.0.1\n"
"Report-Msgid-Bugs-To: \n" "Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2019-10-05 14:10+0200\n" "POT-Creation-Date: 2019-10-23 18:37+0200\n"
"PO-Revision-Date: 2019-10-03 10:34+0200\n" "PO-Revision-Date: 2019-10-03 10:34+0200\n"
"Last-Translator: quantumsheep <7271496+quantumsheep@users.noreply.github." "Last-Translator: quantumsheep <7271496+quantumsheep@users.noreply.github."
"com>\n" "com>\n"
@@ -19,7 +19,7 @@ msgstr ""
"Content-Transfer-Encoding: 8bit\n" "Content-Transfer-Encoding: 8bit\n"
msgid "ZzzzZZzzzzZzzz" msgid "ZzzzZZzzzzZzzz"
msgstr "" msgstr "ZzzzZZzzzzZzzz"
msgid "Hi, I'm Pwnagotchi! Starting ..." msgid "Hi, I'm Pwnagotchi! Starting ..."
msgstr "Bonjour, je suis Pwnagotchi ! Démarrage..." msgstr "Bonjour, je suis Pwnagotchi ! Démarrage..."
@@ -36,9 +36,12 @@ msgstr "L'IA est prête."
msgid "The neural network is ready." msgid "The neural network is ready."
msgstr "Le réseau neuronal est prêt." msgstr "Le réseau neuronal est prêt."
msgid "Generating keys, do not turn off ..."
msgstr "Génération des clés, ne pas éteindre..."
#, python-brace-format #, python-brace-format
msgid "Hey, channel {channel} is free! Your AP will say thanks." msgid "Hey, channel {channel} is free! Your AP will say thanks."
msgstr "Hey, le channel {channel} est libre! Ton point d'accès va te remercier." msgstr "Hey, le canal {channel} est libre! Ton point d'accès va te remercier."
msgid "I'm bored ..." msgid "I'm bored ..."
msgstr "Je m'ennuie..." msgstr "Je m'ennuie..."
@@ -77,12 +80,12 @@ msgid "My crime is that of curiosity ..."
msgstr "Mon crime, c'est la curiosité..." msgstr "Mon crime, c'est la curiosité..."
#, python-brace-format #, python-brace-format
msgid "Hello {name}! Nice to meet you. {name}" msgid "Hello {name}! Nice to meet you."
msgstr "Bonjour {name}! Ravi de te rencontrer. {name}" msgstr "Bonjour {name} ! Ravi de te rencontrer."
#, python-brace-format #, python-brace-format
msgid "Unit {name} is nearby! {name}" msgid "Unit {name} is nearby!"
msgstr "L'unité {name} est proche! {name}" msgstr "L'unité {name} est proche !"
#, python-brace-format #, python-brace-format
msgid "Uhm ... goodbye {name}" msgid "Uhm ... goodbye {name}"
@@ -90,7 +93,7 @@ msgstr "Hum ... au revoir {name}"
#, python-brace-format #, python-brace-format
msgid "{name} is gone ..." msgid "{name} is gone ..."
msgstr "{name} est parti ..." msgstr "{name} est part ..."
#, python-brace-format #, python-brace-format
msgid "Whoops ... {name} is gone." msgid "Whoops ... {name} is gone."
@@ -123,6 +126,12 @@ msgstr ""
msgid "ZzzZzzz ({secs}s)" msgid "ZzzZzzz ({secs}s)"
msgstr "" msgstr ""
msgid "Good night."
msgstr "Bonne nuit."
msgid "Zzz"
msgstr "Zzz"
#, python-brace-format #, python-brace-format
msgid "Waiting for {secs}s ..." msgid "Waiting for {secs}s ..."
msgstr "Attends pendant {secs}s..." msgstr "Attends pendant {secs}s..."
@@ -141,7 +150,7 @@ msgstr "Association à {what}"
#, python-brace-format #, python-brace-format
msgid "Yo {what}!" msgid "Yo {what}!"
msgstr "" msgstr "Yo {what} !"
#, python-brace-format #, python-brace-format
msgid "Just decided that {mac} needs no WiFi!" msgid "Just decided that {mac} needs no WiFi!"
@@ -159,6 +168,10 @@ msgstr "Je kick et je bannis {mac}!"
msgid "Cool, we got {num} new handshake{plural}!" msgid "Cool, we got {num} new handshake{plural}!"
msgstr "Cool, on a {num} nouveaux handshake{plural} !" msgstr "Cool, on a {num} nouveaux handshake{plural} !"
#, python-brace-format
msgid "You have {count} new message{plural}!"
msgstr "Tu as {num} nouveaux message{plural} !"
msgid "Ops, something went wrong ... Rebooting ..." msgid "Ops, something went wrong ... Rebooting ..."
msgstr "Oups, quelque chose s'est mal passé... Redémarrage..." msgstr "Oups, quelque chose s'est mal passé... Redémarrage..."

View File

@@ -1,24 +1,23 @@
# Polish voice data for pwnagotchi. # Polish voice data for pwnagotchi.
# Copyright (C) 2019 # Copyright (C) 2019
# This file is distributed under the same license as the PACKAGE package. # This file is distributed under the same license as the pwnagotchi package.
# FIRST AUTHOR szymex73 <szymex73@gmail.com>, 2019. # szymex73 <szymex73@gmail.com>, 2019.
# #
#, fuzzy
msgid "" msgid ""
msgstr "" msgstr ""
"Project-Id-Version: 0.0.1\n" "Project-Id-Version: 0.0.2\n"
"Report-Msgid-Bugs-To: \n" "Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2019-10-09 17:42+0200\n" "POT-Creation-Date: 2019-10-21 08:39+0200\n"
"PO-Revision-Date: 2019-10-09 17:42+0200\n" "PO-Revision-Date: 2019-10-21 10:55+0200\n"
"Last-Translator: szymex73 <szymex73@gmail.com>\n" "Last-Translator: gkrs <457603+gkrs@users.noreply.github.com>\n"
"Language-Team: LANGUAGE <LL@li.org>\n" "Language-Team: PL <457603+gkrs@users.noreply.github.com>\n"
"Language: polish\n" "Language: polish\n"
"MIME-Version: 1.0\n" "MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=utf-8\n" "Content-Type: text/plain; charset=utf-8\n"
"Content-Transfer-Encoding: 8bit\n" "Content-Transfer-Encoding: 8bit\n"
msgid "ZzzzZZzzzzZzzz" msgid "ZzzzZZzzzzZzzz"
msgstr "ZzzzZZzzzzZzzz" msgstr ""
msgid "Hi, I'm Pwnagotchi! Starting ..." msgid "Hi, I'm Pwnagotchi! Starting ..."
msgstr "Hej, jestem Pwnagotchi! Uruchamianie ..." msgstr "Hej, jestem Pwnagotchi! Uruchamianie ..."
@@ -35,9 +34,12 @@ msgstr "SI gotowa."
msgid "The neural network is ready." msgid "The neural network is ready."
msgstr "Sieć neuronowa jest gotowa." msgstr "Sieć neuronowa jest gotowa."
msgid "Generating keys, do not turn off ..."
msgstr "Generuję klucze, nie wyłączaj ..."
#, python-brace-format #, python-brace-format
msgid "Hey, channel {channel} is free! Your AP will say thanks." msgid "Hey, channel {channel} is free! Your AP will say thanks."
msgstr "Hej, kanał {channel} jest wolny! Twój AP mi podziękuje." msgstr "Hej, kanał {channel} jest wolny! Twój AP będzie Ci wdzięczny."
msgid "I'm bored ..." msgid "I'm bored ..."
msgstr "Nudzi mi się ..." msgstr "Nudzi mi się ..."
@@ -46,10 +48,10 @@ msgid "Let's go for a walk!"
msgstr "Chodźmy na spacer!" msgstr "Chodźmy na spacer!"
msgid "This is the best day of my life!" msgid "This is the best day of my life!"
msgstr "To jest najlepszy dzień w moim życiu!" msgstr "To najlepszy dzień mojego życia!"
msgid "Shitty day :/" msgid "Shitty day :/"
msgstr "Ten dzień jest do dupy :/" msgstr "Gówniany dzień :/"
msgid "I'm extremely bored ..." msgid "I'm extremely bored ..."
msgstr "Straaaasznie się nudzę ..." msgstr "Straaaasznie się nudzę ..."
@@ -64,7 +66,7 @@ msgid "I'm living the life!"
msgstr "Cieszę się życiem!" msgstr "Cieszę się życiem!"
msgid "I pwn therefore I am." msgid "I pwn therefore I am."
msgstr "Pwnuje więc jestem." msgstr "Pwnuję więc jestem."
msgid "So many networks!!!" msgid "So many networks!!!"
msgstr "Jak dużo sieci!!!" msgstr "Jak dużo sieci!!!"
@@ -76,12 +78,12 @@ msgid "My crime is that of curiosity ..."
msgstr "Moją zbrodnią jest ciekawość ..." msgstr "Moją zbrodnią jest ciekawość ..."
#, python-brace-format #, python-brace-format
msgid "Hello {name}! Nice to meet you. {name}" msgid "Hello {name}! Nice to meet you."
msgstr "Cześć {name}! Miło cię poznać. {name}" msgstr "Cześć {name}! Miło Cię poznać."
#, python-brace-format #, python-brace-format
msgid "Unit {name} is nearby! {name}" msgid "Unit {name} is nearby!"
msgstr "Jednostka {name} jest niedaleko! {name}" msgstr "Urządzenie {name} jest w pobliżu!"
#, python-brace-format #, python-brace-format
msgid "Uhm ... goodbye {name}" msgid "Uhm ... goodbye {name}"
@@ -97,16 +99,16 @@ msgstr "Ups ... {name} zniknął."
#, python-brace-format #, python-brace-format
msgid "{name} missed!" msgid "{name} missed!"
msgstr "{name} przeoczył!" msgstr "{name} pudło!"
msgid "Missed!" msgid "Missed!"
msgstr "Spóźniony!" msgstr "Pudło!"
msgid "Nobody wants to play with me ..." msgid "Nobody wants to play with me ..."
msgstr "Nikt się nie chce ze mną bawić ..." msgstr "Nikt nie chce się ze mną bawić ..."
msgid "I feel so alone ..." msgid "I feel so alone ..."
msgstr "Czuję się tak samotnie ..." msgstr "Czuję się taki samotny ..."
msgid "Where's everybody?!" msgid "Where's everybody?!"
msgstr "Gdzie są wszyscy?!" msgstr "Gdzie są wszyscy?!"
@@ -116,17 +118,17 @@ msgid "Napping for {secs}s ..."
msgstr "Zdrzemnę się przez {secs}s ..." msgstr "Zdrzemnę się przez {secs}s ..."
msgid "Zzzzz" msgid "Zzzzz"
msgstr "Zzzzz" msgstr ""
#, python-brace-format #, python-brace-format
msgid "ZzzZzzz ({secs}s)" msgid "ZzzZzzz ({secs}s)"
msgstr "ZzzZzzz ({secs}s)" msgstr ""
msgid "Good night." msgid "Good night."
msgstr "Dobranoc." msgstr "Dobranoc."
msgid "Zzz" msgid "Zzz"
msgstr "Zzz" msgstr ""
#, python-brace-format #, python-brace-format
msgid "Waiting for {secs}s ..." msgid "Waiting for {secs}s ..."
@@ -138,15 +140,15 @@ msgstr "Rozglądam się ({secs}s)"
#, python-brace-format #, python-brace-format
msgid "Hey {what} let's be friends!" msgid "Hey {what} let's be friends!"
msgstr "Hej {what}, zostańmy przyjaciółmi!" msgstr "Hej {what} zostańmy przyjaciółmi!"
#, python-brace-format #, python-brace-format
msgid "Associating to {what}" msgid "Associating to {what}"
msgstr "Łączenie się z {what}" msgstr "Dołączam do {what}"
#, python-brace-format #, python-brace-format
msgid "Yo {what}!" msgid "Yo {what}!"
msgstr "Ej {what}!" msgstr "Siema {what}!"
#, python-brace-format #, python-brace-format
msgid "Just decided that {mac} needs no WiFi!" msgid "Just decided that {mac} needs no WiFi!"
@@ -158,33 +160,33 @@ msgstr "Rozłączam {mac}"
#, python-brace-format #, python-brace-format
msgid "Kickbanning {mac}!" msgid "Kickbanning {mac}!"
msgstr "Banowanie {mac}!" msgstr "Banuję {mac}!"
#, python-brace-format #, python-brace-format
msgid "Cool, we got {num} new handshake{plural}!" msgid "Cool, we got {num} new handshake{plural}!"
msgstr "Super, zdobylismy {num} handshake{plural}!" msgstr "Super, zdobyliśmy {num} nowych handshake'ów!"
msgid "Ops, something went wrong ... Rebooting ..." msgid "Ops, something went wrong ... Rebooting ..."
msgstr "Ups, coś się stało ... Restartuję ..." msgstr "Ups, coś poszło nie tak ... Restaruję ..."
#, python-brace-format #, python-brace-format
msgid "Kicked {num} stations\n" msgid "Kicked {num} stations\n"
msgstr "Wyrzucono {num} stacji\n" msgstr "Wyrzuciłem {num} stacji\n"
#, python-brace-format #, python-brace-format
msgid "Made {num} new friends\n" msgid "Made {num} new friends\n"
msgstr "Zdobyto {num} przyjaciół\n" msgstr "Zdobyłem {num} nowych przyjaciół\n"
#, python-brace-format #, python-brace-format
msgid "Got {num} handshakes\n" msgid "Got {num} handshakes\n"
msgstr "Zdobyto {num} handshakow\n" msgstr "Zdobyłem {num} handshake'ów\n"
msgid "Met 1 peer" msgid "Met 1 peer"
msgstr "Spotkano 1 kolegę" msgstr "Spotkałem 1 kolegę"
#, python-brace-format #, python-brace-format
msgid "Met {num} peers" msgid "Met {num} peers"
msgstr "Spotkano {num} kolegów" msgstr "Spotkałem {num} kolegów"
#, python-brace-format #, python-brace-format
msgid "" msgid ""
@@ -192,8 +194,8 @@ msgid ""
"{associated} new friends and ate {handshakes} handshakes! #pwnagotchi " "{associated} new friends and ate {handshakes} handshakes! #pwnagotchi "
"#pwnlog #pwnlife #hacktheplanet #skynet" "#pwnlog #pwnlife #hacktheplanet #skynet"
msgstr "" msgstr ""
"Pwnowalem przez {duration} i wyrzuciłem {deauthed} klientów! Spotkałem także " "Pwnowałem {duration} i wyrzuciłem {deauthed} klientów! Spotkałem także "
"{associated} nowych przyjaciół i zjadłem {handshakes} handshaków! #pwnagotchi " "{associated} nowych przyjaciół i zjadłem {handshakes} handshake'ow! #pwnagotchi "
"#pwnlog #pwnlife #hacktheplanet #skynet" "#pwnlog #pwnlife #hacktheplanet #skynet"
msgid "hours" msgid "hours"

View File

@@ -8,7 +8,7 @@ msgid ""
msgstr "" msgstr ""
"Project-Id-Version: PACKAGE VERSION\n" "Project-Id-Version: PACKAGE VERSION\n"
"Report-Msgid-Bugs-To: \n" "Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2019-10-09 17:42+0200\n" "POT-Creation-Date: 2019-10-23 20:56+0200\n"
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n" "Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
"Language-Team: LANGUAGE <LL@li.org>\n" "Language-Team: LANGUAGE <LL@li.org>\n"
@@ -35,6 +35,9 @@ msgstr ""
msgid "The neural network is ready." msgid "The neural network is ready."
msgstr "" msgstr ""
msgid "Generating keys, do not turn off ..."
msgstr ""
#, python-brace-format #, python-brace-format
msgid "Hey, channel {channel} is free! Your AP will say thanks." msgid "Hey, channel {channel} is free! Your AP will say thanks."
msgstr "" msgstr ""
@@ -76,11 +79,11 @@ msgid "My crime is that of curiosity ..."
msgstr "" msgstr ""
#, python-brace-format #, python-brace-format
msgid "Hello {name}! Nice to meet you. {name}" msgid "Hello {name}! Nice to meet you."
msgstr "" msgstr ""
#, python-brace-format #, python-brace-format
msgid "Unit {name} is nearby! {name}" msgid "Unit {name} is nearby!"
msgstr "" msgstr ""
#, python-brace-format #, python-brace-format
@@ -102,6 +105,12 @@ msgstr ""
msgid "Missed!" msgid "Missed!"
msgstr "" msgstr ""
msgid "Good friends are a blessing!"
msgstr ""
msgid "I love my friends!"
msgstr ""
msgid "Nobody wants to play with me ..." msgid "Nobody wants to play with me ..."
msgstr "" msgstr ""
@@ -164,6 +173,10 @@ msgstr ""
msgid "Cool, we got {num} new handshake{plural}!" msgid "Cool, we got {num} new handshake{plural}!"
msgstr "" msgstr ""
#, python-brace-format
msgid "You have {count} new message{plural}!"
msgstr ""
msgid "Ops, something went wrong ... Rebooting ..." msgid "Ops, something went wrong ... Rebooting ..."
msgstr "" msgstr ""

View File

@@ -167,7 +167,12 @@ class LastSession(object):
self.duration_human = ', '.join(self.duration_human) self.duration_human = ', '.join(self.duration_human)
self.avg_reward /= (self.epochs if self.epochs else 1) self.avg_reward /= (self.epochs if self.epochs else 1)
def parse(self): def parse(self, skip=False):
if skip:
logging.debug("skipping parsing of the last session logs ...")
else:
logging.debug("parsing last session logs ...")
lines = [] lines = []
if os.path.exists(self.path): if os.path.exists(self.path):

View File

@@ -1,17 +1,38 @@
import time import time
import logging import logging
import datetime
import pwnagotchi.ui.faces as faces import pwnagotchi.ui.faces as faces
def parse_rfc3339(dt):
if dt == "0001-01-01T00:00:00Z":
return datetime.datetime.now()
return datetime.datetime.strptime(dt.split('.')[0], "%Y-%m-%dT%H:%M:%S")
class Peer(object): class Peer(object):
def __init__(self, obj): def __init__(self, obj):
self.first_seen = time.time() now = time.time()
self.last_seen = self.first_seen just_met = datetime.datetime.now().strftime("%Y-%m-%dT%H:%M:%S")
self.session_id = obj['session_id']
self.last_channel = obj['channel'] try:
self.rssi = obj['rssi'] self.first_met = parse_rfc3339(obj.get('met_at', just_met))
self.adv = obj['advertisement'] self.first_seen = parse_rfc3339(obj.get('detected_at', just_met))
self.prev_seen = parse_rfc3339(obj.get('prev_seen_at', just_met))
except Exception as e:
logging.warning("error while parsing peer timestamps: %s" % e)
logging.debug(e, exc_info=True)
self.first_met = just_met
self.first_seen = just_met
self.prev_seen = just_met
self.last_seen = now # should be seen_at
self.encounters = obj.get('encounters', 0)
self.session_id = obj.get('session_id', '')
self.last_channel = obj.get('channel', 1)
self.rssi = obj.get('rssi', 0)
self.adv = obj.get('advertisement', {})
def update(self, new): def update(self, new):
if self.name() != new.name(): if self.name() != new.name():
@@ -24,36 +45,45 @@ class Peer(object):
self.rssi = new.rssi self.rssi = new.rssi
self.session_id = new.session_id self.session_id = new.session_id
self.last_seen = time.time() self.last_seen = time.time()
self.prev_seen = new.prev_seen
self.first_met = new.first_met
self.encounters = new.encounters
def inactive_for(self): def inactive_for(self):
return time.time() - self.last_seen return time.time() - self.last_seen
def _adv_field(self, name, default='???'): def first_encounter(self):
return self.adv[name] if name in self.adv else default return self.encounters == 1
def is_good_friend(self, config):
return self.encounters >= config['personality']['bond_encounters_factor']
def face(self): def face(self):
return self._adv_field('face', default=faces.FRIEND) return self.adv.get('face', faces.FRIEND)
def name(self): def name(self):
return self._adv_field('name') return self.adv.get('name', '???')
def identity(self): def identity(self):
return self._adv_field('identity') return self.adv.get('identity', '???')
def full_name(self):
return "%s@%s" % (self.name(), self.identity())
def version(self): def version(self):
return self._adv_field('version') return self.adv.get('version', '1.0.0a')
def pwnd_run(self): def pwnd_run(self):
return int(self._adv_field('pwnd_run', default=0)) return int(self.adv.get('pwnd_run', 0))
def pwnd_total(self): def pwnd_total(self):
return int(self._adv_field('pwnd_tot', default=0)) return int(self.adv.get('pwnd_tot', 0))
def uptime(self): def uptime(self):
return self._adv_field('uptime', default=0) return self.adv.get('uptime', 0)
def epoch(self): def epoch(self):
return self._adv_field('epoch', default=0) return self.adv.get('epoch', 0)
def full_name(self): def full_name(self):
return '%s@%s' % (self.name(), self.identity()) return '%s@%s' % (self.name(), self.identity())

View File

@@ -53,11 +53,27 @@ class AsyncAdvertiser(object):
self._advertisement['face'] = new self._advertisement['face'] = new
grid.set_advertisement_data(self._advertisement) grid.set_advertisement_data(self._advertisement)
def cumulative_encounters(self):
return sum(peer.encounters for _, peer in self._peers.items())
def _on_new_peer(self, peer):
logging.info("new peer %s detected (%d encounters)" % (peer.full_name(), peer.encounters))
self._view.on_new_peer(peer)
plugins.on('peer_detected', self, peer)
def _on_lost_peer(self, peer):
logging.info("lost peer %s" % peer.full_name())
self._view.on_lost_peer(peer)
plugins.on('peer_lost', self, peer)
def _adv_poller(self): def _adv_poller(self):
# give the system a few seconds to start the first time so that any expressions
# due to nearby units will be rendered properly
time.sleep(20)
while True: while True:
try:
logging.debug("polling pwngrid-peer for peers ...") logging.debug("polling pwngrid-peer for peers ...")
try:
grid_peers = grid.peers() grid_peers = grid.peers()
new_peers = {} new_peers = {}
@@ -72,24 +88,23 @@ class AsyncAdvertiser(object):
to_delete = [] to_delete = []
for ident, peer in self._peers.items(): for ident, peer in self._peers.items():
if ident not in new_peers: if ident not in new_peers:
self._view.on_lost_peer(peer)
plugins.on('peer_lost', self, peer)
to_delete.append(ident) to_delete.append(ident)
for ident in to_delete: for ident in to_delete:
self._on_lost_peer(self._peers[ident])
del self._peers[ident] del self._peers[ident]
for ident, peer in new_peers.items(): for ident, peer in new_peers.items():
# check who's new # check who's new
if ident not in self._peers: if ident not in self._peers:
self._peers[ident] = peer self._peers[ident] = peer
self._view.on_new_peer(peer) self._on_new_peer(peer)
plugins.on('peer_detected', self, peer)
# update the rest # update the rest
else: else:
self._peers[ident].update(peer) self._peers[ident].update(peer)
except Exception as e: except Exception as e:
logging.exception("error while polling pwngrid-peer") logging.warning("error while polling pwngrid-peer: %s" % e)
logging.debug(e, exc_info=True)
time.sleep(1) time.sleep(3)

View File

@@ -16,11 +16,24 @@ def on(event_name, *args, **kwargs):
cb_name = 'on_%s' % event_name cb_name = 'on_%s' % event_name
for plugin_name, plugin in loaded.items(): for plugin_name, plugin in loaded.items():
if cb_name in plugin.__dict__: if cb_name in plugin.__dict__:
# print("calling %s %s(%s)" %(cb_name, args, kwargs))
try: try:
plugin.__dict__[cb_name](*args, **kwargs) plugin.__dict__[cb_name](*args, **kwargs)
except Exception as e: except Exception as e:
logging.error("error while running %s.%s : %s" % (plugin_name, cb_name, e)) logging.error("error while running %s.%s : %s" % (plugin_name, cb_name, e))
logging.error(e, exc_info=True)
def one(plugin_name, event_name, *args, **kwargs):
global loaded
if plugin_name in loaded:
plugin = loaded[plugin_name]
cb_name = 'on_%s' % event_name
if cb_name in plugin.__dict__:
try:
plugin.__dict__[cb_name](*args, **kwargs)
except Exception as e:
logging.error("error while running %s.%s : %s" % (plugin_name, cb_name, e))
logging.error(e, exc_info=True)
def load_from_file(filename): def load_from_file(filename):
@@ -34,6 +47,7 @@ def load_from_file(filename):
def load_from_path(path, enabled=()): def load_from_path(path, enabled=()):
global loaded global loaded
for filename in glob.glob(os.path.join(path, "*.py")): for filename in glob.glob(os.path.join(path, "*.py")):
try:
name, plugin = load_from_file(filename) name, plugin = load_from_file(filename)
if name in loaded: if name in loaded:
raise Exception("plugin %s already loaded from %s" % (name, plugin.__file__)) raise Exception("plugin %s already loaded from %s" % (name, plugin.__file__))
@@ -42,12 +56,16 @@ def load_from_path(path, enabled=()):
pass pass
else: else:
loaded[name] = plugin loaded[name] = plugin
except Exception as e:
logging.warning("error while loading %s: %s" % (filename, e))
logging.debug(e, exc_info=True)
return loaded return loaded
def load(config): def load(config):
enabled = [name for name, options in config['main']['plugins'].items() if 'enabled' in options and options['enabled']] enabled = [name for name, options in config['main']['plugins'].items() if
'enabled' in options and options['enabled']]
custom_path = config['main']['custom_plugins'] if 'custom_plugins' in config['main'] else None custom_path = config['main']['custom_plugins'] if 'custom_plugins' in config['main'] else None
# load default plugins # load default plugins
loaded = load_from_path(default_path, enabled=enabled) loaded = load_from_path(default_path, enabled=enabled)

View File

@@ -18,7 +18,7 @@ import os
OPTIONS = dict() OPTIONS = dict()
def on_loaded(): def on_loaded():
logging.info("cleancap plugin loaded") logging.info("aircrackonly plugin loaded")
def on_handshake(agent, filename, access_point, client_station): def on_handshake(agent, filename, access_point, client_station):
display = agent._view display = agent._view

View File

@@ -40,7 +40,10 @@ def on_internet_available(agent):
if STATUS.newer_then_days(OPTIONS['interval']): if STATUS.newer_then_days(OPTIONS['interval']):
return return
files_to_backup = " ".join(OPTIONS['files']) # Only backup existing files to prevent errors
existing_files = list(filter(lambda f: os.path.exists(f), OPTIONS['files']))
files_to_backup = " ".join(existing_files)
try: try:
display = agent.view() display = agent.view()
@@ -57,9 +60,10 @@ def on_internet_available(agent):
raise OSError(f"Command failed (rc: {process.returncode})") raise OSError(f"Command failed (rc: {process.returncode})")
logging.info("AUTO-BACKUP: backup done") logging.info("AUTO-BACKUP: backup done")
display.set('status', 'Backup done!')
display.update()
STATUS.update() STATUS.update()
except OSError as os_e: except OSError as os_e:
logging.info(f"AUTO-BACKUP: Error: {os_e}") logging.info(f"AUTO-BACKUP: Error: {os_e}")
display.set('status', 'Backup failed!')
display.set('status', 'Backup done!')
display.update() display.update()

View File

@@ -0,0 +1,206 @@
__author__ = 'evilsocket@gmail.com'
__version__ = '1.1.0'
__name__ = 'auto-update'
__license__ = 'GPL3'
__description__ = 'This plugin checks when updates are available and applies them when internet is available.'
import os
import logging
import subprocess
import requests
import platform
import shutil
import glob
import pkg_resources
import pwnagotchi
from pwnagotchi.utils import StatusFile
OPTIONS = dict()
READY = False
STATUS = StatusFile('/root/.auto-update')
def on_loaded():
global READY
if 'interval' not in OPTIONS or ('interval' in OPTIONS and OPTIONS['interval'] is None):
logging.error("[update] main.plugins.auto-update.interval is not set")
return
READY = True
logging.info("[update] plugin loaded.")
def check(version, repo, native=True):
logging.debug("checking remote version for %s, local is %s" % (repo, version))
info = {
'repo': repo,
'current': version,
'available': None,
'url': None,
'native': native,
'arch': platform.machine()
}
resp = requests.get("https://api.github.com/repos/%s/releases/latest" % repo)
latest = resp.json()
info['available'] = latest_ver = latest['tag_name'].replace('v', '')
is_arm = info['arch'].startswith('arm')
local = pkg_resources.parse_version(info['current'])
remote = pkg_resources.parse_version(latest_ver)
if remote > local:
if not native:
info['url'] = "https://github.com/%s/archive/%s.zip" % (repo, latest['tag_name'])
else:
# check if this release is compatible with arm6
for asset in latest['assets']:
download_url = asset['browser_download_url']
if download_url.endswith('.zip') and (
info['arch'] in download_url or (is_arm and 'armhf' in download_url)):
info['url'] = download_url
break
return info
def make_path_for(name):
path = os.path.join("/tmp/updates/", name)
if os.path.exists(path):
logging.debug("[update] deleting %s" % path)
shutil.rmtree(path, ignore_errors=True, onerror=None)
os.makedirs(path)
return path
def download_and_unzip(name, path, display, update):
target = "%s_%s.zip" % (name, update['available'])
target_path = os.path.join(path, target)
logging.info("[update] downloading %s to %s ..." % (update['url'], target_path))
display.update(force=True, new_data={'status': 'Downloading %s %s ...' % (name, update['available'])})
os.system('wget -q "%s" -O "%s"' % (update['url'], target_path))
logging.info("[update] extracting %s to %s ..." % (target_path, path))
display.update(force=True, new_data={'status': 'Extracting %s %s ...' % (name, update['available'])})
os.system('unzip "%s" -d "%s"' % (target_path, path))
def verify(name, path, source_path, display, update):
display.update(force=True, new_data={'status': 'Verifying %s %s ...' % (name, update['available'])})
checksums = glob.glob("%s/*.sha256" % path)
if len(checksums) == 0:
if update['native']:
logging.warning("[update] native update without SHA256 checksum file")
return False
else:
checksum = checksums[0]
logging.info("[update] verifying %s for %s ..." % (checksum, source_path))
with open(checksum, 'rt') as fp:
expected = fp.read().split('=')[1].strip().lower()
real = subprocess.getoutput('sha256sum "%s"' % source_path).split(' ')[0].strip().lower()
if real != expected:
logging.warning("[update] checksum mismatch for %s: expected=%s got=%s" % (source_path, expected, real))
return False
return True
def install(display, update):
name = update['repo'].split('/')[1]
path = make_path_for(name)
download_and_unzip(name, path, display, update)
source_path = os.path.join(path, name)
if not verify(name, path, source_path, display, update):
return False
logging.info("[update] installing %s ..." % name)
display.update(force=True, new_data={'status': 'Installing %s %s ...' % (name, update['available'])})
if update['native']:
dest_path = subprocess.getoutput("which %s" % name)
if dest_path == "":
logging.warning("[update] can't find path for %s" % name)
return False
logging.info("[update] stopping %s ..." % update['service'])
os.system("service %s stop" % update['service'])
os.system("mv %s %s" % (source_path, dest_path))
logging.info("[update] restarting %s ..." % update['service'])
os.system("service %s start" % update['service'])
else:
if not os.path.exists(source_path):
source_path = "%s-%s" % (source_path, update['available'])
os.system("cd %s && pip3 install ." % source_path)
return True
def on_internet_available(agent):
global STATUS
logging.debug("[update] internet connectivity is available (ready %s)" % READY)
if READY:
if STATUS.newer_then_hours(OPTIONS['interval']):
logging.debug("[update] last check happened less than %d hours ago" % OPTIONS['interval'])
return
logging.info("[update] checking for updates ...")
display = agent.view()
prev_status = display.get('status')
try:
display.update(force=True, new_data={'status': 'Checking for updates ...'})
to_install = []
to_check = [
('bettercap/bettercap', subprocess.getoutput('bettercap -version').split(' ')[1].replace('v', ''),
True, 'bettercap'),
('evilsocket/pwngrid', subprocess.getoutput('pwngrid -version').replace('v', ''), True, 'pwngrid-peer'),
('evilsocket/pwnagotchi', pwnagotchi.version, False, 'pwnagotchi')
]
for repo, local_version, is_native, svc_name in to_check:
info = check(local_version, repo, is_native)
if info['url'] is not None:
logging.warning(
"update for %s available (local version is '%s'): %s" % (repo, info['current'], info['url']))
info['service'] = svc_name
to_install.append(info)
num_updates = len(to_install)
num_installed = 0
if num_updates > 0:
if OPTIONS['install']:
for update in to_install:
if install(display, update):
num_installed += 1
else:
prev_status = '%d new update%c available!' % (num_updates, 's' if num_updates > 1 else '')
logging.info("[update] done")
STATUS.update()
if num_installed > 0:
display.update(force=True, new_data={'status': 'Rebooting ...'})
pwnagotchi.reboot()
except Exception as e:
logging.error("[update] %s" % e)
display.update(force=True, new_data={'status': prev_status if prev_status is not None else ''})

View File

@@ -141,9 +141,14 @@ class BTNap:
""" """
Set power of devices to on/off Set power of devices to on/off
""" """
logging.debug("BT-TETHER: Changing bluetooth device to %s", str(on))
try:
devs = list(BTNap.find_adapter()) devs = list(BTNap.find_adapter())
devs = dict((BTNap.prop_get(dev, 'Address'), dev) for dev in devs) devs = dict((BTNap.prop_get(dev, 'Address'), dev) for dev in devs)
except BTError as bt_err:
logging.error(bt_err)
return None
for dev_addr, dev in devs.items(): for dev_addr, dev in devs.items():
BTNap.prop_set(dev, 'Powered', on) BTNap.prop_set(dev, 'Powered', on)
@@ -158,52 +163,62 @@ class BTNap:
""" """
Check if already connected Check if already connected
""" """
logging.debug("BT-TETHER: Checking if device is connected.")
bt_dev = self.power(True) bt_dev = self.power(True)
if not bt_dev: if not bt_dev:
return False logging.debug("BT-TETHER: No bluetooth device found.")
return None, False
try: try:
dev_remote = BTNap.find_device(self._mac, bt_dev) dev_remote = BTNap.find_device(self._mac, bt_dev)
return bool(BTNap.prop_get(dev_remote, 'Connected')) return dev_remote, bool(BTNap.prop_get(dev_remote, 'Connected'))
except BTError: except BTError:
pass logging.debug("BT-TETHER: Device is not connected.")
return False return None, False
def is_paired(self): def is_paired(self):
""" """
Check if already connected Check if already connected
""" """
logging.debug("BT-TETHER: Checking if device is paired")
bt_dev = self.power(True) bt_dev = self.power(True)
if not bt_dev: if not bt_dev:
logging.debug("BT-TETHER: No bluetooth device found.")
return False return False
try: try:
dev_remote = BTNap.find_device(self._mac, bt_dev) dev_remote = BTNap.find_device(self._mac, bt_dev)
return bool(BTNap.prop_get(dev_remote, 'Paired')) return bool(BTNap.prop_get(dev_remote, 'Paired'))
except BTError: except BTError:
pass logging.debug("BT-TETHER: Device is not paired.")
return False return False
def wait_for_device(self, timeout=15): def wait_for_device(self, timeout=15):
""" """
Wait for device Wait for device
returns device if found None if not returns device if found None if not
""" """
logging.debug("BT-TETHER: Waiting for device")
bt_dev = self.power(True) bt_dev = self.power(True)
if not bt_dev: if not bt_dev:
logging.debug("BT-TETHER: No bluetooth device found.")
return None return None
try: try:
logging.debug("BT-TETHER: Starting discovery ...")
bt_dev.StartDiscovery() bt_dev.StartDiscovery()
except Exception: except Exception as bt_ex:
# can fail with org.bluez.Error.NotReady / org.bluez.Error.Failed logging.error(bt_ex)
# TODO: add loop? raise bt_ex
pass
dev_remote = None dev_remote = None
@@ -211,73 +226,65 @@ class BTNap:
while timeout > -1: while timeout > -1:
try: try:
dev_remote = BTNap.find_device(self._mac, bt_dev) dev_remote = BTNap.find_device(self._mac, bt_dev)
logging.debug('Using remote device (addr: %s): %s', logging.debug("BT-TETHER: Using remote device (addr: %s): %s",
BTNap.prop_get(dev_remote, 'Address'), dev_remote.object_path ) BTNap.prop_get(dev_remote, 'Address'), dev_remote.object_path )
break break
except BTError: except BTError:
pass logging.debug("BT-TETHER: Not found yet ...")
time.sleep(1) time.sleep(1)
timeout -= 1 timeout -= 1
try: try:
logging.debug("BT-TETHER: Stoping Discovery ...")
bt_dev.StopDiscovery() bt_dev.StopDiscovery()
except Exception: except Exception as bt_ex:
# can fail with org.bluez.Error.NotReady / org.bluez.Error.Failed / org.bluez.Error.NotAuthorized logging.error(bt_ex)
pass raise bt_ex
return dev_remote return dev_remote
@staticmethod
def connect(self, reconnect=False): def pair(device):
""" logging.debug('BT-TETHER: Trying to pair ...')
Connect to device
return True if connected; False if failed
"""
# check if device is close
dev_remote = self.wait_for_device()
if not dev_remote:
return False
try: try:
dev_remote.Pair() device.Pair()
logging.info('BT-TETHER: Successful paired with device ;)') logging.info('BT-TETHER: Successful paired with device ;)')
time.sleep(10) # wait for bnep0 return True
except Exception:
# can fail because of AlreadyExists etc.
pass
try:
dev_remote.ConnectProfile('nap')
except Exception:
pass
net = dbus.Interface(dev_remote, 'org.bluez.Network1')
try:
net.Connect('nap')
except dbus.exceptions.DBusException as err: except dbus.exceptions.DBusException as err:
if err.get_dbus_name() != 'org.bluez.Error.Failed': if err.get_dbus_name() == 'org.bluez.Error.AlreadyExists':
raise logging.debug('BT-TETHER: Already paired ...')
return True
connected = BTNap.prop_get(net, 'Connected') except Exception:
pass
if not connected:
return False return False
if reconnect:
net.Disconnect()
return self.connect(reconnect=False)
@staticmethod
def nap(device):
logging.debug('BT-TETHER: Trying to nap ...')
try:
logging.debug('BT-TETHER: Connecting to profile ...')
device.ConnectProfile('nap')
except Exception: # raises exception, but still works
pass
net = dbus.Interface(device, 'org.bluez.Network1')
try:
logging.debug('BT-TETHER: Connecting to nap network ...')
net.Connect('nap')
return True
except dbus.exceptions.DBusException as err:
if err.get_dbus_name() == 'org.bluez.Error.AlreadyConnected':
return True return True
connected = BTNap.prop_get(net, 'Connected')
if not connected:
return False
return True
#################################################
#################################################
#################################################
class SystemdUnitWrapper: class SystemdUnitWrapper:
""" """
@@ -445,20 +452,51 @@ def on_ui_update(ui):
bt = BTNap(OPTIONS['mac']) bt = BTNap(OPTIONS['mac'])
logging.debug('BT-TETHER: Check if already connected and paired') logging.debug('BT-TETHER: Check if already connected and paired')
if bt.is_connected() and bt.is_paired(): dev_remote, connected = bt.is_connected()
logging.debug('BT-TETHER: Already connected and paired')
ui.set('bluetooth', 'CON') if connected:
else: logging.debug('BT-TETHER: Already connected.')
logging.debug('BT-TETHER: Try to connect to mac') ui.set('bluetooth', 'C')
if bt.connect(): return
logging.info('BT-TETHER: Successfuly connected')
else: try:
logging.error('BT-TETHER: Could not connect') logging.info('BT-TETHER: Search device ...')
dev_remote = bt.wait_for_device()
if dev_remote is None:
logging.info('BT-TETHER: Could not find device.')
ui.set('bluetooth', 'NF')
return
except Exception as bt_ex:
logging.error(bt_ex)
ui.set('bluetooth', 'NF') ui.set('bluetooth', 'NF')
return return
paired = bt.is_paired()
if not paired:
if BTNap.pair(dev_remote):
logging.info('BT-TETHER: Paired with device.')
else:
logging.info('BT-TETHER: Pairing failed ...')
ui.set('bluetooth', 'PE')
return
else:
logging.debug('BT-TETHER: Already paired.')
btnap_iface = IfaceWrapper('bnep0') btnap_iface = IfaceWrapper('bnep0')
logging.debug('BT-TETHER: Check interface') logging.debug('BT-TETHER: Check interface')
if not btnap_iface.exists():
# connected and paired but not napping
logging.debug('BT-TETHER: Try to connect to nap ...')
if BTNap.nap(dev_remote):
logging.info('BT-TETHER: Napping!')
ui.set('bluetooth', 'C')
time.sleep(5)
else:
logging.info('BT-TETHER: Napping failed ...')
ui.set('bluetooth', 'NF')
return
if btnap_iface.exists(): if btnap_iface.exists():
logging.debug('BT-TETHER: Interface found') logging.debug('BT-TETHER: Interface found')
@@ -467,10 +505,10 @@ def on_ui_update(ui):
logging.debug('BT-TETHER: Try to set ADDR to interface') logging.debug('BT-TETHER: Try to set ADDR to interface')
if not btnap_iface.set_addr(addr): if not btnap_iface.set_addr(addr):
ui.set('bluetooth', 'ERR1') ui.set('bluetooth', 'AE')
logging.error("BT-TETHER: Could not set ip of bnep0 to %s", addr) logging.error("BT-TETHER: Could not set ip of bnep0 to %s", addr)
return return
else:
logging.debug('BT-TETHER: Set ADDR to interface') logging.debug('BT-TETHER: Set ADDR to interface')
# change route if sharking # change route if sharking
@@ -485,10 +523,10 @@ def on_ui_update(ui):
resolv.seek(0) resolv.seek(0)
resolv.write(nameserver + 'nameserver 9.9.9.9\n') resolv.write(nameserver + 'nameserver 9.9.9.9\n')
ui.set('bluetooth', 'CON') ui.set('bluetooth', 'C')
else: else:
logging.error('BT-TETHER: bnep0 not found') logging.error('BT-TETHER: bnep0 not found')
ui.set('bluetooth', 'ERR2') ui.set('bluetooth', 'BE')
def on_ui_setup(ui): def on_ui_setup(ui):

View File

@@ -14,6 +14,18 @@ import pwnagotchi.ui.fonts as fonts
# Will be set with the options in config.yml config['main']['plugins'][__name__] # Will be set with the options in config.yml config['main']['plugins'][__name__]
OPTIONS = dict() OPTIONS = dict()
# called when <host>:<port>/plugins/<pluginname> is opened
def on_webhook(response, path):
res = "<html><body><a>Hook triggered</a></body></html>"
response.send_response(200)
response.send_header('Content-type', 'text/html')
response.end_headers()
try:
response.wfile.write(bytes(res, "utf-8"))
except Exception as ex:
logging.error(ex)
# called when the plugin is loaded # called when the plugin is loaded
def on_loaded(): def on_loaded():
logging.warning("WARNING: plugin %s should be disabled!" % __name__) logging.warning("WARNING: plugin %s should be disabled!" % __name__)

View File

@@ -1,11 +1,13 @@
__author__ = 'evilsocket@gmail.com' __author__ = 'evilsocket@gmail.com'
__version__ = '1.0.0' __version__ = '1.0.1'
__name__ = 'grid' __name__ = 'grid'
__license__ = 'GPL3' __license__ = 'GPL3'
__description__ = 'This plugin signals the unit cryptographic identity and list of pwned networks and list of pwned networks to api.pwnagotchi.ai' __description__ = 'This plugin signals the unit cryptographic identity and list of pwned networks and list of pwned ' \
'networks to api.pwnagotchi.ai '
import os import os
import logging import logging
import time
import glob import glob
import pwnagotchi.grid as grid import pwnagotchi.grid as grid
@@ -63,49 +65,27 @@ def is_excluded(what):
return False return False
def on_ui_update(ui):
new_value = ' %d (%d)' % (UNREAD_MESSAGES, TOTAL_MESSAGES)
if not ui.has_element('mailbox') and UNREAD_MESSAGES > 0:
if ui.is_inky():
pos = (80, 0)
else:
pos = (100, 0)
ui.add_element('mailbox',
LabeledValue(color=BLACK, label='MSG', value=new_value,
position=pos,
label_font=fonts.Bold,
text_font=fonts.Medium))
ui.set('mailbox', new_value)
def set_reported(reported, net_id): def set_reported(reported, net_id):
global REPORT global REPORT
reported.append(net_id) reported.append(net_id)
REPORT.update(data={'reported': reported}) REPORT.update(data={'reported': reported})
def on_internet_available(agent): def check_inbox(agent):
global REPORT, UNREAD_MESSAGES, TOTAL_MESSAGES global REPORT, UNREAD_MESSAGES, TOTAL_MESSAGES
logging.debug("internet available")
try:
grid.update_data(agent.last_session)
except Exception as e:
logging.error("error connecting to the pwngrid-peer service: %s" % e)
return
try:
logging.debug("checking mailbox ...") logging.debug("checking mailbox ...")
messages = grid.inbox() messages = grid.inbox()
TOTAL_MESSAGES = len(messages) TOTAL_MESSAGES = len(messages)
UNREAD_MESSAGES = len([m for m in messages if m['seen_at'] is None]) UNREAD_MESSAGES = len([m for m in messages if m['seen_at'] is None])
if TOTAL_MESSAGES: if UNREAD_MESSAGES:
on_ui_update(agent.view()) logging.debug("[grid] unread:%d total:%d" % (UNREAD_MESSAGES, TOTAL_MESSAGES))
logging.debug(" %d unread messages of %d total" % (UNREAD_MESSAGES, TOTAL_MESSAGES)) agent.view().on_unread_messages(UNREAD_MESSAGES, TOTAL_MESSAGES)
def check_handshakes(agent):
logging.debug("checking pcaps") logging.debug("checking pcaps")
pcap_files = glob.glob(os.path.join(agent.config()['bettercap']['handshakes'], "*.pcap")) pcap_files = glob.glob(os.path.join(agent.config()['bettercap']['handshakes'], "*.pcap"))
@@ -130,17 +110,39 @@ def on_internet_available(agent):
essid, bssid = parse_pcap(pcap_file) essid, bssid = parse_pcap(pcap_file)
if bssid: if bssid:
add_as_reported = False
if is_excluded(essid) or is_excluded(bssid): if is_excluded(essid) or is_excluded(bssid):
logging.debug("not reporting %s due to exclusion filter" % pcap_file) logging.debug("not reporting %s due to exclusion filter" % pcap_file)
set_reported(reported, net_id) set_reported(reported, net_id)
else: else:
if grid.report_ap(essid, bssid): if grid.report_ap(essid, bssid):
set_reported(reported, net_id) set_reported(reported, net_id)
time.sleep(1.5)
else: else:
logging.warning("no bssid found?!") logging.warning("no bssid found?!")
else: else:
logging.debug("grid: reporting disabled") logging.debug("grid: reporting disabled")
def on_internet_available(agent):
global REPORT, UNREAD_MESSAGES, TOTAL_MESSAGES
logging.debug("internet available")
try:
grid.update_data(agent.last_session)
except Exception as e: except Exception as e:
logging.exception("grid api error") logging.error("error connecting to the pwngrid-peer service: %s" % e)
logging.debug(e, exc_info=True)
return
try:
check_inbox(agent)
except Exception as e:
logging.error("[grid] error while checking inbox: %s" % e)
logging.debug(e, exc_info=True)
try:
check_handshakes(agent)
except Exception as e:
logging.error("[grid] error while checking pcaps: %s" % e)
logging.debug(e, exc_info=True)

View File

@@ -1,6 +1,6 @@
# tempmem shows memory infos and cpu temperature # memtemp shows memory infos and cpu temperature
# #
# totalmem usedmem freemem cputemp # mem usage, cpu load, cpu temp
# #
############################################################### ###############################################################
# #
@@ -10,71 +10,55 @@
# - removed the label so we wont waste screen space # - removed the label so we wont waste screen space
# - Updated version to 1.0.1 # - Updated version to 1.0.1
# #
# 20-10-2019 by spees <speeskonijn@gmail.com>
# - Refactored to use the already existing functions
# - Now only shows memory usage in percentage
# - Added CPU load
# - Added horizontal and vertical orientation
#
############################################################### ###############################################################
__author__ = 'https://github.com/xenDE' __author__ = 'https://github.com/xenDE'
__version__ = '1.0.1' __version__ = '1.0.1'
__name__ = 'memtemp' __name__ = 'memtemp'
__license__ = 'GPL3' __license__ = 'GPL3'
__description__ = 'A plugin that will add a memory and temperature indicator' __description__ = 'A plugin that will display memory/cpu usage and temperature'
import struct
from pwnagotchi.ui.components import LabeledValue from pwnagotchi.ui.components import LabeledValue
from pwnagotchi.ui.view import BLACK from pwnagotchi.ui.view import BLACK
import pwnagotchi.ui.fonts as fonts import pwnagotchi.ui.fonts as fonts
import pwnagotchi
import logging
import time OPTIONS = dict()
class MEMTEMP:
# set the minimum seconds before refresh the values
refresh_wait = 30
refresh_ts_last = time.time() - refresh_wait
def __init__(self):
# only import when the module is loaded and enabled
import os
def get_temp(self):
try:
temp = os.popen('/opt/vc/bin/vcgencmd measure_temp').readlines()[0].split('=')[1].replace("\n", '').replace("'","")
return 't:' + temp
except:
return 't:-'
# cpu:37.4C
def get_mem_info(self):
try:
# includes RAM + Swap Memory:
# total, used, free = map(int, os.popen('free -t -m').readlines()[-1].split()[1:])
# without Swap, only real memory:
total, used, free = map(int, os.popen('free -t -m').readlines()[-3].split()[1:4])
return "\nT:"+str(total)+"M U:"+str(used)+"M\nF:"+str(free)+"M"
except:
return "\nT:- U:-\nF:- "
# tm:532 um:82 fm:353
memtemp = None
def on_loaded(): def on_loaded():
global memtemp logging.info("memtemp plugin loaded.")
memtemp = MEMTEMP()
def mem_usage():
return int(pwnagotchi.mem_usage() * 100)
def cpu_load():
return int(pwnagotchi.cpu_load() * 100)
def on_ui_setup(ui): def on_ui_setup(ui):
ui.add_element('memtemp', LabeledValue(color=BLACK, label='', value='\nT:- U:-\nF:- -', position=(ui.width() / 2 + 17, ui.height() / 2), if OPTIONS['orientation'] == "horizontal":
label_font=fonts.Bold, text_font=fonts.Medium)) ui.add_element('memtemp', LabeledValue(color=BLACK, label='', value='mem cpu temp\n - - -',
position=(ui.width() / 2 + 30, ui.height() / 2 + 15),
label_font=fonts.Small, text_font=fonts.Small))
elif OPTIONS['orientation'] == "vertical":
ui.add_element('memtemp', LabeledValue(color=BLACK, label='', value=' mem:-\n cpu:-\ntemp:-',
position=(ui.width() / 2 + 55, ui.height() / 2),
label_font=fonts.Small, text_font=fonts.Small))
def on_ui_update(ui): def on_ui_update(ui):
if time.time() > memtemp.refresh_ts_last + memtemp.refresh_wait: if OPTIONS['orientation'] == "horizontal":
ui.set('memtemp', "%s %s" % (memtemp.get_mem_info(), memtemp.get_temp())) ui.set('memtemp', " mem cpu temp\n %s%% %s%% %sc" % (mem_usage(), cpu_load(), pwnagotchi.temperature()))
memtemp.refresh_ts_last = time.time()
elif OPTIONS['orientation'] == "vertical":
ui.set('memtemp', " mem:%s%%\n cpu:%s%%\ntemp:%sc" % (mem_usage(), cpu_load(), pwnagotchi.temperature()))

View File

@@ -0,0 +1,33 @@
__author__ = 'leont'
__version__ = '1.0.0'
__name__ = 'pawgps'
__license__ = 'GPL3'
__description__ = 'Saves GPS coordinates whenever an handshake is captured. The GPS data is get from PAW on android '
'''
You need an bluetooth connection to your android phone which is running PAW server with the GPS "hack" from Systemic:
https://raw.githubusercontent.com/systemik/pwnagotchi-bt-tether/master/GPS-via-PAW
'''
import logging
import json
import requests
OPTIONS = dict()
def on_loaded():
logging.info("PAW-GPS loaded")
if 'ip' not in OPTIONS or ('ip' in OPTIONS and OPTIONS['ip'] is None):
logging.info("PAW-GPS: No IP Address in the config file is defined, it uses the default (192.168.44.1)")
def on_handshake(agent, filename, access_point, client_station):
if 'ip' not in OPTIONS or ('ip' in OPTIONS and OPTIONS['ip'] is None):
ip = "192.168.44.1"
gps = requests.get('http://' + ip + '/gps.xhtml')
gps_filename = filename.replace('.pcap', '.gps.json')
logging.info("saving GPS to %s (%s)" % (gps_filename, gps))
with open(gps_filename, 'w+t') as f:
f.write(gps.text)

View File

@@ -63,7 +63,7 @@ def _transform_wigle_entry(gps_data, pcap_data):
dummy.write("WigleWifi-1.4,appRelease=20190201,model=Kismet,release=2019.02.01.{},device=kismet,display=kismet,board=kismet,brand=kismet\n") dummy.write("WigleWifi-1.4,appRelease=20190201,model=Kismet,release=2019.02.01.{},device=kismet,display=kismet,board=kismet,brand=kismet\n")
dummy.write("MAC,SSID,AuthMode,FirstSeen,Channel,RSSI,CurrentLatitude,CurrentLongitude,AltitudeMeters,AccuracyMeters,Type") dummy.write("MAC,SSID,AuthMode,FirstSeen,Channel,RSSI,CurrentLatitude,CurrentLongitude,AltitudeMeters,AccuracyMeters,Type")
writer = csv.writer(dummy, delimiter=",", quoting=csv.QUOTE_NONE) writer = csv.writer(dummy, delimiter=",", quoting=csv.QUOTE_NONE, escapechar="\\")
writer.writerow([ writer.writerow([
pcap_data[WifiInfo.BSSID], pcap_data[WifiInfo.BSSID],
pcap_data[WifiInfo.ESSID], pcap_data[WifiInfo.ESSID],
@@ -155,7 +155,7 @@ def on_internet_available(agent):
continue continue
if gps_data['Latitude'] == 0 and gps_data['Longitude'] == 0: if gps_data['Latitude'] == 0 and gps_data['Longitude'] == 0:
logging.warning("WIGLE: Not enough gps-informations for %s. Trying again next time.", gps_file) logging.warning("WIGLE: Not enough gps-information for %s. Trying again next time.", gps_file)
SKIP.append(gps_file) SKIP.append(gps_file)
continue continue
@@ -167,7 +167,7 @@ def on_internet_available(agent):
WifiInfo.CHANNEL, WifiInfo.CHANNEL,
WifiInfo.RSSI]) WifiInfo.RSSI])
except FieldNotFoundError: except FieldNotFoundError:
logging.error("WIGLE: Could not extract all informations. Skip %s", gps_file) logging.error("WIGLE: Could not extract all information. Skip %s", gps_file)
SKIP.append(gps_file) SKIP.append(gps_file)
continue continue
except Scapy_Exception as sc_e: except Scapy_Exception as sc_e:

View File

@@ -25,19 +25,23 @@ def on_loaded():
logging.error("WPA_SEC: API-KEY isn't set. Can't upload to wpa-sec.stanev.org") logging.error("WPA_SEC: API-KEY isn't set. Can't upload to wpa-sec.stanev.org")
return return
if 'api_url' not in OPTIONS or ('api_url' in OPTIONS and OPTIONS['api_url'] is None):
logging.error("WPA_SEC: API-URL isn't set. Can't upload, no endpoint configured.")
return
READY = True READY = True
def _upload_to_wpasec(path, timeout=30): def _upload_to_wpasec(path, timeout=30):
""" """
Uploads the file to wpa-sec.stanev.org Uploads the file to https://wpa-sec.stanev.org, or another endpoint.
""" """
with open(path, 'rb') as file_to_upload: with open(path, 'rb') as file_to_upload:
cookie = {'key': OPTIONS['api_key']} cookie = {'key': OPTIONS['api_key']}
payload = {'file': file_to_upload} payload = {'file': file_to_upload}
try: try:
result = requests.post('https://wpa-sec.stanev.org', result = requests.post(OPTIONS['api_url'],
cookies=cookie, cookies=cookie,
files=payload, files=payload,
timeout=timeout) timeout=timeout)

View File

@@ -1,112 +1,31 @@
import _thread import os
from threading import Lock
import shutil
import logging import logging
import pwnagotchi, pwnagotchi.plugins as plugins import threading
import pwnagotchi.plugins as plugins
import pwnagotchi.ui.hw as hw import pwnagotchi.ui.hw as hw
import pwnagotchi.ui.web as web
from pwnagotchi.ui.view import View from pwnagotchi.ui.view import View
from http.server import BaseHTTPRequestHandler, HTTPServer
class VideoHandler(BaseHTTPRequestHandler):
_lock = Lock()
_index = """<html>
<head>
<title>%s</title>
<style>
.block {
-webkit-appearance: button;
-moz-appearance: button;
appearance: button;
display: block;
cursor: pointer;
text-align: center;
}
</style>
</head>
<body>
<div style="position: absolute; top:0; left:0; width:100%%;">
<img src="/ui" id="ui" style="width:100%%"/>
<br/>
<hr/>
<form action="/shutdown" onsubmit="return confirm('This will halt the unit, continue?');">
<input type="submit" class="block" value="Shutdown"/>
</form>
</div>
<script type="text/javascript">
window.onload = function() {
var image = document.getElementById("ui");
function updateImage() {
image.src = image.src.split("?")[0] + "?" + new Date().getTime();
}
setInterval(updateImage, %d);
}
</script>
</body>
</html>"""
@staticmethod
def render(img):
with VideoHandler._lock:
img.save("/root/pwnagotchi.png", format='PNG')
def log_message(self, format, *args):
return
def do_GET(self):
if self.path == '/':
self.send_response(200)
self.send_header('Content-type', 'text/html')
self.end_headers()
try:
self.wfile.write(bytes(self._index % (pwnagotchi.name(), 1000), "utf8"))
except:
pass
elif self.path.startswith('/shutdown'):
pwnagotchi.shutdown()
elif self.path.startswith('/ui'):
with self._lock:
self.send_response(200)
self.send_header('Content-type', 'image/png')
self.end_headers()
try:
with open("/root/pwnagotchi.png", 'rb') as fp:
shutil.copyfileobj(fp, self.wfile)
except:
pass
else:
self.send_response(404)
class Display(View): class Display(View):
def __init__(self, config, state={}): def __init__(self, config, state={}):
super(Display, self).__init__(config, hw.display_for(config), state) super(Display, self).__init__(config, hw.display_for(config), state)
self._enabled = config['ui']['display']['enabled'] config = config['ui']['display']
self._rotation = config['ui']['display']['rotation']
self._video_enabled = config['ui']['display']['video']['enabled'] self._enabled = config['enabled']
self._video_port = config['ui']['display']['video']['port'] self._rotation = config['rotation']
self._video_address = config['ui']['display']['video']['address'] self._webui = web.Server(config)
self._httpd = None
self.init_display() self.init_display()
if self._video_enabled: self._canvas_next_event = threading.Event()
_thread.start_new_thread(self._http_serve, ()) self._canvas_next = None
self._render_thread_instance = threading.Thread(
def _http_serve(self): target=self._render_thread,
if self._video_address is not None: daemon=True
self._httpd = HTTPServer((self._video_address, self._video_port), VideoHandler) )
logging.info("ui available at http://%s:%d/" % (self._video_address, self._video_port)) self._render_thread_instance.start()
self._httpd.serve_forever()
else:
logging.info("could not get ip of usb0, video server not starting")
def is_inky(self): def is_inky(self):
return self._implementation.name == 'inky' return self._implementation.name == 'inky'
@@ -120,13 +39,15 @@ class Display(View):
def is_waveshare_v2(self): def is_waveshare_v2(self):
return self._implementation.name == 'waveshare_2' return self._implementation.name == 'waveshare_2'
def is_waveshare27inch(self):
return self._implementation.name == 'waveshare27inch'
def is_oledhat(self): def is_oledhat(self):
return self._implementation.name == 'oledhat' return self._implementation.name == 'oledhat'
def is_lcdhat(self): def is_lcdhat(self):
return self._implementation.name == 'lcdhat' return self._implementation.name == 'lcdhat'
def is_waveshare_any(self): def is_waveshare_any(self):
return self.is_waveshare_v1() or self.is_waveshare_v2() return self.is_waveshare_v1() or self.is_waveshare_v2()
@@ -147,9 +68,24 @@ class Display(View):
img = self._canvas if self._rotation == 0 else self._canvas.rotate(-self._rotation) img = self._canvas if self._rotation == 0 else self._canvas.rotate(-self._rotation)
return img return img
def _render_thread(self):
"""Used for non-blocking screen updating."""
while True:
self._canvas_next_event.wait()
self._canvas_next_event.clear()
self._implementation.render(self._canvas_next)
def _on_view_rendered(self, img): def _on_view_rendered(self, img):
VideoHandler.render(img) web.update_frame(img)
try:
if self._config['ui']['display']['video']['on_frame'] != '':
os.system(self._config['ui']['display']['video']['on_frame'])
except Exception as e:
logging.error("%s" % e)
if self._enabled: if self._enabled:
self._canvas = (img if self._rotation == 0 else img.rotate(self._rotation)) self._canvas = (img if self._rotation == 0 else img.rotate(self._rotation))
if self._implementation is not None: if self._implementation is not None:
self._implementation.render(self._canvas) self._canvas_next = self._canvas
self._canvas_next_event.set()

View File

@@ -1,5 +1,7 @@
LOOK_R = '( ⚆_⚆)' LOOK_R = '( ⚆_⚆)'
LOOK_L = '(☉_☉ )' LOOK_L = '(☉_☉ )'
LOOK_R_HAPPY = '( ◕‿◕)'
LOOK_L_HAPPY = '(◕‿◕ )'
SLEEP = '(⇀‿‿↼)' SLEEP = '(⇀‿‿↼)'
SLEEP2 = '(≖‿‿≖)' SLEEP2 = '(≖‿‿≖)'
AWAKE = '(◕‿‿◕)' AWAKE = '(◕‿‿◕)'
@@ -7,6 +9,7 @@ BORED = '(-__-)'
INTENSE = '(°▃▃°)' INTENSE = '(°▃▃°)'
COOL = '(⌐■_■)' COOL = '(⌐■_■)'
HAPPY = '(•‿‿•)' HAPPY = '(•‿‿•)'
GRATEFUL = '(^‿‿^)'
EXCITED = '(ᵔ◡◡ᵔ)' EXCITED = '(ᵔ◡◡ᵔ)'
MOTIVATED = '(☼‿‿☼)' MOTIVATED = '(☼‿‿☼)'
DEMOTIVATED = '(≖__≖)' DEMOTIVATED = '(≖__≖)'
@@ -16,3 +19,8 @@ SAD = '(╥☁╥ )'
FRIEND = '(♥‿‿♥)' FRIEND = '(♥‿‿♥)'
BROKEN = '(☓‿‿☓)' BROKEN = '(☓‿‿☓)'
DEBUG = '(#__#)' DEBUG = '(#__#)'
def load_from_config(config):
for face_name, face_value in config.items():
globals()[face_name.upper()] = face_value

View File

@@ -4,6 +4,7 @@ from pwnagotchi.ui.hw.oledhat import OledHat
from pwnagotchi.ui.hw.lcdhat import LcdHat from pwnagotchi.ui.hw.lcdhat import LcdHat
from pwnagotchi.ui.hw.waveshare1 import WaveshareV1 from pwnagotchi.ui.hw.waveshare1 import WaveshareV1
from pwnagotchi.ui.hw.waveshare2 import WaveshareV2 from pwnagotchi.ui.hw.waveshare2 import WaveshareV2
from pwnagotchi.ui.hw.waveshare27inch import Waveshare27inch
def display_for(config): def display_for(config):
@@ -26,3 +27,6 @@ def display_for(config):
elif config['ui']['display']['type'] == 'waveshare_2': elif config['ui']['display']['type'] == 'waveshare_2':
return WaveshareV2(config) return WaveshareV2(config)
elif config['ui']['display']['type'] == 'waveshare27inch':
return Waveshare27inch(config)

View File

@@ -25,6 +25,7 @@ class ST7789(object):
self._spi.max_speed_hz = 40000000 self._spi.max_speed_hz = 40000000
""" Write register address and data """ """ Write register address and data """
def command(self, cmd): def command(self, cmd):
GPIO.output(self._dc, GPIO.LOW) GPIO.output(self._dc, GPIO.LOW)
self._spi.writebytes([cmd]) self._spi.writebytes([cmd])

View File

@@ -7,15 +7,8 @@
# * | Date : 2019-10-18 # * | Date : 2019-10-18
# * | Info : # * | Info :
# ******************************************************************************/ # ******************************************************************************/
import RPi.GPIO as GPIO
import time
from smbus import SMBus
import spidev import spidev
import ctypes
# import spidev
# Pin definition # Pin definition
RST_PIN = 27 RST_PIN = 27
DC_PIN = 25 DC_PIN = 25
@@ -26,51 +19,3 @@ Device_I2C = 0
Device = Device_SPI Device = Device_SPI
spi = spidev.SpiDev(0, 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)
spi.writebytes([data[0]])
def i2c_writebyte(reg, value):
bus.write_byte_data(address, reg, value)
# time.sleep(0.01)
def module_init():
# print("module_init")
GPIO.setmode(GPIO.BCM)
GPIO.setwarnings(False)
GPIO.setup(RST_PIN, GPIO.OUT)
GPIO.setup(DC_PIN, GPIO.OUT)
# SPI.max_speed_hz = 2000000
# SPI.mode = 0b00
# i2c_writebyte(0xff,0xff)
# spi.SYSFS_software_spi_begin()
# spi.SYSFS_software_spi_setDataMode(0);
# spi.SYSFS_software_spi_setClockDivider(1);
#spi.max_speed_hz = 2000000
#spi.mode = 0b00
GPIO.output(BL_PIN, 1)
GPIO.output(DC_PIN, 0)
return 0
def module_exit():
spi.SYSFS_software_spi_end()
GPIO.output(RST_PIN, 0)
GPIO.output(DC_PIN, 0)
### END OF FILE ###

View File

@@ -1,28 +1,21 @@
from . import ST7789 from . import ST7789
from . import config from . import config
# Display resolution
EPD_WIDTH = 240
EPD_HEIGHT = 240
disp = ST7789.ST7789(config.spi,config.RST_PIN, config.DC_PIN, config.BL_PIN)
class EPD(object): class EPD(object):
def __init__(self): def __init__(self):
self.reset_pin = config.RST_PIN self.reset_pin = config.RST_PIN
self.dc_pin = config.DC_PIN self.dc_pin = config.DC_PIN
#self.busy_pin = config.BUSY_PIN self.width = 240
#self.cs_pin = config.CS_PIN self.height = 240
self.width = EPD_WIDTH self.st7789 = ST7789.ST7789(config.spi, config.RST_PIN, config.DC_PIN, config.BL_PIN)
self.height = EPD_HEIGHT
def init(self): def init(self):
disp.Init() self.st7789.Init()
def Clear(self): def clear(self):
disp.clear() self.st7789.clear()
def display(self, image): def display(self, image):
rgb_im = image.convert('RGB') rgb_im = image.convert('RGB')
disp.ShowImage(rgb_im,0,0) self.st7789.ShowImage(rgb_im, 0, 0)

View File

@@ -66,6 +66,7 @@ class EPD:
epdconfig.digital_write(self.cs_pin, GPIO.HIGH) epdconfig.digital_write(self.cs_pin, GPIO.HIGH)
def ReadBusy(self): def ReadBusy(self):
epdconfig.delay_ms(20)
while(epdconfig.digital_read(self.busy_pin) == 0): # 0: idle, 1: busy while(epdconfig.digital_read(self.busy_pin) == 0): # 0: idle, 1: busy
epdconfig.delay_ms(100) epdconfig.delay_ms(100)

View File

@@ -0,0 +1,520 @@
# *****************************************************************************
# * | File : epd2in7.py
# * | Author : Waveshare team
# * | Function : Electronic paper driver
# * | Info :
# *----------------
# * | This version: V4.0
# * | Date : 2019-06-20
# # | Info : python demo
# -----------------------------------------------------------------------------
# Permission is hereby granted, free of charge, to any person obtaining a copy
# of this software and associated documnetation files (the "Software"), to deal
# in the Software without restriction, including without limitation the rights
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
# copies of the Software, and to permit persons to whom the Software is
# furished to do so, subject to the following conditions:
#
# The above copyright notice and this permission notice shall be included in
# all copies or substantial portions of the Software.
#
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
# FITNESS OR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
# LIABILITY WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
# THE SOFTWARE.
#
import logging
from . import epdconfig
# Display resolution
EPD_WIDTH = 176
EPD_HEIGHT = 264
GRAY1 = 0xff #white
GRAY2 = 0xC0
GRAY3 = 0x80 #gray
GRAY4 = 0x00 #Blackest
class EPD:
def __init__(self):
self.reset_pin = epdconfig.RST_PIN
self.dc_pin = epdconfig.DC_PIN
self.busy_pin = epdconfig.BUSY_PIN
self.cs_pin = epdconfig.CS_PIN
self.width = EPD_WIDTH
self.height = EPD_HEIGHT
self.GRAY1 = GRAY1 #white
self.GRAY2 = GRAY2
self.GRAY3 = GRAY3 #gray
self.GRAY4 = GRAY4 #Blackest
lut_vcom_dc = [0x00, 0x00,
0x00, 0x08, 0x00, 0x00, 0x00, 0x02,
0x60, 0x28, 0x28, 0x00, 0x00, 0x01,
0x00, 0x14, 0x00, 0x00, 0x00, 0x01,
0x00, 0x12, 0x12, 0x00, 0x00, 0x01,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00
]
lut_ww = [
0x40, 0x08, 0x00, 0x00, 0x00, 0x02,
0x90, 0x28, 0x28, 0x00, 0x00, 0x01,
0x40, 0x14, 0x00, 0x00, 0x00, 0x01,
0xA0, 0x12, 0x12, 0x00, 0x00, 0x01,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
]
lut_bw = [
0x40, 0x08, 0x00, 0x00, 0x00, 0x02,
0x90, 0x28, 0x28, 0x00, 0x00, 0x01,
0x40, 0x14, 0x00, 0x00, 0x00, 0x01,
0xA0, 0x12, 0x12, 0x00, 0x00, 0x01,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
]
lut_bb = [
0x80, 0x08, 0x00, 0x00, 0x00, 0x02,
0x90, 0x28, 0x28, 0x00, 0x00, 0x01,
0x80, 0x14, 0x00, 0x00, 0x00, 0x01,
0x50, 0x12, 0x12, 0x00, 0x00, 0x01,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
]
lut_wb = [
0x80, 0x08, 0x00, 0x00, 0x00, 0x02,
0x90, 0x28, 0x28, 0x00, 0x00, 0x01,
0x80, 0x14, 0x00, 0x00, 0x00, 0x01,
0x50, 0x12, 0x12, 0x00, 0x00, 0x01,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
]
###################full screen update LUT######################
#0~3 gray
gray_lut_vcom = [
0x00, 0x00,
0x00, 0x0A, 0x00, 0x00, 0x00, 0x01,
0x60, 0x14, 0x14, 0x00, 0x00, 0x01,
0x00, 0x14, 0x00, 0x00, 0x00, 0x01,
0x00, 0x13, 0x0A, 0x01, 0x00, 0x01,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
]
#R21
gray_lut_ww =[
0x40, 0x0A, 0x00, 0x00, 0x00, 0x01,
0x90, 0x14, 0x14, 0x00, 0x00, 0x01,
0x10, 0x14, 0x0A, 0x00, 0x00, 0x01,
0xA0, 0x13, 0x01, 0x00, 0x00, 0x01,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
]
#R22H r
gray_lut_bw =[
0x40, 0x0A, 0x00, 0x00, 0x00, 0x01,
0x90, 0x14, 0x14, 0x00, 0x00, 0x01,
0x00, 0x14, 0x0A, 0x00, 0x00, 0x01,
0x99, 0x0C, 0x01, 0x03, 0x04, 0x01,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
]
#R23H w
gray_lut_wb =[
0x40, 0x0A, 0x00, 0x00, 0x00, 0x01,
0x90, 0x14, 0x14, 0x00, 0x00, 0x01,
0x00, 0x14, 0x0A, 0x00, 0x00, 0x01,
0x99, 0x0B, 0x04, 0x04, 0x01, 0x01,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
]
#R24H b
gray_lut_bb =[
0x80, 0x0A, 0x00, 0x00, 0x00, 0x01,
0x90, 0x14, 0x14, 0x00, 0x00, 0x01,
0x20, 0x14, 0x0A, 0x00, 0x00, 0x01,
0x50, 0x13, 0x01, 0x00, 0x00, 0x01,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
]
# Hardware reset
def reset(self):
epdconfig.digital_write(self.reset_pin, 1)
epdconfig.delay_ms(200)
epdconfig.digital_write(self.reset_pin, 0)
epdconfig.delay_ms(10)
epdconfig.digital_write(self.reset_pin, 1)
epdconfig.delay_ms(200)
def send_command(self, command):
epdconfig.digital_write(self.dc_pin, 0)
epdconfig.digital_write(self.cs_pin, 0)
epdconfig.spi_writebyte([command])
epdconfig.digital_write(self.cs_pin, 1)
def send_data(self, data):
epdconfig.digital_write(self.dc_pin, 1)
epdconfig.digital_write(self.cs_pin, 0)
epdconfig.spi_writebyte([data])
epdconfig.digital_write(self.cs_pin, 1)
def ReadBusy(self):
logging.debug("e-Paper busy")
while(epdconfig.digital_read(self.busy_pin) == 0): # 0: idle, 1: busy
epdconfig.delay_ms(200)
logging.debug("e-Paper busy release")
def set_lut(self):
self.send_command(0x20) # vcom
for count in range(0, 44):
self.send_data(self.lut_vcom_dc[count])
self.send_command(0x21) # ww --
for count in range(0, 42):
self.send_data(self.lut_ww[count])
self.send_command(0x22) # bw r
for count in range(0, 42):
self.send_data(self.lut_bw[count])
self.send_command(0x23) # wb w
for count in range(0, 42):
self.send_data(self.lut_bb[count])
self.send_command(0x24) # bb b
for count in range(0, 42):
self.send_data(self.lut_wb[count])
def gray_SetLut(self):
self.send_command(0x20)
for count in range(0, 44): #vcom
self.send_data(self.gray_lut_vcom[count])
self.send_command(0x21) #red not use
for count in range(0, 42):
self.send_data(self.gray_lut_ww[count])
self.send_command(0x22) #bw r
for count in range(0, 42):
self.send_data(self.gray_lut_bw[count])
self.send_command(0x23) #wb w
for count in range(0, 42):
self.send_data(self.gray_lut_wb[count])
self.send_command(0x24) #bb b
for count in range(0, 42):
self.send_data(self.gray_lut_bb[count])
self.send_command(0x25) #vcom
for count in range(0, 42):
self.send_data(self.gray_lut_ww[count])
def init(self):
if (epdconfig.module_init() != 0):
return -1
# EPD hardware init start
self.reset()
self.send_command(0x01) # POWER_SETTING
self.send_data(0x03) # VDS_EN, VDG_EN
self.send_data(0x00) # VCOM_HV, VGHL_LV[1], VGHL_LV[0]
self.send_data(0x2b) # VDH
self.send_data(0x2b) # VDL
self.send_data(0x09) # VDHR
self.send_command(0x06) # BOOSTER_SOFT_START
self.send_data(0x07)
self.send_data(0x07)
self.send_data(0x17)
# Power optimization
self.send_command(0xF8)
self.send_data(0x60)
self.send_data(0xA5)
# Power optimization
self.send_command(0xF8)
self.send_data(0x89)
self.send_data(0xA5)
# Power optimization
self.send_command(0xF8)
self.send_data(0x90)
self.send_data(0x00)
# Power optimization
self.send_command(0xF8)
self.send_data(0x93)
self.send_data(0x2A)
# Power optimization
self.send_command(0xF8)
self.send_data(0xA0)
self.send_data(0xA5)
# Power optimization
self.send_command(0xF8)
self.send_data(0xA1)
self.send_data(0x00)
# Power optimization
self.send_command(0xF8)
self.send_data(0x73)
self.send_data(0x41)
self.send_command(0x16) # PARTIAL_DISPLAY_REFRESH
self.send_data(0x00)
self.send_command(0x04) # POWER_ON
self.ReadBusy()
self.send_command(0x00) # PANEL_SETTING
self.send_data(0xAF) # KW-BF KWR-AF BWROTP 0f
self.send_command(0x30) # PLL_CONTROL
self.send_data(0x3A) # 3A 100HZ 29 150Hz 39 200HZ 31 171HZ
self.send_command(0x82) # VCM_DC_SETTING_REGISTER
self.send_data(0x12)
self.set_lut()
return 0
def Init_4Gray(self):
if (epdconfig.module_init() != 0):
return -1
self.reset()
self.send_command(0x01) #POWER SETTING
self.send_data (0x03)
self.send_data (0x00)
self.send_data (0x2b)
self.send_data (0x2b)
self.send_command(0x06) #booster soft start
self.send_data (0x07) #A
self.send_data (0x07) #B
self.send_data (0x17) #C
self.send_command(0xF8) #boost??
self.send_data (0x60)
self.send_data (0xA5)
self.send_command(0xF8) #boost??
self.send_data (0x89)
self.send_data (0xA5)
self.send_command(0xF8) #boost??
self.send_data (0x90)
self.send_data (0x00)
self.send_command(0xF8) #boost??
self.send_data (0x93)
self.send_data (0x2A)
self.send_command(0xF8) #boost??
self.send_data (0xa0)
self.send_data (0xa5)
self.send_command(0xF8) #boost??
self.send_data (0xa1)
self.send_data (0x00)
self.send_command(0xF8) #boost??
self.send_data (0x73)
self.send_data (0x41)
self.send_command(0x16)
self.send_data(0x00)
self.send_command(0x04)
self.ReadBusy()
self.send_command(0x00) #panel setting
self.send_data(0xbf) #KW-BF KWR-AF BWROTP 0f
self.send_command(0x30) #PLL setting
self.send_data (0x90) #100hz
self.send_command(0x61) #resolution setting
self.send_data (0x00) #176
self.send_data (0xb0)
self.send_data (0x01) #264
self.send_data (0x08)
self.send_command(0x82) #vcom_DC setting
self.send_data (0x12)
self.send_command(0X50) #VCOM AND DATA INTERVAL SETTING
self.send_data(0x97)
def getbuffer(self, image):
# logging.debug("bufsiz = ",int(self.width/8) * self.height)
buf = [0xFF] * (int(self.width/8) * self.height)
image_monocolor = image.convert('1')
imwidth, imheight = image_monocolor.size
pixels = image_monocolor.load()
# logging.debug("imwidth = %d, imheight = %d",imwidth,imheight)
if(imwidth == self.width and imheight == self.height):
logging.debug("Vertical")
for y in range(imheight):
for x in range(imwidth):
# Set the bits for the column of pixels at the current position.
if pixels[x, y] == 0:
buf[int((x + y * self.width) / 8)] &= ~(0x80 >> (x % 8))
elif(imwidth == self.height and imheight == self.width):
logging.debug("Horizontal")
for y in range(imheight):
for x in range(imwidth):
newx = y
newy = self.height - x - 1
if pixels[x, y] == 0:
buf[int((newx + newy*self.width) / 8)] &= ~(0x80 >> (y % 8))
return buf
def getbuffer_4Gray(self, image):
# logging.debug("bufsiz = ",int(self.width/8) * self.height)
buf = [0xFF] * (int(self.width / 4) * self.height)
image_monocolor = image.convert('L')
imwidth, imheight = image_monocolor.size
pixels = image_monocolor.load()
i=0
# logging.debug("imwidth = %d, imheight = %d",imwidth,imheight)
if(imwidth == self.width and imheight == self.height):
logging.debug("Vertical")
for y in range(imheight):
for x in range(imwidth):
# Set the bits for the column of pixels at the current position.
if(pixels[x, y] == 0xC0):
pixels[x, y] = 0x80
elif (pixels[x, y] == 0x80):
pixels[x, y] = 0x40
i= i+1
if(i%4 == 0):
buf[int((x + (y * self.width))/4)] = ((pixels[x-3, y]&0xc0) | (pixels[x-2, y]&0xc0)>>2 | (pixels[x-1, y]&0xc0)>>4 | (pixels[x, y]&0xc0)>>6)
elif(imwidth == self.height and imheight == self.width):
logging.debug("Horizontal")
for x in range(imwidth):
for y in range(imheight):
newx = y
newy = x
if(pixels[x, y] == 0xC0):
pixels[x, y] = 0x80
elif (pixels[x, y] == 0x80):
pixels[x, y] = 0x40
i= i+1
if(i%4 == 0):
buf[int((newx + (newy * self.width))/4)] = ((pixels[x, y-3]&0xc0) | (pixels[x, y-2]&0xc0)>>2 | (pixels[x, y-1]&0xc0)>>4 | (pixels[x, y]&0xc0)>>6)
return buf
def display(self, image):
self.send_command(0x10)
for i in range(0, int(self.width * self.height / 8)):
self.send_data(0xFF)
self.send_command(0x13)
for i in range(0, int(self.width * self.height / 8)):
self.send_data(image[i])
self.send_command(0x12)
self.ReadBusy()
def display_4Gray(self, image):
self.send_command(0x10)
for i in range(0, 5808): #5808*4 46464
temp3=0
for j in range(0, 2):
temp1 = image[i*2+j]
for k in range(0, 2):
temp2 = temp1&0xC0
if(temp2 == 0xC0):
temp3 |= 0x01#white
elif(temp2 == 0x00):
temp3 |= 0x00 #black
elif(temp2 == 0x80):
temp3 |= 0x01 #gray1
else: #0x40
temp3 |= 0x00 #gray2
temp3 <<= 1
temp1 <<= 2
temp2 = temp1&0xC0
if(temp2 == 0xC0): #white
temp3 |= 0x01
elif(temp2 == 0x00): #black
temp3 |= 0x00
elif(temp2 == 0x80):
temp3 |= 0x01 #gray1
else : #0x40
temp3 |= 0x00 #gray2
if(j!=1 or k!=1):
temp3 <<= 1
temp1 <<= 2
self.send_data(temp3)
self.send_command(0x13)
for i in range(0, 5808): #5808*4 46464
temp3=0
for j in range(0, 2):
temp1 = image[i*2+j]
for k in range(0, 2):
temp2 = temp1&0xC0
if(temp2 == 0xC0):
temp3 |= 0x01#white
elif(temp2 == 0x00):
temp3 |= 0x00 #black
elif(temp2 == 0x80):
temp3 |= 0x00 #gray1
else: #0x40
temp3 |= 0x01 #gray2
temp3 <<= 1
temp1 <<= 2
temp2 = temp1&0xC0
if(temp2 == 0xC0): #white
temp3 |= 0x01
elif(temp2 == 0x00): #black
temp3 |= 0x00
elif(temp2 == 0x80):
temp3 |= 0x00 #gray1
else: #0x40
temp3 |= 0x01 #gray2
if(j!=1 or k!=1):
temp3 <<= 1
temp1 <<= 2
self.send_data(temp3)
self.gray_SetLut()
self.send_command(0x12)
epdconfig.delay_ms(200)
self.ReadBusy()
# pass
def Clear(self, color):
self.send_command(0x10)
for i in range(0, int(self.width * self.height / 8)):
self.send_data(0xFF)
self.send_command(0x13)
for i in range(0, int(self.width * self.height / 8)):
self.send_data(0xFF)
self.send_command(0x12)
self.ReadBusy()
def sleep(self):
self.send_command(0X50)
self.send_data(0xf7)
self.send_command(0X02)
self.send_command(0X07)
self.send_data(0xA5)
epdconfig.module_exit()
### END OF FILE ###

View File

@@ -0,0 +1,154 @@
# /*****************************************************************************
# * | File : epdconfig.py
# * | Author : Waveshare team
# * | Function : Hardware underlying interface
# * | Info :
# *----------------
# * | This version: V1.0
# * | Date : 2019-06-21
# * | Info :
# ******************************************************************************
# Permission is hereby granted, free of charge, to any person obtaining a copy
# of this software and associated documnetation files (the "Software"), to deal
# in the Software without restriction, including without limitation the rights
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
# copies of the Software, and to permit persons to whom the Software is
# furished to do so, subject to the following conditions:
#
# The above copyright notice and this permission notice shall be included in
# all copies or substantial portions of the Software.
#
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
# FITNESS OR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
# LIABILITY WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
# THE SOFTWARE.
#
import os
import logging
import sys
import time
class RaspberryPi:
# Pin definition
RST_PIN = 17
DC_PIN = 25
CS_PIN = 8
BUSY_PIN = 24
def __init__(self):
import spidev
import RPi.GPIO
self.GPIO = RPi.GPIO
# SPI device, bus = 0, device = 0
self.SPI = spidev.SpiDev(0, 0)
def digital_write(self, pin, value):
self.GPIO.output(pin, value)
def digital_read(self, pin):
return self.GPIO.input(pin)
def delay_ms(self, delaytime):
time.sleep(delaytime / 1000.0)
def spi_writebyte(self, data):
self.SPI.writebytes(data)
def module_init(self):
self.GPIO.setmode(self.GPIO.BCM)
self.GPIO.setwarnings(False)
self.GPIO.setup(self.RST_PIN, self.GPIO.OUT)
self.GPIO.setup(self.DC_PIN, self.GPIO.OUT)
self.GPIO.setup(self.CS_PIN, self.GPIO.OUT)
self.GPIO.setup(self.BUSY_PIN, self.GPIO.IN)
self.SPI.max_speed_hz = 4000000
self.SPI.mode = 0b00
return 0
def module_exit(self):
logging.debug("spi end")
self.SPI.close()
logging.debug("close 5V, Module enters 0 power consumption ...")
self.GPIO.output(self.RST_PIN, 0)
self.GPIO.output(self.DC_PIN, 0)
self.GPIO.cleanup()
class JetsonNano:
# Pin definition
RST_PIN = 17
DC_PIN = 25
CS_PIN = 8
BUSY_PIN = 24
def __init__(self):
import ctypes
find_dirs = [
os.path.dirname(os.path.realpath(__file__)),
'/usr/local/lib',
'/usr/lib',
]
self.SPI = None
for find_dir in find_dirs:
so_filename = os.path.join(find_dir, 'sysfs_software_spi.so')
if os.path.exists(so_filename):
self.SPI = ctypes.cdll.LoadLibrary(so_filename)
break
if self.SPI is None:
raise RuntimeError('Cannot find sysfs_software_spi.so')
import Jetson.GPIO
self.GPIO = Jetson.GPIO
def digital_write(self, pin, value):
self.GPIO.output(pin, value)
def digital_read(self, pin):
return self.GPIO.input(self.BUSY_PIN)
def delay_ms(self, delaytime):
time.sleep(delaytime / 1000.0)
def spi_writebyte(self, data):
self.SPI.SYSFS_software_spi_transfer(data[0])
def module_init(self):
self.GPIO.setmode(self.GPIO.BCM)
self.GPIO.setwarnings(False)
self.GPIO.setup(self.RST_PIN, self.GPIO.OUT)
self.GPIO.setup(self.DC_PIN, self.GPIO.OUT)
self.GPIO.setup(self.CS_PIN, self.GPIO.OUT)
self.GPIO.setup(self.BUSY_PIN, self.GPIO.IN)
self.SPI.SYSFS_software_spi_begin()
return 0
def module_exit(self):
logging.debug("spi end")
self.SPI.SYSFS_software_spi_end()
logging.debug("close 5V, Module enters 0 power consumption ...")
self.GPIO.output(self.RST_PIN, 0)
self.GPIO.output(self.DC_PIN, 0)
self.GPIO.cleanup()
if os.path.exists('/sys/bus/platform/drivers/gpiomem-bcm2835'):
implementation = RaspberryPi()
else:
implementation = JetsonNano()
for func in [x for x in dir(implementation) if not x.startswith('_')]:
setattr(sys.modules[__name__], func, getattr(implementation, func))
### END OF FILE ###

View File

@@ -0,0 +1,46 @@
import logging
import pwnagotchi.ui.fonts as fonts
from pwnagotchi.ui.hw.base import DisplayImpl
class Waveshare27inch(DisplayImpl):
def __init__(self, config):
super(Waveshare27inch, self).__init__(config, 'waveshare_2_7inch')
self._display = None
def layout(self):
fonts.setup(10, 9, 10, 35)
self._layout['width'] = 264
self._layout['height'] = 176
self._layout['face'] = (66, 27)
self._layout['name'] = (5, 73)
self._layout['channel'] = (0, 0)
self._layout['aps'] = (28, 0)
self._layout['uptime'] = (199, 0)
self._layout['line1'] = [0, 14, 264, 14]
self._layout['line2'] = [0, 162, 264, 162]
self._layout['friend_face'] = (0, 146)
self._layout['friend_name'] = (40, 146)
self._layout['shakes'] = (0, 163)
self._layout['mode'] = (239, 163)
self._layout['status'] = {
'pos': (38, 93),
'font': fonts.Medium,
'max': 40
}
return self._layout
def initialize(self):
logging.info("initializing waveshare v1 2.7 inch display")
from pwnagotchi.ui.hw.libs.waveshare.v27inch.epd2in7 import EPD
self._display = EPD()
self._display.init()
self._display.Clear(0xFF)
def render(self, canvas):
buf = self._display.getbuffer(canvas)
self._display.display(buf)
def clear(self):
self._display.Clear(0xff)

View File

@@ -2,6 +2,7 @@ import _thread
from threading import Lock from threading import Lock
import time import time
import logging import logging
import random
from PIL import ImageDraw from PIL import ImageDraw
import pwnagotchi.utils as utils import pwnagotchi.utils as utils
@@ -22,6 +23,10 @@ class View(object):
def __init__(self, config, impl, state=None): def __init__(self, config, impl, state=None):
global ROOT global ROOT
# setup faces from the configuration in case the user customized them
faces.load_from_config(config['ui']['faces'])
self._agent = None
self._render_cbs = [] self._render_cbs = []
self._config = config self._config = config
self._canvas = None self._canvas = None
@@ -85,6 +90,9 @@ class View(object):
ROOT = self ROOT = self
def set_agent(self, agent):
self._agent = agent
def has_element(self, key): def has_element(self, key):
self._state.has_element(key) self._state.has_element(key)
@@ -120,6 +128,9 @@ class View(object):
def set(self, key, value): def set(self, key, value):
self._state.set(key, value) self._state.set(key, value)
def get(self, key):
return self._state.get(key)
def on_starting(self): def on_starting(self):
self.set('status', self._voice.on_starting()) self.set('status', self._voice.on_starting())
self.set('face', faces.AWAKE) self.set('face', faces.AWAKE)
@@ -132,7 +143,7 @@ class View(object):
def on_manual_mode(self, last_session): def on_manual_mode(self, last_session):
self.set('mode', 'MANU') self.set('mode', 'MANU')
self.set('face', faces.SAD if last_session.handshakes == 0 else faces.HAPPY) self.set('face', faces.SAD if (last_session.epochs > 3 and last_session.handshakes == 0) else faces.HAPPY)
self.set('status', self._voice.on_last_session_data(last_session)) self.set('status', self._voice.on_last_session_data(last_session))
self.set('epoch', "%04d" % last_session.epochs) self.set('epoch', "%04d" % last_session.epochs)
self.set('uptime', last_session.duration) self.set('uptime', last_session.duration)
@@ -141,6 +152,7 @@ class View(object):
self.set('shakes', '%d (%s)' % (last_session.handshakes, \ self.set('shakes', '%d (%s)' % (last_session.handshakes, \
utils.total_unique_handshakes(self._config['bettercap']['handshakes']))) utils.total_unique_handshakes(self._config['bettercap']['handshakes'])))
self.set_closest_peer(last_session.last_peer, last_session.peers) self.set_closest_peer(last_session.last_peer, last_session.peers)
self.update()
def is_normal(self): def is_normal(self):
return self._state.get('face') not in ( return self._state.get('face') not in (
@@ -195,9 +207,21 @@ class View(object):
self.update() self.update()
def on_new_peer(self, peer): def on_new_peer(self, peer):
self.set('face', faces.FRIEND) face = ''
# first time they met, neutral mood
if peer.first_encounter():
face = random.choice((faces.AWAKE, faces.COOL))
# a good friend, positive expression
elif peer.is_good_friend(self._config):
face = random.choice((faces.MOTIVATED, faces.FRIEND, faces.HAPPY))
# normal friend, neutral-positive
else:
face = random.choice((faces.EXCITED, faces.HAPPY))
self.set('face', face)
self.set('status', self._voice.on_new_peer(peer)) self.set('status', self._voice.on_new_peer(peer))
self.update() self.update()
time.sleep(3)
def on_lost_peer(self, peer): def on_lost_peer(self, peer):
self.set('face', faces.LONELY) self.set('face', faces.LONELY)
@@ -228,10 +252,11 @@ class View(object):
self.set('status', self._voice.on_awakening()) self.set('status', self._voice.on_awakening())
else: else:
self.set('status', self._voice.on_waiting(int(secs))) self.set('status', self._voice.on_waiting(int(secs)))
good_mood = self._agent.in_good_mood()
if step % 2 == 0: if step % 2 == 0:
self.set('face', faces.LOOK_R) self.set('face', faces.LOOK_R_HAPPY if good_mood else faces.LOOK_R)
else: else:
self.set('face', faces.LOOK_L) self.set('face', faces.LOOK_L_HAPPY if good_mood else faces.LOOK_L)
time.sleep(part) time.sleep(part)
secs -= part secs -= part
@@ -284,6 +309,11 @@ class View(object):
self.set('status', self._voice.on_miss(who)) self.set('status', self._voice.on_miss(who))
self.update() self.update()
def on_grateful(self):
self.set('face', faces.GRATEFUL)
self.set('status', self._voice.on_grateful())
self.update()
def on_lonely(self): def on_lonely(self):
self.set('face', faces.LONELY) self.set('face', faces.LONELY)
self.set('status', self._voice.on_lonely()) self.set('status', self._voice.on_lonely())
@@ -294,6 +324,12 @@ class View(object):
self.set('status', self._voice.on_handshakes(new_shakes)) self.set('status', self._voice.on_handshakes(new_shakes))
self.update() self.update()
def on_unread_messages(self, count, total):
self.set('face', faces.EXCITED)
self.set('status', self._voice.on_unread_messages(count, total))
self.update()
time.sleep(5.0)
def on_rebooting(self): def on_rebooting(self):
self.set('face', faces.BROKEN) self.set('face', faces.BROKEN)
self.set('status', self._voice.on_rebooting()) self.set('status', self._voice.on_rebooting())
@@ -304,7 +340,10 @@ class View(object):
self.set('status', self._voice.custom(text)) self.set('status', self._voice.custom(text))
self.update() self.update()
def update(self, force=False): def update(self, force=False, new_data={}):
for key, val in new_data.items():
self.set(key, val)
with self._lock: with self._lock:
if self._frozen: if self._frozen:
return return

204
pwnagotchi/ui/web.py Normal file
View File

@@ -0,0 +1,204 @@
import re
import _thread
from http.server import BaseHTTPRequestHandler, HTTPServer
from threading import Lock
import shutil
import logging
import pwnagotchi
from pwnagotchi import plugins
frame_path = '/root/pwnagotchi.png'
frame_format = 'PNG'
frame_ctype = 'image/png'
frame_lock = Lock()
def update_frame(img):
global frame_lock, frame_path, frame_format
with frame_lock:
img.save(frame_path, format=frame_format)
STYLE = """
.block {
-webkit-appearance: button;
-moz-appearance: button;
appearance: button;
display: block;
cursor: pointer;
text-align: center;
}
"""
SCRIPT = """
window.onload = function() {
var image = document.getElementById("ui");
function updateImage() {
image.src = image.src.split("?")[0] + "?" + new Date().getTime();
}
setInterval(updateImage, %d);
}
"""
INDEX = """<html>
<head>
<title>%s</title>
<style>""" + STYLE + """</style>
</head>
<body>
<div style="position: absolute; top:0; left:0; width:100%%;">
<img src="/ui" id="ui" style="width:100%%"/>
<br/>
<hr/>
<form method="POST" action="/shutdown" onsubmit="return confirm('This will halt the unit, continue?');">
<input type="submit" class="block" value="Shutdown"/>
</form>
</div>
<script type="text/javascript">""" + SCRIPT + """</script>
</body>
</html>"""
SHUTDOWN = """<html>
<head>
<title>%s</title>
<style>""" + STYLE + """</style>
</head>
<body>
<div style="position: absolute; top:0; left:0; width:100%%;">
Shutting down ...
</div>
</body>
</html>"""
class Handler(BaseHTTPRequestHandler):
AllowedOrigin = '*'
# suppress internal logging
def log_message(self, format, *args):
return
def _send_cors_headers(self):
# misc security
self.send_header("X-Frame-Options", "DENY")
self.send_header("X-Content-Type-Options", "nosniff")
self.send_header("X-XSS-Protection", "1; mode=block")
self.send_header("Referrer-Policy", "same-origin")
# cors
self.send_header("Access-Control-Allow-Origin", Handler.AllowedOrigin)
self.send_header('Access-Control-Allow-Credentials', 'true')
self.send_header("Access-Control-Allow-Methods", "GET, POST, PUT, DELETE, OPTIONS")
self.send_header("Access-Control-Allow-Headers",
"Accept, Content-Type, Content-Length, Accept-Encoding, X-CSRF-Token, Authorization")
self.send_header("Vary", "Origin")
# just render some html in a 200 response
def _html(self, html):
self.send_response(200)
self._send_cors_headers()
self.send_header('Content-type', 'text/html')
self.end_headers()
try:
self.wfile.write(bytes(html, "utf8"))
except:
pass
# serve the main html page
def _index(self):
self._html(INDEX % (pwnagotchi.name(), 1000))
# serve a message and shuts down the unit
def _shutdown(self):
self._html(SHUTDOWN % pwnagotchi.name())
pwnagotchi.shutdown()
# serve the PNG file with the display image
def _image(self):
global frame_lock, frame_path, frame_ctype
with frame_lock:
self.send_response(200)
self._send_cors_headers()
self.send_header('Content-type', frame_ctype)
self.end_headers()
try:
with open(frame_path, 'rb') as fp:
shutil.copyfileobj(fp, self.wfile)
except:
pass
# check the Origin header vs CORS
def _is_allowed(self):
origin = self.headers.get('origin')
if not origin and Handler.AllowedOrigin != '*':
logging.warning("request with no Origin header from %s" % self.address_string())
return False
if Handler.AllowedOrigin != '*':
if origin != Handler.AllowedOrigin:
logging.warning("request with blocked Origin from %s: %s" % (self.address_string(), origin))
return False
return True
def do_OPTIONS(self):
self.send_response(200)
self._send_cors_headers()
self.end_headers()
def do_POST(self):
if not self._is_allowed():
return
if self.path.startswith('/shutdown'):
self._shutdown()
else:
self.send_response(404)
def do_GET(self):
if not self._is_allowed():
return
if self.path == '/':
self._index()
elif self.path.startswith('/ui'):
self._image()
elif self.path.startswith('/plugins'):
matches = re.match(r'\/plugins\/([^\/]+)(\/.*)?', self.path)
if matches:
groups = matches.groups()
plugin_name = groups[0]
right_path = groups[1] if len(groups) == 2 else None
plugins.one(plugin_name, 'webhook', right_path)
else:
self.send_response(404)
class Server(object):
def __init__(self, config):
self._enabled = config['video']['enabled']
self._port = config['video']['port']
self._address = config['video']['address']
self._httpd = None
if 'origin' in config['video'] and config['video']['origin'] != '*':
Handler.AllowedOrigin = config['video']['origin']
else:
logging.warning("THE WEB UI IS RUNNING WITH ALLOWED ORIGIN SET TO *, READ WHY YOU SHOULD CHANGE IT HERE " +
"https://developer.mozilla.org/it/docs/Web/HTTP/CORS")
if self._enabled:
_thread.start_new_thread(self._http_serve, ())
def _http_serve(self):
if self._address is not None:
self._httpd = HTTPServer((self._address, self._port), Handler)
logging.info("web ui available at http://%s:%d/" % (self._address, self._port))
self._httpd.serve_forever()
else:
logging.info("could not get ip of usb0, video server not starting")

View File

@@ -38,6 +38,12 @@ def load_config(args):
# https://stackoverflow.com/questions/42392600/oserror-errno-18-invalid-cross-device-link # https://stackoverflow.com/questions/42392600/oserror-errno-18-invalid-cross-device-link
shutil.move("/boot/config.yml", args.user_config) shutil.move("/boot/config.yml", args.user_config)
# check for an entire pwnagotchi folder on /boot/
if os.path.isdir('/boot/pwnagotchi'):
print("installing /boot/pwnagotchi to /etc/pwnagotchi ...")
shutil.rmtree('/etc/pwnagotchi', ignore_errors=True)
shutil.move('/boot/pwnagotchi', '/etc/')
# if not config is found, copy the defaults # if not config is found, copy the defaults
if not os.path.exists(args.config): if not os.path.exists(args.config):
print("copying %s to %s ..." % (ref_defaults_file, args.config)) print("copying %s to %s ..." % (ref_defaults_file, args.config))
@@ -82,12 +88,16 @@ def load_config(args):
elif config['ui']['display']['type'] in ('ws_2', 'ws2', 'waveshare_2', 'waveshare2'): elif config['ui']['display']['type'] in ('ws_2', 'ws2', 'waveshare_2', 'waveshare2'):
config['ui']['display']['type'] = 'waveshare_2' config['ui']['display']['type'] = 'waveshare_2'
elif config['ui']['display']['type'] in ('ws_27inch', 'ws27inch', 'waveshare_27inch', 'waveshare27inch'):
config['ui']['display']['type'] = 'waveshare27inch'
elif config['ui']['display']['type'] in ('lcdhat'):
config['ui']['display']['type'] = 'lcdhat'
else: else:
print("unsupported display type %s" % config['ui']['display']['type']) print("unsupported display type %s" % config['ui']['display']['type'])
exit(1) exit(1)
print("Effective Configuration:")
print(yaml.dump(config, default_flow_style=False))
return config return config
@@ -106,6 +116,12 @@ def setup_logging(args, config):
console_handler.setFormatter(formatter) console_handler.setFormatter(formatter)
root.addHandler(console_handler) root.addHandler(console_handler)
# https://stackoverflow.com/questions/24344045/how-can-i-completely-remove-any-logging-from-requests-module-in-python?noredirect=1&lq=1
logging.getLogger("urllib3").propagate = False
requests_log = logging.getLogger("requests")
requests_log.addHandler(logging.NullHandler())
requests_log.propagate = False
def secs_to_hhmmss(secs): def secs_to_hhmmss(secs):
mins, secs = divmod(secs, 60) mins, secs = divmod(secs, 60)
@@ -263,6 +279,9 @@ class StatusFile(object):
def newer_then_minutes(self, minutes): def newer_then_minutes(self, minutes):
return self._updated is not None and ((datetime.now() - self._updated).seconds / 60) < minutes return self._updated is not None and ((datetime.now() - self._updated).seconds / 60) < minutes
def newer_then_hours(self, hours):
return self._updated is not None and ((datetime.now() - self._updated).seconds / (60 * 60)) < hours
def newer_then_days(self, days): def newer_then_days(self, days):
return self._updated is not None and (datetime.now() - self._updated).days < days return self._updated is not None and (datetime.now() - self._updated).days < days

View File

@@ -70,8 +70,13 @@ class Voice:
self._('My crime is that of curiosity ...')]) self._('My crime is that of curiosity ...')])
def on_new_peer(self, peer): def on_new_peer(self, peer):
if peer.first_encounter():
return random.choice([ return random.choice([
self._('Hello {name}! Nice to meet you.').format(name=peer.name()), self._('Hello {name}! Nice to meet you.').format(name=peer.name())])
else:
return random.choice([
self._('Yo {name}! Sup?').format(name=peer.name()),
self._('Hey {name} how are you doing?').format(name=peer.name()),
self._('Unit {name} is nearby!').format(name=peer.name())]) self._('Unit {name} is nearby!').format(name=peer.name())])
def on_lost_peer(self, peer): def on_lost_peer(self, peer):
@@ -85,6 +90,11 @@ class Voice:
self._('{name} missed!').format(name=who), self._('{name} missed!').format(name=who),
self._('Missed!')]) self._('Missed!')])
def on_grateful(self):
return random.choice([
self._('Good friends are a blessing!'),
self._('I love my friends!')])
def on_lonely(self): def on_lonely(self):
return random.choice([ return random.choice([
self._('Nobody wants to play with me ...'), self._('Nobody wants to play with me ...'),
@@ -129,6 +139,10 @@ class Voice:
s = 's' if new_shakes > 1 else '' s = 's' if new_shakes > 1 else ''
return self._('Cool, we got {num} new handshake{plural}!').format(num=new_shakes, plural=s) return self._('Cool, we got {num} new handshake{plural}!').format(num=new_shakes, plural=s)
def on_unread_messages(self, count, total):
s = 's' if count > 1 else ''
return self._('You have {count} new message{plural}!').format(count=count, plural=s)
def on_rebooting(self): def on_rebooting(self):
return self._("Ops, something went wrong ... Rebooting ...") return self._("Ops, something went wrong ... Rebooting ...")

View File

@@ -6,7 +6,7 @@ gym==0.14.0
stable-baselines==2.7.0 stable-baselines==2.7.0
tensorflow==1.13.1 tensorflow==1.13.1
tensorflow-estimator==1.14.0 tensorflow-estimator==1.14.0
tweepy==3.6.0 tweepy==3.7.0
file-read-backwards==2.0.0 file-read-backwards==2.0.0
numpy==1.17.2 numpy==1.17.2
inky==0.0.5 inky==0.0.5

View File

@@ -4,18 +4,14 @@
UNIT_HOSTNAME=${1:-10.0.0.2} UNIT_HOSTNAME=${1:-10.0.0.2}
# output backup zip file # output backup zip file
OUTPUT=${2:-pwnagotchi-backup.zip} OUTPUT=${2:-pwnagotchi-backup.zip}
# temporary folder
TEMP_BACKUP_FOLDER=/tmp/pwnagotchi_backup
# what to backup # what to backup
FILES_TO_BACKUP=( FILES_TO_BACKUP=(
/root/brain.nn /root/brain.nn
/root/brain.json /root/brain.json
/root/.api-report.json /root/.api-report.json
/root/handshakes /root/handshakes
/root/peers
/etc/pwnagotchi/ /etc/pwnagotchi/
/etc/hostname
/etc/hosts
/etc/motd
/var/log/pwnagotchi.log /var/log/pwnagotchi.log
) )
@@ -26,17 +22,24 @@ ping -c 1 $UNIT_HOSTNAME >/dev/null || {
echo "@ backing up $UNIT_HOSTNAME to $OUTPUT ..." echo "@ backing up $UNIT_HOSTNAME to $OUTPUT ..."
rm -rf "$TEMP_BACKUP_FOLDER" ssh pi@$UNIT_HOSTNAME "sudo rm -rf /tmp/backup && sudo rm -rf /tmp/backup.zip" > /dev/null
for file in "${FILES_TO_BACKUP[@]}"; do for file in "${FILES_TO_BACKUP[@]}"; do
dir=$(dirname $file) dir=$(dirname $file)
echo " $file -> $TEMP_BACKUP_FOLDER$dir/"
mkdir -p "$TEMP_BACKUP_FOLDER/$dir" echo "@ copying $file to /tmp/backup$dir"
scp -Cr root@$UNIT_HOSTNAME:$file "$TEMP_BACKUP_FOLDER$dir/"
ssh pi@$UNIT_HOSTNAME "mkdir -p /tmp/backup$dir" > /dev/null
ssh pi@$UNIT_HOSTNAME "sudo cp -r $file /tmp/backup$dir" > /dev/null
done done
ZIPFILE="$PWD/$OUTPUT" echo "@ pulling from $UNIT_HOSTNAME ..."
pushd $PWD
cd "$TEMP_BACKUP_FOLDER" rm -rf /tmp/backup
zip -r -9 -q "$ZIPFILE" . scp -rC pi@$UNIT_HOSTNAME:/tmp/backup /tmp/
popd
echo "@ compressing ..."
zip -r -9 -q $OUTPUT /tmp/backup
rm -rf /tmp/backup