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/),
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.

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

127
pwnagotchi/automata.py Normal file
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,6 +3,20 @@ import requests
from requests.auth import HTTPBasicAuth
def decode(r, verbose_errors=True):
try:
return r.json()
except Exception as e:
if r.status_code == 200:
logging.error("error while decoding json: error='%s' resp='%s'" % (e, r.text))
else:
err = "error %d: %s" % (r.status_code, r.text.strip())
if verbose_errors:
logging.info(err)
raise Exception(err)
return r.text
class Client(object):
def __init__(self, hostname='localhost', scheme='http', port=8081, username='user', password='pass'):
self.hostname = hostname
@@ -13,27 +27,14 @@ class Client(object):
self.url = "%s://%s:%d/api" % (scheme, hostname, port)
self.auth = HTTPBasicAuth(username, password)
def _decode(self, r, verbose_errors=True):
try:
return r.json()
except Exception as e:
if r.status_code == 200:
logging.error("error while decoding json: error='%s' resp='%s'" % (e, r.text))
else:
err = "error %d: %s" % (r.status_code, r.text.strip())
if verbose_errors:
logging.info(err)
raise Exception(err)
return r.text
def session(self):
r = requests.get("%s/session" % self.url, auth=self.auth)
return self._decode(r)
return decode(r)
def events(self):
r = requests.get("%s/events" % self.url, auth=self.auth)
return self._decode(r)
return decode(r)
def run(self, command, verbose_errors=True):
r = requests.post("%s/session" % self.url, auth=self.auth, json={'cmd': command})
return self._decode(r, verbose_errors=verbose_errors)
return decode(r, verbose_errors=verbose_errors)

View File

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

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

View File

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

View File

