Compare commits
72 Commits
Author | SHA1 | Date | |
---|---|---|---|
|
95b871aade | ||
|
7b05f10c6f | ||
|
6b1bca7cb2 | ||
|
79688305fd | ||
|
13b1fb6d14 | ||
|
8a1aad1a99 | ||
|
aeb6536959 | ||
|
970b6922b7 | ||
|
18dd71b989 | ||
|
256ccab05c | ||
|
0aa80d2307 | ||
|
5987f93009 | ||
|
ebeb22081b | ||
|
f97b106858 | ||
|
c449c77ef9 | ||
|
a05ea2f48a | ||
|
beb2fedf02 | ||
|
1936c309f0 | ||
|
ee3fb285be | ||
|
aa60a369a9 | ||
|
0e9f9c0f2e | ||
|
6645c80db3 | ||
|
13d68c7c24 | ||
|
df33d20cb2 | ||
|
f3eb208c6a | ||
|
ae5ca2a05e | ||
|
a9b9c6677e | ||
|
f85d80d3fd | ||
|
4be54cf3ee | ||
|
c8953d4654 | ||
|
26ff5c95f2 | ||
|
2f0f0edab0 | ||
|
b81c80cf99 | ||
|
baf20a9ac8 | ||
|
280ca22261 | ||
|
277dbd5a16 | ||
|
5b29f65042 | ||
|
9f66d7ab96 | ||
|
9625cf1122 | ||
|
6b42e48dff | ||
|
d814de75ab | ||
|
35b442f941 | ||
|
0f2ad47c17 | ||
|
748dbea13e | ||
|
b66f1b66e5 | ||
|
1642663c84 | ||
|
54a8fd81a5 | ||
|
2fe7ac0a71 | ||
|
4b563398f4 | ||
|
84be7c0d34 | ||
|
82bf9b9853 | ||
|
d9d38e7a1e | ||
|
5b78a13e95 | ||
|
a8a0f842a3 | ||
|
f8a28d375b | ||
|
cfa8a02abc | ||
|
e32be6ff27 | ||
|
ab74395602 | ||
|
b7a806c8ad | ||
|
e146a87b44 | ||
|
dfb4bcaf21 | ||
|
9aca3a3a5b | ||
|
d15f8c18b5 | ||
|
87a3fb5f0c | ||
|
8b366ca736 | ||
|
2bbcc36f2a | ||
|
308746a7de | ||
|
d648f7cdf5 | ||
|
5a53670133 | ||
|
e9899dda94 | ||
|
f0c5ad4b74 | ||
|
3773f96901 |
@@ -3,7 +3,6 @@ maintainers:
|
|||||||
- caquino
|
- caquino
|
||||||
- dadav
|
- dadav
|
||||||
- justin-p
|
- justin-p
|
||||||
- hexwaxwing
|
|
||||||
|
|
||||||
features:
|
features:
|
||||||
- comments
|
- comments
|
||||||
|
2
Makefile
2
Makefile
@@ -1,7 +1,7 @@
|
|||||||
PWN_HOSTNAME=pwnagotchi
|
PWN_HOSTNAME=pwnagotchi
|
||||||
PWN_VERSION=master
|
PWN_VERSION=master
|
||||||
|
|
||||||
all: install image clean
|
all: clean install image
|
||||||
|
|
||||||
install:
|
install:
|
||||||
curl https://releases.hashicorp.com/packer/1.3.5/packer_1.3.5_linux_amd64.zip -o /tmp/packer.zip
|
curl https://releases.hashicorp.com/packer/1.3.5/packer_1.3.5_linux_amd64.zip -o /tmp/packer.zip
|
||||||
|
18
README.md
18
README.md
@@ -11,30 +11,20 @@
|
|||||||
</p>
|
</p>
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
[Pwnagotchi](https://twitter.com/pwnagotchi) 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 in order to maximize the crackable WPA key material it captures (either passively, or by performing deauthentication and association attacks). This material is collected as PCAP files containing any form of handshake supported by [hashcat](https://hashcat.net/hashcat/), including [PMKIDs](https://www.evilsocket.net/2019/02/13/Pwning-WiFi-networks-with-bettercap-and-the-PMKID-client-less-attack/),
|
[Pwnagotchi](https://pwnagotchi.ai/) is an [A2C](https://hackernoon.com/intuitive-rl-intro-to-advantage-actor-critic-a2c-4ff545978752)-based "AI" leveraging [bettercap](https://www.bettercap.org/) that learns from its surrounding WiFi environment to maximize the crackable WPA key material it captures (either passively, or by performing authentication and association attacks). This material is collected as PCAP files containing any form of handshake supported by [hashcat](https://hashcat.net/hashcat/), including [PMKIDs](https://www.evilsocket.net/2019/02/13/Pwning-WiFi-networks-with-bettercap-and-the-PMKID-client-less-attack/),
|
||||||
full and half WPA handshakes.
|
full and half WPA handshakes.
|
||||||
|
|
||||||

|

|
||||||
|
|
||||||
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 own parameters](https://github.com/evilsocket/pwnagotchi/blob/master/pwnagotchi/defaults.yml#L73) over time to **get better at pwning WiFi things** in the environments you expose it to.
|
Instead of merely playing [Super Mario or Atari games](https://becominghuman.ai/getting-mario-back-into-the-gym-setting-up-super-mario-bros-in-openais-gym-8e39a96c1e41?gi=c4b66c3d5ced) like most reinforcement learning-based "AI" *(yawn)*, Pwnagotchi tunes [its parameters](https://github.com/evilsocket/pwnagotchi/blob/master/pwnagotchi/defaults.yml#L73) over time to **get better at pwning WiFi things to** in the environments you expose it to.
|
||||||
|
|
||||||
More specifically, Pwnagotchi is using an [LSTM with MLP feature extractor](https://stable-baselines.readthedocs.io/en/master/modules/policies.html#stable_baselines.common.policies.MlpLstmPolicy) as its policy network for the [A2C agent](https://stable-baselines.readthedocs.io/en/master/modules/a2c.html). If you're unfamiliar with A2C, here is [a very good introductory explanation](https://hackernoon.com/intuitive-rl-intro-to-advantage-actor-critic-a2c-4ff545978752) (in comic form!) of the basic principles behind how Pwnagotchi learns. (You can read more about how Pwnagotchi learns in the [Usage](https://www.pwnagotchi.ai/usage/#training-the-ai) doc.)
|
More specifically, Pwnagotchi is using an [LSTM with MLP feature extractor](https://stable-baselines.readthedocs.io/en/master/modules/policies.html#stable_baselines.common.policies.MlpLstmPolicy) as its policy network for the [A2C agent](https://stable-baselines.readthedocs.io/en/master/modules/a2c.html). If you're unfamiliar with A2C, here is [a very good introductory explanation](https://hackernoon.com/intuitive-rl-intro-to-advantage-actor-critic-a2c-4ff545978752) (in comic form!) of the basic principles behind how Pwnagotchi learns. (You can read more about how Pwnagotchi learns in the [Usage](https://www.pwnagotchi.ai/usage/#training-the-ai) doc.)
|
||||||
|
|
||||||
**Keep in mind:** Unlike the usual RL simulations, Pwnagotchi actually learns over time. Time for a Pwnagotchi is measured in epochs; a single epoch can last from a few seconds to minutes, depending on how many access points and client stations are visible. Do not expect your Pwnagotchi to perform amazingly well at the very beginning, as it will be [exploring](https://hackernoon.com/intuitive-rl-intro-to-advantage-actor-critic-a2c-4ff545978752) several combinations of [key parameters](https://www.pwnagotchi.ai/usage/#training-the-ai) to determine ideal adjustments for pwning the particular environment you are exposing it to during its beginning epochs ... but **definitely listen to your Pwnagotchi when it tells you it's bored!** Bring it into novel WiFi environments with you and have it observe new networks and capture new handshakes—and you'll see. :)
|
**Keep in mind:** Unlike the usual RL simulations, Pwnagotchi learns over time. Time for a Pwnagotchi is measured in epochs; a single epoch can last from a few seconds to minutes, depending on how many access points and client stations are visible. Do not expect your Pwnagotchi to perform amazingly well at the very beginning, as it will be [exploring](https://hackernoon.com/intuitive-rl-intro-to-advantage-actor-critic-a2c-4ff545978752) several combinations of [key parameters](https://www.pwnagotchi.ai/usage/#training-the-ai) to determine ideal adjustments for pwning the particular environment you are exposing it to during its beginning epochs ... but ** listen to your Pwnagotchi when it tells you it's boring!** Bring it into novel WiFi environments with you and have it observe new networks and capture new handshakes—and you'll see. :)
|
||||||
|
|
||||||
Multiple units within close physical proximity can "talk" to each other, advertising their own presence to each other by broadcasting custom information elements using a parasite protocol I've built on top of the existing dot11 standard. Over time, two or more units trained together will learn to cooperate upon detecting each other's presence by dividing the available channels among them for optimal pwnage.
|
Multiple units within close physical proximity can "talk" to each other, advertising their presence to each other by broadcasting custom information elements using a parasite protocol I've built on top of the existing dot11 standard. Over time, two or more units trained together will learn to cooperate upon detecting each other's presence by dividing the available channels among them for optimal pwnage.
|
||||||
|
|
||||||
## Why does Pwnagotchi exist?
|
|
||||||
|
|
||||||
For hackers to learn reinforcement learning, WiFi networking, and have an excuse to get out for more walks. Also? **It's cute as f---**.
|
|
||||||
|
|
||||||
**In case you're curious about the name:** *Pwnagotchi* is a portmanteau of *pwn* (which we shouldn't have to explain if you are interested in this project :kissing_heart:) and *-gotchi*. It is a nostalgic reference made in homage to a very popular children's toy from the 1990s called the [Tamagotchi](https://en.wikipedia.org/wiki/Tamagotchi). The Tamagotchi (たまごっち, derived from *tamago* (たまご) "egg" + *uotchi* (ウオッチ) "watch") is a cultural touchstone for many Millennial hackers as a formative electronic toy from our collective childhoods. Were you lucky enough to possess a Tamagotchi as a kid? Well, with your Pwnagotchi, you too can enjoy the nostalgic delight of being strangely emotionally attached to a handheld automata *yet again!* Except, this time around...you get to #HackThePlanet. >:D
|
|
||||||
|
|
||||||
## Documentation
|
## Documentation
|
||||||
---
|
|
||||||
:warning: **THE FOLLOWING DOCUMENTATION IS BEING PREPARED FOR THE v1.0 RELEASE OF PWNAGOTCHI. Since this effort is an active (and unstable) work-in-progress, the docs displayed here are in various stages of [in]completion. There will be dead links and placeholders throughout as we are still building things out in preparation for the v1.0 release.** :warning:
|
|
||||||
|
|
||||||
**IMPORTANT NOTE:** If you'd like to alphatest Pwnagotchi and are trying to get yours up and running while the project is still very unstable, please understand that the documentation here may not reflect what is currently implemented. If you have questions, ask the community of alphatesters in the [official Pwnagotchi Slack](https://pwnagotchi.herokuapp.com). The Pwnagotchi dev team is entirely focused on the v1.0 release and will NOT be providing support for alphatesters trying to get their Pwnagotchis working in the meantime. All technical support during this period of development is being provided by your fellow alphatesters in the Slack (thanks, everybody! :heart:).
|
|
||||||
|
|
||||||
https://www.pwnagotchi.ai
|
https://www.pwnagotchi.ai
|
||||||
|
|
||||||
|
@@ -37,7 +37,7 @@ if __name__ == '__main__':
|
|||||||
keypair = KeyPair(view=display)
|
keypair = KeyPair(view=display)
|
||||||
agent = Agent(view=display, config=config, keypair=keypair)
|
agent = Agent(view=display, config=config, keypair=keypair)
|
||||||
|
|
||||||
logging.info("%s@%s (v%s)" % (pwnagotchi.name(), agent._keypair.fingerprint, pwnagotchi.version))
|
logging.info("%s@%s (v%s)" % (pwnagotchi.name(), agent.fingerprint(), pwnagotchi.version))
|
||||||
|
|
||||||
for _, plugin in plugins.loaded.items():
|
for _, plugin in plugins.loaded.items():
|
||||||
logging.debug("plugin '%s' v%s loaded from %s" % (plugin.__name__, plugin.__version__, plugin.__file__))
|
logging.debug("plugin '%s' v%s loaded from %s" % (plugin.__name__, plugin.__version__, plugin.__file__))
|
||||||
|
@@ -34,7 +34,7 @@
|
|||||||
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.25/bettercap_linux_armv6l_2.25.zip"
|
||||||
ui: "https://github.com/bettercap/ui/releases/download/v1.3.0/ui.zip"
|
ui: "https://github.com/bettercap/ui/releases/download/v1.3.0/ui.zip"
|
||||||
pwngrid:
|
pwngrid:
|
||||||
url: "https://github.com/evilsocket/pwngrid/releases/download/v1.7.3/pwngrid_linux_armv6l_1.7.3.zip"
|
url: "https://github.com/evilsocket/pwngrid/releases/download/v1.7.6/pwngrid_linux_armv6l_1.7.6.zip"
|
||||||
apt:
|
apt:
|
||||||
hold:
|
hold:
|
||||||
- firmware-atheros
|
- firmware-atheros
|
||||||
@@ -379,7 +379,7 @@
|
|||||||
copy:
|
copy:
|
||||||
dest: /etc/pwnagotchi/config.yml
|
dest: /etc/pwnagotchi/config.yml
|
||||||
content: |
|
content: |
|
||||||
# Add your configuration overrides on this file any configuration changes done to defaults.yml will be lost!
|
# Add your configuration overrides on this file any configuration changes done to default.yml will be lost!
|
||||||
# Example:
|
# Example:
|
||||||
#
|
#
|
||||||
# ui:
|
# ui:
|
||||||
@@ -461,7 +461,7 @@
|
|||||||
|
|
||||||
If you want to change my configuration, use /etc/pwnagotchi/config.yml
|
If you want to change my configuration, use /etc/pwnagotchi/config.yml
|
||||||
|
|
||||||
All the configuration options can be found on /etc/pwnagotchi/defaults.yml,
|
All the configuration options can be found on /etc/pwnagotchi/default.yml,
|
||||||
but don't change this file because I will recreate it every time I'm restarted!
|
but don't change this file because I will recreate it every time I'm restarted!
|
||||||
|
|
||||||
I'm managed by systemd. Here are some basic commands.
|
I'm managed by systemd. Here are some basic commands.
|
||||||
@@ -502,7 +502,7 @@
|
|||||||
[Service]
|
[Service]
|
||||||
Type=simple
|
Type=simple
|
||||||
PermissionsStartOnly=true
|
PermissionsStartOnly=true
|
||||||
ExecStart=/usr/bin/pwngrid -keys /etc/pwnagotchi -address 127.0.0.1:8666 -wait -log /var/log/pwngrid-peer.log -iface mon0
|
ExecStart=/usr/bin/pwngrid -keys /etc/pwnagotchi -address 127.0.0.1:8666 -client-token /root/.api-enrollment.json -wait -log /var/log/pwngrid-peer.log -iface mon0
|
||||||
Restart=always
|
Restart=always
|
||||||
RestartSec=30
|
RestartSec=30
|
||||||
|
|
||||||
|
@@ -4,7 +4,7 @@ import logging
|
|||||||
import time
|
import time
|
||||||
import pwnagotchi.ui.view as view
|
import pwnagotchi.ui.view as view
|
||||||
|
|
||||||
version = '1.0.0RC4'
|
version = '1.0.0RC5'
|
||||||
|
|
||||||
_name = None
|
_name = None
|
||||||
|
|
||||||
@@ -65,3 +65,9 @@ def shutdown():
|
|||||||
time.sleep(5)
|
time.sleep(5)
|
||||||
os.system("sync")
|
os.system("sync")
|
||||||
os.system("halt")
|
os.system("halt")
|
||||||
|
|
||||||
|
|
||||||
|
def reboot():
|
||||||
|
logging.warning("rebooting ...")
|
||||||
|
os.system("sync")
|
||||||
|
os.system("shutdown -r now")
|
||||||
|
@@ -190,6 +190,7 @@ class Agent(Client, AsyncAdvertiser, AsyncTrainer):
|
|||||||
aps = []
|
aps = []
|
||||||
try:
|
try:
|
||||||
s = self.session()
|
s = self.session()
|
||||||
|
plugins.on("unfiltered_ap_list", self, s['wifi']['aps'])
|
||||||
for ap in s['wifi']['aps']:
|
for ap in s['wifi']['aps']:
|
||||||
if ap['hostname'] not in whitelist:
|
if ap['hostname'] not in whitelist:
|
||||||
if self._filter_included(ap):
|
if self._filter_included(ap):
|
||||||
@@ -480,9 +481,7 @@ class Agent(Client, AsyncAdvertiser, AsyncTrainer):
|
|||||||
def _reboot(self):
|
def _reboot(self):
|
||||||
self.set_rebooting()
|
self.set_rebooting()
|
||||||
self._save_recovery_data()
|
self._save_recovery_data()
|
||||||
logging.warning("rebooting the system ...")
|
pwnagotchi.reboot()
|
||||||
os.system("/usr/bin/sync")
|
|
||||||
os.system("/usr/sbin/shutdown -r now")
|
|
||||||
|
|
||||||
def next_epoch(self):
|
def next_epoch(self):
|
||||||
was_stale = self.is_stale()
|
was_stale = self.is_stale()
|
||||||
|
@@ -73,6 +73,7 @@ main:
|
|||||||
ip: '192.168.44.44' # ip from which your pwnagotchi should be reachable
|
ip: '192.168.44.44' # ip from which your pwnagotchi should be reachable
|
||||||
netmask: 24
|
netmask: 24
|
||||||
interval: 1 # check every x minutes for device
|
interval: 1 # check every x minutes for device
|
||||||
|
share_internet: false
|
||||||
# monitor interface to use
|
# monitor interface to use
|
||||||
iface: mon0
|
iface: mon0
|
||||||
# command to run to bring the mon interface up in case it's not up already
|
# command to run to bring the mon interface up in case it's not up already
|
||||||
@@ -168,7 +169,7 @@ ui:
|
|||||||
display:
|
display:
|
||||||
enabled: true
|
enabled: true
|
||||||
rotation: 180
|
rotation: 180
|
||||||
# Possible options inkyphat/inky, papirus/papi, waveshare_1/ws_1 or waveshare_2/ws_2
|
# Possible options inkyphat/inky, papirus/papi, waveshare_1/ws_1 or waveshare_2/ws_2, oledhat
|
||||||
type: 'waveshare_2'
|
type: 'waveshare_2'
|
||||||
# Possible options red/yellow/black (black used for monocromatic displays)
|
# Possible options red/yellow/black (black used for monocromatic displays)
|
||||||
color: 'black'
|
color: 'black'
|
||||||
|
@@ -16,6 +16,7 @@ class KeyPair(object):
|
|||||||
self.priv_key = None
|
self.priv_key = None
|
||||||
self.pub_path = "%s.pub" % self.priv_path
|
self.pub_path = "%s.pub" % self.priv_path
|
||||||
self.pub_key = None
|
self.pub_key = None
|
||||||
|
self.fingerprint_path = os.path.join(path, "fingerprint")
|
||||||
self._view = view
|
self._view = view
|
||||||
|
|
||||||
if not os.path.exists(self.path):
|
if not os.path.exists(self.path):
|
||||||
@@ -46,6 +47,9 @@ class KeyPair(object):
|
|||||||
self.pub_key_pem_b64 = base64.b64encode(pem_ascii).decode("ascii")
|
self.pub_key_pem_b64 = base64.b64encode(pem_ascii).decode("ascii")
|
||||||
self.fingerprint = hashlib.sha256(pem_ascii).hexdigest()
|
self.fingerprint = hashlib.sha256(pem_ascii).hexdigest()
|
||||||
|
|
||||||
|
with open(self.fingerprint_path, 'w+t') as fp:
|
||||||
|
fp.write(self.fingerprint)
|
||||||
|
|
||||||
# no exception, keys loaded correctly.
|
# no exception, keys loaded correctly.
|
||||||
self._view.on_starting()
|
self._view.on_starting()
|
||||||
return
|
return
|
||||||
|
@@ -192,19 +192,19 @@ msgstr ""
|
|||||||
"#pwnlog #pwnlife #hacktheplanet #skynet"
|
"#pwnlog #pwnlife #hacktheplanet #skynet"
|
||||||
|
|
||||||
msgid "hours"
|
msgid "hours"
|
||||||
msgstr ""
|
msgstr "heures"
|
||||||
|
|
||||||
msgid "minutes"
|
msgid "minutes"
|
||||||
msgstr ""
|
msgstr "minutes"
|
||||||
|
|
||||||
msgid "seconds"
|
msgid "seconds"
|
||||||
msgstr ""
|
msgstr "secondes"
|
||||||
|
|
||||||
msgid "hour"
|
msgid "hour"
|
||||||
msgstr ""
|
msgstr "heure"
|
||||||
|
|
||||||
msgid "minute"
|
msgid "minute"
|
||||||
msgstr ""
|
msgstr "minute"
|
||||||
|
|
||||||
msgid "second"
|
msgid "second"
|
||||||
msgstr ""
|
msgstr "seconde"
|
||||||
|
BIN
pwnagotchi/locale/ga/LC_MESSAGES/voice.mo
Normal file
BIN
pwnagotchi/locale/ga/LC_MESSAGES/voice.mo
Normal file
Binary file not shown.
215
pwnagotchi/locale/ga/LC_MESSAGES/voice.po
Normal file
215
pwnagotchi/locale/ga/LC_MESSAGES/voice.po
Normal file
@@ -0,0 +1,215 @@
|
|||||||
|
# SOME DESCRIPTIVE TITLE.
|
||||||
|
# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER
|
||||||
|
# This file is distributed under the same license as the PACKAGE package.
|
||||||
|
# FIRST AUTHOR <EMAIL@ADDRESS>, YEAR.
|
||||||
|
#
|
||||||
|
msgid ""
|
||||||
|
msgstr ""
|
||||||
|
"Project-Id-Version: \n"
|
||||||
|
"Report-Msgid-Bugs-To: \n"
|
||||||
|
"POT-Creation-Date: 2019-10-09 17:42+0200\n"
|
||||||
|
"PO-Revision-Date: 2019-10-15 23:46+0100\n"
|
||||||
|
"Language: ga\n"
|
||||||
|
"MIME-Version: 1.0\n"
|
||||||
|
"Content-Type: text/plain; charset=UTF-8\n"
|
||||||
|
"Content-Transfer-Encoding: 8bit\n"
|
||||||
|
"Last-Translator: \n"
|
||||||
|
"Language-Team: \n"
|
||||||
|
"X-Generator: Poedit 2.2.4\n"
|
||||||
|
|
||||||
|
msgid "ZzzzZZzzzzZzzz"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
msgid "Hi, I'm Pwnagotchi! Starting ..."
|
||||||
|
msgstr "Dia Duit, Pwnagotchi is ainm dom! Ag tosú ..."
|
||||||
|
|
||||||
|
msgid "New day, new hunt, new pwns!"
|
||||||
|
msgstr "Lá nua, seilg nua, pwns nua!"
|
||||||
|
|
||||||
|
msgid "Hack the Planet!"
|
||||||
|
msgstr "Haic An Phláinéid!"
|
||||||
|
|
||||||
|
msgid "AI ready."
|
||||||
|
msgstr "AI réidh."
|
||||||
|
|
||||||
|
msgid "The neural network is ready."
|
||||||
|
msgstr "Tá an líonra néarach réidh."
|
||||||
|
|
||||||
|
#, python-brace-format
|
||||||
|
msgid "Hey, channel {channel} is free! Your AP will say thanks."
|
||||||
|
msgstr "Hé, tá cainéal {channel} ar fail! Déarfaidh do PR go raibh maith agat."
|
||||||
|
|
||||||
|
msgid "I'm bored ..."
|
||||||
|
msgstr "Tá leadrán orm ..."
|
||||||
|
|
||||||
|
msgid "Let's go for a walk!"
|
||||||
|
msgstr "Siúil liom, le do thoil!"
|
||||||
|
|
||||||
|
msgid "This is the best day of my life!"
|
||||||
|
msgstr "Tá sé an lá is fearr i mo shaol!"
|
||||||
|
|
||||||
|
msgid "Shitty day :/"
|
||||||
|
msgstr "Tá lá damanta agam :/"
|
||||||
|
|
||||||
|
msgid "I'm extremely bored ..."
|
||||||
|
msgstr "Tá mé ag dul as mo mheabhair le leadrán ..."
|
||||||
|
|
||||||
|
msgid "I'm very sad ..."
|
||||||
|
msgstr "Ta brón an domhain orm ..."
|
||||||
|
|
||||||
|
msgid "I'm sad"
|
||||||
|
msgstr "Tá brón orm"
|
||||||
|
|
||||||
|
msgid "I'm living the life!"
|
||||||
|
msgstr "Tá an saol ar a thoil agam!"
|
||||||
|
|
||||||
|
msgid "I pwn therefore I am."
|
||||||
|
msgstr "Déanaim pwnáil, dá bhrí sin táim ann."
|
||||||
|
|
||||||
|
msgid "So many networks!!!"
|
||||||
|
msgstr "Gréasáin - Tá an iliomad acu ann!!!"
|
||||||
|
|
||||||
|
msgid "I'm having so much fun!"
|
||||||
|
msgstr "Tá craic iontach agam!"
|
||||||
|
|
||||||
|
msgid "My crime is that of curiosity ..."
|
||||||
|
msgstr "Ní haon pheaca é bheith fiosrach ..."
|
||||||
|
|
||||||
|
#, python-brace-format
|
||||||
|
msgid "Hello {name}! Nice to meet you. {name}"
|
||||||
|
msgstr "Dia Duit {name}! Is deas bualadh leat. {name}"
|
||||||
|
|
||||||
|
#, python-brace-format
|
||||||
|
msgid "Unit {name} is nearby! {name}"
|
||||||
|
msgstr "Aonad {name} in aice láimhe! {name}"
|
||||||
|
|
||||||
|
#, python-brace-format
|
||||||
|
msgid "Uhm ... goodbye {name}"
|
||||||
|
msgstr "Uhm... slán leat {name}"
|
||||||
|
|
||||||
|
#, python-brace-format
|
||||||
|
msgid "{name} is gone ..."
|
||||||
|
msgstr "Tá {name} imithe ..."
|
||||||
|
|
||||||
|
#, python-brace-format
|
||||||
|
msgid "Whoops ... {name} is gone."
|
||||||
|
msgstr "Hoips … Tá {name} imithe."
|
||||||
|
|
||||||
|
#, python-brace-format
|
||||||
|
msgid "{name} missed!"
|
||||||
|
msgstr "Chaill mé ar {name}!"
|
||||||
|
|
||||||
|
msgid "Missed!"
|
||||||
|
msgstr "Chaill mé é sin !"
|
||||||
|
|
||||||
|
msgid "Nobody wants to play with me ..."
|
||||||
|
msgstr "Níl aon duine ag iarraidh imirt liom ..."
|
||||||
|
|
||||||
|
msgid "I feel so alone ..."
|
||||||
|
msgstr "Tá uaigneas an domhain orm ..."
|
||||||
|
|
||||||
|
msgid "Where's everybody?!"
|
||||||
|
msgstr "Cá bhfuil gach duine?!"
|
||||||
|
|
||||||
|
#, python-brace-format
|
||||||
|
msgid "Napping for {secs}s ..."
|
||||||
|
msgstr "Néal a chodladh ar {secs}s ..."
|
||||||
|
|
||||||
|
msgid "Zzzzz"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#, python-brace-format
|
||||||
|
msgid "ZzzZzzz ({secs}s)"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
msgid "Good night."
|
||||||
|
msgstr "Oíche mhaith."
|
||||||
|
|
||||||
|
msgid "Zzz"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#, python-brace-format
|
||||||
|
msgid "Waiting for {secs}s ..."
|
||||||
|
msgstr "Fan ar {secs}s ..."
|
||||||
|
|
||||||
|
#, python-brace-format
|
||||||
|
msgid "Looking around ({secs}s)"
|
||||||
|
msgstr "Ag amharc uaim ar ({secs}s)"
|
||||||
|
|
||||||
|
#, python-brace-format
|
||||||
|
msgid "Hey {what} let's be friends!"
|
||||||
|
msgstr "Hé {what} déanaimis síocháin!"
|
||||||
|
|
||||||
|
#, python-brace-format
|
||||||
|
msgid "Associating to {what}"
|
||||||
|
msgstr "Ag coinneáil le {what}"
|
||||||
|
|
||||||
|
#, python-brace-format
|
||||||
|
msgid "Yo {what}!"
|
||||||
|
msgstr "Hé {what}!"
|
||||||
|
|
||||||
|
#, python-brace-format
|
||||||
|
msgid "Just decided that {mac} needs no WiFi!"
|
||||||
|
msgstr "Tá cinneadh déanta agam. Níl {mac} sin de dhíth air WiFi!"
|
||||||
|
|
||||||
|
#, python-brace-format
|
||||||
|
msgid "Deauthenticating {mac}"
|
||||||
|
msgstr "Bain fíordheimhniúde {mac}"
|
||||||
|
|
||||||
|
#, python-brace-format
|
||||||
|
msgid "Kickbanning {mac}!"
|
||||||
|
msgstr "Chiceáil mé agus cosc mé ar {mac}!"
|
||||||
|
|
||||||
|
#, python-brace-format
|
||||||
|
msgid "Cool, we got {num} new handshake{plural}!"
|
||||||
|
msgstr "Go hiontach, fuaireamar {num} handshake{plural}!"
|
||||||
|
|
||||||
|
msgid "Ops, something went wrong ... Rebooting ..."
|
||||||
|
msgstr "Hoips...Tháinig ainghléas éigin..."
|
||||||
|
|
||||||
|
#, python-brace-format
|
||||||
|
msgid "Kicked {num} stations\n"
|
||||||
|
msgstr "{num} stáisiún kick\n"
|
||||||
|
|
||||||
|
#, python-brace-format
|
||||||
|
msgid "Made {num} new friends\n"
|
||||||
|
msgstr "Rinne mé {num} cairde nua\n"
|
||||||
|
|
||||||
|
#, python-brace-format
|
||||||
|
msgid "Got {num} handshakes\n"
|
||||||
|
msgstr "Fuair me {num} cumarsáid thionscantach\n"
|
||||||
|
|
||||||
|
msgid "Met 1 peer"
|
||||||
|
msgstr "Bhuail mé piara amháin"
|
||||||
|
|
||||||
|
#, python-brace-format
|
||||||
|
msgid "Met {num} peers"
|
||||||
|
msgstr "Bhuail me {num} piara"
|
||||||
|
|
||||||
|
#, python-brace-format
|
||||||
|
msgid ""
|
||||||
|
"I've been pwning for {duration} and kicked {deauthed} clients! I've also met "
|
||||||
|
"{associated} new friends and ate {handshakes} handshakes! #pwnagotchi "
|
||||||
|
"#pwnlog #pwnlife #hacktheplanet #skynet"
|
||||||
|
msgstr ""
|
||||||
|
"Bhí me ag pwnáil ar {duration} agus chiceáil me ar {deauthed} cliaint! Chomh "
|
||||||
|
"maith, bhuail me {associated} cairde nua and d'ith mé {handshakes}! "
|
||||||
|
"#pwnagotchi #pwnlog #pwnlife #hacktheplanet #skynet"
|
||||||
|
|
||||||
|
msgid "hours"
|
||||||
|
msgstr "uair on chloig"
|
||||||
|
|
||||||
|
msgid "minutes"
|
||||||
|
msgstr "nóiméad"
|
||||||
|
|
||||||
|
msgid "seconds"
|
||||||
|
msgstr "soicind"
|
||||||
|
|
||||||
|
msgid "hour"
|
||||||
|
msgstr "uair an chloig"
|
||||||
|
|
||||||
|
msgid "minute"
|
||||||
|
msgstr "nóiméad"
|
||||||
|
|
||||||
|
msgid "second"
|
||||||
|
msgstr "soicind"
|
BIN
pwnagotchi/locale/jp/LC_MESSAGES/voice.mo
Normal file
BIN
pwnagotchi/locale/jp/LC_MESSAGES/voice.mo
Normal file
Binary file not shown.
212
pwnagotchi/locale/jp/LC_MESSAGES/voice.po
Normal file
212
pwnagotchi/locale/jp/LC_MESSAGES/voice.po
Normal file
@@ -0,0 +1,212 @@
|
|||||||
|
# SOME DESCRIPTIVE TITLE.
|
||||||
|
# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER
|
||||||
|
# This file is distributed under the same license as the PACKAGE package.
|
||||||
|
# FIRST AUTHOR 24534649+wytshadow@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-16 15:05+0200\n"
|
||||||
|
"PO-Revision-Date: 2019-10-16 15:05+0200\n"
|
||||||
|
"Last-Translator: wytshadow <24534649+wytshadow@users.noreply.github.com>\n"
|
||||||
|
"Language-Team: pwnagotchi <24534649+wytshadow@users.noreply.github.com>\n"
|
||||||
|
"Language: jp\n"
|
||||||
|
"MIME-Version: 1.0\n"
|
||||||
|
"Content-Type: text/plain; charset=utf-8\n"
|
||||||
|
"Content-Transfer-Encoding: 8bit\n"
|
||||||
|
|
||||||
|
msgid "ZzzzZZzzzzZzzz"
|
||||||
|
msgstr "すやすや〜"
|
||||||
|
|
||||||
|
msgid "Hi, I'm Pwnagotchi! Starting ..."
|
||||||
|
msgstr "こんにちは、ポウナゴッチです!始めている。。。"
|
||||||
|
|
||||||
|
msgid "New day, new hunt, new pwns!"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
msgid "Hack the Planet!"
|
||||||
|
msgstr "ハックザプラネット!"
|
||||||
|
|
||||||
|
msgid "AI ready."
|
||||||
|
msgstr "人工知能の準備ができました。"
|
||||||
|
|
||||||
|
msgid "The neural network is ready."
|
||||||
|
msgstr "ニューラルネットワークの準備ができました。"
|
||||||
|
|
||||||
|
#, python-brace-format
|
||||||
|
msgid "Hey, channel {channel} is free! Your AP will say thanks."
|
||||||
|
msgstr "ねえ、チャンネル{channel}は無料です! キミのAPは感謝を言います。"
|
||||||
|
|
||||||
|
msgid "I'm bored ..."
|
||||||
|
msgstr "退屈です。。。"
|
||||||
|
|
||||||
|
msgid "Let's go for a walk!"
|
||||||
|
msgstr "散歩に行きましょう!"
|
||||||
|
|
||||||
|
msgid "This is the best day of my life!"
|
||||||
|
msgstr "今日は私の人生で最高の日です!"
|
||||||
|
|
||||||
|
msgid "Shitty day :/"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
msgid "I'm extremely bored ..."
|
||||||
|
msgstr "とても退屈です。"
|
||||||
|
|
||||||
|
msgid "I'm very sad ..."
|
||||||
|
msgstr "とても悲しいです。。。"
|
||||||
|
|
||||||
|
msgid "I'm sad"
|
||||||
|
msgstr "悲しいです。"
|
||||||
|
|
||||||
|
msgid "I'm living the life!"
|
||||||
|
msgstr "人生を生きている!"
|
||||||
|
|
||||||
|
msgid "I pwn therefore I am."
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
msgid "So many networks!!!"
|
||||||
|
msgstr "たくさんネットワークがある!!!"
|
||||||
|
|
||||||
|
msgid "I'm having so much fun!"
|
||||||
|
msgstr "とても楽しんでいます!"
|
||||||
|
|
||||||
|
msgid "My crime is that of curiosity ..."
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#, python-brace-format
|
||||||
|
msgid "Hello {name}! Nice to meet you. {name}"
|
||||||
|
msgstr "こんにちは{name}!初めまして。{name}"
|
||||||
|
|
||||||
|
#, python-brace-format
|
||||||
|
msgid "Unit {name} is nearby! {name}"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#, python-brace-format
|
||||||
|
msgid "Uhm ... goodbye {name}"
|
||||||
|
msgstr "ええと。。。さようなら{name}"
|
||||||
|
|
||||||
|
#, python-brace-format
|
||||||
|
msgid "{name} is gone ..."
|
||||||
|
msgstr "{name}がなくなった。。。"
|
||||||
|
|
||||||
|
#, python-brace-format
|
||||||
|
msgid "Whoops ... {name} is gone."
|
||||||
|
msgstr "おっと。。。{name}がなくなった。"
|
||||||
|
|
||||||
|
#, python-brace-format
|
||||||
|
msgid "{name} missed!"
|
||||||
|
msgstr "{name}逃した!"
|
||||||
|
|
||||||
|
msgid "Missed!"
|
||||||
|
msgstr "逃した!"
|
||||||
|
|
||||||
|
msgid "Nobody wants to play with me ..."
|
||||||
|
msgstr "誰も僕と一緒にプレーしたくない。。。"
|
||||||
|
|
||||||
|
msgid "I feel so alone ..."
|
||||||
|
msgstr "僕は孤独を感じる。。。"
|
||||||
|
|
||||||
|
msgid "Where's everybody?!"
|
||||||
|
msgstr "みんなどこ?!"
|
||||||
|
|
||||||
|
#, python-brace-format
|
||||||
|
msgid "Napping for {secs}s ..."
|
||||||
|
msgstr "{secs}寝ている。"
|
||||||
|
|
||||||
|
msgid "Zzzzz"
|
||||||
|
msgstr "すや〜"
|
||||||
|
|
||||||
|
#, python-brace-format
|
||||||
|
msgid "ZzzZzzz ({secs}s)"
|
||||||
|
msgstr "すやすや〜 ({secs})"
|
||||||
|
|
||||||
|
msgid "Good night."
|
||||||
|
msgstr "お休みなさい。"
|
||||||
|
|
||||||
|
msgid "Zzz"
|
||||||
|
msgstr "す〜"
|
||||||
|
|
||||||
|
#, python-brace-format
|
||||||
|
msgid "Waiting for {secs}s ..."
|
||||||
|
msgstr "{secs}を待っている。。。"
|
||||||
|
|
||||||
|
#, python-brace-format
|
||||||
|
msgid "Looking around ({secs}s)"
|
||||||
|
msgstr "{secs}を探している。"
|
||||||
|
|
||||||
|
#, python-brace-format
|
||||||
|
msgid "Hey {what} let's be friends!"
|
||||||
|
msgstr "ちょっと{what}友だちになりましょう!"
|
||||||
|
|
||||||
|
#, python-brace-format
|
||||||
|
msgid "Associating to {what}"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#, python-brace-format
|
||||||
|
msgid "Yo {what}!"
|
||||||
|
msgstr "よー{what}!"
|
||||||
|
|
||||||
|
#, python-brace-format
|
||||||
|
msgid "Just decided that {mac} needs no WiFi!"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#, python-brace-format
|
||||||
|
msgid "Deauthenticating {mac}"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#, python-brace-format
|
||||||
|
msgid "Kickbanning {mac}!"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#, python-brace-format
|
||||||
|
msgid "Cool, we got {num} new handshake{plural}!"
|
||||||
|
msgstr "よし、{num}新しいハンドシェイクがある!"
|
||||||
|
|
||||||
|
msgid "Ops, something went wrong ... Rebooting ..."
|
||||||
|
msgstr "おっと!何かが間違っていた。。。リブートしている。。。"
|
||||||
|
|
||||||
|
#, python-brace-format
|
||||||
|
msgid "Kicked {num} stations\n"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#, python-brace-format
|
||||||
|
msgid "Made {num} new friends\n"
|
||||||
|
msgstr "{num}人の新しい友達を作りました\n"
|
||||||
|
|
||||||
|
#, python-brace-format
|
||||||
|
msgid "Got {num} handshakes\n"
|
||||||
|
msgstr "{num}ハンドシェイクがある。\n"
|
||||||
|
|
||||||
|
msgid "Met 1 peer"
|
||||||
|
msgstr "1人の仲間を会いました。"
|
||||||
|
|
||||||
|
#, python-brace-format
|
||||||
|
msgid "Met {num} peers"
|
||||||
|
msgstr "{num}人の仲間を会いました。"
|
||||||
|
|
||||||
|
#, python-brace-format
|
||||||
|
msgid ""
|
||||||
|
"I've been pwning for {duration} and kicked {deauthed} clients! I've also met "
|
||||||
|
"{associated} new friends and ate {handshakes} handshakes! #pwnagotchi "
|
||||||
|
"#pwnlog #pwnlife #hacktheplanet #skynet"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
msgid "hours"
|
||||||
|
msgstr "時間"
|
||||||
|
|
||||||
|
msgid "minutes"
|
||||||
|
msgstr "分"
|
||||||
|
|
||||||
|
msgid "seconds"
|
||||||
|
msgstr "秒"
|
||||||
|
|
||||||
|
msgid "hour"
|
||||||
|
msgstr "時間"
|
||||||
|
|
||||||
|
msgid "minute"
|
||||||
|
msgstr "分"
|
||||||
|
|
||||||
|
msgid "second"
|
||||||
|
msgstr "秒"
|
@@ -29,10 +29,10 @@ msgid "New day, new hunt, new pwns!"
|
|||||||
msgstr "Новый день, новая охота, новые взломы!"
|
msgstr "Новый день, новая охота, новые взломы!"
|
||||||
|
|
||||||
msgid "Hack the Planet!"
|
msgid "Hack the Planet!"
|
||||||
msgstr "Взломаем всю планету!"
|
msgstr "Хак зе планет!"
|
||||||
|
|
||||||
msgid "AI ready."
|
msgid "AI ready."
|
||||||
msgstr "Искусственный интеллект готов."
|
msgstr "AI готов."
|
||||||
|
|
||||||
msgid "The neural network is ready."
|
msgid "The neural network is ready."
|
||||||
msgstr "Нейронная сеть готова."
|
msgstr "Нейронная сеть готова."
|
||||||
@@ -48,7 +48,7 @@ msgid "Let's go for a walk!"
|
|||||||
msgstr "Пойдем прогуляемся!"
|
msgstr "Пойдем прогуляемся!"
|
||||||
|
|
||||||
msgid "This is the best day of my life!"
|
msgid "This is the best day of my life!"
|
||||||
msgstr "Это лучший день в моей жизни!"
|
msgstr "Лучший день в моей жизни!"
|
||||||
|
|
||||||
msgid "Shitty day :/"
|
msgid "Shitty day :/"
|
||||||
msgstr "Дерьмовый день :/"
|
msgstr "Дерьмовый день :/"
|
||||||
@@ -63,19 +63,19 @@ msgid "I'm sad"
|
|||||||
msgstr "Мне грустно"
|
msgstr "Мне грустно"
|
||||||
|
|
||||||
msgid "I'm living the life!"
|
msgid "I'm living the life!"
|
||||||
msgstr "Я живу своей жизнью!"
|
msgstr "Угараю по полной!"
|
||||||
|
|
||||||
msgid "I pwn therefore I am."
|
msgid "I pwn therefore I am."
|
||||||
msgstr "Я взламываю, поэтому я существую."
|
msgstr "Я взламываю, поэтому я существую."
|
||||||
|
|
||||||
msgid "So many networks!!!"
|
msgid "So many networks!!!"
|
||||||
msgstr "Так, много сетей!!!"
|
msgstr "Так много сетей!!!"
|
||||||
|
|
||||||
msgid "I'm having so much fun!"
|
msgid "I'm having so much fun!"
|
||||||
msgstr "Мне так весело!"
|
msgstr "Мне так весело!"
|
||||||
|
|
||||||
msgid "My crime is that of curiosity ..."
|
msgid "My crime is that of curiosity ..."
|
||||||
msgstr "Моё преступление - это любопытство …"
|
msgstr "Моe преступление - это любопытство …"
|
||||||
|
|
||||||
#, python-brace-format
|
#, python-brace-format
|
||||||
msgid "Hello {name}! Nice to meet you. {name}"
|
msgid "Hello {name}! Nice to meet you. {name}"
|
||||||
@@ -105,7 +105,7 @@ msgid "Missed!"
|
|||||||
msgstr "Промахнулся!"
|
msgstr "Промахнулся!"
|
||||||
|
|
||||||
msgid "Nobody wants to play with me ..."
|
msgid "Nobody wants to play with me ..."
|
||||||
msgstr "Никто не хочет играть со мной …"
|
msgstr "Никто не хочет со мной играть ..."
|
||||||
|
|
||||||
msgid "I feel so alone ..."
|
msgid "I feel so alone ..."
|
||||||
msgstr "Мне так одиноко …"
|
msgstr "Мне так одиноко …"
|
||||||
|
@@ -29,6 +29,9 @@ class AsyncAdvertiser(object):
|
|||||||
self._peers = {}
|
self._peers = {}
|
||||||
self._closest_peer = None
|
self._closest_peer = None
|
||||||
|
|
||||||
|
def fingerprint(self):
|
||||||
|
return self._keypair.fingerprint
|
||||||
|
|
||||||
def _update_advertisement(self, s):
|
def _update_advertisement(self, s):
|
||||||
self._advertisement['pwnd_run'] = len(self._handshakes)
|
self._advertisement['pwnd_run'] = len(self._handshakes)
|
||||||
self._advertisement['pwnd_tot'] = utils.total_unique_handshakes(self._config['bettercap']['handshakes'])
|
self._advertisement['pwnd_tot'] = utils.total_unique_handshakes(self._config['bettercap']['handshakes'])
|
||||||
|
@@ -154,7 +154,40 @@ class BTNap:
|
|||||||
|
|
||||||
return None
|
return None
|
||||||
|
|
||||||
def wait_for_device(self, timeout=30):
|
def is_connected(self):
|
||||||
|
"""
|
||||||
|
Check if already connected
|
||||||
|
"""
|
||||||
|
bt_dev = self.power(True)
|
||||||
|
|
||||||
|
if not bt_dev:
|
||||||
|
return False
|
||||||
|
|
||||||
|
try:
|
||||||
|
dev_remote = BTNap.find_device(self._mac, bt_dev)
|
||||||
|
return bool(BTNap.prop_get(dev_remote, 'Connected'))
|
||||||
|
except BTError:
|
||||||
|
pass
|
||||||
|
return False
|
||||||
|
|
||||||
|
|
||||||
|
def is_paired(self):
|
||||||
|
"""
|
||||||
|
Check if already connected
|
||||||
|
"""
|
||||||
|
bt_dev = self.power(True)
|
||||||
|
|
||||||
|
if not bt_dev:
|
||||||
|
return False
|
||||||
|
|
||||||
|
try:
|
||||||
|
dev_remote = BTNap.find_device(self._mac, bt_dev)
|
||||||
|
return bool(BTNap.prop_get(dev_remote, 'Paired'))
|
||||||
|
except BTError:
|
||||||
|
pass
|
||||||
|
return False
|
||||||
|
|
||||||
|
def wait_for_device(self, timeout=15):
|
||||||
"""
|
"""
|
||||||
Wait for device
|
Wait for device
|
||||||
|
|
||||||
@@ -165,21 +198,35 @@ class BTNap:
|
|||||||
if not bt_dev:
|
if not bt_dev:
|
||||||
return None
|
return None
|
||||||
|
|
||||||
|
try:
|
||||||
|
bt_dev.StartDiscovery()
|
||||||
|
except Exception:
|
||||||
|
# can fail with org.bluez.Error.NotReady / org.bluez.Error.Failed
|
||||||
|
# TODO: add loop?
|
||||||
|
pass
|
||||||
|
|
||||||
|
dev_remote = None
|
||||||
|
|
||||||
# could be set to 0, so check if > -1
|
# could be set to 0, so check if > -1
|
||||||
while timeout > -1:
|
while timeout > -1:
|
||||||
try:
|
try:
|
||||||
dev_remote = BTNap.find_device(self._mac, bt_dev)
|
dev_remote = BTNap.find_device(self._mac, bt_dev)
|
||||||
logging.debug('Using remote device (addr: %s): %s',
|
logging.debug('Using remote device (addr: %s): %s',
|
||||||
BTNap.prop_get(dev_remote, 'Address'), dev_remote.object_path )
|
BTNap.prop_get(dev_remote, 'Address'), dev_remote.object_path )
|
||||||
return dev_remote
|
break
|
||||||
except BTError:
|
except BTError:
|
||||||
pass
|
pass
|
||||||
|
|
||||||
time.sleep(1)
|
time.sleep(1)
|
||||||
timeout -= 1
|
timeout -= 1
|
||||||
|
|
||||||
# Device not found :(
|
try:
|
||||||
return None
|
bt_dev.StopDiscovery()
|
||||||
|
except Exception:
|
||||||
|
# can fail with org.bluez.Error.NotReady / org.bluez.Error.Failed / org.bluez.Error.NotAuthorized
|
||||||
|
pass
|
||||||
|
|
||||||
|
return dev_remote
|
||||||
|
|
||||||
|
|
||||||
def connect(self, reconnect=False):
|
def connect(self, reconnect=False):
|
||||||
@@ -189,19 +236,18 @@ class BTNap:
|
|||||||
return True if connected; False if failed
|
return True if connected; False if failed
|
||||||
"""
|
"""
|
||||||
|
|
||||||
# power up devices
|
|
||||||
bt_dev = self.power(True)
|
|
||||||
if not bt_dev:
|
|
||||||
return False
|
|
||||||
|
|
||||||
# check if device is close
|
# check if device is close
|
||||||
dev_remote = self.wait_for_device()
|
dev_remote = self.wait_for_device()
|
||||||
|
|
||||||
if not dev_remote:
|
if not dev_remote:
|
||||||
return False
|
return False
|
||||||
|
|
||||||
#wait_iter = lambda: time.sleep(3600)
|
try:
|
||||||
# signal.signal(signal.SIGTERM, lambda sig,frm: sys.exit(0))
|
dev_remote.Pair()
|
||||||
|
logging.info('BT-TETHER: Successful paired with device ;)')
|
||||||
|
except Exception:
|
||||||
|
# can fail because of AlreadyExists etc.
|
||||||
|
pass
|
||||||
|
|
||||||
try:
|
try:
|
||||||
dev_remote.ConnectProfile('nap')
|
dev_remote.ConnectProfile('nap')
|
||||||
@@ -347,6 +393,18 @@ class IfaceWrapper:
|
|||||||
|
|
||||||
return False
|
return False
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def set_route(addr):
|
||||||
|
process = subprocess.Popen(f"ip route replace default via {addr}", shell=True, stdin=None,
|
||||||
|
stdout=open("/dev/null", "w"), stderr=None, executable="/bin/bash")
|
||||||
|
process.wait()
|
||||||
|
|
||||||
|
if process.returncode > 0:
|
||||||
|
return False
|
||||||
|
|
||||||
|
return True
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
def on_loaded():
|
def on_loaded():
|
||||||
"""
|
"""
|
||||||
@@ -355,7 +413,7 @@ def on_loaded():
|
|||||||
global READY
|
global READY
|
||||||
global INTERVAL
|
global INTERVAL
|
||||||
|
|
||||||
for opt in ['mac', 'ip', 'netmask', 'interval']:
|
for opt in ['share_internet', 'mac', 'ip', 'netmask', 'interval']:
|
||||||
if opt not in OPTIONS or (opt in OPTIONS and OPTIONS[opt] is None):
|
if opt not in OPTIONS or (opt in OPTIONS and OPTIONS[opt] is None):
|
||||||
logging.error("BT-TET: Pleace specify the %s in your config.yml.", opt)
|
logging.error("BT-TET: Pleace specify the %s in your config.yml.", opt)
|
||||||
return
|
return
|
||||||
@@ -384,23 +442,52 @@ def on_ui_update(ui):
|
|||||||
INTERVAL.update()
|
INTERVAL.update()
|
||||||
|
|
||||||
bt = BTNap(OPTIONS['mac'])
|
bt = BTNap(OPTIONS['mac'])
|
||||||
if bt.connect():
|
|
||||||
btnap_iface = IfaceWrapper('bnep0')
|
|
||||||
|
|
||||||
|
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')
|
||||||
|
ui.set('bluetooth', 'NF')
|
||||||
|
return
|
||||||
|
|
||||||
|
btnap_iface = IfaceWrapper('bnep0')
|
||||||
|
logging.debug('BT-TETHER: Check interface')
|
||||||
if btnap_iface.exists():
|
if btnap_iface.exists():
|
||||||
|
logging.debug('BT-TETHER: Interface found')
|
||||||
|
|
||||||
# check ip
|
# check ip
|
||||||
addr = f"{OPTIONS['ip']}/{OPTIONS['netmask']}"
|
addr = f"{OPTIONS['ip']}/{OPTIONS['netmask']}"
|
||||||
|
|
||||||
|
logging.debug('BT-TETHER: Try to set ADDR to interface')
|
||||||
if not btnap_iface.set_addr(addr):
|
if not btnap_iface.set_addr(addr):
|
||||||
ui.set('bluetooth', 'ERR1')
|
ui.set('bluetooth', 'ERR1')
|
||||||
logging.error("Could not set ip of bnep0 to %s", addr)
|
logging.error("BT-TETHER: Could not set ip of bnep0 to %s", addr)
|
||||||
return
|
return
|
||||||
|
else:
|
||||||
|
logging.debug('BT-TETHER: Set ADDR to interface')
|
||||||
|
|
||||||
|
# change route if sharking
|
||||||
|
if OPTIONS['share_internet']:
|
||||||
|
logging.debug('BT-TETHER: Set routing and change resolv.conf')
|
||||||
|
IfaceWrapper.set_route(".".join(OPTIONS['ip'].split('.')[:-1] + ['1'])) # im not proud about that
|
||||||
|
# fix resolv.conf; dns over https ftw!
|
||||||
|
with open('/etc/resolv.conf', 'r+') as resolv:
|
||||||
|
nameserver = resolv.read()
|
||||||
|
if 'nameserver 9.9.9.9' not in nameserver:
|
||||||
|
logging.info('BT-TETHER: Added nameserver')
|
||||||
|
resolv.seek(0)
|
||||||
|
resolv.write(nameserver + 'nameserver 9.9.9.9\n')
|
||||||
|
|
||||||
ui.set('bluetooth', 'CON')
|
ui.set('bluetooth', 'CON')
|
||||||
else:
|
else:
|
||||||
|
logging.error('BT-TETHER: bnep0 not found')
|
||||||
ui.set('bluetooth', 'ERR2')
|
ui.set('bluetooth', 'ERR2')
|
||||||
else:
|
|
||||||
ui.set('bluetooth', 'NF')
|
|
||||||
|
|
||||||
|
|
||||||
def on_ui_setup(ui):
|
def on_ui_setup(ui):
|
||||||
|
@@ -1,5 +1,5 @@
|
|||||||
__author__ = 'zenzen san'
|
__author__ = 'zenzen san'
|
||||||
__version__ = '1.0.0'
|
__version__ = '2.0.0'
|
||||||
__name__ = 'net-pos'
|
__name__ = 'net-pos'
|
||||||
__license__ = 'GPL3'
|
__license__ = 'GPL3'
|
||||||
__description__ = """Saves a json file with the access points with more signal
|
__description__ = """Saves a json file with the access points with more signal
|
||||||
@@ -11,33 +11,24 @@ import logging
|
|||||||
import json
|
import json
|
||||||
import os
|
import os
|
||||||
import requests
|
import requests
|
||||||
|
from pwnagotchi.utils import StatusFile
|
||||||
|
|
||||||
MOZILLA_API_URL = 'https://location.services.mozilla.com/v1/geolocate?key={api}'
|
MOZILLA_API_URL = 'https://location.services.mozilla.com/v1/geolocate?key={api}'
|
||||||
ALREADY_SAVED = None
|
REPORT = StatusFile('/root/.net_pos_saved', data_format='json')
|
||||||
SKIP = None
|
SKIP = list()
|
||||||
READY = False
|
READY = False
|
||||||
OPTIONS = {}
|
OPTIONS = dict()
|
||||||
|
|
||||||
|
|
||||||
def on_loaded():
|
def on_loaded():
|
||||||
global ALREADY_SAVED
|
|
||||||
global SKIP
|
|
||||||
global READY
|
global READY
|
||||||
|
|
||||||
SKIP = list()
|
|
||||||
|
|
||||||
if 'api_key' not in OPTIONS or ('api_key' in OPTIONS and OPTIONS['api_key'] is None):
|
if 'api_key' not in OPTIONS or ('api_key' in OPTIONS and OPTIONS['api_key'] is None):
|
||||||
logging.error("NET-POS: api_key isn't set. Can't use mozilla's api.")
|
logging.error("NET-POS: api_key isn't set. Can't use mozilla's api.")
|
||||||
return
|
return
|
||||||
|
|
||||||
try:
|
|
||||||
with open('/root/.net_pos_saved', 'r') as f:
|
|
||||||
ALREADY_SAVED = f.read().splitlines()
|
|
||||||
except OSError:
|
|
||||||
logging.warning('NET-POS: No net-pos-file found.')
|
|
||||||
ALREADY_SAVED = []
|
|
||||||
|
|
||||||
READY = True
|
READY = True
|
||||||
|
|
||||||
logging.info("net-pos plugin loaded.")
|
logging.info("net-pos plugin loaded.")
|
||||||
|
|
||||||
def _append_saved(path):
|
def _append_saved(path):
|
||||||
@@ -55,20 +46,22 @@ def _append_saved(path):
|
|||||||
|
|
||||||
def on_internet_available(agent):
|
def on_internet_available(agent):
|
||||||
global SKIP
|
global SKIP
|
||||||
|
global REPORT
|
||||||
|
|
||||||
if READY:
|
if READY:
|
||||||
config = agent.config()
|
config = agent.config()
|
||||||
display = agent.view()
|
display = agent.view()
|
||||||
|
reported = REPORT.data_field_or('reported', default=list())
|
||||||
handshake_dir = config['bettercap']['handshakes']
|
handshake_dir = config['bettercap']['handshakes']
|
||||||
|
|
||||||
all_files = os.listdir(handshake_dir)
|
all_files = os.listdir(handshake_dir)
|
||||||
all_np_files = [os.path.join(handshake_dir, filename)
|
all_np_files = [os.path.join(handshake_dir, filename)
|
||||||
for filename in all_files
|
for filename in all_files
|
||||||
if filename.endswith('.net-pos.json')]
|
if filename.endswith('.net-pos.json')]
|
||||||
new_np_files = set(all_np_files) - set(ALREADY_SAVED) - set(SKIP)
|
new_np_files = set(all_np_files) - set(reported) - set(SKIP)
|
||||||
|
|
||||||
if new_np_files:
|
if new_np_files:
|
||||||
logging.info("NET-POS: Found {num} new net-pos files. Fetching positions ...", len(new_np_files))
|
logging.info("NET-POS: Found %d new net-pos files. Fetching positions ...", len(new_np_files))
|
||||||
display.set('status', f"Found {len(new_np_files)} new net-pos files. Fetching positions ...")
|
display.set('status', f"Found {len(new_np_files)} new net-pos files. Fetching positions ...")
|
||||||
display.update(force=True)
|
display.update(force=True)
|
||||||
for idx, np_file in enumerate(new_np_files):
|
for idx, np_file in enumerate(new_np_files):
|
||||||
@@ -76,8 +69,8 @@ def on_internet_available(agent):
|
|||||||
geo_file = np_file.replace('.net-pos.json', '.geo.json')
|
geo_file = np_file.replace('.net-pos.json', '.geo.json')
|
||||||
if os.path.exists(geo_file):
|
if os.path.exists(geo_file):
|
||||||
# got already the position
|
# got already the position
|
||||||
ALREADY_SAVED.append(np_file)
|
reported.append(np_file)
|
||||||
_append_saved(np_file)
|
REPORT.update(data={'reported': reported})
|
||||||
continue
|
continue
|
||||||
|
|
||||||
try:
|
try:
|
||||||
@@ -85,18 +78,21 @@ def on_internet_available(agent):
|
|||||||
except requests.exceptions.RequestException as req_e:
|
except requests.exceptions.RequestException as req_e:
|
||||||
logging.error("NET-POS: %s", req_e)
|
logging.error("NET-POS: %s", req_e)
|
||||||
SKIP += np_file
|
SKIP += np_file
|
||||||
|
continue
|
||||||
except json.JSONDecodeError as js_e:
|
except json.JSONDecodeError as js_e:
|
||||||
logging.error("NET-POS: %s", js_e)
|
logging.error("NET-POS: %s", js_e)
|
||||||
SKIP += np_file
|
SKIP += np_file
|
||||||
|
continue
|
||||||
except OSError as os_e:
|
except OSError as os_e:
|
||||||
logging.error("NET-POS: %s", os_e)
|
logging.error("NET-POS: %s", os_e)
|
||||||
SKIP += np_file
|
SKIP += np_file
|
||||||
|
continue
|
||||||
|
|
||||||
with open(geo_file, 'w+t') as sf:
|
with open(geo_file, 'w+t') as sf:
|
||||||
json.dump(geo_data, sf)
|
json.dump(geo_data, sf)
|
||||||
|
|
||||||
ALREADY_SAVED.append(np_file)
|
reported.append(np_file)
|
||||||
_append_saved(np_file)
|
REPORT.update(data={'reported': reported})
|
||||||
|
|
||||||
display.set('status', f"Fetching positions ({idx+1}/{len(new_np_files)})")
|
display.set('status', f"Fetching positions ({idx+1}/{len(new_np_files)})")
|
||||||
display.update(force=True)
|
display.update(force=True)
|
||||||
@@ -108,15 +104,15 @@ def on_handshake(agent, filename, access_point, client_station):
|
|||||||
logging.info("NET-POS: Saving net-location to %s", netpos_filename)
|
logging.info("NET-POS: Saving net-location to %s", netpos_filename)
|
||||||
|
|
||||||
try:
|
try:
|
||||||
with open(netpos_filename, 'w+t') as fp:
|
with open(netpos_filename, 'w+t') as net_pos_file:
|
||||||
json.dump(netpos, fp)
|
json.dump(netpos, net_pos_file)
|
||||||
except OSError as os_e:
|
except OSError as os_e:
|
||||||
logging.error("NET-POS: %s", os_e)
|
logging.error("NET-POS: %s", os_e)
|
||||||
|
|
||||||
|
|
||||||
def _get_netpos(agent):
|
def _get_netpos(agent):
|
||||||
aps = agent.get_access_points()
|
aps = agent.get_access_points()
|
||||||
netpos = {}
|
netpos = dict()
|
||||||
netpos['wifiAccessPoints'] = list()
|
netpos['wifiAccessPoints'] = list()
|
||||||
# 6 seems a good number to save a wifi networks location
|
# 6 seems a good number to save a wifi networks location
|
||||||
for access_point in sorted(aps, key=lambda i: i['rssi'], reverse=True)[:6]:
|
for access_point in sorted(aps, key=lambda i: i['rssi'], reverse=True)[:6]:
|
||||||
|
@@ -1,5 +1,5 @@
|
|||||||
__author__ = '33197631+dadav@users.noreply.github.com'
|
__author__ = '33197631+dadav@users.noreply.github.com'
|
||||||
__version__ = '1.0.0'
|
__version__ = '2.0.0'
|
||||||
__name__ = 'onlinehashcrack'
|
__name__ = 'onlinehashcrack'
|
||||||
__license__ = 'GPL3'
|
__license__ = 'GPL3'
|
||||||
__description__ = 'This plugin automatically uploades handshakes to https://onlinehashcrack.com'
|
__description__ = 'This plugin automatically uploades handshakes to https://onlinehashcrack.com'
|
||||||
@@ -7,9 +7,11 @@ __description__ = 'This plugin automatically uploades handshakes to https://onli
|
|||||||
import os
|
import os
|
||||||
import logging
|
import logging
|
||||||
import requests
|
import requests
|
||||||
|
from pwnagotchi.utils import StatusFile
|
||||||
|
|
||||||
READY = False
|
READY = False
|
||||||
ALREADY_UPLOADED = None
|
REPORT = StatusFile('/root/.ohc_uploads', data_format='json')
|
||||||
|
SKIP = list()
|
||||||
OPTIONS = dict()
|
OPTIONS = dict()
|
||||||
|
|
||||||
|
|
||||||
@@ -18,20 +20,11 @@ def on_loaded():
|
|||||||
Gets called when the plugin gets loaded
|
Gets called when the plugin gets loaded
|
||||||
"""
|
"""
|
||||||
global READY
|
global READY
|
||||||
global EMAIL
|
|
||||||
global ALREADY_UPLOADED
|
|
||||||
|
|
||||||
if not 'email' in OPTIONS or ('email' in OPTIONS and OPTIONS['email'] is None):
|
if 'email' not in OPTIONS or ('email' in OPTIONS and OPTIONS['email'] is None):
|
||||||
logging.error("OHC: Email isn't set. Can't upload to onlinehashcrack.com")
|
logging.error("OHC: Email isn't set. Can't upload to onlinehashcrack.com")
|
||||||
return
|
return
|
||||||
|
|
||||||
try:
|
|
||||||
with open('/root/.ohc_uploads', 'r') as f:
|
|
||||||
ALREADY_UPLOADED = f.read().splitlines()
|
|
||||||
except OSError:
|
|
||||||
logging.warning('OHC: No upload-file found.')
|
|
||||||
ALREADY_UPLOADED = []
|
|
||||||
|
|
||||||
READY = True
|
READY = True
|
||||||
|
|
||||||
|
|
||||||
@@ -59,14 +52,17 @@ def on_internet_available(agent):
|
|||||||
"""
|
"""
|
||||||
Called in manual mode when there's internet connectivity
|
Called in manual mode when there's internet connectivity
|
||||||
"""
|
"""
|
||||||
|
global REPORT
|
||||||
|
global SKIP
|
||||||
if READY:
|
if READY:
|
||||||
display = agent.view()
|
display = agent.view()
|
||||||
config = agent.config()
|
config = agent.config()
|
||||||
|
reported = REPORT.data_field_or('reported', default=list())
|
||||||
|
|
||||||
handshake_dir = config['bettercap']['handshakes']
|
handshake_dir = config['bettercap']['handshakes']
|
||||||
handshake_filenames = os.listdir(handshake_dir)
|
handshake_filenames = os.listdir(handshake_dir)
|
||||||
handshake_paths = [os.path.join(handshake_dir, filename) for filename in handshake_filenames if filename.endswith('.pcap')]
|
handshake_paths = [os.path.join(handshake_dir, filename) for filename in handshake_filenames if filename.endswith('.pcap')]
|
||||||
handshake_new = set(handshake_paths) - set(ALREADY_UPLOADED)
|
handshake_new = set(handshake_paths) - set(reported) - set(SKIP)
|
||||||
|
|
||||||
if handshake_new:
|
if handshake_new:
|
||||||
logging.info("OHC: Internet connectivity detected. Uploading new handshakes to onelinehashcrack.com")
|
logging.info("OHC: Internet connectivity detected. Uploading new handshakes to onelinehashcrack.com")
|
||||||
@@ -76,12 +72,15 @@ def on_internet_available(agent):
|
|||||||
display.update(force=True)
|
display.update(force=True)
|
||||||
try:
|
try:
|
||||||
_upload_to_ohc(handshake)
|
_upload_to_ohc(handshake)
|
||||||
ALREADY_UPLOADED.append(handshake)
|
reported.append(handshake)
|
||||||
with open('/root/.ohc_uploads', 'a') as f:
|
REPORT.update(data={'reported': reported})
|
||||||
f.write(handshake + "\n")
|
|
||||||
logging.info(f"OHC: Successfuly uploaded {handshake}")
|
logging.info(f"OHC: Successfuly uploaded {handshake}")
|
||||||
except requests.exceptions.RequestException:
|
except requests.exceptions.RequestException as req_e:
|
||||||
pass
|
SKIP.append(handshake)
|
||||||
|
logging.error("OHC: %s", req_e)
|
||||||
|
continue
|
||||||
except OSError as os_e:
|
except OSError as os_e:
|
||||||
logging.error(f"OHC: Got the following error: {os_e}")
|
SKIP.append(handshake)
|
||||||
|
logging.error("OHC: %s", os_e)
|
||||||
|
continue
|
||||||
|
|
||||||
|
@@ -18,7 +18,7 @@ def on_ui_update(ui):
|
|||||||
global update_count
|
global update_count
|
||||||
update_count += 1
|
update_count += 1
|
||||||
if update_count == OPTIONS['refresh_interval']:
|
if update_count == OPTIONS['refresh_interval']:
|
||||||
ui._init_display()
|
ui.init_display()
|
||||||
ui.set('status', "Screen cleaned")
|
ui.set('status', "Screen cleaned")
|
||||||
logging.info("Screen refreshing")
|
logging.info("Screen refreshing")
|
||||||
update_count = 0
|
update_count = 0
|
||||||
|
22
pwnagotchi/plugins/default/unfiltered_example.py
Normal file
22
pwnagotchi/plugins/default/unfiltered_example.py
Normal file
@@ -0,0 +1,22 @@
|
|||||||
|
__author__ = 'diemelcw@gmail.com'
|
||||||
|
__version__ = '1.0.0'
|
||||||
|
__name__ = 'unfiltered_example'
|
||||||
|
__license__ = 'GPL3'
|
||||||
|
__description__ = 'An example plugin for pwnagotchi that implements on_unfiltered_ap_list(agent,aps)'
|
||||||
|
|
||||||
|
import logging
|
||||||
|
|
||||||
|
# Will be set with the options in config.yml config['main']['plugins'][__name__]
|
||||||
|
OPTIONS = dict()
|
||||||
|
|
||||||
|
# called when the plugin is loaded
|
||||||
|
def on_loaded():
|
||||||
|
logging.warning("%s plugin loaded" % __name__)
|
||||||
|
|
||||||
|
# called when AP list is ready, before whitelist filtering has occured
|
||||||
|
def on_unfiltered_ap_list(agent,aps):
|
||||||
|
logging.info("Unfiltered AP list to follow")
|
||||||
|
for ap in aps:
|
||||||
|
logging.info(ap['hostname'])
|
||||||
|
|
||||||
|
## Additional logic here ##
|
@@ -1,5 +1,5 @@
|
|||||||
__author__ = '33197631+dadav@users.noreply.github.com'
|
__author__ = '33197631+dadav@users.noreply.github.com'
|
||||||
__version__ = '1.0.0'
|
__version__ = '2.0.0'
|
||||||
__name__ = 'wigle'
|
__name__ = 'wigle'
|
||||||
__license__ = 'GPL3'
|
__license__ = 'GPL3'
|
||||||
__description__ = 'This plugin automatically uploades collected wifis to wigle.net'
|
__description__ = 'This plugin automatically uploades collected wifis to wigle.net'
|
||||||
@@ -11,111 +11,24 @@ from io import StringIO
|
|||||||
import csv
|
import csv
|
||||||
from datetime import datetime
|
from datetime import datetime
|
||||||
import requests
|
import requests
|
||||||
from pwnagotchi.mesh.wifi import freq_to_channel
|
from pwnagotchi.utils import WifiInfo, FieldNotFoundError, extract_from_pcap, StatusFile
|
||||||
from pwnagotchi.utils import WifiInfo, FieldNotFoundError, extract_from_pcap
|
|
||||||
|
|
||||||
READY = False
|
READY = False
|
||||||
ALREADY_UPLOADED = None
|
REPORT = StatusFile('/root/.wigle_uploads', data_format='json')
|
||||||
SKIP = None
|
SKIP = list()
|
||||||
OPTIONS = dict()
|
OPTIONS = dict()
|
||||||
|
|
||||||
AKMSUITE_TYPES = {
|
|
||||||
0x00: "Reserved",
|
|
||||||
0x01: "802.1X",
|
|
||||||
0x02: "PSK",
|
|
||||||
}
|
|
||||||
|
|
||||||
def _handle_packet(packet, result):
|
|
||||||
from scapy.all import RadioTap, Dot11Elt, Dot11Beacon, rdpcap, Scapy_Exception, Dot11, Dot11ProbeResp, Dot11AssoReq, \
|
|
||||||
Dot11ReassoReq, Dot11EltRSN, Dot11EltVendorSpecific, Dot11EltMicrosoftWPA
|
|
||||||
"""
|
|
||||||
Analyze each packet and extract the data from Dot11 layers
|
|
||||||
"""
|
|
||||||
|
|
||||||
if hasattr(packet, 'cap') and 'privacy' in packet.cap:
|
|
||||||
# packet is encrypted
|
|
||||||
if 'encryption' not in result:
|
|
||||||
result['encryption'] = set()
|
|
||||||
|
|
||||||
if packet.haslayer(Dot11Beacon):
|
|
||||||
if packet.haslayer(Dot11Beacon)\
|
|
||||||
or packet.haslayer(Dot11ProbeResp)\
|
|
||||||
or packet.haslayer(Dot11AssoReq)\
|
|
||||||
or packet.haslayer(Dot11ReassoReq):
|
|
||||||
if 'bssid' not in result and hasattr(packet[Dot11], 'addr3'):
|
|
||||||
result['bssid'] = packet[Dot11].addr3
|
|
||||||
if 'essid' not in result and hasattr(packet[Dot11Elt], 'info'):
|
|
||||||
result['essid'] = packet[Dot11Elt].info
|
|
||||||
if 'channel' not in result and hasattr(packet[Dot11Elt:3], 'info'):
|
|
||||||
result['channel'] = int(ord(packet[Dot11Elt:3].info))
|
|
||||||
|
|
||||||
if packet.haslayer(RadioTap):
|
|
||||||
if 'rssi' not in result and hasattr(packet[RadioTap], 'dBm_AntSignal'):
|
|
||||||
result['rssi'] = packet[RadioTap].dBm_AntSignal
|
|
||||||
if 'channel' not in result and hasattr(packet[RadioTap], 'ChannelFrequency'):
|
|
||||||
result['channel'] = freq_to_channel(packet[RadioTap].ChannelFrequency)
|
|
||||||
|
|
||||||
# see: https://fossies.org/linux/scapy/scapy/layers/dot11.py
|
|
||||||
if packet.haslayer(Dot11EltRSN):
|
|
||||||
if hasattr(packet[Dot11EltRSN], 'akm_suites'):
|
|
||||||
auth = AKMSUITE_TYPES.get(packet[Dot11EltRSN].akm_suites[0].suite)
|
|
||||||
result['encryption'].add(f"WPA2/{auth}")
|
|
||||||
else:
|
|
||||||
result['encryption'].add("WPA2")
|
|
||||||
|
|
||||||
if packet.haslayer(Dot11EltVendorSpecific)\
|
|
||||||
and (packet.haslayer(Dot11EltMicrosoftWPA)
|
|
||||||
or packet.info.startswith(b'\x00P\xf2\x01\x01\x00')):
|
|
||||||
|
|
||||||
if hasattr(packet, 'akm_suites'):
|
|
||||||
auth = AKMSUITE_TYPES.get(packet.akm_suites[0].suite)
|
|
||||||
result['encryption'].add(f"WPA2/{auth}")
|
|
||||||
else:
|
|
||||||
result['encryption'].add("WPA2")
|
|
||||||
# end see
|
|
||||||
|
|
||||||
return result
|
|
||||||
|
|
||||||
|
|
||||||
def _analyze_pcap(pcap):
|
|
||||||
from scapy.all import RadioTap, Dot11Elt, Dot11Beacon, rdpcap, Scapy_Exception, Dot11, Dot11ProbeResp, Dot11AssoReq, \
|
|
||||||
Dot11ReassoReq, Dot11EltRSN, Dot11EltVendorSpecific, Dot11EltMicrosoftWPA
|
|
||||||
"""
|
|
||||||
Iterate over the packets and extract data
|
|
||||||
"""
|
|
||||||
result = dict()
|
|
||||||
|
|
||||||
try:
|
|
||||||
packets = rdpcap(pcap)
|
|
||||||
for packet in packets:
|
|
||||||
result = _handle_packet(packet, result)
|
|
||||||
except Scapy_Exception as sc_e:
|
|
||||||
raise sc_e
|
|
||||||
|
|
||||||
return result
|
|
||||||
|
|
||||||
|
|
||||||
def on_loaded():
|
def on_loaded():
|
||||||
"""
|
"""
|
||||||
Gets called when the plugin gets loaded
|
Gets called when the plugin gets loaded
|
||||||
"""
|
"""
|
||||||
global READY
|
global READY
|
||||||
global ALREADY_UPLOADED
|
|
||||||
global SKIP
|
|
||||||
|
|
||||||
SKIP = list()
|
|
||||||
|
|
||||||
if 'api_key' not in OPTIONS or ('api_key' in OPTIONS and OPTIONS['api_key'] is None):
|
if 'api_key' not in OPTIONS or ('api_key' in OPTIONS and OPTIONS['api_key'] is None):
|
||||||
logging.error("WIGLE: api_key isn't set. Can't upload to wigle.net")
|
logging.error("WIGLE: api_key isn't set. Can't upload to wigle.net")
|
||||||
return
|
return
|
||||||
|
|
||||||
try:
|
|
||||||
with open('/root/.wigle_uploads', 'r') as f:
|
|
||||||
ALREADY_UPLOADED = f.read().splitlines()
|
|
||||||
except OSError:
|
|
||||||
logging.warning('WIGLE: No upload-file found.')
|
|
||||||
ALREADY_UPLOADED = []
|
|
||||||
|
|
||||||
READY = True
|
READY = True
|
||||||
|
|
||||||
|
|
||||||
@@ -197,24 +110,24 @@ def _send_to_wigle(lines, api_key, timeout=30):
|
|||||||
|
|
||||||
|
|
||||||
def on_internet_available(agent):
|
def on_internet_available(agent):
|
||||||
from scapy.all import RadioTap, Dot11Elt, Dot11Beacon, rdpcap, Scapy_Exception, Dot11, Dot11ProbeResp, Dot11AssoReq, \
|
from scapy.all import Scapy_Exception
|
||||||
Dot11ReassoReq, Dot11EltRSN, Dot11EltVendorSpecific, Dot11EltMicrosoftWPA
|
|
||||||
"""
|
"""
|
||||||
Called in manual mode when there's internet connectivity
|
Called in manual mode when there's internet connectivity
|
||||||
"""
|
"""
|
||||||
global ALREADY_UPLOADED
|
global REPORT
|
||||||
global SKIP
|
global SKIP
|
||||||
|
|
||||||
if READY:
|
if READY:
|
||||||
config = agent.config()
|
config = agent.config()
|
||||||
display = agent.view()
|
display = agent.view()
|
||||||
|
reported = REPORT.data_field_or('reported', default=list())
|
||||||
|
|
||||||
handshake_dir = config['bettercap']['handshakes']
|
handshake_dir = config['bettercap']['handshakes']
|
||||||
all_files = os.listdir(handshake_dir)
|
all_files = os.listdir(handshake_dir)
|
||||||
all_gps_files = [os.path.join(handshake_dir, filename)
|
all_gps_files = [os.path.join(handshake_dir, filename)
|
||||||
for filename in all_files
|
for filename in all_files
|
||||||
if filename.endswith('.gps.json')]
|
if filename.endswith('.gps.json')]
|
||||||
new_gps_files = set(all_gps_files) - set(ALREADY_UPLOADED) - set(SKIP)
|
new_gps_files = set(all_gps_files) - set(reported) - set(SKIP)
|
||||||
|
|
||||||
if new_gps_files:
|
if new_gps_files:
|
||||||
logging.info("WIGLE: Internet connectivity detected. Uploading new handshakes to wigle.net")
|
logging.info("WIGLE: Internet connectivity detected. Uploading new handshakes to wigle.net")
|
||||||
@@ -271,10 +184,8 @@ def on_internet_available(agent):
|
|||||||
display.update(force=True)
|
display.update(force=True)
|
||||||
try:
|
try:
|
||||||
_send_to_wigle(csv_entries, OPTIONS['api_key'])
|
_send_to_wigle(csv_entries, OPTIONS['api_key'])
|
||||||
ALREADY_UPLOADED += no_err_entries
|
reported += no_err_entries
|
||||||
with open('/root/.wigle_uploads', 'a') as up_file:
|
REPORT.update(data={'reported': reported})
|
||||||
for gps in no_err_entries:
|
|
||||||
up_file.write(gps + "\n")
|
|
||||||
logging.info("WIGLE: Successfuly uploaded %d files", len(no_err_entries))
|
logging.info("WIGLE: Successfuly uploaded %d files", len(no_err_entries))
|
||||||
except requests.exceptions.RequestException as re_e:
|
except requests.exceptions.RequestException as re_e:
|
||||||
SKIP += no_err_entries
|
SKIP += no_err_entries
|
||||||
|
@@ -1,5 +1,5 @@
|
|||||||
__author__ = '33197631+dadav@users.noreply.github.com'
|
__author__ = '33197631+dadav@users.noreply.github.com'
|
||||||
__version__ = '1.0.0'
|
__version__ = '2.0.1'
|
||||||
__name__ = 'wpa-sec'
|
__name__ = 'wpa-sec'
|
||||||
__license__ = 'GPL3'
|
__license__ = 'GPL3'
|
||||||
__description__ = 'This plugin automatically uploades handshakes to https://wpa-sec.stanev.org'
|
__description__ = 'This plugin automatically uploades handshakes to https://wpa-sec.stanev.org'
|
||||||
@@ -7,9 +7,12 @@ __description__ = 'This plugin automatically uploades handshakes to https://wpa-
|
|||||||
import os
|
import os
|
||||||
import logging
|
import logging
|
||||||
import requests
|
import requests
|
||||||
|
from pwnagotchi.utils import StatusFile
|
||||||
|
|
||||||
READY = False
|
READY = False
|
||||||
ALREADY_UPLOADED = None
|
REPORT = StatusFile('/root/.wpa_sec_uploads', data_format='json')
|
||||||
|
OPTIONS = dict()
|
||||||
|
SKIP = list()
|
||||||
|
|
||||||
|
|
||||||
def on_loaded():
|
def on_loaded():
|
||||||
@@ -17,20 +20,11 @@ def on_loaded():
|
|||||||
Gets called when the plugin gets loaded
|
Gets called when the plugin gets loaded
|
||||||
"""
|
"""
|
||||||
global READY
|
global READY
|
||||||
global API_KEY
|
|
||||||
global ALREADY_UPLOADED
|
|
||||||
|
|
||||||
if not 'api_key' in OPTIONS or ('api_key' in OPTIONS and OPTIONS['api_key'] is None):
|
if 'api_key' not in OPTIONS or ('api_key' in OPTIONS and OPTIONS['api_key'] is None):
|
||||||
logging.error("WPA_SEC: API-KEY isn't set. Can't upload to wpa-sec.stanev.org")
|
logging.error("WPA_SEC: API-KEY isn't set. Can't upload to wpa-sec.stanev.org")
|
||||||
return
|
return
|
||||||
|
|
||||||
try:
|
|
||||||
with open('/root/.wpa_sec_uploads', 'r') as f:
|
|
||||||
ALREADY_UPLOADED = f.read().splitlines()
|
|
||||||
except OSError:
|
|
||||||
logging.warning('WPA_SEC: No upload-file found.')
|
|
||||||
ALREADY_UPLOADED = []
|
|
||||||
|
|
||||||
READY = True
|
READY = True
|
||||||
|
|
||||||
|
|
||||||
@@ -39,48 +33,51 @@ def _upload_to_wpasec(path, timeout=30):
|
|||||||
Uploads the file to wpa-sec.stanev.org
|
Uploads the file to wpa-sec.stanev.org
|
||||||
"""
|
"""
|
||||||
with open(path, 'rb') as file_to_upload:
|
with open(path, 'rb') as file_to_upload:
|
||||||
headers = {'key': OPTIONS['api_key']}
|
cookie = {'key': OPTIONS['api_key']}
|
||||||
payload = {'file': file_to_upload}
|
payload = {'file': file_to_upload}
|
||||||
|
|
||||||
try:
|
try:
|
||||||
result = requests.post('https://wpa-sec.stanev.org/?submit',
|
result = requests.post('https://wpa-sec.stanev.org',
|
||||||
headers=headers,
|
cookies=cookie,
|
||||||
files=payload,
|
files=payload,
|
||||||
timeout=timeout)
|
timeout=timeout)
|
||||||
if ' already submitted' in result.text:
|
if ' already submitted' in result.text:
|
||||||
logging.warning(f"{path} was already submitted.")
|
logging.warning("%s was already submitted.", path)
|
||||||
except requests.exceptions.RequestException as e:
|
except requests.exceptions.RequestException as req_e:
|
||||||
logging.error(f"WPA_SEC: Got an exception while uploading {path} -> {e}")
|
raise req_e
|
||||||
raise e
|
|
||||||
|
|
||||||
|
|
||||||
def on_internet_available(agent):
|
def on_internet_available(agent):
|
||||||
"""
|
"""
|
||||||
Called in manual mode when there's internet connectivity
|
Called in manual mode when there's internet connectivity
|
||||||
"""
|
"""
|
||||||
|
global REPORT
|
||||||
|
global SKIP
|
||||||
if READY:
|
if READY:
|
||||||
config = agent.config()
|
config = agent.config()
|
||||||
display = agent.view()
|
display = agent.view()
|
||||||
|
reported = REPORT.data_field_or('reported', default=list())
|
||||||
|
|
||||||
handshake_dir = config['bettercap']['handshakes']
|
handshake_dir = config['bettercap']['handshakes']
|
||||||
handshake_filenames = os.listdir(handshake_dir)
|
handshake_filenames = os.listdir(handshake_dir)
|
||||||
handshake_paths = [os.path.join(handshake_dir, filename) for filename in handshake_filenames if filename.endswith('.pcap')]
|
handshake_paths = [os.path.join(handshake_dir, filename) for filename in handshake_filenames if filename.endswith('.pcap')]
|
||||||
handshake_new = set(handshake_paths) - set(ALREADY_UPLOADED)
|
handshake_new = set(handshake_paths) - set(reported) - set(SKIP)
|
||||||
|
|
||||||
if handshake_new:
|
if handshake_new:
|
||||||
logging.info("WPA_SEC: Internet connectivity detected.\
|
logging.info("WPA_SEC: Internet connectivity detected. Uploading new handshakes to wpa-sec.stanev.org")
|
||||||
Uploading new handshakes to wpa-sec.stanev.org")
|
|
||||||
|
|
||||||
for idx, handshake in enumerate(handshake_new):
|
for idx, handshake in enumerate(handshake_new):
|
||||||
display.set('status', f"Uploading handshake to wpa-sec.stanev.org ({idx + 1}/{len(handshake_new)})")
|
display.set('status', f"Uploading handshake to wpa-sec.stanev.org ({idx + 1}/{len(handshake_new)})")
|
||||||
display.update(force=True)
|
display.update(force=True)
|
||||||
try:
|
try:
|
||||||
_upload_to_wpasec(handshake)
|
_upload_to_wpasec(handshake)
|
||||||
ALREADY_UPLOADED.append(handshake)
|
reported.append(handshake)
|
||||||
with open('/root/.wpa_sec_uploads', 'a') as f:
|
REPORT.update(data={'reported': reported})
|
||||||
f.write(handshake + "\n")
|
logging.info("WPA_SEC: Successfuly uploaded %s", handshake)
|
||||||
logging.info(f"WPA_SEC: Successfuly uploaded {handshake}")
|
except requests.exceptions.RequestException as req_e:
|
||||||
except requests.exceptions.RequestException:
|
SKIP.append(handshake)
|
||||||
pass
|
logging.error("WPA_SEC: %s", req_e)
|
||||||
|
continue
|
||||||
except OSError as os_e:
|
except OSError as os_e:
|
||||||
logging.error(f"WPA_SEC: Got the following error: {os_e}")
|
logging.error("WPA_SEC: %s", os_e)
|
||||||
|
continue
|
||||||
|
@@ -1,13 +1,12 @@
|
|||||||
import _thread
|
import _thread
|
||||||
from threading import Lock
|
from threading import Lock
|
||||||
from PIL import Image
|
|
||||||
|
|
||||||
import shutil
|
import shutil
|
||||||
import logging
|
import logging
|
||||||
import os
|
|
||||||
import pwnagotchi, pwnagotchi.plugins as plugins
|
import pwnagotchi, pwnagotchi.plugins as plugins
|
||||||
|
|
||||||
from pwnagotchi.ui.view import WHITE, View
|
import pwnagotchi.ui.hw as hw
|
||||||
|
from pwnagotchi.ui.view import View
|
||||||
|
|
||||||
from http.server import BaseHTTPRequestHandler, HTTPServer
|
from http.server import BaseHTTPRequestHandler, HTTPServer
|
||||||
|
|
||||||
@@ -88,24 +87,15 @@ class VideoHandler(BaseHTTPRequestHandler):
|
|||||||
|
|
||||||
class Display(View):
|
class Display(View):
|
||||||
def __init__(self, config, state={}):
|
def __init__(self, config, state={}):
|
||||||
super(Display, self).__init__(config, state)
|
super(Display, self).__init__(config, hw.display_for(config), state)
|
||||||
self._enabled = config['ui']['display']['enabled']
|
self._enabled = config['ui']['display']['enabled']
|
||||||
self._rotation = config['ui']['display']['rotation']
|
self._rotation = config['ui']['display']['rotation']
|
||||||
self._video_enabled = config['ui']['display']['video']['enabled']
|
self._video_enabled = config['ui']['display']['video']['enabled']
|
||||||
self._video_port = config['ui']['display']['video']['port']
|
self._video_port = config['ui']['display']['video']['port']
|
||||||
self._video_address = config['ui']['display']['video']['address']
|
self._video_address = config['ui']['display']['video']['address']
|
||||||
self._display_type = config['ui']['display']['type']
|
|
||||||
self._display_color = config['ui']['display']['color']
|
|
||||||
|
|
||||||
self._render_cb = None
|
|
||||||
self._display = None
|
|
||||||
self._httpd = None
|
self._httpd = None
|
||||||
|
|
||||||
if self._enabled:
|
self.init_display()
|
||||||
self._init_display()
|
|
||||||
else:
|
|
||||||
self.on_render(self._on_view_rendered)
|
|
||||||
logging.warning("display module is disabled")
|
|
||||||
|
|
||||||
if self._video_enabled:
|
if self._video_enabled:
|
||||||
_thread.start_new_thread(self._http_serve, ())
|
_thread.start_new_thread(self._http_serve, ())
|
||||||
@@ -119,133 +109,33 @@ class Display(View):
|
|||||||
logging.info("could not get ip of usb0, video server not starting")
|
logging.info("could not get ip of usb0, video server not starting")
|
||||||
|
|
||||||
def is_inky(self):
|
def is_inky(self):
|
||||||
return self._display_type in ('inkyphat', 'inky')
|
return self._implementation.name == 'inky'
|
||||||
|
|
||||||
def is_papirus(self):
|
def is_papirus(self):
|
||||||
return self._display_type in ('papirus', 'papi')
|
return self._implementation.name == 'papirus'
|
||||||
|
|
||||||
def is_waveshare_v1(self):
|
def is_waveshare_v1(self):
|
||||||
return self._display_type in ('waveshare_1', 'ws_1', 'waveshare1', 'ws1')
|
return self._implementation.name == 'waveshare_1'
|
||||||
|
|
||||||
def is_waveshare_v2(self):
|
def is_waveshare_v2(self):
|
||||||
return self._display_type in ('waveshare_2', 'ws_2', 'waveshare2', 'ws2')
|
return self._implementation.name == 'waveshare_2'
|
||||||
|
|
||||||
|
def is_oledhat(self):
|
||||||
|
return self._implementation.name == 'oledhat'
|
||||||
|
|
||||||
def is_waveshare_any(self):
|
def is_waveshare_any(self):
|
||||||
return self.is_waveshare_v1() or self.is_waveshare_v2()
|
return self.is_waveshare_v1() or self.is_waveshare_v2()
|
||||||
|
|
||||||
def _init_display(self):
|
def init_display(self):
|
||||||
if self.is_inky():
|
if self._enabled:
|
||||||
logging.info("initializing inky display")
|
self._implementation.initialize()
|
||||||
from inky import InkyPHAT
|
plugins.on('display_setup', self._implementation)
|
||||||
self._display = InkyPHAT(self._display_color)
|
|
||||||
self._display.set_border(InkyPHAT.BLACK)
|
|
||||||
self._render_cb = self._inky_render
|
|
||||||
|
|
||||||
elif self.is_papirus():
|
|
||||||
logging.info("initializing papirus display")
|
|
||||||
from pwnagotchi.ui.papirus.epd import EPD
|
|
||||||
os.environ['EPD_SIZE'] = '2.0'
|
|
||||||
self._display = EPD()
|
|
||||||
self._display.clear()
|
|
||||||
self._render_cb = self._papirus_render
|
|
||||||
|
|
||||||
elif self.is_waveshare_v1():
|
|
||||||
if self._display_color == 'black':
|
|
||||||
logging.info("initializing waveshare v1 display in monochromatic mode")
|
|
||||||
from pwnagotchi.ui.waveshare.v1.epd2in13 import EPD
|
|
||||||
self._display = EPD()
|
|
||||||
self._display.init(self._display.lut_full_update)
|
|
||||||
self._display.Clear(0xFF)
|
|
||||||
self._display.init(self._display.lut_partial_update)
|
|
||||||
self._render_cb = self._waveshare_render
|
|
||||||
|
|
||||||
else:
|
else:
|
||||||
logging.info("initializing waveshare v1 display 3-color mode")
|
logging.warning("display module is disabled")
|
||||||
from pwnagotchi.ui.waveshare.v1.epd2in13bc import EPD
|
|
||||||
self._display = EPD()
|
|
||||||
self._display.init()
|
|
||||||
self._display.Clear()
|
|
||||||
self._render_cb = self._waveshare_bc_render
|
|
||||||
|
|
||||||
elif self.is_waveshare_v2():
|
|
||||||
logging.info("initializing waveshare v2 display")
|
|
||||||
from pwnagotchi.ui.waveshare.v2.waveshare import EPD
|
|
||||||
self._display = EPD()
|
|
||||||
self._display.init(self._display.FULL_UPDATE)
|
|
||||||
self._display.Clear(WHITE)
|
|
||||||
self._display.init(self._display.PART_UPDATE)
|
|
||||||
self._render_cb = self._waveshare_render
|
|
||||||
|
|
||||||
else:
|
|
||||||
logging.critical("unknown display type %s" % self._display_type)
|
|
||||||
|
|
||||||
plugins.on('display_setup', self._display)
|
|
||||||
|
|
||||||
self.on_render(self._on_view_rendered)
|
self.on_render(self._on_view_rendered)
|
||||||
|
|
||||||
def clear(self):
|
def clear(self):
|
||||||
if self._display is None:
|
self._implementation.clear()
|
||||||
logging.error("no display object created")
|
|
||||||
elif self.is_inky():
|
|
||||||
self._display.Clear()
|
|
||||||
elif self.is_papirus():
|
|
||||||
self._display.clear()
|
|
||||||
elif self.is_waveshare_any():
|
|
||||||
self._display.Clear(WHITE)
|
|
||||||
else:
|
|
||||||
logging.critical("unknown display type %s" % self._display_type)
|
|
||||||
|
|
||||||
def _inky_render(self):
|
|
||||||
if self._display_color != 'mono':
|
|
||||||
display_colors = 3
|
|
||||||
else:
|
|
||||||
display_colors = 2
|
|
||||||
|
|
||||||
img_buffer = self._canvas.convert('RGB').convert('P', palette=1, colors=display_colors)
|
|
||||||
if self._display_color == 'red':
|
|
||||||
img_buffer.putpalette([
|
|
||||||
255, 255, 255, # index 0 is white
|
|
||||||
0, 0, 0, # index 1 is black
|
|
||||||
255, 0, 0 # index 2 is red
|
|
||||||
])
|
|
||||||
elif self._display_color == 'yellow':
|
|
||||||
img_buffer.putpalette([
|
|
||||||
255, 255, 255, # index 0 is white
|
|
||||||
0, 0, 0, # index 1 is black
|
|
||||||
255, 255, 0 # index 2 is yellow
|
|
||||||
])
|
|
||||||
else:
|
|
||||||
img_buffer.putpalette([
|
|
||||||
255, 255, 255, # index 0 is white
|
|
||||||
0, 0, 0 # index 1 is black
|
|
||||||
])
|
|
||||||
|
|
||||||
self._display.set_image(img_buffer)
|
|
||||||
try:
|
|
||||||
self._display.show()
|
|
||||||
except:
|
|
||||||
logging.exception("error while rendering on inky")
|
|
||||||
|
|
||||||
def _papirus_render(self):
|
|
||||||
self._display.display(self._canvas)
|
|
||||||
self._display.partial_update()
|
|
||||||
|
|
||||||
def _waveshare_render(self):
|
|
||||||
buf = self._display.getbuffer(self._canvas)
|
|
||||||
if self.is_waveshare_v1():
|
|
||||||
self._display.display(buf)
|
|
||||||
elif self.is_waveshare_v2():
|
|
||||||
self._display.displayPartial(buf)
|
|
||||||
|
|
||||||
def _waveshare_bc_render(self):
|
|
||||||
buf_black = self._display.getbuffer(self._canvas)
|
|
||||||
# emptyImage = Image.new('1', (self._display.height, self._display.width), 255)
|
|
||||||
# buf_color = self._display.getbuffer(emptyImage)
|
|
||||||
# self._display.display(buf_black,buf_color)
|
|
||||||
|
|
||||||
# Custom display function that only handles black
|
|
||||||
# Was included in epd2in13bc.py
|
|
||||||
self._display.displayBlack(buf_black)
|
|
||||||
|
|
||||||
def image(self):
|
def image(self):
|
||||||
img = None
|
img = None
|
||||||
@@ -255,8 +145,7 @@ class Display(View):
|
|||||||
|
|
||||||
def _on_view_rendered(self, img):
|
def _on_view_rendered(self, img):
|
||||||
VideoHandler.render(img)
|
VideoHandler.render(img)
|
||||||
|
|
||||||
if self._enabled:
|
if self._enabled:
|
||||||
self._canvas = (img if self._rotation == 0 else img.rotate(self._rotation))
|
self._canvas = (img if self._rotation == 0 else img.rotate(self._rotation))
|
||||||
if self._render_cb is not None:
|
if self._implementation is not None:
|
||||||
self._render_cb()
|
self._implementation.render(self._canvas)
|
||||||
|
@@ -1,11 +1,11 @@
|
|||||||
LOOK_R = '(⌐■_■)'
|
LOOK_R = '( ⚆_⚆)'
|
||||||
LOOK_L = '(■_■¬)'
|
LOOK_L = '(☉_☉ )'
|
||||||
SLEEP = '(⇀‿‿↼)'
|
SLEEP = '(⇀‿‿↼)'
|
||||||
SLEEP2 = '(≖‿‿≖)'
|
SLEEP2 = '(≖‿‿≖)'
|
||||||
AWAKE = '(◕‿‿◕)'
|
AWAKE = '(◕‿‿◕)'
|
||||||
BORED = '(-__-)'
|
BORED = '(-__-)'
|
||||||
INTENSE = '(°▃▃°)'
|
INTENSE = '(°▃▃°)'
|
||||||
COOL = '(⊙☁◉┐)'
|
COOL = '(⌐■_■)'
|
||||||
HAPPY = '(•‿‿•)'
|
HAPPY = '(•‿‿•)'
|
||||||
EXCITED = '(ᵔ◡◡ᵔ)'
|
EXCITED = '(ᵔ◡◡ᵔ)'
|
||||||
MOTIVATED = '(☼‿‿☼)'
|
MOTIVATED = '(☼‿‿☼)'
|
||||||
|
23
pwnagotchi/ui/hw/__init__.py
Normal file
23
pwnagotchi/ui/hw/__init__.py
Normal file
@@ -0,0 +1,23 @@
|
|||||||
|
from pwnagotchi.ui.hw.inky import Inky
|
||||||
|
from pwnagotchi.ui.hw.papirus import Papirus
|
||||||
|
from pwnagotchi.ui.hw.oledhat import OledHat
|
||||||
|
from pwnagotchi.ui.hw.waveshare1 import WaveshareV1
|
||||||
|
from pwnagotchi.ui.hw.waveshare2 import WaveshareV2
|
||||||
|
|
||||||
|
|
||||||
|
def display_for(config):
|
||||||
|
# config has been normalized already in utils.load_config
|
||||||
|
if config['ui']['display']['type'] == 'inky':
|
||||||
|
return Inky(config)
|
||||||
|
|
||||||
|
elif config['ui']['display']['type'] == 'papirus':
|
||||||
|
return Papirus(config)
|
||||||
|
|
||||||
|
if config['ui']['display']['type'] == 'oledhat':
|
||||||
|
return OledHat(config)
|
||||||
|
|
||||||
|
elif config['ui']['display']['type'] == 'waveshare_1':
|
||||||
|
return WaveshareV1(config)
|
||||||
|
|
||||||
|
elif config['ui']['display']['type'] == 'waveshare_2':
|
||||||
|
return WaveshareV2(config)
|
40
pwnagotchi/ui/hw/base.py
Normal file
40
pwnagotchi/ui/hw/base.py
Normal file
@@ -0,0 +1,40 @@
|
|||||||
|
import pwnagotchi.ui.fonts as fonts
|
||||||
|
|
||||||
|
|
||||||
|
class DisplayImpl(object):
|
||||||
|
def __init__(self, config, name):
|
||||||
|
self.name = name
|
||||||
|
self.config = config['ui']['display']
|
||||||
|
self._layout = {
|
||||||
|
'width': 0,
|
||||||
|
'height': 0,
|
||||||
|
'face': (0, 0),
|
||||||
|
'name': (0, 0),
|
||||||
|
'channel': (0, 0),
|
||||||
|
'aps': (0, 0),
|
||||||
|
'uptime': (0, 0),
|
||||||
|
'line1': (0, 0),
|
||||||
|
'line2': (0, 0),
|
||||||
|
'friend_face': (0, 0),
|
||||||
|
'friend_name': (0, 0),
|
||||||
|
'shakes': (0, 0),
|
||||||
|
'mode': (0, 0),
|
||||||
|
# status is special :D
|
||||||
|
'status': {
|
||||||
|
'pos': (0, 0),
|
||||||
|
'font': fonts.Medium,
|
||||||
|
'max': 20
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
def layout(self):
|
||||||
|
raise NotImplementedError
|
||||||
|
|
||||||
|
def initialize(self):
|
||||||
|
raise NotImplementedError
|
||||||
|
|
||||||
|
def render(self, canvas):
|
||||||
|
raise NotImplementedError
|
||||||
|
|
||||||
|
def clear(self):
|
||||||
|
raise NotImplementedError
|
72
pwnagotchi/ui/hw/inky.py
Normal file
72
pwnagotchi/ui/hw/inky.py
Normal file
@@ -0,0 +1,72 @@
|
|||||||
|
import logging
|
||||||
|
|
||||||
|
import pwnagotchi.ui.fonts as fonts
|
||||||
|
from pwnagotchi.ui.hw.base import DisplayImpl
|
||||||
|
|
||||||
|
|
||||||
|
class Inky(DisplayImpl):
|
||||||
|
def __init__(self, config):
|
||||||
|
super(Inky, self).__init__(config, 'inky')
|
||||||
|
self._display = None
|
||||||
|
|
||||||
|
def layout(self):
|
||||||
|
fonts.setup(10, 8, 10, 28)
|
||||||
|
self._layout['width'] = 212
|
||||||
|
self._layout['height'] = 104
|
||||||
|
self._layout['face'] = (0, 37)
|
||||||
|
self._layout['name'] = (5, 18)
|
||||||
|
self._layout['channel'] = (0, 0)
|
||||||
|
self._layout['aps'] = (25, 0)
|
||||||
|
self._layout['uptime'] = (147, 0)
|
||||||
|
self._layout['line1'] = [0, 12, 212, 12]
|
||||||
|
self._layout['line2'] = [0, 92, 212, 92]
|
||||||
|
self._layout['friend_face'] = (0, 76)
|
||||||
|
self._layout['friend_name'] = (40, 78)
|
||||||
|
self._layout['shakes'] = (0, 93)
|
||||||
|
self._layout['mode'] = (187, 93)
|
||||||
|
self._layout['status'] = {
|
||||||
|
'pos': (102, 18),
|
||||||
|
'font': fonts.Small,
|
||||||
|
'max': 20
|
||||||
|
}
|
||||||
|
return self._layout
|
||||||
|
|
||||||
|
def initialize(self):
|
||||||
|
logging.info("initializing inky display")
|
||||||
|
from pwnagotchi.ui.hw.libs.inkyphat.inkyphatfast import InkyPHATFast
|
||||||
|
self._display = InkyPHATFast(self.config['color'])
|
||||||
|
self._display.set_border(InkyPHATFast.BLACK)
|
||||||
|
|
||||||
|
def render(self, canvas):
|
||||||
|
if self.config['color'] != 'mono':
|
||||||
|
display_colors = 3
|
||||||
|
else:
|
||||||
|
display_colors = 2
|
||||||
|
|
||||||
|
img_buffer = canvas.convert('RGB').convert('P', palette=1, colors=display_colors)
|
||||||
|
if self.config['color'] == 'red':
|
||||||
|
img_buffer.putpalette([
|
||||||
|
255, 255, 255, # index 0 is white
|
||||||
|
0, 0, 0, # index 1 is black
|
||||||
|
255, 0, 0 # index 2 is red
|
||||||
|
])
|
||||||
|
elif self.config['color'] == 'yellow':
|
||||||
|
img_buffer.putpalette([
|
||||||
|
255, 255, 255, # index 0 is white
|
||||||
|
0, 0, 0, # index 1 is black
|
||||||
|
255, 255, 0 # index 2 is yellow
|
||||||
|
])
|
||||||
|
else:
|
||||||
|
img_buffer.putpalette([
|
||||||
|
255, 255, 255, # index 0 is white
|
||||||
|
0, 0, 0 # index 1 is black
|
||||||
|
])
|
||||||
|
|
||||||
|
self._display.set_image(img_buffer)
|
||||||
|
try:
|
||||||
|
self._display.show()
|
||||||
|
except:
|
||||||
|
logging.exception("error while rendering on inky")
|
||||||
|
|
||||||
|
def clear(self):
|
||||||
|
self._display.Clear()
|
24
pwnagotchi/ui/hw/libs/inkyphat/inkyfast.py
Normal file
24
pwnagotchi/ui/hw/libs/inkyphat/inkyfast.py
Normal file
@@ -0,0 +1,24 @@
|
|||||||
|
from inky.inky import Inky, CS0_PIN, DC_PIN, RESET_PIN, BUSY_PIN
|
||||||
|
|
||||||
|
|
||||||
|
class InkyFast(Inky):
|
||||||
|
|
||||||
|
def __init__(self, resolution=(400, 300), colour='black', cs_pin=CS0_PIN, dc_pin=DC_PIN, reset_pin=RESET_PIN,
|
||||||
|
busy_pin=BUSY_PIN, h_flip=False, v_flip=False):
|
||||||
|
super(InkyFast, self).__init__(resolution, colour, cs_pin, dc_pin, reset_pin, busy_pin, h_flip, v_flip)
|
||||||
|
|
||||||
|
self._luts['black'] = [
|
||||||
|
0b01001000, 0b10100000, 0b00010000, 0b00010000, 0b00010011, 0b00000000, 0b00000000,
|
||||||
|
0b01001000, 0b10100000, 0b10000000, 0b00000000, 0b00000011, 0b00000000, 0b00000000,
|
||||||
|
0b00000000, 0b00000000, 0b00000000, 0b00000000, 0b00000000, 0b00000000, 0b00000000,
|
||||||
|
0b01001000, 0b10100101, 0b00000000, 0b10111011, 0b00000000, 0b00000000, 0b00000000,
|
||||||
|
0b00000000, 0b00000000, 0b00000000, 0b00000000, 0b00000000, 0b00000000, 0b00000000,
|
||||||
|
# The following timings have been reduced to avoid the fade to black
|
||||||
|
0x00, 0x00, 0x00, 0x00, 0x00,
|
||||||
|
0x10, 0x04, 0x04, 0x04, 0x04,
|
||||||
|
0x04, 0x08, 0x08, 0x10, 0x10,
|
||||||
|
0x00, 0x00, 0x00, 0x00, 0x00,
|
||||||
|
0x00, 0x00, 0x00, 0x00, 0x00,
|
||||||
|
0x00, 0x00, 0x00, 0x00, 0x00,
|
||||||
|
0x00, 0x00, 0x00, 0x00, 0x00,
|
||||||
|
]
|
27
pwnagotchi/ui/hw/libs/inkyphat/inkyphatfast.py
Normal file
27
pwnagotchi/ui/hw/libs/inkyphat/inkyphatfast.py
Normal file
@@ -0,0 +1,27 @@
|
|||||||
|
"""Inky pHAT e-Ink Display Driver."""
|
||||||
|
from . import inkyfast
|
||||||
|
|
||||||
|
|
||||||
|
class InkyPHATFast(inkyfast.InkyFast):
|
||||||
|
"""Inky wHAT e-Ink Display Driver."""
|
||||||
|
|
||||||
|
WIDTH = 212
|
||||||
|
HEIGHT = 104
|
||||||
|
|
||||||
|
WHITE = 0
|
||||||
|
BLACK = 1
|
||||||
|
RED = 2
|
||||||
|
YELLOW = 2
|
||||||
|
|
||||||
|
def __init__(self, colour):
|
||||||
|
"""Initialise an Inky pHAT Display.
|
||||||
|
|
||||||
|
:param colour: one of red, black or yellow, default: black
|
||||||
|
|
||||||
|
"""
|
||||||
|
inkyfast.InkyFast.__init__(
|
||||||
|
self,
|
||||||
|
resolution=(self.WIDTH, self.HEIGHT),
|
||||||
|
colour=colour,
|
||||||
|
h_flip=False,
|
||||||
|
v_flip=False)
|
@@ -15,7 +15,7 @@
|
|||||||
|
|
||||||
from PIL import Image
|
from PIL import Image
|
||||||
from PIL import ImageOps
|
from PIL import ImageOps
|
||||||
from pwnagotchi.ui.papirus.lm75b import LM75B
|
from pwnagotchi.ui.hw.libs.papirus import LM75B
|
||||||
import re
|
import re
|
||||||
import os
|
import os
|
||||||
import sys
|
import sys
|
135
pwnagotchi/ui/hw/libs/waveshare/oledhat/SH1106.py
Normal file
135
pwnagotchi/ui/hw/libs/waveshare/oledhat/SH1106.py
Normal file
@@ -0,0 +1,135 @@
|
|||||||
|
from . import config
|
||||||
|
import RPi.GPIO as GPIO
|
||||||
|
import time
|
||||||
|
|
||||||
|
Device_SPI = config.Device_SPI
|
||||||
|
Device_I2C = config.Device_I2C
|
||||||
|
|
||||||
|
LCD_WIDTH = 128 #LCD width
|
||||||
|
LCD_HEIGHT = 64 #LCD height
|
||||||
|
|
||||||
|
class SH1106(object):
|
||||||
|
def __init__(self):
|
||||||
|
self.width = LCD_WIDTH
|
||||||
|
self.height = LCD_HEIGHT
|
||||||
|
#Initialize DC RST pin
|
||||||
|
self._dc = config.DC_PIN
|
||||||
|
self._rst = config.RST_PIN
|
||||||
|
self._bl = config.BL_PIN
|
||||||
|
self.Device = config.Device
|
||||||
|
|
||||||
|
|
||||||
|
""" Write register address and data """
|
||||||
|
def command(self, cmd):
|
||||||
|
if(self.Device == Device_SPI):
|
||||||
|
GPIO.output(self._dc, GPIO.LOW)
|
||||||
|
config.spi_writebyte([cmd])
|
||||||
|
else:
|
||||||
|
config.i2c_writebyte(0x00, cmd)
|
||||||
|
|
||||||
|
# def data(self, val):
|
||||||
|
# GPIO.output(self._dc, GPIO.HIGH)
|
||||||
|
# config.spi_writebyte([val])
|
||||||
|
|
||||||
|
def Init(self):
|
||||||
|
if (config.module_init() != 0):
|
||||||
|
return -1
|
||||||
|
"""Initialize dispaly"""
|
||||||
|
self.reset()
|
||||||
|
self.command(0xAE);#--turn off oled panel
|
||||||
|
self.command(0x02);#---set low column address
|
||||||
|
self.command(0x10);#---set high column address
|
||||||
|
self.command(0x40);#--set start line address Set Mapping RAM Display Start Line (0x00~0x3F)
|
||||||
|
self.command(0x81);#--set contrast control register
|
||||||
|
self.command(0xA0);#--Set SEG/Column Mapping
|
||||||
|
self.command(0xC0);#Set COM/Row Scan Direction
|
||||||
|
self.command(0xA6);#--set normal display
|
||||||
|
self.command(0xA8);#--set multiplex ratio(1 to 64)
|
||||||
|
self.command(0x3F);#--1/64 duty
|
||||||
|
self.command(0xD3);#-set display offset Shift Mapping RAM Counter (0x00~0x3F)
|
||||||
|
self.command(0x00);#-not offset
|
||||||
|
self.command(0xd5);#--set display clock divide ratio/oscillator frequency
|
||||||
|
self.command(0x80);#--set divide ratio, Set Clock as 100 Frames/Sec
|
||||||
|
self.command(0xD9);#--set pre-charge period
|
||||||
|
self.command(0xF1);#Set Pre-Charge as 15 Clocks & Discharge as 1 Clock
|
||||||
|
self.command(0xDA);#--set com pins hardware configuration
|
||||||
|
self.command(0x12);
|
||||||
|
self.command(0xDB);#--set vcomh
|
||||||
|
self.command(0x40);#Set VCOM Deselect Level
|
||||||
|
self.command(0x20);#-Set Page Addressing Mode (0x00/0x01/0x02)
|
||||||
|
self.command(0x02);#
|
||||||
|
self.command(0xA4);# Disable Entire Display On (0xa4/0xa5)
|
||||||
|
self.command(0xA6);# Disable Inverse Display On (0xa6/a7)
|
||||||
|
time.sleep(0.1)
|
||||||
|
self.command(0xAF);#--turn on oled panel
|
||||||
|
|
||||||
|
|
||||||
|
def reset(self):
|
||||||
|
"""Reset the display"""
|
||||||
|
GPIO.output(self._rst,GPIO.HIGH)
|
||||||
|
time.sleep(0.1)
|
||||||
|
GPIO.output(self._rst,GPIO.LOW)
|
||||||
|
time.sleep(0.1)
|
||||||
|
GPIO.output(self._rst,GPIO.HIGH)
|
||||||
|
time.sleep(0.1)
|
||||||
|
|
||||||
|
def getbuffer(self, image):
|
||||||
|
# print "bufsiz = ",(self.width/8) * self.height
|
||||||
|
buf = [0xFF] * ((self.width//8) * self.height)
|
||||||
|
image_monocolor = image.convert('1')
|
||||||
|
imwidth, imheight = image_monocolor.size
|
||||||
|
pixels = image_monocolor.load()
|
||||||
|
# print "imwidth = %d, imheight = %d",imwidth,imheight
|
||||||
|
if(imwidth == self.width and imheight == self.height):
|
||||||
|
#print ("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[x + (y // 8) * self.width] &= ~(1 << (y % 8))
|
||||||
|
# print x,y,x + (y * self.width)/8,buf[(x + y * self.width) / 8]
|
||||||
|
|
||||||
|
elif(imwidth == self.height and imheight == self.width):
|
||||||
|
#print ("Vertical")
|
||||||
|
for y in range(imheight):
|
||||||
|
for x in range(imwidth):
|
||||||
|
newx = y
|
||||||
|
newy = self.height - x - 1
|
||||||
|
if pixels[x, y] == 0:
|
||||||
|
buf[(newx + (newy // 8 )*self.width) ] &= ~(1 << (y % 8))
|
||||||
|
return buf
|
||||||
|
|
||||||
|
|
||||||
|
# def ShowImage(self,Image):
|
||||||
|
# self.SetWindows()
|
||||||
|
# GPIO.output(self._dc, GPIO.HIGH);
|
||||||
|
# for i in range(0,self.width * self.height/8):
|
||||||
|
# config.spi_writebyte([~Image[i]])
|
||||||
|
|
||||||
|
def ShowImage(self, pBuf):
|
||||||
|
for page in range(0,8):
|
||||||
|
# set page address #
|
||||||
|
self.command(0xB0 + page);
|
||||||
|
# set low column address #
|
||||||
|
self.command(0x02);
|
||||||
|
# set high column address #
|
||||||
|
self.command(0x10);
|
||||||
|
# write data #
|
||||||
|
time.sleep(0.01)
|
||||||
|
if(self.Device == Device_SPI):
|
||||||
|
GPIO.output(self._dc, GPIO.HIGH);
|
||||||
|
for i in range(0,self.width):#for(int i=0;i<self.width; i++)
|
||||||
|
if(self.Device == Device_SPI):
|
||||||
|
config.spi_writebyte([~pBuf[i + self.width * page]]);
|
||||||
|
else :
|
||||||
|
config.i2c_writebyte(0x40, ~pBuf[i + self.width * page])
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
def clear(self):
|
||||||
|
"""Clear contents of image buffer"""
|
||||||
|
_buffer = [0xff]*(self.width * self.height//8)
|
||||||
|
self.ShowImage(_buffer)
|
||||||
|
#print "%d",_buffer[i:i+4096]
|
0
pwnagotchi/ui/hw/libs/waveshare/oledhat/__init__.py
Normal file
0
pwnagotchi/ui/hw/libs/waveshare/oledhat/__init__.py
Normal file
111
pwnagotchi/ui/hw/libs/waveshare/oledhat/config.py
Normal file
111
pwnagotchi/ui/hw/libs/waveshare/oledhat/config.py
Normal file
@@ -0,0 +1,111 @@
|
|||||||
|
# /*****************************************************************************
|
||||||
|
# * | File : config.py
|
||||||
|
# * | Author : Waveshare team
|
||||||
|
# * | Function : Hardware underlying interface,for Jetson nano
|
||||||
|
# * | Info :
|
||||||
|
# *----------------
|
||||||
|
# * | This version: V1.0
|
||||||
|
# * | Date : 2019-06-06
|
||||||
|
# * | 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 RPi.GPIO as GPIO
|
||||||
|
import time
|
||||||
|
from smbus import SMBus
|
||||||
|
import spidev
|
||||||
|
|
||||||
|
import ctypes
|
||||||
|
# import spidev
|
||||||
|
|
||||||
|
# Pin definition
|
||||||
|
RST_PIN = 25
|
||||||
|
DC_PIN = 24
|
||||||
|
CS_PIN = 8
|
||||||
|
BL_PIN = 18
|
||||||
|
BUSY_PIN = 18
|
||||||
|
|
||||||
|
Device_SPI = 1
|
||||||
|
Device_I2C = 0
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
if(Device_SPI == 1):
|
||||||
|
Device = Device_SPI
|
||||||
|
spi = spidev.SpiDev(0, 0)
|
||||||
|
else :
|
||||||
|
Device = Device_I2C
|
||||||
|
address = 0x3C
|
||||||
|
bus = SMBus(1)
|
||||||
|
|
||||||
|
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)
|
||||||
|
GPIO.setup(CS_PIN, GPIO.OUT)
|
||||||
|
GPIO.setup(BL_PIN, GPIO.OUT)
|
||||||
|
|
||||||
|
|
||||||
|
# SPI.max_speed_hz = 2000000
|
||||||
|
# SPI.mode = 0b00
|
||||||
|
# i2c_writebyte(0xff,0xff)
|
||||||
|
if(Device == Device_SPI):
|
||||||
|
# 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(CS_PIN, 0)
|
||||||
|
GPIO.output(BL_PIN, 1)
|
||||||
|
GPIO.output(DC_PIN, 0)
|
||||||
|
return 0
|
||||||
|
|
||||||
|
def module_exit():
|
||||||
|
if(Device == Device_SPI):
|
||||||
|
spi.SYSFS_software_spi_end()
|
||||||
|
else :
|
||||||
|
bus.close()
|
||||||
|
GPIO.output(RST_PIN, 0)
|
||||||
|
GPIO.output(DC_PIN, 0)
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
### END OF FILE ###
|
27
pwnagotchi/ui/hw/libs/waveshare/oledhat/epd.py
Normal file
27
pwnagotchi/ui/hw/libs/waveshare/oledhat/epd.py
Normal file
@@ -0,0 +1,27 @@
|
|||||||
|
from . import SH1106
|
||||||
|
from . import config
|
||||||
|
|
||||||
|
# Display resolution
|
||||||
|
EPD_WIDTH = 64
|
||||||
|
EPD_HEIGHT = 128
|
||||||
|
|
||||||
|
disp = SH1106.SH1106()
|
||||||
|
|
||||||
|
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
|
||||||
|
|
||||||
|
def init(self):
|
||||||
|
disp.Init()
|
||||||
|
|
||||||
|
def Clear(self):
|
||||||
|
disp.clear()
|
||||||
|
|
||||||
|
def display(self, image):
|
||||||
|
disp.ShowImage(disp.getbuffer(image))
|
0
pwnagotchi/ui/hw/libs/waveshare/v1/__init__.py
Normal file
0
pwnagotchi/ui/hw/libs/waveshare/v1/__init__.py
Normal file
@@ -30,7 +30,6 @@
|
|||||||
|
|
||||||
import logging
|
import logging
|
||||||
from . import epdconfig
|
from . import epdconfig
|
||||||
import numpy as np
|
|
||||||
|
|
||||||
# Display resolution
|
# Display resolution
|
||||||
EPD_WIDTH = 122
|
EPD_WIDTH = 122
|
@@ -27,9 +27,7 @@
|
|||||||
# THE SOFTWARE.
|
# THE SOFTWARE.
|
||||||
#
|
#
|
||||||
|
|
||||||
import logging
|
|
||||||
from . import epdconfig
|
from . import epdconfig
|
||||||
from PIL import Image
|
|
||||||
import RPi.GPIO as GPIO
|
import RPi.GPIO as GPIO
|
||||||
# import numpy as np
|
# import numpy as np
|
||||||
|
|
0
pwnagotchi/ui/hw/libs/waveshare/v2/__init__.py
Normal file
0
pwnagotchi/ui/hw/libs/waveshare/v2/__init__.py
Normal file
45
pwnagotchi/ui/hw/oledhat.py
Normal file
45
pwnagotchi/ui/hw/oledhat.py
Normal file
@@ -0,0 +1,45 @@
|
|||||||
|
import logging
|
||||||
|
|
||||||
|
import pwnagotchi.ui.fonts as fonts
|
||||||
|
from pwnagotchi.ui.hw.base import DisplayImpl
|
||||||
|
|
||||||
|
|
||||||
|
class OledHat(DisplayImpl):
|
||||||
|
def __init__(self, config):
|
||||||
|
super(OledHat, self).__init__(config, 'oledhat')
|
||||||
|
self._display = None
|
||||||
|
|
||||||
|
def layout(self):
|
||||||
|
fonts.setup(8, 8, 8, 8)
|
||||||
|
self._layout['width'] = 128
|
||||||
|
self._layout['height'] = 64
|
||||||
|
self._layout['face'] = (0, 32)
|
||||||
|
self._layout['name'] = (0, 10)
|
||||||
|
self._layout['channel'] = (0, 0)
|
||||||
|
self._layout['aps'] = (25, 0)
|
||||||
|
self._layout['uptime'] = (65, 0)
|
||||||
|
self._layout['line1'] = [0, 9, 128, 9]
|
||||||
|
self._layout['line2'] = [0, 53, 128, 53]
|
||||||
|
self._layout['friend_face'] = (0, 41)
|
||||||
|
self._layout['friend_name'] = (40, 43)
|
||||||
|
self._layout['shakes'] = (0, 53)
|
||||||
|
self._layout['mode'] = (103, 10)
|
||||||
|
self._layout['status'] = {
|
||||||
|
'pos': (30, 18),
|
||||||
|
'font': fonts.Small,
|
||||||
|
'max': 18
|
||||||
|
}
|
||||||
|
return self._layout
|
||||||
|
|
||||||
|
def initialize(self):
|
||||||
|
logging.info("initializing oledhat display")
|
||||||
|
from pwnagotchi.ui.hw.libs.waveshare.oledhat.epd import EPD
|
||||||
|
self._display = EPD()
|
||||||
|
self._display.init()
|
||||||
|
self._display.Clear()
|
||||||
|
|
||||||
|
def render(self, canvas):
|
||||||
|
self._display.display(canvas)
|
||||||
|
|
||||||
|
def clear(self):
|
||||||
|
self._display.clear()
|
47
pwnagotchi/ui/hw/papirus.py
Normal file
47
pwnagotchi/ui/hw/papirus.py
Normal file
@@ -0,0 +1,47 @@
|
|||||||
|
import logging
|
||||||
|
import os
|
||||||
|
|
||||||
|
import pwnagotchi.ui.fonts as fonts
|
||||||
|
from pwnagotchi.ui.hw.base import DisplayImpl
|
||||||
|
|
||||||
|
|
||||||
|
class Papirus(DisplayImpl):
|
||||||
|
def __init__(self, config):
|
||||||
|
super(Papirus, self).__init__(config, 'papirus')
|
||||||
|
self._display = None
|
||||||
|
|
||||||
|
def layout(self):
|
||||||
|
fonts.setup(10, 8, 10, 23)
|
||||||
|
self._layout['width'] = 200
|
||||||
|
self._layout['height'] = 96
|
||||||
|
self._layout['face'] = (0, 24)
|
||||||
|
self._layout['name'] = (5, 14)
|
||||||
|
self._layout['channel'] = (0, 0)
|
||||||
|
self._layout['aps'] = (25, 0)
|
||||||
|
self._layout['uptime'] = (135, 0)
|
||||||
|
self._layout['line1'] = [0, 11, 200, 11]
|
||||||
|
self._layout['line2'] = [0, 85, 200, 85]
|
||||||
|
self._layout['friend_face'] = (0, 69)
|
||||||
|
self._layout['friend_name'] = (40, 71)
|
||||||
|
self._layout['shakes'] = (0, 86)
|
||||||
|
self._layout['mode'] = (175, 86)
|
||||||
|
self._layout['status'] = {
|
||||||
|
'pos': (85, 14),
|
||||||
|
'font': fonts.Medium,
|
||||||
|
'max': 16
|
||||||
|
}
|
||||||
|
return self._layout
|
||||||
|
|
||||||
|
def initialize(self):
|
||||||
|
logging.info("initializing papirus display")
|
||||||
|
from pwnagotchi.ui.hw.libs.papirus.epd import EPD
|
||||||
|
os.environ['EPD_SIZE'] = '2.0'
|
||||||
|
self._display = EPD()
|
||||||
|
self._display.clear()
|
||||||
|
|
||||||
|
def render(self, canvas):
|
||||||
|
self._display.display(canvas)
|
||||||
|
self._display.partial_update()
|
||||||
|
|
||||||
|
def clear(self):
|
||||||
|
self._display.clear()
|
86
pwnagotchi/ui/hw/waveshare1.py
Normal file
86
pwnagotchi/ui/hw/waveshare1.py
Normal file
@@ -0,0 +1,86 @@
|
|||||||
|
import logging
|
||||||
|
|
||||||
|
import pwnagotchi.ui.fonts as fonts
|
||||||
|
from pwnagotchi.ui.hw.base import DisplayImpl
|
||||||
|
|
||||||
|
|
||||||
|
class WaveshareV1(DisplayImpl):
|
||||||
|
def __init__(self, config):
|
||||||
|
super(WaveshareV1, self).__init__(config, 'waveshare_1')
|
||||||
|
self._display = None
|
||||||
|
|
||||||
|
def layout(self):
|
||||||
|
if self.config['color'] == 'black':
|
||||||
|
fonts.setup(10, 9, 10, 35)
|
||||||
|
self._layout['width'] = 250
|
||||||
|
self._layout['height'] = 122
|
||||||
|
self._layout['face'] = (0, 40)
|
||||||
|
self._layout['name'] = (5, 20)
|
||||||
|
self._layout['channel'] = (0, 0)
|
||||||
|
self._layout['aps'] = (28, 0)
|
||||||
|
self._layout['uptime'] = (185, 0)
|
||||||
|
self._layout['line1'] = [0, 14, 250, 14]
|
||||||
|
self._layout['line2'] = [0, 108, 250, 108]
|
||||||
|
self._layout['friend_face'] = (0, 92)
|
||||||
|
self._layout['friend_name'] = (40, 94)
|
||||||
|
self._layout['shakes'] = (0, 109)
|
||||||
|
self._layout['mode'] = (225, 109)
|
||||||
|
self._layout['status'] = {
|
||||||
|
'pos': (125, 20),
|
||||||
|
'font': fonts.Medium,
|
||||||
|
'max': 20
|
||||||
|
}
|
||||||
|
else:
|
||||||
|
fonts.setup(10, 8, 10, 25)
|
||||||
|
self._layout['width'] = 212
|
||||||
|
self._layout['height'] = 104
|
||||||
|
self._layout['face'] = (0, 26)
|
||||||
|
self._layout['name'] = (5, 15)
|
||||||
|
self._layout['channel'] = (0, 0)
|
||||||
|
self._layout['aps'] = (28, 0)
|
||||||
|
self._layout['status'] = (91, 15)
|
||||||
|
self._layout['uptime'] = (147, 0)
|
||||||
|
self._layout['line1'] = [0, 12, 212, 12]
|
||||||
|
self._layout['line2'] = [0, 92, 212, 92]
|
||||||
|
self._layout['friend_face'] = (0, 76)
|
||||||
|
self._layout['friend_name'] = (40, 78)
|
||||||
|
self._layout['shakes'] = (0, 93)
|
||||||
|
self._layout['mode'] = (187, 93)
|
||||||
|
self._layout['status'] = {
|
||||||
|
'pos': (125, 20),
|
||||||
|
'font': fonts.Medium,
|
||||||
|
'max': 14
|
||||||
|
}
|
||||||
|
return self._layout
|
||||||
|
|
||||||
|
def initialize(self):
|
||||||
|
if self.config['color'] == 'black':
|
||||||
|
logging.info("initializing waveshare v1 display in monochromatic mode")
|
||||||
|
from pwnagotchi.ui.hw.libs.waveshare.v1.epd2in13 import EPD
|
||||||
|
self._display = EPD()
|
||||||
|
self._display.init(self._display.lut_full_update)
|
||||||
|
self._display.Clear(0xFF)
|
||||||
|
self._display.init(self._display.lut_partial_update)
|
||||||
|
|
||||||
|
else:
|
||||||
|
logging.info("initializing waveshare v1 display 3-color mode")
|
||||||
|
from pwnagotchi.ui.hw.libs.waveshare.v1.epd2in13bc import EPD
|
||||||
|
self._display = EPD()
|
||||||
|
self._display.init()
|
||||||
|
self._display.Clear()
|
||||||
|
|
||||||
|
def render(self, canvas):
|
||||||
|
if self.config['color'] == 'black':
|
||||||
|
buf = self._display.getbuffer(canvas)
|
||||||
|
self._display.display(buf)
|
||||||
|
else:
|
||||||
|
buf_black = self._display.getbuffer(canvas)
|
||||||
|
# emptyImage = Image.new('1', (self._display.height, self._display.width), 255)
|
||||||
|
# buf_color = self._display.getbuffer(emptyImage)
|
||||||
|
# self._display.display(buf_black,buf_color)
|
||||||
|
# Custom display function that only handles black
|
||||||
|
# Was included in epd2in13bc.py
|
||||||
|
self._display.displayBlack(buf_black)
|
||||||
|
|
||||||
|
def clear(self):
|
||||||
|
self._display.Clear(0xff)
|
69
pwnagotchi/ui/hw/waveshare2.py
Normal file
69
pwnagotchi/ui/hw/waveshare2.py
Normal file
@@ -0,0 +1,69 @@
|
|||||||
|
import logging
|
||||||
|
|
||||||
|
import pwnagotchi.ui.fonts as fonts
|
||||||
|
from pwnagotchi.ui.hw.base import DisplayImpl
|
||||||
|
|
||||||
|
|
||||||
|
class WaveshareV2(DisplayImpl):
|
||||||
|
def __init__(self, config):
|
||||||
|
super(WaveshareV2, self).__init__(config, 'waveshare_2')
|
||||||
|
self._display = None
|
||||||
|
|
||||||
|
def layout(self):
|
||||||
|
if self.config['color'] == 'black':
|
||||||
|
fonts.setup(10, 9, 10, 35)
|
||||||
|
self._layout['width'] = 250
|
||||||
|
self._layout['height'] = 122
|
||||||
|
self._layout['face'] = (0, 40)
|
||||||
|
self._layout['name'] = (5, 20)
|
||||||
|
self._layout['channel'] = (0, 0)
|
||||||
|
self._layout['aps'] = (28, 0)
|
||||||
|
self._layout['uptime'] = (185, 0)
|
||||||
|
self._layout['line1'] = [0, 14, 250, 14]
|
||||||
|
self._layout['line2'] = [0, 108, 250, 108]
|
||||||
|
self._layout['friend_face'] = (0, 92)
|
||||||
|
self._layout['friend_name'] = (40, 94)
|
||||||
|
self._layout['shakes'] = (0, 109)
|
||||||
|
self._layout['mode'] = (225, 109)
|
||||||
|
self._layout['status'] = {
|
||||||
|
'pos': (125, 20),
|
||||||
|
'font': fonts.Medium,
|
||||||
|
'max': 20
|
||||||
|
}
|
||||||
|
else:
|
||||||
|
fonts.setup(10, 8, 10, 25)
|
||||||
|
self._layout['width'] = 212
|
||||||
|
self._layout['height'] = 104
|
||||||
|
self._layout['face'] = (0, 26)
|
||||||
|
self._layout['name'] = (5, 15)
|
||||||
|
self._layout['channel'] = (0, 0)
|
||||||
|
self._layout['aps'] = (28, 0)
|
||||||
|
self._layout['status'] = (91, 15)
|
||||||
|
self._layout['uptime'] = (147, 0)
|
||||||
|
self._layout['line1'] = [0, 12, 212, 12]
|
||||||
|
self._layout['line2'] = [0, 92, 212, 92]
|
||||||
|
self._layout['friend_face'] = (0, 76)
|
||||||
|
self._layout['friend_name'] = (40, 78)
|
||||||
|
self._layout['shakes'] = (0, 93)
|
||||||
|
self._layout['mode'] = (187, 93)
|
||||||
|
self._layout['status'] = {
|
||||||
|
'pos': (125, 20),
|
||||||
|
'font': fonts.Medium,
|
||||||
|
'max': 14
|
||||||
|
}
|
||||||
|
return self._layout
|
||||||
|
|
||||||
|
def initialize(self):
|
||||||
|
logging.info("initializing waveshare v2 display")
|
||||||
|
from pwnagotchi.ui.hw.libs.waveshare.v2.waveshare import EPD
|
||||||
|
self._display = EPD()
|
||||||
|
self._display.init(self._display.FULL_UPDATE)
|
||||||
|
self._display.Clear(0xff)
|
||||||
|
self._display.init(self._display.PART_UPDATE)
|
||||||
|
|
||||||
|
def render(self, canvas):
|
||||||
|
buf = self._display.getbuffer(canvas)
|
||||||
|
self._display.displayPartial(buf)
|
||||||
|
|
||||||
|
def clear(self):
|
||||||
|
self._display.Clear(0xff)
|
@@ -18,64 +18,8 @@ BLACK = 0x00
|
|||||||
ROOT = None
|
ROOT = None
|
||||||
|
|
||||||
|
|
||||||
def setup_display_specifics(config):
|
|
||||||
width = 0
|
|
||||||
height = 0
|
|
||||||
face_pos = (0, 0)
|
|
||||||
name_pos = (0, 0)
|
|
||||||
status_pos = (0, 0)
|
|
||||||
status_font = fonts.Medium
|
|
||||||
status_max_length = None
|
|
||||||
|
|
||||||
if config['ui']['display']['type'] in ('inky', 'inkyphat'):
|
|
||||||
fonts.setup(10, 8, 10, 28)
|
|
||||||
|
|
||||||
width = 212
|
|
||||||
height = 104
|
|
||||||
face_pos = (0, 37)
|
|
||||||
name_pos = (5, 18)
|
|
||||||
status_pos = (102, 18)
|
|
||||||
status_font = fonts.Small
|
|
||||||
status_max_length = 20
|
|
||||||
|
|
||||||
elif config['ui']['display']['type'] in ('papirus', 'papi'):
|
|
||||||
fonts.setup(10, 8, 10, 23)
|
|
||||||
|
|
||||||
width = 200
|
|
||||||
height = 96
|
|
||||||
face_pos = (0, int(height / 4))
|
|
||||||
name_pos = (5, int(height * .15))
|
|
||||||
status_pos = (int(width / 2) - 15, int(height * .15))
|
|
||||||
status_font = fonts.Medium
|
|
||||||
status_max_length = (width - status_pos[0]) // 6
|
|
||||||
|
|
||||||
elif config['ui']['display']['type'] in ('ws_1', 'ws1', 'waveshare_1', 'waveshare1',
|
|
||||||
'ws_2', 'ws2', 'waveshare_2', 'waveshare2'):
|
|
||||||
if config['ui']['display']['color'] == 'black':
|
|
||||||
fonts.setup(10, 9, 10, 35)
|
|
||||||
|
|
||||||
width = 250
|
|
||||||
height = 122
|
|
||||||
face_pos = (0, 40)
|
|
||||||
name_pos = (5, 20)
|
|
||||||
status_pos = (125, 20)
|
|
||||||
status_font = fonts.Medium
|
|
||||||
else:
|
|
||||||
fonts.setup(10, 8, 10, 25)
|
|
||||||
|
|
||||||
width = 212
|
|
||||||
height = 104
|
|
||||||
face_pos = (0, int(height / 4))
|
|
||||||
name_pos = (5, int(height * .15))
|
|
||||||
status_pos = (int(width / 2) - 15, int(height * .15))
|
|
||||||
status_font = fonts.Medium
|
|
||||||
status_max_length = (width - status_pos[0]) // 6
|
|
||||||
|
|
||||||
return width, height, face_pos, name_pos, status_pos, status_font, status_max_length
|
|
||||||
|
|
||||||
|
|
||||||
class View(object):
|
class View(object):
|
||||||
def __init__(self, config, state={}):
|
def __init__(self, config, impl, state=None):
|
||||||
global ROOT
|
global ROOT
|
||||||
|
|
||||||
self._render_cbs = []
|
self._render_cbs = []
|
||||||
@@ -84,51 +28,49 @@ class View(object):
|
|||||||
self._frozen = False
|
self._frozen = False
|
||||||
self._lock = Lock()
|
self._lock = Lock()
|
||||||
self._voice = Voice(lang=config['main']['lang'])
|
self._voice = Voice(lang=config['main']['lang'])
|
||||||
|
self._implementation = impl
|
||||||
self._width, self._height, \
|
self._layout = impl.layout()
|
||||||
face_pos, name_pos, status_pos, status_font, status_max_length = setup_display_specifics(config)
|
self._width = self._layout['width']
|
||||||
|
self._height = self._layout['height']
|
||||||
self._state = State(state={
|
self._state = State(state={
|
||||||
'channel': LabeledValue(color=BLACK, label='CH', value='00', position=(0, 0), label_font=fonts.Bold,
|
'channel': LabeledValue(color=BLACK, label='CH', value='00', position=self._layout['channel'],
|
||||||
|
label_font=fonts.Bold,
|
||||||
text_font=fonts.Medium),
|
text_font=fonts.Medium),
|
||||||
'aps': LabeledValue(color=BLACK, label='APS', value='0 (00)', position=(30, 0), label_font=fonts.Bold,
|
'aps': LabeledValue(color=BLACK, label='APS', value='0 (00)', position=self._layout['aps'],
|
||||||
text_font=fonts.Medium),
|
|
||||||
|
|
||||||
# 'epoch': LabeledValue(color=BLACK, label='E', value='0000', position=(145, 0), label_font=fonts.Bold,
|
|
||||||
# text_font=fonts.Medium),
|
|
||||||
|
|
||||||
'uptime': LabeledValue(color=BLACK, label='UP', value='00:00:00', position=(self._width - 65, 0),
|
|
||||||
label_font=fonts.Bold,
|
label_font=fonts.Bold,
|
||||||
text_font=fonts.Medium),
|
text_font=fonts.Medium),
|
||||||
|
|
||||||
'line1': Line([0, int(self._height * .12), self._width, int(self._height * .12)], color=BLACK),
|
'uptime': LabeledValue(color=BLACK, label='UP', value='00:00:00', position=self._layout['uptime'],
|
||||||
'line2': Line(
|
label_font=fonts.Bold,
|
||||||
[0, self._height - int(self._height * .12), self._width, self._height - int(self._height * .12)],
|
text_font=fonts.Medium),
|
||||||
|
|
||||||
|
'line1': Line(self._layout['line1'], color=BLACK),
|
||||||
|
'line2': Line(self._layout['line2'], color=BLACK),
|
||||||
|
|
||||||
|
'face': Text(value=faces.SLEEP, position=self._layout['face'], color=BLACK, font=fonts.Huge),
|
||||||
|
|
||||||
|
'friend_face': Text(value=None, position=self._layout['friend_face'], font=fonts.Bold, color=BLACK),
|
||||||
|
'friend_name': Text(value=None, position=self._layout['friend_name'], font=fonts.BoldSmall,
|
||||||
color=BLACK),
|
color=BLACK),
|
||||||
|
|
||||||
'face': Text(value=faces.SLEEP, position=face_pos, color=BLACK, font=fonts.Huge),
|
'name': Text(value='%s>' % 'pwnagotchi', position=self._layout['name'], color=BLACK, font=fonts.Bold),
|
||||||
|
|
||||||
'friend_face': Text(value=None, position=(0, (self._height * 0.88) - 15), font=fonts.Bold, color=BLACK),
|
|
||||||
'friend_name': Text(value=None, position=(40, (self._height * 0.88) - 13), font=fonts.BoldSmall,
|
|
||||||
color=BLACK),
|
|
||||||
|
|
||||||
'name': Text(value='%s>' % 'pwnagotchi', position=name_pos, color=BLACK, font=fonts.Bold),
|
|
||||||
|
|
||||||
'status': Text(value=self._voice.default(),
|
'status': Text(value=self._voice.default(),
|
||||||
position=status_pos,
|
position=self._layout['status']['pos'],
|
||||||
color=BLACK,
|
color=BLACK,
|
||||||
font=status_font,
|
font=self._layout['status']['font'],
|
||||||
wrap=True,
|
wrap=True,
|
||||||
# the current maximum number of characters per line, assuming each character is 6 pixels wide
|
# the current maximum number of characters per line, assuming each character is 6 pixels wide
|
||||||
max_length=status_max_length),
|
max_length=self._layout['status']['max']),
|
||||||
|
|
||||||
'shakes': LabeledValue(label='PWND ', value='0 (00)', color=BLACK,
|
'shakes': LabeledValue(label='PWND ', value='0 (00)', color=BLACK,
|
||||||
position=(0, self._height - int(self._height * .12) + 1), label_font=fonts.Bold,
|
position=self._layout['shakes'], label_font=fonts.Bold,
|
||||||
text_font=fonts.Medium),
|
text_font=fonts.Medium),
|
||||||
'mode': Text(value='AUTO', position=(self._width - 25, self._height - int(self._height * .12) + 1),
|
'mode': Text(value='AUTO', position=self._layout['mode'],
|
||||||
font=fonts.Bold, color=BLACK),
|
font=fonts.Bold, color=BLACK),
|
||||||
})
|
})
|
||||||
|
|
||||||
|
if state:
|
||||||
for key, value in state.items():
|
for key, value in state.items():
|
||||||
self._state.set(key, value)
|
self._state.set(key, value)
|
||||||
|
|
||||||
|
@@ -56,6 +56,28 @@ def load_config(args):
|
|||||||
if user_config:
|
if user_config:
|
||||||
config = merge_config(user_config, config)
|
config = merge_config(user_config, config)
|
||||||
|
|
||||||
|
# the very first step is to normalize the display name so we don't need dozens of if/elif around
|
||||||
|
if config['ui']['display']['type'] in ('inky', 'inkyphat'):
|
||||||
|
config['ui']['display']['type'] = 'inky'
|
||||||
|
|
||||||
|
elif config['ui']['display']['type'] in ('papirus', 'papi'):
|
||||||
|
config['ui']['display']['type'] = 'papirus'
|
||||||
|
|
||||||
|
elif config['ui']['display']['type'] in ('oledhat'):
|
||||||
|
config['ui']['display']['type'] = 'oledhat'
|
||||||
|
|
||||||
|
elif config['ui']['display']['type'] in ('ws_1', 'ws1', 'waveshare_1', 'waveshare1'):
|
||||||
|
config['ui']['display']['type'] = 'waveshare_1'
|
||||||
|
|
||||||
|
elif config['ui']['display']['type'] in ('ws_2', 'ws2', 'waveshare_2', 'waveshare2'):
|
||||||
|
config['ui']['display']['type'] = 'waveshare_2'
|
||||||
|
|
||||||
|
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
|
return config
|
||||||
|
|
||||||
|
|
||||||
|
@@ -14,6 +14,9 @@ class Voice:
|
|||||||
translation.install()
|
translation.install()
|
||||||
self._ = translation.gettext
|
self._ = translation.gettext
|
||||||
|
|
||||||
|
def custom(self, s):
|
||||||
|
return s
|
||||||
|
|
||||||
def default(self):
|
def default(self):
|
||||||
return self._('ZzzzZZzzzzZzzz')
|
return self._('ZzzzZZzzzzZzzz')
|
||||||
|
|
||||||
@@ -68,8 +71,8 @@ class Voice:
|
|||||||
|
|
||||||
def on_new_peer(self, peer):
|
def on_new_peer(self, peer):
|
||||||
return random.choice([
|
return random.choice([
|
||||||
self._('Hello {name}! Nice to meet you. {name}').format(name=peer.name()),
|
self._('Hello {name}! Nice to meet you.').format(name=peer.name()),
|
||||||
self._('Unit {name} is nearby! {name}').format(name=peer.name())])
|
self._('Unit {name} is nearby!').format(name=peer.name())])
|
||||||
|
|
||||||
def on_lost_peer(self, peer):
|
def on_lost_peer(self, peer):
|
||||||
return random.choice([
|
return random.choice([
|
||||||
|
@@ -4,6 +4,9 @@
|
|||||||
|
|
||||||
set -eu
|
set -eu
|
||||||
|
|
||||||
|
echo "THIS SCRIPT IS DEPRECATED, PLEASE REFER TO THE OFFICIAL DOCUMENTATION AT https://pwnagotchi.ai/contributing/#creating-an-image"
|
||||||
|
exit 1
|
||||||
|
|
||||||
REQUIREMENTS=( wget gunzip git dd e2fsck resize2fs parted losetup qemu-system-x86_64 )
|
REQUIREMENTS=( wget gunzip git dd e2fsck resize2fs parted losetup qemu-system-x86_64 )
|
||||||
DEBREQUIREMENTS=( wget gzip git parted qemu-system-x86 qemu-user-static )
|
DEBREQUIREMENTS=( wget gzip git parted qemu-system-x86 qemu-user-static )
|
||||||
REPO_DIR="$(dirname "$(dirname "$(realpath "$0")")")"
|
REPO_DIR="$(dirname "$(dirname "$(realpath "$0")")")"
|
||||||
|
Reference in New Issue
Block a user