@@ -3,12 +3,12 @@
# This file is distributed under the same license as the pwnagotchi package.
# FIRST AUTHOR <7271496+quantumsheep@users.noreply.github.com>, 2019.
#
#, fuzzy
#,
msgid ""
msgstr ""
"Project-Id-Version: 0.0.1\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2019-10-05 14:10+0200\n"
"POT-Creation-Date: 2019-10-23 18:37+0200\n"
"PO-Revision-Date: 2019-10-03 10:34+0200\n"
"Last-Translator: quantumsheep <7271496+quantumsheep@users.noreply.github."
"com>\n"
@@ -19,16 +19,16 @@ msgstr ""
"Content-Transfer-Encoding: 8bit\n"
msgid "ZzzzZZzzzzZzzz"
msgstr ""
msgstr "ZzzzZZzzzzZzzz"
msgid "Hi, I'm Pwnagotchi! Starting ..."
msgstr "Bonjour, je suis Pwnagotchi! Démarrage ..."
msgstr "Bonjour, je suis Pwnagotchi ! Démarrage..."
msgid "New day, new hunt, new pwns!"
msgstr "Nouveau jour, nouvelle chasse, nouveaux pwns !"
msgid "Hack the Planet!"
msgstr "Hack la planète!"
msgstr "Hack la planète !"
msgid "AI ready."
msgstr "L'IA est prête."
@@ -36,85 +36,88 @@ msgstr "L'IA est prête."
msgid "The neural network is ready."
msgstr "Le réseau neuronal est prêt."
msgid "Generating keys, do not turn off ..."
msgstr "Génération des clés, ne pas éteindre..."
#, python-brace-format
msgid "Hey, channel {channel} is free! Your AP will say thanks."
msgstr "Hey, le channel {channel} est libre! Ton point d'accès va te remercier."
msgstr "Hey, le canal {channel} est libre! Ton point d'accès va te remercier."
msgid "I'm bored ..."
msgstr "Je m'ennuie ..."
msgstr "Je m'ennuie..."
msgid "Let's go for a walk!"
msgstr "Allons faire un tour!"
msgstr "Allons faire un tour !"
msgid "This is the best day of my life!"
msgstr "C'est le meilleur jour de ma vie!"
msgstr "C'est le meilleur jour de ma vie !"
msgid "Shitty day :/"
msgstr "Journée de merde :/"
msgid "I'm extremely bored ..."
msgstr "Je m'ennuie énormément ..."
msgstr "Je m'ennuie énormément..."
msgid "I'm very sad ..."
msgstr "Je suis très triste ..."
msgstr "Je suis très triste..."
msgid "I'm sad"
msgstr "Je suis triste"
msgid "I'm living the life!"
msgstr "Je vis la vie!"
msgstr "Je vis la vie !"
msgid "I pwn therefore I am."
msgstr "Je pwn donc je suis."
msgid "So many networks!!!"
msgstr "Tellement de réseaux!!!"
msgstr "Tellement de réseaux !!!"
msgid "I'm having so much fun!"
msgstr "Je m'amuse tellement!"
msgstr "Je m'amuse tellement !"
msgid "My crime is that of curiosity ..."
msgstr "Mon crime, c'est la curiosité ..."
msgstr "Mon crime, c'est la curiosité..."
#, python-brace-format
msgid "Hello {name}! Nice to meet you. {name}"
msgstr "Bonjour {name}! Ravi de te rencontrer. {name}"
msgid "Hello {name}! Nice to meet you."
msgstr "Bonjour {name} ! Ravi de te rencontrer."
#, python-brace-format
msgid "Unit {name} is nearby! {name}"
msgstr "L'unité {name} est proche! {name}"
msgid "Unit {name} is nearby!"
msgstr "L'unité {name} est proche !"
#, python-brace-format
msgid "Uhm ... goodbye {name}"
msgstr "Hum ... au revoir {name}"
msgstr "Hum... au revoir {name}"
#, python-brace-format
msgid "{name} is gone ..."
msgstr "{name} est parti ..."
msgstr "{name} est part ..."
#, python-brace-format
msgid "Whoops ... {name} is gone."
msgstr "Oups ... {name} est parti."
msgstr "Oups... {name} est parti."
#, python-brace-format
msgid "{name} missed!"
msgstr "{name} raté!"
msgstr "{name} raté !"
msgid "Missed!"
msgstr "Raté!"
msgstr "Raté !"
msgid "Nobody wants to play with me ..."
msgstr "Personne ne veut jouer avec moi ..."
msgstr "Personne ne veut jouer avec moi..."
msgid "I feel so alone ..."
msgstr "Je me sens si seul ..."
msgstr "Je me sens si seul..."
msgid "Where's everybody?!"
msgstr "Où est tout le monde?!"
msgstr "Où est tout le monde ?!"
#, python-brace-format
msgid "Napping for {secs}s ..."
msgstr "Fais la sieste pendant {secs}s ..."
msgstr "Fais la sieste pendant {secs}s..."
msgid "Zzzzz"
msgstr ""
@@ -123,9 +126,15 @@ msgstr ""
msgid "ZzzZzzz ({secs}s)"
msgstr ""
msgid "Good night."
msgstr "Bonne nuit."
msgid "Zzz"
msgstr "Zzz"
#, python-brace-format
msgid "Waiting for {secs}s ..."
msgstr "Attends pendant {secs}s ..."
msgstr "Attends pendant {secs}s..."
#, python-brace-format
msgid "Looking around ({secs}s)"
@@ -133,7 +142,7 @@ msgstr "Regarde autour ({secs}s)"
#, python-brace-format
msgid "Hey {what} let's be friends!"
msgstr "Hey {what}, soyons amis!"
msgstr "Hey {what}, soyons amis !"
#, python-brace-format
msgid "Associating to {what}"
@@ -141,11 +150,11 @@ msgstr "Association à {what}"
#, python-brace-format
msgid "Yo {what}!"
msgstr ""
msgstr "Yo {what} !"
#, python-brace-format
msgid "Just decided that {mac} needs no WiFi!"
msgstr "Je viens de décider que {mac} n'a pas besoin de WiFi!"
msgstr "Je viens de décider que {mac} n'a pas besoin de WiFi !"
#, python-brace-format
msgid "Deauthenticating {mac}"
@@ -153,14 +162,18 @@ msgstr "Désauthentification de {mac}"
#, python-brace-format
msgid "Kickbanning {mac}!"
msgstr "Je kick et je bannis {mac}!"
msgstr "Je kick et je bannis {mac} !"
#, python-brace-format
msgid "Cool, we got {num} new handshake{plural}!"
msgstr "Cool, on a {num} nouveaux handshake{plural}!"
msgstr "Cool, on a {num} nouveaux handshake{plural} !"
#, python-brace-format
msgid "You have {count} new message{plural}!"
msgstr "Tu as {num} nouveaux message{plural} !"
msgid "Ops, something went wrong ... Rebooting ..."
msgstr "Oups, quelque chose s'est mal passé ... Redémarrage ..."
msgstr "Oups, quelque chose s'est mal passé... Redémarrage..."
#, python-brace-format
msgid "Kicked {num} stations\n"
@@ -187,8 +200,8 @@ msgid ""
"{associated} new friends and ate {handshakes} handshakes! #pwnagotchi "
"#pwnlog #pwnlife #hacktheplanet #skynet"
msgstr ""
"J'ai pwn durant {duration} et kick {deauthed} clients! J'ai aussi rencontré "
"{associated} nouveaux amis et dévoré {handshakes} handshakes! #pwnagotchi "
"J'ai pwn durant {duration} et kick {deauthed} clients ! J'ai aussi rencontré "
"{associated} nouveaux amis et dévoré {handshakes} handshakes ! #pwnagotchi "
"#pwnlog #pwnlife #hacktheplanet #skynet"
msgid "hours"

View File

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

View File

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

View File

@@ -167,28 +167,33 @@ class LastSession(object):
self.duration_human = ', '.join(self.duration_human)
self.avg_reward /= (self.epochs if self.epochs else 1)
def parse(self):
lines = []
def parse(self, skip=False):
if skip:
logging.debug("skipping parsing of the last session logs ...")
else:
logging.debug("parsing last session logs ...")
if os.path.exists(self.path):
with FileReadBackwards(self.path, encoding="utf-8") as fp:
for line in fp:
line = line.strip()
if line != "" and line[0] != '[':
continue
lines.append(line)
if LastSession.START_TOKEN in line:
break
lines.reverse()
lines = []
if len(lines) == 0:
lines.append("Initial Session");
if os.path.exists(self.path):
with FileReadBackwards(self.path, encoding="utf-8") as fp:
for line in fp:
line = line.strip()
if line != "" and line[0] != '[':
continue
lines.append(line)
if LastSession.START_TOKEN in line:
break
lines.reverse()
self.last_session = lines
self.last_session_id = hashlib.md5(lines[0].encode()).hexdigest()
self.last_saved_session_id = self._get_last_saved_session_id()
if len(lines) == 0:
lines.append("Initial Session");
self._parse_stats()
self.last_session = lines
self.last_session_id = hashlib.md5(lines[0].encode()).hexdigest()
self.last_saved_session_id = self._get_last_saved_session_id()
self._parse_stats()
self.parsed = True
def is_new(self):

View File

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

View File

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

View File

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

View File

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

View File

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

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

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__]
OPTIONS = dict()
# called when <host>:<port>/plugins/<pluginname> is opened
def on_webhook(response, path):
res = "<html><body><a>Hook triggered</a></body></html>"
response.send_response(200)
response.send_header('Content-type', 'text/html')
response.end_headers()
try:
response.wfile.write(bytes(res, "utf-8"))
except Exception as ex:
logging.error(ex)
# called when the plugin is loaded
def on_loaded():
logging.warning("WARNING: plugin %s should be disabled!" % __name__)

View File

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

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

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("MAC,SSID,AuthMode,FirstSeen,Channel,RSSI,CurrentLatitude,CurrentLongitude,AltitudeMeters,AccuracyMeters,Type")
writer = csv.writer(dummy, delimiter=",", quoting=csv.QUOTE_NONE)
writer = csv.writer(dummy, delimiter=",", quoting=csv.QUOTE_NONE, escapechar="\\")
writer.writerow([
pcap_data[WifiInfo.BSSID],
pcap_data[WifiInfo.ESSID],
@@ -155,7 +155,7 @@ def on_internet_available(agent):
continue
if gps_data['Latitude'] == 0 and gps_data['Longitude'] == 0:
logging.warning("WIGLE: Not enough gps-informations for %s. Trying again next time.", gps_file)
logging.warning("WIGLE: Not enough gps-information for %s. Trying again next time.", gps_file)
SKIP.append(gps_file)
continue
@@ -167,7 +167,7 @@ def on_internet_available(agent):
WifiInfo.CHANNEL,
WifiInfo.RSSI])
except FieldNotFoundError:
logging.error("WIGLE: Could not extract all informations. Skip %s", gps_file)
logging.error("WIGLE: Could not extract all information. Skip %s", gps_file)
SKIP.append(gps_file)
continue
except Scapy_Exception as sc_e:

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

View File

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

View File

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

View File

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

View File

@@ -7,24 +7,25 @@ import numpy as np
class ST7789(object):
"""class for ST7789 240*240 1.3inch OLED displays."""
def __init__(self,spi,rst = 27,dc = 25,bl = 24):
def __init__(self, spi, rst=27, dc=25, bl=24):
self.width = 240
self.height = 240
#Initialize DC RST pin
# Initialize DC RST pin
self._dc = dc
self._rst = rst
self._bl = bl
GPIO.setmode(GPIO.BCM)
GPIO.setwarnings(False)
GPIO.setup(self._dc,GPIO.OUT)
GPIO.setup(self._rst,GPIO.OUT)
GPIO.setup(self._bl,GPIO.OUT)
GPIO.setup(self._dc, GPIO.OUT)
GPIO.setup(self._rst, GPIO.OUT)
GPIO.setup(self._bl, GPIO.OUT)
GPIO.output(self._bl, GPIO.HIGH)
#Initialize SPI
# Initialize SPI
self._spi = spi
self._spi.max_speed_hz = 40000000
""" Write register address and data """
def command(self, cmd):
GPIO.output(self._dc, GPIO.LOW)
self._spi.writebytes([cmd])
@@ -34,13 +35,13 @@ class ST7789(object):
self._spi.writebytes([val])
def Init(self):
"""Initialize dispaly"""
"""Initialize dispaly"""
self.reset()
self.command(0x36)
self.data(0x70) #self.data(0x00)
self.data(0x70) # self.data(0x00)
self.command(0x3A)
self.command(0x3A)
self.data(0x05)
self.command(0xB2)
@@ -51,7 +52,7 @@ class ST7789(object):
self.data(0x33)
self.command(0xB7)
self.data(0x35)
self.data(0x35)
self.command(0xBB)
self.data(0x19)
@@ -63,13 +64,13 @@ class ST7789(object):
self.data(0x01)
self.command(0xC3)
self.data(0x12)
self.data(0x12)
self.command(0xC4)
self.data(0x20)
self.command(0xC6)
self.data(0x0F)
self.data(0x0F)
self.command(0xD0)
self.data(0xA4)
@@ -106,7 +107,7 @@ class ST7789(object):
self.data(0x1F)
self.data(0x20)
self.data(0x23)
self.command(0x21)
self.command(0x11)
@@ -115,51 +116,51 @@ class ST7789(object):
def reset(self):
"""Reset the display"""
GPIO.output(self._rst,GPIO.HIGH)
GPIO.output(self._rst, GPIO.HIGH)
time.sleep(0.01)
GPIO.output(self._rst,GPIO.LOW)
GPIO.output(self._rst, GPIO.LOW)
time.sleep(0.01)
GPIO.output(self._rst,GPIO.HIGH)
GPIO.output(self._rst, GPIO.HIGH)
time.sleep(0.01)
def SetWindows(self, Xstart, Ystart, Xend, Yend):
#set the X coordinates
# set the X coordinates
self.command(0x2A)
self.data(0x00) #Set the horizontal starting point to the high octet
self.data(Xstart & 0xff) #Set the horizontal starting point to the low octet
self.data(0x00) #Set the horizontal end to the high octet
self.data((Xend - 1) & 0xff) #Set the horizontal end to the low octet
#set the Y coordinates
self.data(0x00) # Set the horizontal starting point to the high octet
self.data(Xstart & 0xff) # Set the horizontal starting point to the low octet
self.data(0x00) # Set the horizontal end to the high octet
self.data((Xend - 1) & 0xff) # Set the horizontal end to the low octet
# set the Y coordinates
self.command(0x2B)
self.data(0x00)
self.data((Ystart & 0xff))
self.data(0x00)
self.data((Yend - 1) & 0xff )
self.data((Yend - 1) & 0xff)
self.command(0x2C)
def ShowImage(self,Image,Xstart,Ystart):
self.command(0x2C)
def ShowImage(self, Image, Xstart, Ystart):
"""Set buffer to value of Python Imaging Library image."""
"""Write display buffer to physical display"""
imwidth, imheight = Image.size
if imwidth != self.width or imheight != self.height:
raise ValueError('Image must be same dimensions as display \
({0}x{1}).' .format(self.width, self.height))
({0}x{1}).'.format(self.width, self.height))
img = np.asarray(Image)
pix = np.zeros((self.width,self.height,2), dtype = np.uint8)
pix[...,[0]] = np.add(np.bitwise_and(img[...,[0]],0xF8),np.right_shift(img[...,[1]],5))
pix[...,[1]] = np.add(np.bitwise_and(np.left_shift(img[...,[1]],3),0xE0),np.right_shift(img[...,[2]],3))
pix = np.zeros((self.width, self.height, 2), dtype=np.uint8)
pix[..., [0]] = np.add(np.bitwise_and(img[..., [0]], 0xF8), np.right_shift(img[..., [1]], 5))
pix[..., [1]] = np.add(np.bitwise_and(np.left_shift(img[..., [1]], 3), 0xE0), np.right_shift(img[..., [2]], 3))
pix = pix.flatten().tolist()
self.SetWindows ( 0, 0, self.width, self.height)
GPIO.output(self._dc,GPIO.HIGH)
for i in range(0,len(pix),4096):
self._spi.writebytes(pix[i:i+4096])
self.SetWindows(0, 0, self.width, self.height)
GPIO.output(self._dc, GPIO.HIGH)
for i in range(0, len(pix), 4096):
self._spi.writebytes(pix[i:i + 4096])
def clear(self):
"""Clear contents of image buffer"""
_buffer = [0xff]*(self.width * self.height * 2)
self.SetWindows ( 0, 0, self.width, self.height)
GPIO.output(self._dc,GPIO.HIGH)
for i in range(0,len(_buffer),4096):
self._spi.writebytes(_buffer[i:i+4096])
_buffer = [0xff] * (self.width * self.height * 2)
self.SetWindows(0, 0, self.width, self.height)
GPIO.output(self._dc, GPIO.HIGH)
for i in range(0, len(_buffer), 4096):
self._spi.writebytes(_buffer[i:i + 4096])

View File

@@ -7,70 +7,15 @@
# * | Date : 2019-10-18
# * | Info :
# ******************************************************************************/
import RPi.GPIO as GPIO
import time
from smbus import SMBus
import spidev
import ctypes
# import spidev
# Pin definition
RST_PIN = 27
DC_PIN = 25
BL_PIN = 24
RST_PIN = 27
DC_PIN = 25
BL_PIN = 24
Device_SPI = 1
Device_I2C = 0
Device = Device_SPI
spi = spidev.SpiDev(0, 0)
def digital_write(pin, value):
GPIO.output(pin, value)
def digital_read(pin):
return GPIO.input(BUSY_PIN)
def delay_ms(delaytime):
time.sleep(delaytime / 1000.0)
def spi_writebyte(data):
# SPI.writebytes(data)
spi.writebytes([data[0]])
def i2c_writebyte(reg, value):
bus.write_byte_data(address, reg, value)
# time.sleep(0.01)
def module_init():
# print("module_init")
GPIO.setmode(GPIO.BCM)
GPIO.setwarnings(False)
GPIO.setup(RST_PIN, GPIO.OUT)
GPIO.setup(DC_PIN, GPIO.OUT)
# SPI.max_speed_hz = 2000000
# SPI.mode = 0b00
# i2c_writebyte(0xff,0xff)
# spi.SYSFS_software_spi_begin()
# spi.SYSFS_software_spi_setDataMode(0);
# spi.SYSFS_software_spi_setClockDivider(1);
#spi.max_speed_hz = 2000000
#spi.mode = 0b00
GPIO.output(BL_PIN, 1)
GPIO.output(DC_PIN, 0)
return 0
def module_exit():
spi.SYSFS_software_spi_end()
GPIO.output(RST_PIN, 0)
GPIO.output(DC_PIN, 0)
### END OF FILE ###

View File

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

View File

@@ -47,11 +47,11 @@ class EPD:
# Hardware reset
def reset(self):
epdconfig.digital_write(self.reset_pin, GPIO.HIGH)
epdconfig.delay_ms(200)
epdconfig.delay_ms(200)
epdconfig.digital_write(self.reset_pin, GPIO.LOW) # module reset
epdconfig.delay_ms(200)
epdconfig.digital_write(self.reset_pin, GPIO.HIGH)
epdconfig.delay_ms(200)
epdconfig.delay_ms(200)
def send_command(self, command):
epdconfig.digital_write(self.dc_pin, GPIO.LOW)
@@ -64,8 +64,9 @@ class EPD:
epdconfig.digital_write(self.cs_pin, GPIO.LOW)
epdconfig.spi_writebyte([data])
epdconfig.digital_write(self.cs_pin, GPIO.HIGH)
def ReadBusy(self):
epdconfig.delay_ms(20)
while(epdconfig.digital_read(self.busy_pin) == 0): # 0: idle, 1: busy
epdconfig.delay_ms(100)
@@ -79,16 +80,16 @@ class EPD:
self.send_data(0x17)
self.send_data(0x17)
self.send_data(0x17)
self.send_command(0x04) # POWER_ON
self.ReadBusy()
self.send_command(0x00) # PANEL_SETTING
self.send_data(0x8F)
self.send_command(0x50) # VCOM_AND_DATA_INTERVAL_SETTING
self.send_data(0xF0)
self.send_command(0x61) # RESOLUTION_SETTING
self.send_data(self.width & 0xff)
self.send_data(self.height >> 8)
@@ -120,7 +121,7 @@ class EPD:
for i in range(0, int(self.width * self.height / 8)):
self.send_data(imageblack[i])
self.send_command(0x92)
self.send_command(0x12) # REFRESH
self.ReadBusy()
@@ -129,26 +130,26 @@ class EPD:
for i in range(0, int(self.width * self.height / 8)):
self.send_data(imageblack[i])
self.send_command(0x92)
self.send_command(0x13)
for i in range(0, int(self.width * self.height / 8)):
self.send_data(imagecolor[i])
self.send_command(0x92)
self.send_command(0x12) # REFRESH
self.ReadBusy()
def Clear(self):
self.send_command(0x10)
for i in range(0, int(self.width * self.height / 8)):
self.send_data(0xFF)
self.send_command(0x92)
self.send_command(0x92)
self.send_command(0x13)
for i in range(0, int(self.width * self.height / 8)):
self.send_data(0xFF)
self.send_command(0x92)
self.send_command(0x12) # REFRESH
self.ReadBusy()
@@ -157,7 +158,7 @@ class EPD:
self.ReadBusy()
self.send_command(0x07) # DEEP_SLEEP
self.send_data(0xA5) # check code
# epdconfig.module_exit()
### END OF FILE ###

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

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

View File

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

View File

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

View File

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