45 Commits

Author SHA1 Message Date
Simone Margaritelli
a3cf49244e Merge pull request #855 from dadav/develop
Bug fixes
2020-04-20 11:13:25 +02:00
dadav
e92751164f typo 2020-04-19 18:49:42 +02:00
dadav
c2f9860bc5 same as in the py file 2020-04-19 16:48:11 +02:00
dadav
f616871068 fix syntax 2020-04-19 16:46:34 +02:00
dadav
c726779f6e only go back 5 mins 2020-04-19 16:36:26 +02:00
dadav
a2e29d64d2 dont overwrite zips 2020-04-19 11:39:49 +02:00
dadav
bc84f22f7a fix ident 2020-04-19 11:37:48 +02:00
dadav
40d8d994d1 version++ 2020-04-19 11:34:39 +02:00
dadav
7ca5eee247 add custom repos 2020-04-19 11:26:18 +02:00
dadav
56c291d302 change treshhold to 5 2020-04-18 18:19:16 +02:00
dadav
1013e7dc19 make less verbose 2020-04-18 17:09:39 +02:00
dadav
ff4f5c672a move code out of ui update hook 2020-04-18 16:46:29 +02:00
dadav
d9d268ea81 fix bug 2020-04-18 13:53:42 +02:00
dadav
de62214970 add missing option 2020-04-18 13:30:43 +02:00
dadav
52cc413152 add check 2020-04-18 13:23:12 +02:00
dadav
716d5cd312 typo 2020-04-18 13:17:18 +02:00
dadav
e436dc8b8e fix 2020-04-18 13:02:42 +02:00
dadav
81db495f33 redirect to dashboard 2020-04-18 12:47:09 +02:00
dadav
7f8380c38a switch to static 2020-04-18 12:26:21 +02:00
Ferny G
8c2b4e2075 DFRobot V2 screen 2020-04-18 11:40:43 +02:00
dadav
2b17e5322b only kill if present 2020-04-18 11:29:54 +02:00
dadav
1be17b1f99 reboot if reload fails 2020-04-18 11:17:46 +02:00
dadav
2dee3987d4 add bash completion 2020-04-18 10:59:08 +02:00
dadav
eb76cc7023 fix location 2020-04-18 10:22:18 +02:00
dadav
35ea36ef33 auto redirect when decrypted 2020-04-18 10:09:20 +02:00
dadav
44e1e79245 scorp fix 2020-04-18 09:50:40 +02:00
dadav
6038f555fa fix 2020-04-18 00:02:25 +02:00
dadav
0b5a63a3d8 add missing var 2020-04-17 22:31:30 +02:00
dadav
430172e3dd remove button 2020-04-17 20:55:11 +02:00
dadav
fa87e03222 add max-lines 2020-04-17 20:18:12 +02:00
dadav
d1411ffa96 sort plugins 2020-04-17 16:45:04 +02:00
dadav
67b4747afa adjust cmd 2020-04-17 16:39:31 +02:00
dadav
311931c81d added watchdog 2020-04-17 16:32:24 +02:00
Simone Margaritelli
1f7bc60de6 Merge pull request #848 from Czechball/master
added Czech translation
2020-04-16 13:01:17 +02:00
Simone Margaritelli
57034d9fc6 Merge pull request #852 from dadav/develop
Various fixes
2020-04-16 13:01:08 +02:00
dadav
74fbf4da32 version++ 2020-04-16 10:31:59 +02:00
dadav
7ec20caf23 fix filter bug 2020-04-15 17:49:57 +02:00
dadav
568c5b020d aaaannnd even better 2020-04-15 17:19:41 +02:00
dadav
3965bdb554 performs better without it 2020-04-15 17:02:50 +02:00
dadav
585b208e9e support multiple passwords 2020-04-15 16:51:43 +02:00
dadav
e53bdc46a4 there is no logging to journald anymore 2020-04-15 13:19:01 +02:00
dadav
6805df858e basename should be dirname 2020-04-15 08:20:47 +02:00
dadav
8a07e822e6 breaks if never used 2020-04-14 22:20:42 +02:00
dadav
a808fd33c7 another toml fix in paradise 2020-04-14 21:53:17 +02:00
Czechball
68065d548a added Czech translation 2020-04-14 06:17:57 +02:00
58 changed files with 2174 additions and 161 deletions

View File

@@ -0,0 +1,29 @@
_show_complete()
{
local cur opts node_names all_options opt_line
all_options="
pwnagotchi -h --help -C --config -U --user-config --manual --skip-session --clear --debug --version --print-config {plugins}
pwnagotchi plugins -h --help {list,install,enable,disable,uninstall,update,upgrade}
pwnagotchi plugins list -i --installed -h --help
pwnagotchi plugins install -h --help
pwnagotchi plugins uninstall -h --help
pwnagotchi plugins enable -h --help
pwnagotchi plugins disable -h --help
pwnagotchi plugins update -h --help
pwnagotchi plugins upgrade -h --help
"
COMPREPLY=()
cur="${COMP_WORDS[COMP_CWORD]}"
cmd="${COMP_WORDS[@]:0:${#COMP_WORDS[@]}-1}"
opt_line="$(grep -m1 "$cmd" <<<$all_options)"
if [[ ${cur} == -* ]] ; then
opts="$(echo $opt_line | tr ' ' '\n' | awk '/^ *-/{gsub("[^a-zA-Z0-9-]","",$1);print $1}')"
COMPREPLY=( $(compgen -W "${opts}" -- ${cur}) )
return 0
fi
opts="$(echo $opt_line | grep -Po '{\K[^}]+' | tr ',' '\n')"
COMPREPLY=( $(compgen -W "${opts}" -- ${cur}) )
}
complete -F _show_complete pwnagotchi

View File

@@ -9,6 +9,15 @@ if is_crypted_mode; then
done
fi
# check if wifi driver is bugged
if ! check_brcm; then
if ! reload_brcm; then
echo "Could not reload wifi driver. Reboot"
reboot
fi
sleep 10
fi
# start mon0
start_monitor_interface

View File

@@ -1,35 +1,36 @@
#!/usr/bin/env python3
from http.server import HTTPServer, BaseHTTPRequestHandler
from urllib.parse import parse_qsl
HTML_FORM = """
_HTML_FORM_TEMPLATE = """
<!DOCTYPE html>
<html>
<head>
<title>Decryption</title>
<style>
body { text-align: center; padding: 150px; }
h1 { font-size: 50px; }
body { font: 20px Helvetica, sans-serif; color: #333; }
article { display: block; text-align: center; width: 650px; margin: 0 auto;}
input {
body {{ text-align: center; padding: 150px; }}
h1 {{ font-size: 50px; }}
body {{ font: 20px Helvetica, sans-serif; color: #333; }}
article {{ display: block; text-align: center; width: 650px; margin: 0 auto;}}
input {{
padding: 12px 20px;
margin: 8px 0;
box-sizing: border-box;
border: 1px solid #ccc;
}
input[type=password] {
}}
input[type=password] {{
width: 75%;
font-size: 24px;
}
input[type=submit] {
}}
input[type=submit] {{
cursor: pointer;
width: 75%;
}
input[type=submit]:hover {
}}
input[type=submit]:hover {{
background-color: #d9d9d9;
}
}}
</style>
</head>
<body>
@@ -39,7 +40,7 @@ HTML_FORM = """
<p>Please provide the decryption password.</p>
<div>
<form action="/set-password" method="POST">
<input type="password" id="password" name="password" value=""><br>
{password_fields}
<input type="submit" value="Submit">
</form>
</div>
@@ -48,6 +49,76 @@ HTML_FORM = """
</html>
"""
POST_RESPONSE = """
<!DOCTYPE html>
<html>
<head>
<meta name="viewport" content="width=device-width, initial-scale=1">
<style>
/* Center the loader */
#loader {
position: absolute;
left: 50%;
top: 50%;
z-index: 1;
width: 150px;
height: 150px;
margin: -75px 0 0 -75px;
border: 16px solid #f3f3f3;
border-radius: 50%;
border-top: 16px solid #3498db;
width: 120px;
height: 120px;
-webkit-animation: spin 2s linear infinite;
animation: spin 2s linear infinite;
}
@-webkit-keyframes spin {
0% { -webkit-transform: rotate(0deg); }
100% { -webkit-transform: rotate(360deg); }
}
@keyframes spin {
0% { transform: rotate(0deg); }
100% { transform: rotate(360deg); }
}
#myDiv {
display: none;
text-align: center;
}
</style>
<script type="text/javascript">
function checkPwnagotchi() {
var target = 'http://' + document.location.hostname + ':8080/';
var xhr = new XMLHttpRequest();
xhr.open('GET', target);
xhr.onreadystatechange = function () {
if (xhr.readyState == 4) {
if (xhr.status == 200 || xhr.status == 401) {
window.location.replace(target);
}else{
setTimeout(checkPwnagotchi, 1000);
}
}
};
xhr.send();
}
setTimeout(checkPwnagotchi, 1000);
</script>
</head>
<body style="margin:0;">
<div id="loader"></div>
</body>
</html>
"""
HTML_FORM = None
class SimpleHTTPRequestHandler(BaseHTTPRequestHandler):
@@ -59,13 +130,19 @@ class SimpleHTTPRequestHandler(BaseHTTPRequestHandler):
def do_POST(self):
content_length = int(self.headers['Content-Length'])
body = self.rfile.read(content_length)
for mapping, password in parse_qsl(body.decode('UTF-8')):
with open('/tmp/.pwnagotchi-secret-{}'.format(mapping), 'wt') as pwfile:
pwfile.write(password)
self.send_response(200)
self.end_headers()
password = body.decode('UTF-8').split('=')[1]
self.wfile.write(POST_RESPONSE.encode())
with open('/tmp/.pwnagotchi-secret', 'wt') as pwfile:
pwfile.write(password)
with open('/root/.pwnagotchi-crypted') as crypted_file:
mappings = [line.split()[0] for line in crypted_file.readlines()]
fields = ''.join(['<label for="{m}">Passphrase for {m}:</label>\n<input type="password" id="{m}" name="{m}" value=""><br>'.format(m=m)
for m in mappings])
HTML_FORM = _HTML_FORM_TEMPLATE.format(password_fields=fields)
httpd = HTTPServer(('0.0.0.0', 80), SimpleHTTPRequestHandler)
httpd.serve_forever()

View File

@@ -12,9 +12,28 @@ blink_led() {
sleep 0.3
}
# check if brcm is stuck
check_brcm() {
if [[ "$(journalctl -n10 -k --since -5m | grep -c 'brcmf_cfg80211_nexmon_set_channel.*Set Channel failed')" -ge 5 ]]; then
return 1
fi
return 0
}
# reload mod
reload_brcm() {
if ! modprobe -r brcmfmac; then
return 1
fi
if ! modprobe brcmfmac; then
return 1
fi
return 0
}
# starts mon0
start_monitor_interface() {
iw phy phy0 interface add mon0 type monitor && ifconfig mon0 up
iw phy "$(iw phy | head -1 | cut -d" " -f2)" interface add mon0 type monitor && ifconfig mon0 up
}
# stops mon0
@@ -103,12 +122,11 @@ is_decrypted() {
# fail if not mounted
if ! mountpoint -q "$mount" >/dev/null 2>&1; then
if [ -f /tmp/.pwnagotchi-secret ]; then
</tmp/.pwnagotchi-secret read -r SECRET
if [ -f /tmp/.pwnagotchi-secret-"$mapping" ]; then
</tmp/.pwnagotchi-secret-"$mapping" read -r SECRET
if ! test -b /dev/disk/by-id/dm-uuid-*"$(cryptsetup luksUUID "$container" | tr -d -)"*; then
if echo -n "$SECRET" | cryptsetup luksOpen -d- "$container" "$mapping" >/dev/null 2>&1; then
echo "Container decrypted!"
fi
fi
@@ -151,13 +169,16 @@ EOF
fi
done </root/.pwnagotchi-crypted
# overwrite password
>/tmp/.pwnagotchi-secret python3 -c 'print("A"*4096)'
# overwrite passwords
python3 -c 'print("A"*4096)' | tee /tmp/.pwnagotchi-secret-* >/dev/null
# delete
rm /tmp/.pwnagotchi-secret-*
sync # flush
pkill wpa_supplicant
pkill dnsmasq
kill "$(pgrep -f "decryption-webserver")"
pid="$(pgrep -f "decryption-webserver")"
[[ -n "$pid" ]] && kill "$pid"
return 0
}

View File

@@ -311,21 +311,17 @@
- name: check if user configuration exists
stat:
path: /etc/pwnagotchi/config.yml
path: /etc/pwnagotchi/config.toml
register: user_config
- name: create /etc/pwnagotchi/config.yml
- name: create /etc/pwnagotchi/config.toml
copy:
dest: /etc/pwnagotchi/config.yml
dest: /etc/pwnagotchi/config.toml
content: |
# Add your configuration overrides on this file any configuration changes done to default.yml will be lost!
# Add your configuration overrides on this file any configuration changes done to default.toml will be lost!
# Example:
#
# ui:
# display:
# type: 'inkyphat'
# color: 'black'
#
# ui.display.enabled = true
# ui.display.type = "waveshare_2"
when: not user_config.stat.exists
- name: enable ssh on boot
@@ -372,15 +368,15 @@
Hi! I'm a pwnagotchi, please take good care of me!
Here are some basic things you need to know to raise me properly!
If you want to change my configuration, use /etc/pwnagotchi/config.yml
If you want to change my configuration, use /etc/pwnagotchi/config.toml
All the configuration options can be found on /etc/pwnagotchi/default.yml,
All the configuration options can be found on /etc/pwnagotchi/default.toml,
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.
If you want to know what I'm doing, you can check my logs with the command
journalctl -fu pwnagotchi
tail -f /var/log/pwnagotchi.log
If you want to know if I'm running, you can use
systemctl status pwnagotchi

View File

@@ -1 +1 @@
__version__ = '1.5.1'
__version__ = '1.5.3'

View File

@@ -307,7 +307,7 @@ class Agent(Client, Automata, AsyncAdvertiser, AsyncTrainer):
def start_session_fetcher(self):
_thread.start_new_thread(self._fetch_stats, ())
_thread.start_new_thread(self._fetch_stats, ())
def _fetch_stats(self):
@@ -323,7 +323,7 @@ class Agent(Client, Automata, AsyncAdvertiser, AsyncTrainer):
async def _on_event(self, msg):
found_handshake = False
jmsg = json.loads(msg)
if jmsg['tag'] == 'wifi.client.handshake':
filename = jmsg['data']['file']
sta_mac = jmsg['data']['station']
@@ -331,6 +331,7 @@ class Agent(Client, Automata, AsyncAdvertiser, AsyncTrainer):
key = "%s -> %s" % (sta_mac, ap_mac)
if key not in self._handshakes:
self._handshakes[key] = jmsg
s = self.session()
ap_and_station = self._find_ap_sta_in(sta_mac, ap_mac, s)
if ap_and_station is None:
logging.warning("!!! captured new handshake: %s !!!", key)
@@ -364,7 +365,7 @@ class Agent(Client, Automata, AsyncAdvertiser, AsyncTrainer):
def start_event_polling(self):
# start a thread and pass in the mainloop
_thread.start_new_thread(self._event_poller, (asyncio.new_event_loop(),))
_thread.start_new_thread(self._event_poller, (asyncio.get_event_loop(),))
def is_module_running(self, module):

View File

@@ -47,6 +47,8 @@ class Client(object):
logging.debug("Error while parsing event (%s)", ex)
except websockets.exceptions.ConnectionClosedError:
logging.debug("Lost websocket connection. Reconnecting...")
except websockets.exceptions.WebSocketException as wex:
logging.debug("Websocket exception (%s)", wex)
def run(self, command, verbose_errors=True):
r = requests.post("%s/session" % self.url, auth=self.auth, json={'cmd': command})

View File

@@ -2,6 +2,9 @@ main.name = ""
main.lang = "en"
main.confd = "/etc/pwnagotchi/conf.d/"
main.custom_plugins = ""
main.custom_plugin_repos = [
"https://github.com/evilsocket/pwnagotchi-plugins-contrib/archive/master.zip"
]
main.iface = "mon0"
main.mon_start_cmd = "/usr/bin/monstart"
main.mon_stop_cmd = "/usr/bin/monstop"
@@ -110,6 +113,7 @@ main.plugins.led.patterns.peer_detected = "oo oo oo oo oo oo oo"
main.plugins.led.patterns.peer_lost = "oo oo oo oo oo oo oo"
main.plugins.logtail.enabled = false
main.plugins.logtail.max-lines = 10000
main.plugins.session-stats.enabled = true
main.plugins.session-stats.save_directory = "/var/tmp/pwnagotchi/sessions/"

Binary file not shown.

View File

@@ -0,0 +1,249 @@
# pwnigotchi voice data
# Copyright (C) 2020
# This file is distributed under the same license as the pwnagotchi package.
# FIRST AUTHOR czechball@users.noreply.github.com, 2020.
#
#, fuzzy
msgid ""
msgstr ""
"Project-Id-Version: 0.0.1\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2020-04-14 06:15+0100\n"
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
"Last-Translator: Czechball <czechball@users.noreply.github.com>\n"
"Language-Team: pwnagotchi <33197631+dadav@users.noreply.github.com>\n"
"Language: cs\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=utf-8\n"
"Content-Transfer-Encoding: 8bit\n"
msgid "ZzzzZZzzzzZzzz"
msgstr "ZzzzZZzzzzZzzz"
msgid "Hi, I'm Pwnagotchi! Starting ..."
msgstr "Ahoj, já jsem Pwnagotchi! Startuju ..."
msgid "New day, new hunt, new pwns!"
msgstr "Nový den, nový lov, nové úlovky!"
msgid "Hack the Planet!"
msgstr "Hackni celou planetu!"
msgid "AI ready."
msgstr "AI připraveno."
msgid "The neural network is ready."
msgstr "Neuronová síť je připravena."
msgid "Generating keys, do not turn off ..."
msgstr "Generování klíčů, nevypínej mě..."
#, python-brace-format
msgid "Hey, channel {channel} is free! Your AP will say thanks."
msgstr "Hej, kanál {channel} je volný! Tvůj AP ti poděkuje."
msgid "Reading last session logs ..."
msgstr "Čtení posledních zpráv z logu ..."
#, python-brace-format
msgid "Read {lines_so_far} log lines so far ..."
msgstr "Zatím přečteno {lines_so_far} řádků logu ..."
msgid "I'm bored ..."
msgstr "Nudím se ..."
msgid "Let's go for a walk!"
msgstr "Pojďme se projít!"
msgid "This is the best day of my life!"
msgstr "Tohle je nejlepší den mého života!"
msgid "Shitty day :/"
msgstr "Na hovno den :/"
msgid "I'm extremely bored ..."
msgstr "Strašně se nudím ..."
msgid "I'm very sad ..."
msgstr "Jsem dost smutný ..."
msgid "I'm sad"
msgstr "Jsem smutný"
msgid "Leave me alone ..."
msgstr "Nech mě být ..."
msgid "I'm mad at you!"
msgstr "Jsem na tebe naštvaný!"
msgid "I'm living the life!"
msgstr "Tohle je život!"
msgid "I pwn therefore I am."
msgstr "Chytám pakety a tedy jsem."
msgid "So many networks!!!"
msgstr "Tolik sítí!!!"
msgid "I'm having so much fun!"
msgstr "Tohle je super zábava!"
msgid "My crime is that of curiosity ..."
msgstr "Jsem kriminálně zvědavý ..."
#, python-brace-format
msgid "Hello {name}! Nice to meet you."
msgstr "Ahoj {name}! Rád tě poznávám."
#, python-brace-format
msgid "Yo {name}! Sup?"
msgstr "Hej {name}! Jak to jde?"
#, python-brace-format
msgid "Hey {name} how are you doing?"
msgstr "Ahoj {name}, jak se máš?"
#, python-brace-format
msgid "Unit {name} is nearby!"
msgstr "Jednotka {name} je nablízku!"
#, python-brace-format
msgid "Uhm ... goodbye {name}"
msgstr "Uhm... Měj se {name}"
#, python-brace-format
msgid "{name} is gone ..."
msgstr "{name} je pryč ..."
#, python-brace-format
msgid "Whoops ... {name} is gone."
msgstr "Whoops ... {name} je pryč."
#, python-brace-format
msgid "{name} missed!"
msgstr "Chybí mi {name}!"
msgid "Missed!"
msgstr "Chybíš mi!"
msgid "Good friends are a blessing!"
msgstr "Dobří kamarádi jsou požehnání!"
msgid "I love my friends!"
msgstr "Miluju svoje kamarády!"
msgid "Nobody wants to play with me ..."
msgstr "Nikdo si se mnou nechce hrát ..."
msgid "I feel so alone ..."
msgstr "Cítím se tak osamělý ..."
msgid "Where's everybody?!"
msgstr "Kde jsou všichni?!"
#, python-brace-format
msgid "Napping for {secs}s ..."
msgstr "Spím {secs} sekund ..."
msgid "Zzzzz"
msgstr "Zzzzz"
#, python-brace-format
msgid "ZzzZzzz ({secs}s)"
msgstr "ZzzZzzz ({secs}s)"
msgid "Good night."
msgstr "Dobrou noc."
msgid "Zzz"
msgstr "Zzz"
#, python-brace-format
msgid "Waiting for {secs}s ..."
msgstr "Čekání {secs} sekund ..."
#, python-brace-format
msgid "Looking around ({secs}s)"
msgstr "Rozhlížím se ({secs}s)"
#, python-brace-format
msgid "Hey {what} let's be friends!"
msgstr "Hej {what} budeme kamarádi!"
#, python-brace-format
msgid "Associating to {what}"
msgstr "Asociuju se s {what}"
#, python-brace-format
msgid "Yo {what}!"
msgstr "Čus {what}!"
#, python-brace-format
msgid "Just decided that {mac} needs no WiFi!"
msgstr "Rozhodl jsem se, že {mac} nepotřebuje žádnou WiFi!"
#, python-brace-format
msgid "Deauthenticating {mac}"
msgstr "Deautentikuju {mac}"
#, python-brace-format
msgid "Kickbanning {mac}!"
msgstr "Kickbanuju {mac}!"
#, python-brace-format
msgid "Cool, we got {num} new handshake{plural}!"
msgstr "Super, máme {num} nových handshaků!"
#, python-brace-format
msgid "You have {count} new message{plural}!"
msgstr "Máš {count} nových zpráv!"
msgid "Oops, something went wrong ... Rebooting ..."
msgstr "Ups, něco se pokazilo ... Restartuju ..."
#, python-brace-format
msgid "Kicked {num} stations\n"
msgstr "Vykopnuto {num} klientů\n"
#, python-brace-format
msgid "Made {num} new friends\n"
msgstr "Mám {num} nových kamarádů\n"
#, python-brace-format
msgid "Got {num} handshakes\n"
msgstr "Mám {num} handshaků\n"
msgid "Met 1 peer"
msgstr "Potkal jsem jednoho kámoše"
#, python-brace-format
msgid "Met {num} peers"
msgstr "Potkal jsem {num} kámošů"
#, 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 ""
"Chytal jsem pakety po dobu {duration} a vykopnul jsem {deauthed} klientů! Taky jsem potkal "
"{associated} nových kamarádů a snědl {handshakes} handshaků! #pwnagotchi "
"#pwnlog #pwnlife #hacktheplanet #skynet"
msgid "hours"
msgstr "hodiny"
msgid "minutes"
msgstr "minuty"
msgid "seconds"
msgstr "sekundy"
msgid "hour"
msgstr "hodina"
msgid "minute"
msgstr "minuta"
msgid "second"
msgstr "sekunda"

View File

@@ -44,6 +44,8 @@ def toggle_plugin(name, enable=True):
global loaded, database
if pwnagotchi.config:
if not name in pwnagotchi.config['main']['plugins']:
pwnagotchi.config['main']['plugins'][name] = dict()
pwnagotchi.config['main']['plugins'][name]['enabled'] = enable
save_config(pwnagotchi.config, '/etc/pwnagotchi/config.toml')
@@ -56,6 +58,8 @@ def toggle_plugin(name, enable=True):
if enable and name in database and name not in loaded:
load_from_file(database[name])
if name in loaded and pwnagotchi.config and name in pwnagotchi.config['main']['plugins']:
loaded[name].options = pwnagotchi.config['main']['plugins'][name]
one(name, 'loaded')
if pwnagotchi.config:
one(name, 'config_changed', pwnagotchi.config)

View File

@@ -1,6 +1,5 @@
# Handles the commandline stuff
import sys
import os
import logging
import glob
@@ -11,7 +10,6 @@ from pwnagotchi.utils import download_file, unzip, save_config, parse_version, m
from pwnagotchi.plugins import default_path
REPO_URL = 'https://github.com/evilsocket/pwnagotchi-plugins-contrib/archive/master.zip'
SAVE_DIR = '/usr/local/share/pwnagotchi/availaible-plugins/'
DEFAULT_INSTALL_PATH = '/usr/local/share/pwnagotchi/installed-plugins/'
@@ -75,7 +73,7 @@ def handle_cmd(args, config):
Parses the arguments and does the thing the user wants
"""
if args.plugincmd == 'update':
return update()
return update(config)
elif args.plugincmd == 'search':
args.installed = True # also search in installed plugins
return list_plugins(args, config, args.pattern)
@@ -349,41 +347,48 @@ def _analyse_dir(path):
return results
def update():
def update(config):
"""
Updates the database
"""
global REPO_URL, SAVE_DIR
global SAVE_DIR
DEST = os.path.join(SAVE_DIR, 'plugins.zip')
logging.info('Downloading plugins to %s', DEST)
try:
os.makedirs(SAVE_DIR, exist_ok=True)
before_update = _analyse_dir(SAVE_DIR)
download_file(REPO_URL, os.path.join(SAVE_DIR, DEST))
logging.info('Unzipping...')
unzip(DEST, SAVE_DIR, strip_dirs=1)
after_update = _analyse_dir(SAVE_DIR)
b_len = len(before_update)
a_len = len(after_update)
if a_len > b_len:
logging.info('Found %d new file(s).', a_len - b_len)
changed = 0
for filename, filehash in after_update.items():
if filename in before_update and filehash != before_update[filename]:
changed += 1
if changed:
logging.info('%d file(s) were changed.', changed)
return 0
except Exception as ex:
logging.error('Error while updating plugins %s', ex)
urls = config['main']['custom_plugin_repos']
if not urls:
logging.info('No plugin repositories configured.')
return 1
rc = 0
for idx, REPO_URL in enumerate(urls):
DEST = os.path.join(SAVE_DIR, 'plugins%d.zip' % idx)
logging.info('Downloading plugins from %s to %s', REPO_URL, DEST)
try:
os.makedirs(SAVE_DIR, exist_ok=True)
before_update = _analyse_dir(SAVE_DIR)
download_file(REPO_URL, os.path.join(SAVE_DIR, DEST))
logging.info('Unzipping...')
unzip(DEST, SAVE_DIR, strip_dirs=1)
after_update = _analyse_dir(SAVE_DIR)
b_len = len(before_update)
a_len = len(after_update)
if a_len > b_len:
logging.info('Found %d new file(s).', a_len - b_len)
changed = 0
for filename, filehash in after_update.items():
if filename in before_update and filehash != before_update[filename]:
changed += 1
if changed:
logging.info('%d file(s) were changed.', changed)
except Exception as ex:
logging.error('Error while updating plugins: %s', ex)
rc = 1
return rc

View File

@@ -419,15 +419,19 @@ class Device:
class BTTether(plugins.Plugin):
__author__ = '33197631+dadav@users.noreply.github.com'
__version__ = '1.0.0'
__version__ = '1.1.0'
__license__ = 'GPL3'
__description__ = 'This makes the display reachable over bluetooth'
def __init__(self):
self.ready = False
self.options = dict()
self.devices = dict()
self.lock = Lock()
self.running = True
self.status = '-'
def on_loaded(self):
# new config
@@ -449,7 +453,7 @@ class BTTether(plugins.Plugin):
if 'mac' in self.options:
for opt in ['share_internet', 'mac', 'ip', 'netmask', 'interval']:
if opt not in self.options or self.options[opt] is None:
logging.error("BT-TETHER: Please specify the %s in your config.yml.", opt)
logging.error("BT-TETHER: Please specify the %s in your config.toml.", opt)
return
self.devices['legacy'] = Device(name='legacy', **self.options)
@@ -466,22 +470,10 @@ class BTTether(plugins.Plugin):
return
logging.info("BT-TETHER: Successfully loaded ...")
self.ready = True
def on_unload(self, ui):
with ui._lock:
ui.remove_element('bluetooth')
while self.running:
time.sleep(1)
def on_ui_setup(self, ui):
with ui._lock:
ui.add_element('bluetooth', LabeledValue(color=BLACK, label='BT', value='-', position=(ui.width() / 2 - 15, 0),
label_font=fonts.Bold, text_font=fonts.Medium))
def on_ui_update(self, ui):
if not self.ready:
return
with self.lock:
devices_to_try = list()
connected_priorities = list()
any_device_connected = False # if this is true, last status on screen should be C
@@ -508,11 +500,11 @@ class BTTether(plugins.Plugin):
dev_remote = bt.wait_for_device(timeout=device.scantime)
if dev_remote is None:
logging.debug('BT-TETHER: Could not find %s, try again in %d minutes.', device.name, device.interval)
ui.set('bluetooth', 'NF')
self.status = 'NF'
continue
except Exception as bt_ex:
logging.error(bt_ex)
ui.set('bluetooth', 'NF')
self.status = 'NF'
continue
paired = bt.is_paired()
@@ -521,7 +513,7 @@ class BTTether(plugins.Plugin):
logging.debug('BT-TETHER: Paired with %s.', device.name)
else:
logging.debug('BT-TETHER: Pairing with %s failed ...', device.name)
ui.set('bluetooth', 'PE')
self.status = 'PE'
continue
else:
logging.debug('BT-TETHER: Already paired.')
@@ -539,17 +531,17 @@ class BTTether(plugins.Plugin):
continue
if interface is None:
ui.set('bluetooth', 'BE')
self.status = 'BE'
logging.debug('BT-TETHER: Could not establish nap connection with %s', device.name)
continue
logging.debug('BT-TETHER: Created interface (%s)', interface)
ui.set('bluetooth', 'C')
self.status = 'C'
any_device_connected = True
device.tries = 0 # reset tries
else:
logging.debug('BT-TETHER: Could not establish nap connection with %s', device.name)
ui.set('bluetooth', 'NF')
self.status = 'NF'
continue
addr = f"{device.ip}/{device.netmask}"
@@ -561,7 +553,7 @@ class BTTether(plugins.Plugin):
wrapped_interface = IfaceWrapper(interface)
logging.debug('BT-TETHER: Add ip to %s', interface)
if not wrapped_interface.set_addr(addr):
ui.set('bluetooth', 'AE')
self.status = 'AE'
logging.debug("BT-TETHER: Could not add ip to %s", interface)
continue
@@ -580,4 +572,20 @@ class BTTether(plugins.Plugin):
resolv.write(nameserver + 'nameserver 9.9.9.9\n')
if any_device_connected:
ui.set('bluetooth', 'C')
self.status = 'C'
def on_unload(self, ui):
self.running = False
with ui._lock:
ui.remove_element('bluetooth')
def on_ui_setup(self, ui):
with ui._lock:
ui.add_element('bluetooth', LabeledValue(color=BLACK, label='BT', value='-', position=(ui.width() / 2 - 15, 0),
label_font=fonts.Bold, text_font=fonts.Medium))
def on_ui_update(self, ui):
ui.set('bluetooth', self.status)

View File

@@ -1,6 +1,7 @@
import os
import logging
import threading
from itertools import islice
from time import sleep
from datetime import datetime,timedelta
from pwnagotchi import plugins
@@ -90,7 +91,7 @@ TEMPLATE = """
{% endblock %}
{% block script %}
var content = document.getElementById('content');
var table = document.getElementById('table');
var filter = document.getElementById('filter');
var filterVal = filter.value.toUpperCase();
@@ -154,10 +155,10 @@ TEMPLATE = """
tr.className = colorClass;
if (filterVal.length > 0 && value.toUpperCase().indexOf(filterVal) == -1) {
tr.style.visibility = "collapse";
tr.style.display = "none";
}
content.appendChild(tr);
table.appendChild(tr);
});
position = messages.length - 1;
}
@@ -193,30 +194,19 @@ TEMPLATE = """
function doneTyping() {
document.body.style.cursor = 'progress';
var table, tr, tds, td, i, txtValue;
var tr, tds, td, i, txtValue;
filterVal = filter.value.toUpperCase();
table = document.getElementById("content");
tr = table.getElementsByTagName("tr");
for (i = 0; i < tr.length; i++) {
tds = tr[i].getElementsByTagName("td");
if (tds) {
for (l = 0; l < tds.length; l++) {
td = tds[l];
if (td) {
txtValue = td.textContent || td.innerText;
if (txtValue.toUpperCase().indexOf(filterVal) > -1) {
tr[i].style.visibility = "visible";
break;
} else {
tr[i].style.visibility = "collapse";
}
}
}
for (i = 1; i < tr.length; i++) {
txtValue = tr[i].textContent || tr[i].innerText;
if (txtValue.toUpperCase().indexOf(filterVal) > -1) {
tr[i].style.display = "table-row";
} else {
tr[i].style.display = "none";
}
}
document.body.style.cursor = 'default';
}
{% endblock %}
{% block content %}
@@ -225,7 +215,7 @@ TEMPLATE = """
<span><input checked type="checkbox" id="autoscroll"></span>
<span><label for="autoscroll"> Autoscroll to bottom</label><br></span>
</div>
<table id="content">
<table id="table">
<thead>
<th>
Time
@@ -263,6 +253,7 @@ class Logtail(plugins.Plugin):
"""
logging.info("Logtail plugin loaded.")
def on_webhook(self, path, request):
if not self.ready:
return "Plugin not ready"
@@ -273,7 +264,7 @@ class Logtail(plugins.Plugin):
if path == 'stream':
def generate():
with open(self.config['main']['log']['path']) as f:
yield f.read()
yield ''.join(f.readlines()[-self.options.get('max-lines', 4096):])
while True:
yield f.readline()

View File

@@ -12,7 +12,7 @@ from json.decoder import JSONDecodeError
class OnlineHashCrack(plugins.Plugin):
__author__ = '33197631+dadav@users.noreply.github.com'
__version__ = '2.0.1'
__version__ = '2.1.0'
__license__ = 'GPL3'
__description__ = 'This plugin automatically uploads handshakes to https://onlinehashcrack.com'
@@ -55,9 +55,9 @@ class OnlineHashCrack(plugins.Plugin):
files=payload,
timeout=timeout)
if 'already been sent' in result.text:
logging.warning(f"{path} was already uploaded.")
logging.debug(f"{path} was already uploaded.")
except requests.exceptions.RequestException as e:
logging.error(f"OHC: Got an exception while uploading {path} -> {e}")
logging.debug(f"OHC: Got an exception while uploading {path} -> {e}")
raise e
def _download_cracked(self, save_file, timeout=120):
@@ -78,6 +78,16 @@ class OnlineHashCrack(plugins.Plugin):
except OSError as os_e:
raise os_e
def on_webhook(self, path, request):
import requests
from flask import redirect
s = requests.Session()
s.get('https://www.onlinehashcrack.com/dashboard')
r = s.post('https://www.onlinehashcrack.com/dashboard', data={'emailTasks': self.options['email'], 'submit': ''})
return redirect(r.url, code=302)
def on_internet_available(self, agent):
"""
Called in manual mode when there's internet connectivity
@@ -108,14 +118,14 @@ class OnlineHashCrack(plugins.Plugin):
if handshake not in reported:
reported.append(handshake)
self.report.update(data={'reported': reported})
logging.info(f"OHC: Successfully uploaded {handshake}")
logging.debug(f"OHC: Successfully uploaded {handshake}")
except requests.exceptions.RequestException as req_e:
self.skip.append(handshake)
logging.error("OHC: %s", req_e)
logging.debug("OHC: %s", req_e)
continue
except OSError as os_e:
self.skip.append(handshake)
logging.error("OHC: %s", os_e)
logging.debug("OHC: %s", os_e)
continue
if 'dashboard' in self.options and self.options['dashboard']:
cracked_file = os.path.join(handshake_dir, 'onlinehashcrack.cracked')

View File

@@ -0,0 +1,36 @@
import os
import logging
import re
import subprocess
from io import TextIOWrapper
from pwnagotchi import plugins
class Watchdog(plugins.Plugin):
__author__ = '33197631+dadav@users.noreply.github.com'
__version__ = '0.1.0'
__license__ = 'GPL3'
__description__ = 'Restart pwnagotchi when blindbug is detected.'
def __init__(self):
self.options = dict()
self.pattern = re.compile(r'brcmf_cfg80211_nexmon_set_channel.*?Set Channel failed')
def on_loaded(self):
"""
Gets called when the plugin gets loaded
"""
logging.info("Watchdog plugin loaded.")
def on_epoch(self, agent, epoch, epoch_data):
# get last 10 lines
last_lines = ''.join(list(TextIOWrapper(subprocess.Popen(['journalctl','-n10','-k', '--since', '-5m'],
stdout=subprocess.PIPE).stdout))[-10:])
if len(self.pattern.findall(last_lines)) >= 5:
display = agent.view()
display.set('status', 'Blind-Bug detected. Restarting.')
display.update(force=True)
logging.info('[WATCHDOG] Blind-Bug detected. Restarting.')
mode = 'MANU' if agent.mode == 'manual' else 'AUTO'
import pwnagotchi
pwnagotchi.restart(mode=mode)

View File

@@ -230,7 +230,7 @@ class Webgpsmap(plugins.Plugin):
}
# get ap password if exist
check_for = os.path.basename(pos_file[:-9]) + ".pcap.cracked"
check_for = os.path.basename(pos_file).split(".")[0] + ".pcap.cracked"
if check_for in all_files:
gps_data[ssid + "_" + mac]["pass"] = pos.password()

View File

@@ -106,7 +106,7 @@ class Wigle(plugins.Plugin):
def on_loaded(self):
if 'api_key' not in self.options or ('api_key' in self.options and self.options['api_key'] is None):
logging.error("WIGLE: api_key isn't set. Can't upload to wigle.net")
logging.debug("WIGLE: api_key isn't set. Can't upload to wigle.net")
return
if not 'whitelist' in self.options:
@@ -141,21 +141,21 @@ class Wigle(plugins.Plugin):
for gps_file in new_gps_files:
pcap_filename = gps_file.replace('.gps.json', '.pcap')
if not os.path.exists(pcap_filename):
logging.error("WIGLE: Can't find pcap for %s", gps_file)
logging.debug("WIGLE: Can't find pcap for %s", gps_file)
self.skip.append(gps_file)
continue
try:
gps_data = _extract_gps_data(gps_file)
except OSError as os_err:
logging.error("WIGLE: %s", os_err)
logging.debug("WIGLE: %s", os_err)
self.skip.append(gps_file)
continue
except json.JSONDecodeError as json_err:
logging.error("WIGLE: %s", json_err)
logging.debug("WIGLE: %s", json_err)
self.skip.append(gps_file)
continue
if gps_data['Latitude'] == 0 and gps_data['Longitude'] == 0:
logging.warning("WIGLE: Not enough gps-information for %s. Trying again next time.", gps_file)
logging.debug("WIGLE: Not enough gps-information for %s. Trying again next time.", gps_file)
self.skip.append(gps_file)
continue
try:
@@ -165,11 +165,11 @@ class Wigle(plugins.Plugin):
WifiInfo.CHANNEL,
WifiInfo.RSSI])
except FieldNotFoundError:
logging.error("WIGLE: Could not extract all information. Skip %s", gps_file)
logging.debug("WIGLE: Could not extract all information. Skip %s", gps_file)
self.skip.append(gps_file)
continue
except Scapy_Exception as sc_e:
logging.error("WIGLE: %s", sc_e)
logging.debug("WIGLE: %s", sc_e)
self.skip.append(gps_file)
continue
new_entry = _transform_wigle_entry(gps_data, pcap_data)
@@ -185,7 +185,7 @@ class Wigle(plugins.Plugin):
logging.info("WIGLE: Successfully uploaded %d files", len(no_err_entries))
except requests.exceptions.RequestException as re_e:
self.skip += no_err_entries
logging.error("WIGLE: Got an exception while uploading %s", re_e)
logging.debug("WIGLE: Got an exception while uploading %s", re_e)
except OSError as os_e:
self.skip += no_err_entries
logging.error("WIGLE: Got the following error: %s", os_e)
logging.debug("WIGLE: Got the following error: %s", os_e)

View File

@@ -39,7 +39,7 @@ class WpaSec(plugins.Plugin):
files=payload,
timeout=timeout)
if ' already submitted' in result.text:
logging.warning("%s was already submitted.", path)
logging.debug("%s was already submitted.", path)
except requests.exceptions.RequestException as req_e:
raise req_e
@@ -83,6 +83,12 @@ class WpaSec(plugins.Plugin):
self.ready = True
def on_webhook(self, path, request):
from flask import make_response, redirect
response = make_response(redirect(self.options['api_url'], code=302))
response.set_cookie('key', self.options['api_key'])
return response
def on_internet_available(self, agent):
"""
Called in manual mode when there's internet connectivity
@@ -110,13 +116,13 @@ class WpaSec(plugins.Plugin):
self._upload_to_wpasec(handshake)
reported.append(handshake)
self.report.update(data={'reported': reported})
logging.info("WPA_SEC: Successfully uploaded %s", handshake)
logging.debug("WPA_SEC: Successfully uploaded %s", handshake)
except requests.exceptions.RequestException as req_e:
self.skip.append(handshake)
logging.error("WPA_SEC: %s", req_e)
logging.debug("WPA_SEC: %s", req_e)
continue
except OSError as os_e:
logging.error("WPA_SEC: %s", os_e)
logging.debug("WPA_SEC: %s", os_e)
continue
if 'download_results' in self.options and self.options['download_results']:

View File

@@ -49,8 +49,11 @@ class Display(View):
def is_lcdhat(self):
return self._implementation.name == 'lcdhat'
def is_dfrobot(self):
return self._implementation.name == 'dfrobot'
def is_dfrobot_v1(self):
return self._implementation.name == 'dfrobot_v1'
def is_dfrobot_v2(self):
return self._implementation.name == 'dfrobot_v2'
def is_waveshare144lcd(self):
return self._implementation.name == 'waveshare144lcd'

View File

@@ -2,7 +2,8 @@ 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.lcdhat import LcdHat
from pwnagotchi.ui.hw.dfrobot import DFRobot
from pwnagotchi.ui.hw.dfrobot1 import DFRobotV1
from pwnagotchi.ui.hw.dfrobot2 import DFRobotV2
from pwnagotchi.ui.hw.waveshare1 import WaveshareV1
from pwnagotchi.ui.hw.waveshare2 import WaveshareV2
from pwnagotchi.ui.hw.waveshare27inch import Waveshare27inch
@@ -27,8 +28,11 @@ def display_for(config):
if config['ui']['display']['type'] == 'lcdhat':
return LcdHat(config)
if config['ui']['display']['type'] == 'dfrobot':
return DFRobot(config)
if config['ui']['display']['type'] == 'dfrobot_1':
return DFRobotV1(config)
if config['ui']['display']['type'] == 'dfrobot_2':
return DFRobotV2(config)
elif config['ui']['display']['type'] == 'waveshare_1':
return WaveshareV1(config)

View File

@@ -3,9 +3,9 @@ import logging
import pwnagotchi.ui.fonts as fonts
from pwnagotchi.ui.hw.base import DisplayImpl
class DFRobot(DisplayImpl):
class DFRobotV1(DisplayImpl):
def __init__(self, config):
super(DFRobot, self).__init__(config, 'dfrobot')
super(DFRobotV1, self).__init__(config, 'dfrobot_1')
self._display = None
def layout(self):
@@ -31,8 +31,8 @@ class DFRobot(DisplayImpl):
return self._layout
def initialize(self):
logging.info("initializing dfrobot display")
from pwnagotchi.ui.hw.libs.dfrobot.dfrobot import DFRobot
logging.info("initializing dfrobot1 display")
from pwnagotchi.ui.hw.libs.dfrobot.v1.dfrobot import DFRobot
self._display = DFRobot()
def render(self, canvas):

View File

@@ -0,0 +1,43 @@
import logging
import pwnagotchi.ui.fonts as fonts
from pwnagotchi.ui.hw.base import DisplayImpl
class DFRobotV2(DisplayImpl):
def __init__(self, config):
super(DFRobotV2, self).__init__(config, 'dfrobot_2')
self._display = None
def layout(self):
fonts.setup(10, 9, 10, 35, 25, 9)
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.status_font(fonts.Medium),
'max': 20
}
return self._layout
def initialize(self):
logging.info("initializing dfrobot2 display")
from pwnagotchi.ui.hw.libs.dfrobot.v2.dfrobot import DFRobot
self._display = DFRobot()
def render(self, canvas):
buf = self._display.getbuffer(canvas)
self._display.display(buf)
def clear(self):
self._display.Clear(0xFF)

View File

@@ -0,0 +1,66 @@
# DFRobot display support
import logging
from . import dfrobot_epaper
#Resolution of display
WIDTH = 250
HEIGHT = 122
RASPBERRY_SPI_BUS = 0
RASPBERRY_SPI_DEV = 0
RASPBERRY_PIN_CS = 27
RASPBERRY_PIN_CD = 17
RASPBERRY_PIN_BUSY = 4
class DFRobot:
def __init__(self):
self._display = dfrobot_epaper.DFRobot_Epaper_SPI(RASPBERRY_SPI_BUS, RASPBERRY_SPI_DEV, RASPBERRY_PIN_CS, RASPBERRY_PIN_CD, RASPBERRY_PIN_BUSY)
self._display.begin()
self.clear(0xFF)
self.FULL = self._display.FULL
self.PART = self._display.PART
def getbuffer(self, image):
if HEIGHT % 8 == 0:
linewidth = HEIGHT // 8
else:
linewidth = HEIGHT // 8 + 1
buf = [0xFF] * (linewidth * WIDTH)
image_monocolor = image.convert('1')
imwidth, imheight = image_monocolor.size
pixels = image_monocolor.load()
if (imwidth == HEIGHT and imheight == WIDTH):
for y in range(imheight):
for x in range(imwidth):
if pixels[x,y] == 0:
x = imwidth - x
buf[x // 8 + y * linewidth] &= ~(0x80 >> (x % 8))
elif (imwidth == WIDTH and imheight == HEIGHT):
for y in range(imheight):
for x in range(imwidth):
newx = y
newy = WIDTH - x - 1
if pixels[x,y] == 0:
newy = imwidth - newy - 1
buf[newx // 8 + newy * linewidth] &= ~(0x80 >> (y % 8))
return buf
def flush(self, type):
self._display.flush(type)
def display(self, buf):
self._display.setBuffer(buf)
self.flush(self._display.PART)
def clear(self, color):
if HEIGHT % 8 == 0:
linewidth = HEIGHT // 8
else:
linewidth = HEIGHT // 8 + 1
buf = [color] * (linewidth * WIDTH)
self._display.setBuffer(buf)
self.flush(self._display.FULL)

View File

@@ -0,0 +1,673 @@
# -*- coding:utf-8 -*-
import sys
from .dfrobot_printString import PrintString
from .dfrobot_fonts import Fonts
def color24to16(color):
return (((color >> 8) & 0xf800) | ((color >> 5) & 0x7e0) | ((color >> 3) & 0x1f))
def color16to24(color):
return (((color & 0xf800) << 8) | ((color & 0x7e0) << 5) | ((color & 0x1f) << 3))
def swap(o1, o2):
return (o2, o1)
class DFRobot_Display(PrintString):
WHITE24 = 0xffffff
SILVER24 = 0xc0c0c0
GRAY24 = 0x808080
BLACK24 = 0x000000
RED24 = 0xff0000
MAROON24 = 0x800000
YELLOW24 = 0xffff00
OLIVE24 = 0x808000
GREEN24 = 0x00ff00
DARKGREEN24 = 0x008000
CYAN24 = 0x00ffff
BLUE24 = 0x0000ff
NAVY24 = 0x000080
FUCHSIA24 = 0xff00ff
PURPLE24 = 0x800080
TEAL24 = 0x008080
WHITE16 = color24to16(WHITE24)
SILVER16 = color24to16(SILVER24)
GRAY16 = color24to16(GRAY24)
BLACK16 = color24to16(BLACK24)
RED16 = color24to16(RED24)
MAROON16 = color24to16(MAROON24)
YELLOW16 = color24to16(YELLOW24)
OLIVE16 = color24to16(OLIVE24)
GREEN16 = color24to16(GREEN24)
DARKGREEN16 = color24to16(DARKGREEN24)
CYAN16 = color24to16(CYAN24)
BLUE16 = color24to16(BLUE24)
NAVY16 = color24to16(NAVY24)
FUCHSIA16 = color24to16(FUCHSIA24)
PURPLE16 = color24to16(PURPLE24)
TEAL16 = color24to16(TEAL24)
WHITE = WHITE16
SILVER = SILVER16
GRAY = GRAY16
BLACK = BLACK16
RED = RED16
MAROON = MAROON16
YELLOW = YELLOW16
OLIVE = OLIVE16
GREEN = GREEN16
DARKGREEN = DARKGREEN16
CYAN = CYAN16
BLUE = BLUE16
NAVY = NAVY16
FUCHSIA = FUCHSIA16
PURPLE = PURPLE16
TEAL = TEAL16
POSITIVE = 1
REVERSE = -1
BITMAP_TBMLLR = "TBMLLR"
BITMAP_TBMRLL = "TBMRLL"
BITMAP_BTMLLR = "BTMLLR"
BITMAP_BTMRLL = "BTMRLL"
BITMAP_LRMTLB = "LRMTLB"
BITMAP_LRMBLT = "LRMBLT"
BITMAP_RLMTLB = "RLMTLB"
BIMTAP_RLMBLT = "RLMBLT"
BITMAP_UNKNOW = "UNKNOW"
def __init__(self, w, h):
PrintString.__init__(self)
print("DFRobot_Display init " + str(w) + " " + str(h))
self._width = w
self._height = h
self._lineWidth = 1
self._bitmapSize = 1
self._bitmapFmt = ""
self._bmpFmt = self.BITMAP_TBMLLR
self._fonts = Fonts()
self._textSize = 1
self._textColor = self.BLACK
self._textBackground = self.WHITE
self._textCursorX = 0
self._textCursorY = 0
self._textIntervalRow = 0
self._textIntervalCol = 0
def _ternaryExpression(self, condition, o1, o2):
if condition:
return o1
return o2
def _getDirection(self, value):
if value >= 0:
return 1
return -1
def color16to24(self, color):
return color16to24(color)
def color24to16(self, color):
return color24to16(color)
def setColorTo16(self):
self.WHITE = self.WHITE16
self.SILVER = self.SILVER16
self.GRAY = self.GRAY16
self.BLACK = self.BLACK16
self.RED = self.RED16
self.MAROON = self.MAROON16
self.YELLOW = self.YELLOW16
self.OLIVE = self.OLIVE16
self.GREEN = self.GREEN16
self.DARKGREEN = self.DARKGREEN16
self.CYAN = self.CYAN16
self.BLUE = self.BLUE16
self.NAVY = self.NAVY16
self.FUCHSIA = self.FUCHSIA16
self.PURPLE = self.PURPLE16
self.TEAL = self.TEAL16
def setColorTo24(self):
self.WHITE = self.WHITE24
self.SILVER = self.SILVER24
self.GRAY = self.GRAY24
self.BLACK = self.BLACK24
self.RED = self.RED24
self.MAROON = self.MAROON24
self.YELLOW = self.YELLOW24
self.OLIVE = self.OLIVE24
self.GREEN = self.GREEN24
self.DARKGREEN = self.DARKGREEN24
self.CYAN = self.CYAN24
self.BLUE = self.BLUE24
self.NAVY = self.NAVY24
self.FUCHSIA = self.FUCHSIA24
self.PURPLE = self.PURPLE24
self.TEAL = self.TEAL24
def setLineWidth(self, w):
if w < 0:
return
self._lineWidth = w
def setTextFormat(self, size, color, background, intervalRow = 2, intervalCol = 0):
self._textColor = color
self._textIntervalRow = intervalRow
self._textIntervalCol = intervalCol
self._textBackground = background
if size < 0:
return
self._textSize = size
def setTextCursor(self, x, y):
self._textCursorX = int(x)
self._textCursorY = int(y)
def setBitmapSize(self, size):
if size < 0:
return
self._bitmapSize = size
def setBitmapFmt(self, fmt):
self._bmpFmt = fmt
def setExFonts(self, obj):
self._fonts.setExFonts(obj)
def setExFontsFmt(self, width, height):
self._fonts.setExFontsFmt(width, height)
def setEnableDefaultFonts(self, opt):
self._fonts.setEnableDefaultFonts(opt)
def pixel(self, x, y, color):
pass
def clear(self, color):
self.fillRect(0, 0, self._width, self._height, color)
self._textCursorX = 0
self._textCursorY = 0
def VLine(self, x, y, h, color):
x = int(x)
y = int(y)
h = int(h)
direction = self._getDirection(h)
x -= self._lineWidth // 2
h = self._ternaryExpression(h > 0, h, -h)
for i in range(self._ternaryExpression(h > 0, h, - h)):
xx = x
for j in range(self._lineWidth):
self.pixel(xx, y, color)
xx += 1
y += direction
def HLine(self, x, y, w, color):
x = int(x)
y = int(y)
w = int(w)
direction = self._getDirection(w)
y -= self._lineWidth // 2
for i in range(self._ternaryExpression(w > 0, w, - w)):
yy = y
for j in range(self._lineWidth):
self.pixel(x, yy, color)
yy += 1
x += direction
def line(self, x, y, x1, y1, color):
x = int(x)
y = int(y)
x1 = int(x1)
y1 = int(y1)
if x == x1:
self.VLine(x, y, y1 - y, color)
return
if y == y1:
self.HLine(x, y, x1 - x, color)
return
dx = abs(x1 - x)
dy = abs(y1 - y)
dirX = self._ternaryExpression(x < x1, 1, -1)
dirY = self._ternaryExpression(y < y1, 1, -1)
if dx > dy:
err = dx / 2
for i in range(dx):
self.HLine(x, y, 1, color)
x += dirX
err -= dy
if err < 0:
err += dx
y += dirY
self.HLine(x1, y1, 1, color)
else:
err = dy / 2
for i in range(dy):
self.VLine(x, y, 1, color)
y += dirY
err -= dx
if err < 0:
err += dy
x += dirX
self.VLine(x1, y1, 1, color)
def triangle(self, x, y, x1, y1, x2, y2, color):
self.line(x, y, x1, y1, color)
self.line(x1, y1, x2, y2, color)
self.line(x2, y2, x, y, color)
def fillTriangle(self, x, y, x1, y1, x2, y2, color):
self.line(x, y, x1, y1, color)
self.line(x1, y1, x2, y2, color)
self.line(x2, y2, x, y, color)
x = int(x)
y = int(y)
x1 = int(x1)
y1 = int(y1)
x2 = int(x2)
y2 = int(y2)
temp = self._lineWidth
self._lineWidth = 1
if x == x1 and x == x2:
ymax = max([y, y1, y2])
ymin = min([y, y1, y2])
self.HLine(x, ymin, ymax - ymin, color)
self._lineWidth = temp
return
if y == y1 and y == y2:
xmax = max([x, x1, x2])
xmin = max([x, x1, x2])
self.VLine(xmin, y, xmax - xmin, color)
self._lineWidth = temp
return
direction = self.POSITIVE
if y == y1 or y1 == y2 or y == y2:
if y == y1:
(x, x2) = swap(x, x2)
(y, y2) = swap(y, y2)
elif y == y2:
(x, x1) = swap(x, x1)
(y, y1) = swap(y, y1)
if y > y1:
direction = self.REVERSE
if x1 > x2:
(x1, x2) = swap(x1, x2)
(y1, y2) = swap(y1, y2)
else:
if y > y1:
(x, x1) = swap(x, x1)
(y, y1) = swap(y, y1)
if y > y2:
(x, x2) = swap(x, x2)
(y, y2) = swap(y, y2)
if y1 > y2:
(x1, x2) = swap(x1, x2)
(y1, y2) = swap(y1, y2)
dx1 = x1 - x
dx2 = x2 - x
dx3 = x2 - x1
dy1 = y1 - y
dy2 = y2 - y
dy3 = y2 - y1
if direction == self.POSITIVE:
for i in range(dy1):
self.HLine(x + dx1 * i / dy1, y + i, (x + dx2 * i / dy2) - (x + dx1 * i / dy1) + 1, color)
for i in range(dy3):
self.HLine(x1 + dx3 * i / dy3, y1 + i, (x + dx2 * (i + dy1) / dy2) - (x1 + dx3 * i / dy3) + 1, color)
else:
y = y1 + dy1
dy1 = - dy1
for i in range(dy1):
self.HLine(x + dx1 * i / dy1, y1 + dy1 - i, (x + dx2 * i / dy1) - (x + dx1 * i / dy1) + 1, color)
self._lineWidth = temp
def rect(self, x, y, w, h, color):
if w < 0:
x += w
w = -w
if h < 0:
y += h
h = -h
self.HLine(x - self._lineWidth // 2, y, w + self._lineWidth, color)
self.HLine(x - self._lineWidth // 2, y + h, w + self._lineWidth, color)
self.VLine(x, y - self._lineWidth // 2, h + self._lineWidth, color)
self.VLine(x + w, y - self._lineWidth // 2, h + self._lineWidth, color)
def fillRect(self, x, y, w, h, color):
temp = self._lineWidth
self._lineWidth = 1
if w < 0:
x += w
w = abs(w)
for i in range(w):
self.VLine(x + i, y, h, color)
self._lineWidth = temp
QUADRANT_1 = 1
QUADRANT_2 = 2
QUADRANT_3 = 4
QUADRANT_4 = 8
QUADRANT_ALL = 15
def circleHelper(self, x, y, r, quadrant, color):
x = int(x)
y = int(y)
r = abs(int(r))
vx = 0
vy = r
dx = 1
dy = -2 * r
p = 1 - r
if quadrant & self.QUADRANT_1:
self.VLine(x + r, y, 1, color)
if quadrant & self.QUADRANT_2:
self.VLine(x, y - r, 1, color)
if quadrant & self.QUADRANT_3:
self.VLine(x - r, y, 1, color)
if quadrant & self.QUADRANT_4:
self.VLine(x, y + r, 1, color)
halfLineWidth = self._lineWidth // 2
while vx < vy:
if p >= 0:
vy -= 1
dy += 2
p += dy
vx += 1
dx += 2
p += dx
if quadrant & self.QUADRANT_1:
self.fillRect(x + vx - halfLineWidth, y - vy - halfLineWidth, self._lineWidth, self._lineWidth, color) # quadrant 1
self.fillRect(x + vy - halfLineWidth, y - vx - halfLineWidth, self._lineWidth, self._lineWidth, color) # quadrant 1
if quadrant & self.QUADRANT_2:
self.fillRect(x - vx - halfLineWidth, y - vy - halfLineWidth, self._lineWidth, self._lineWidth, color) # quadrant 2
self.fillRect(x - vy - halfLineWidth, y - vx - halfLineWidth, self._lineWidth, self._lineWidth, color) # quadrant 2
if quadrant & self.QUADRANT_3:
self.fillRect(x - vx - halfLineWidth, y + vy - halfLineWidth, self._lineWidth, self._lineWidth, color) # quadrant 3
self.fillRect(x - vy - halfLineWidth, y + vx - halfLineWidth, self._lineWidth, self._lineWidth, color) # quadrant 3
if quadrant & self.QUADRANT_4:
self.fillRect(x + vx - halfLineWidth, y + vy - halfLineWidth, self._lineWidth, self._lineWidth, color) # quadrant 4
self.fillRect(x + vy - halfLineWidth, y + vx - halfLineWidth, self._lineWidth, self._lineWidth, color) # quadrant 4
def circle(self, x, y, r, color):
self.circleHelper(x, y, r, self.QUADRANT_ALL, color)
def fillCircleHelper(self, x, y, r, quadrant, color):
x = int(x)
y = int(y)
r = abs(int(r))
temp = self._lineWidth
self._lineWidth = 1
vx = 0
vy = r
dx = 1
dy = -2 * r
p = 1 - r
if quadrant & self.QUADRANT_1:
self.HLine(x, y, r + 1, color)
if quadrant & self.QUADRANT_2:
self.VLine(x, y, - r - 1, color)
if quadrant & self.QUADRANT_3:
self.HLine(x, y, - r - 1, color)
if quadrant & self.QUADRANT_4:
self.VLine(x, y, r + 1, color)
while vx < vy:
if p >= 0:
vy -= 1
dy += 2
p += dy
vx += 1
dx += 2
p += dx
if quadrant & self.QUADRANT_1:
self.VLine(x + vx, y - vy, vy, color) # quadrant 1
self.VLine(x + vy, y - vx, vx, color) # quadrant 1
if quadrant & self.QUADRANT_2:
self.VLine(x - vx, y - vy, vy, color) # quadrant 2
self.VLine(x - vy, y - vx, vx, color) # quadrant 2
if quadrant & self.QUADRANT_3:
self.VLine(x - vx, y + vy, - vy, color) # quadrant 3
self.VLine(x - vy, y + vx, - vx, color) # quadrant 3
if quadrant & self.QUADRANT_4:
self.VLine(x + vx, y + vy, - vy, color) # quadrant 4
self.VLine(x + vy, y + vx, - vx, color) # quadrant 4
self._lineWidth = temp
def fillCircle(self, x, y, r, color):
self.fillCircleHelper(x, y, r, self.QUADRANT_ALL, color)
def roundRect(self, x, y, w, h, r, color):
x = int(x)
y = int(y)
w = int(w)
h = int(h)
r = abs(int(r))
if w < 0:
x += w
w = abs(w)
if h < 0:
y += h
h = abs(h)
self.HLine(x + r, y, w - 2 * r + 1, color)
self.HLine(x + r, y + h, w - 2 * r + 1, color)
self.VLine(x, y + r, h - 2 * r + 1, color)
self.VLine(x + w, y + r, h - 2 * r + 1, color)
self.circleHelper(x + r, y + r, r, self.QUADRANT_2, color)
self.circleHelper(x + w - r, y + r, r, self.QUADRANT_1, color)
self.circleHelper(x + r, y + h - r, r, self.QUADRANT_3, color)
self.circleHelper(x + w - r, y + h - r, r, self.QUADRANT_4, color)
def fillRoundRect(self, x, y, w, h, r, color):
x = int(x)
y = int(y)
w = int(w)
h = int(h)
r = abs(int(r))
if w < 0:
x += w
w = abs(w)
if h < 0:
y += h
h = abs(h)
self.fillRect(x + r, y, w - 2 * r, h, color)
self.fillRect(x, y + r, r, h - 2 * r, color)
self.fillRect(x + w - r, y + r, r, h - 2 * r, color)
self.fillCircleHelper(x + r, y + r, r, self.QUADRANT_2, color)
self.fillCircleHelper(x + w - r - 1, y + r, r, self.QUADRANT_1, color)
self.fillCircleHelper(x + r, y + h - r - 1, r, self.QUADRANT_3, color)
self.fillCircleHelper(x + w - r - 1, y + h - r - 1, r, self.QUADRANT_4, color)
def _bitmapHelper(self, increaseAxis, staticAxis, data, dataBit, exchange, color, background):
for i in data:
for j in range(8):
if i & dataBit:
if exchange:
self.fillRect(staticAxis, increaseAxis, self._bitmapSize, self._bitmapSize, color)
else:
self.fillRect(increaseAxis, staticAxis, self._bitmapSize, self._bitmapSize, color)
else:
if exchange:
self.fillRect(staticAxis, increaseAxis, self._bitmapSize, self._bitmapSize, background)
else:
self.fillRect(increaseAxis, staticAxis, self._bitmapSize, self._bitmapSize, background)
increaseAxis += self._bitmapSize
if dataBit & 0x80:
i <<= 1
else:
i >>= 1
def bitmap(self, x, y, bitmap, w, h, color, background):
if w < 0 or h < 0:
return
x = abs(int(x))
y = abs(int(y))
if self._bmpFmt == self.BITMAP_TBMLLR:
oneLineDataLen = (w - 1) // 8 + 1
for i in range(h):
yMask = y + i * self._bitmapSize
self._bitmapHelper(x, yMask, bitmap[i * oneLineDataLen : oneLineDataLen * (i + 1)], 0x80, False, color, background)
elif self._bmpFmt == self.BITMAP_TBMRLL:
oneLineDataLen = (w - 1) // 8 + 1
for i in range(h):
yMask = y + i * self._bitmapSize
self._bitmapHelper(x, yMask, bitmap[i * oneLineDataLen : oneLineDataLen * (i + 1)], 0x01, False, color, background)
elif self._bmpFmt == self.BITMAP_BTMLLR:
oneLineDataLen = (w - 1) // 8 + 1
for i in range(h):
yMask = y + h * self._bitmapSize - i * self._bitmapSize
self._bitmapHelper(x, yMask, bitmap[i * oneLineDataLen : oneLineDataLen * (i + 1)], 0x80, False, color, background)
elif self._bmpFmt == self.BITMAP_BTMRLL:
oneLineDataLen = (w - 1) // 8 + 1
for i in range(h):
yMask = y + h * self._bitmapSize - i * self._bitmapSize
self._bitmapHelper(x, yMask, bitmap[i * oneLineDataLen : oneLineDataLen * (i + 1)], 0x01, False, color, background)
elif self._bmpFmt == self.BITMAP_LRMTLB:
oneLineDataLen = (h - 1) // 8 + 1
for i in range(w):
xMask = x + i * self._bitmapSize
self._bitmapHelper(y, xMask, bitmap[i * oneLineDataLen : oneLineDataLen * (i + 1)], 0x80, True, color, background)
elif self._bmpFmt == self.BITMAP_LRMBLT:
oneLineDataLen = (h - 1) // 8 + 1
for i in range(w):
xMask = x + i * self._bitmapSize
self._bitmapHelper(y, xMask, bitmap[i * oneLineDataLen : oneLineDataLen * (i + 1)], 0x01, True, color, background)
elif self._bmpFmt == self.BITMAP_RLMTLB:
oneLineDataLen = (h - 1) // 8 + 1
for i in range(w):
xMask = x + w * self._bitmapSize - i * self._bitmapSize
self._bitmapHelper(y, xMask, bitmap[i * oneLineDataLen : oneLineDataLen * (i + 1)], 0x80, True, color, background)
elif self._bmpFmt == self.BIMTAP_RLMBLT:
oneLineDataLen = (h - 1) // 8 + 1
for i in range(w):
xMask = x + w * self._bitmapSize - i * self._bitmapSize
self._bitmapHelper(y, xMask, bitmap[i * oneLineDataLen : oneLineDataLen * (i + 1)], 0x01, True, color, background)
def _bytesToNumber(self, data):
r = 0
i = len(data)
while i > 0:
i -= 1
r = r << 8 | data[i]
return r
def _getQuads(self, data, count):
r = []
for i in range(count):
r.append(data[i * 4 + 54 : i * 4 + 58])
return r
BITMAP_COMPRESSION_NO = 0
BITMAP_COMPRESSION_RLE8 = 1
BITMAP_COMPRESSION_RLE4 = 2
BITMAP_COMPRESSION_FIELDS = 3
def startDrawBitmapFile(self, x, y):
pass
def bitmapFileHelper(self, buf):
pass
def endDrawBitmapFile(self):
pass
def bitmapFile(self, x, y, path):
try:
f = open(path, "rb")
except:
print("open file error")
return
c = bytearray(f.read())
f.close()
if c[0] != 0x42 and c[1] != 0x4d:
print("file error")
print(c[0])
print(c[1])
return
DIBOffset = self._bytesToNumber(c[10:14])
width = self._bytesToNumber(c[18:22])
height = self._bytesToNumber(c[22:26])
colorBits = self._bytesToNumber(c[28:30])
compression = self._bytesToNumber(c[30:32])
# print("w: %d, h: %d, colorBits: %d" %(width, height, colorBits))
if colorBits == 24:
width3 = width * 3
for i in range(height):
self.startDrawBitmapFile(x, y + height - i)
buf = []
left = DIBOffset + i * width3
i = 0
while i < width3:
buf.append(c[left + i + 2])
buf.append(c[left + i + 1])
buf.append(c[left + i + 0])
i += 3
self.bitmapFileHelper(buf)
self.endDrawBitmapFile()
elif colorBits == 1:
quads = self._getQuads(c, 2)
addr = DIBOffset
if compression == self.BITMAP_COMPRESSION_NO:
addrCountComplement = (width // 8 + 1) % 4
if addrCountComplement != 0:
addrCountComplement = 4 - addrCountComplement
for i in range(height):
w = width
addrCount = 0
self.startDrawBitmapFile(x, y + height - i - 1)
buf = []
while w > 0:
d = c[addr + addrCount]
addrCount = addrCount + 1
j = 8
while w > 0 and j > 0:
j -= 1
quad = d & (0x01 << j)
if quad > 0:
quad = 1
buf.append(quads[quad][2])
buf.append(quads[quad][1])
buf.append(quads[quad][0])
w -= 1
self.bitmapFileHelper(buf)
addrCount += addrCountComplement
addr += addrCount
self.endDrawBitmapFile()
else:
print("dont support this bitmap file format yet")
def writeOneChar(self, c):
if len(c) > 1:
c = c[0]
(l, width, height, fmt) = self._fonts.getOneCharacter(c)
temp = self._bmpFmt
self._bmpFmt = fmt
ts = self._textSize
if ord(c) == ord("\n"):
self._textCursorX = 0
self._textCursorY += height * ts + self._textIntervalCol
elif len(l):
temp1 = self._bitmapSize
self._bitmapSize = ts
self._textCursorX += self._textIntervalRow
if self._textCursorX + ts * width > self._width:
self.fillRect(self._textCursorX, self._textCursorY, self._width - self._textCursorX, self._fonts._extensionFontsHeight * ts + self._textIntervalCol, self._textBackground)
self._textCursorX = self._textIntervalRow
self._textCursorY += ts * self._fonts._extensionFontsHeight + self._textIntervalCol
self.fillRect(self._textCursorX, self._textCursorY, self._fonts._extensionFontsWidth * ts + self._textIntervalRow, self._fonts._extensionFontsHeight * ts + self._textIntervalCol, self._textBackground)
self.bitmap(self._textCursorX, self._textCursorY, l, width, height, self._textColor, self._textBackground)
self._textCursorX += ts * width
self._bitmapSize = temp1
self._bmpFmt = temp

View File

@@ -0,0 +1,69 @@
# -*- coding:utf-8 -*-
import json
class Fonts:
def __init__(self):
self._haveFontsABC = False
self._fontsABC = {}
self._fontsABCWidth = 0
self._fontsABCHeight = 0
self._fontsABCFmt = ""
self._haveExtensionFonts = False
self._extensionFontsWidth = 0
self._extensionFontsHeight = 0
self._enableDefaultFonts = True
def setFontsABC(self, fonts):
self._haveFontsABC = True
self._fontsABC = fonts.fonts
self._fontsABCWidth = fonts.width
self._fontsABCHeight = fonts.height
self._fontsABCFmt = fonts.fmt
self._extensionFontsWidth = fonts.width * 2
self._extensionFontsHeight = fonts.height * 2
def setExFonts(self, obj):
self._haveExtensionFonts = True
self._extensionFonts = obj
self._enableDefaultFonts = False
def setEnableDefaultFonts(self, opt):
if opt:
self._enableDefaultFonts = True
else:
self._enableDefaultFonts = False
def setExFontsFmt(self, width, height):
if self._haveExtensionFonts:
self._extensionFonts.setFmt(width, height)
self._extensionFontsWidth = width
self._extensionFontsHeight = height
def getOneCharacter(self, c):
w = 0
h = 0
fmt = "UNKNOW"
rslt = []
done = False
if self._haveFontsABC and self._enableDefaultFonts:
try:
rslt = self._fontsABC[c]
w = self._fontsABCWidth
h = self._fontsABCHeight
fmt = self._fontsABCFmt
done = True
except:
# print("try get fonts ABC faild")
pass
if self._haveExtensionFonts and done == False:
try:
(rslt, w, h, fmt) = self._extensionFonts.getOne(c)
done = True
except:
print("try get unicode fonts faild: %s" %(c))
return (rslt, w, h, fmt)

View File

@@ -0,0 +1,25 @@
# -*- coding:utf-8 -*-
import sys
class PrintString:
def __init__(self):
pass
def writeOneChar(self, ch):
pass
def printStr(self, c):
try:
c = str(c)
except:
return
if sys.version_info.major == 2:
c = c.decode("utf-8")
for i in c:
self.writeOneChar(i)
def printStrLn(self, c):
self.printStr(c)
self.writeOneChar("\n")

View File

@@ -0,0 +1,250 @@
# -*- coding:utf-8 -*-
import time
import sys
sys.path.append("..")
import RPi.GPIO as RPIGPIO
from .dfrobot_display.dfrobot_display import DFRobot_Display
from .display_extension import fonts_8_16 as fonts_ABC
try:
from .spi import SPI
from .gpio import GPIO
except:
print("unknow platform")
exit()
CONFIG_IL0376F = {
}
CONFIG_IL3895 = {
}
class DFRobot_Epaper(DFRobot_Display):
XDOT = 128
YDOT = 250
FULL = True
PART = False
def __init__(self, width = 250, height = 122):
DFRobot_Display.__init__(self, width, height)
# length = width * height // 8
length = 4000
self._displayBuffer = bytearray(length)
i = 0
while i < length:
self._displayBuffer[i] = 0xff
i = i + 1
self._isBusy = False
self._busyExitEdge = GPIO.RISING
self._fonts.setFontsABC(fonts_ABC)
self.setExFontsFmt(16, 16)
def _busyCB(self, channel):
self._isBusy = False
def setBusyExitEdge(self, edge):
if edge != GPIO.HIGH and edge != GPIO.LOW:
return
self._busyEdge = edge
def begin(self):
pass
#self._init()
#self._powerOn()
#self.setBusyCB(self._busyCB)
#self._powerOn()
def setBuffer(self, buffer):
self._displayBuffer = buffer
def pixel(self, x, y, color):
if x < 0 or x >= self._width:
return
if y < 0 or y >= self._height:
return
x = int(x)
y = int(y)
m = int(x * 16 + (y + 1) / 8)
sy = int((y + 1) % 8)
if color == self.WHITE:
if sy != 0:
self._displayBuffer[m] = self._displayBuffer[m] | int(pow(2, 8 - sy))
else:
self._displayBuffer[m - 1] = self._displayBuffer[m - 1] | 1
elif color == self.BLACK:
if sy != 0:
self._displayBuffer[m] = self._displayBuffer[m] & (0xff - int(pow(2, 8 - sy)))
else:
self._displayBuffer[m - 1] = self._displayBuffer[m - 1] & 0xfe
def _initLut(self, mode):
if mode == self.FULL:
self.writeCmdAndData(0x32, [ 0xA0, 0x90, 0x50, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x50, 0x90, 0xA0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0xA0, 0x90, 0x50, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x50, 0x90, 0xA0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x0F, 0x0F, 0x00, 0x00, 0x00,
0x0F, 0x0F, 0x00, 0x00, 0x03,
0x0F, 0x0F, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00,
])
elif mode == self.PART:
self.writeCmdAndData(0x32, [0xA0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x50, 0x00, 0x00, 0x00, 0x00, 0x00,0x00, 0x00, 0x00, 0x00,
0xA0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x50, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x0f, 0x00, 0x00, 0x00, 0x00,
0x0, 0x00, 0x00, 0x00, 0x00,
0x0, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00,
])
def _setRamData(self, xStart, xEnd, yStart, yStart1, yEnd, yEnd1):
self.writeCmdAndData(0x44, [xStart, xEnd])
self.writeCmdAndData(0x45, [yStart, yStart1, yEnd, yEnd1])
def _setRamPointer(self, x, y, y1):
self.writeCmdAndData(0x4e, [x])
self.writeCmdAndData(0x4f, [y, y1])
def _init(self,mode):
self.writeCmdAndData(0x12, [])
self.writeCmdAndData(0x01, [0xf9, 0x00, 0x00])
self.writeCmdAndData(0x74, [0x54])
self.writeCmdAndData(0x7e, [0x3b])
self.writeCmdAndData(0x11, [0x01])
self._setRamData(0x00, 0x0f, 0xf9,0x00, 0x00, 0x00)
self.writeCmdAndData(0x3c, [0x03])
self._setRamPointer(0x00, 0xf9, 0x00)
self.writeCmdAndData(0x21, [0x08])
self.writeCmdAndData(0x2c, [0x50])
self.writeCmdAndData(0x03, [0x15])
self.writeCmdAndData(0x04, [0x41,0xa8,0x32])
self.writeCmdAndData(0x3a, [0x2c])
self.writeCmdAndData(0x3b, [0x0b])
self.writeCmdAndData(0x0c, [0x8b,0x9c,0x96,0x0f])
def _writeDisRam(self, sizeX, sizeY):
if sizeX % 8 != 0:
sizeX = sizeX + (8 - sizeX % 8)
sizeX = sizeX // 8
self.writeCmdAndData(0x24, self._displayBuffer[0: sizeX * sizeY])
def _updateDis(self, mode):
if mode == self.FULL:
self.writeCmdAndData(0x22, [0xc7])
elif mode == self.PART:
self.writeCmdAndData(0x22, [0xc7])
else:
return
self.writeCmdAndData(0x20, [])
def _waitBusyExit(self):
temp = 0
while self.readBusy() != False:
time.sleep(0.01)
temp = temp + 1
if (temp % 200) == 0:
print("waitBusyExit")
def _powerOn(self):
self.writeCmdAndData(0x22, [0xC0])
self.writeCmdAndData(0x20, [])
def _powerOff(self):
self.writeCmdAndData(0x10, [0x01])
time.sleep(0.1)
def _disPart(self, xStart, xEnd, yStart, yEnd):
self._setRamData(xStart // 8, xEnd // 8, yEnd % 256, yEnd // 256, yStart % 256, yStart // 256)
self._setRamPointer(xStart // 8, yEnd % 256, yEnd // 256)
self._writeDisRam(xEnd - xStart, yEnd - yStart + 1)
self._updateDis(self.PART)
def flush(self, mode):
if mode != self.FULL and mode != self.PART:
return
self._init(mode)
self._initLut(mode)
self._powerOn()
if mode == self.PART:
self._disPart(0, self.XDOT - 1, 0, self.YDOT - 1)
else:
self._setRamPointer(0x00, (self.YDOT - 1) % 256, (self.YDOT - 1) // 256)
self._writeDisRam(self.XDOT, self.YDOT)
self._updateDis(self.FULL)
def startDrawBitmapFile(self, x, y):
self._bitmapFileStartX = x
self._bitmapFileStartY = y
def bitmapFileHelper(self, buf):
for i in range(len(buf) // 3):
addr = i * 3
if buf[addr] == 0x00 and buf[addr + 1] == 0x00 and buf[addr + 2] == 0x00:
self.pixel(self._bitmapFileStartX, self._bitmapFileStartY, self.BLACK)
else:
self.pixel(self._bitmapFileStartX, self._bitmapFileStartY, self.WHITE)
self._bitmapFileStartX += 1
def endDrawBitmapFile(self):
self.flush(self.PART)
class DFRobot_Epaper_SPI(DFRobot_Epaper):
def __init__(self, bus, dev, cs, cd, busy):
DFRobot_Epaper.__init__(self)
self._spi = SPI(bus, dev)
self._cs = GPIO(cs, GPIO.OUT)
self._cd = GPIO(cd, GPIO.OUT)
self._busy = GPIO(busy, GPIO.IN)
def writeCmdAndData(self, cmd, data = []):
self._waitBusyExit()
self._cs.setOut(GPIO.LOW)
self._cd.setOut(GPIO.LOW)
self._spi.transfer([cmd])
self._cd.setOut(GPIO.HIGH)
self._spi.transfer(data)
self._cs.setOut(GPIO.HIGH)
def readBusy(self):
return self._busy.read()
def setBusyCB(self, cb):
self._busy.setInterrupt(self._busyExitEdge, cb)
def __del__(self):
RPIGPIO.cleanup()

View File

@@ -0,0 +1,101 @@
fonts = { # left to right, msb to bottom, lsb to top
" ": [0x00,0x00,0x00,0x00,0x00,0x00],
"!": [0x00,0x00,0x5F,0x00,0x00,0x00],
"\"": [0x00,0x07,0x00,0x07,0x00,0x00],
"#": [0x14,0x7F,0x14,0x7F,0x14,0x00],
"$": [0x24,0x2A,0x7F,0x2A,0x12,0x00],
"%": [0x23,0x13,0x08,0x64,0x62,0x00],
"&": [0x36,0x49,0x56,0x20,0x50,0x00],
"'": [0x00,0x08,0x07,0x03,0x00,0x00],
"(": [0x00,0x1C,0x22,0x41,0x00,0x00],
")": [0x00,0x41,0x22,0x1C,0x00,0x00],
"*": [0x24,0x18,0x7E,0x18,0x24,0x00],
"+": [0x08,0x08,0x3E,0x08,0x08,0x00],
",": [0x00,0x80,0x70,0x30,0x00,0x00],
"-": [0x08,0x08,0x08,0x08,0x08,0x00],
".": [0x00,0x00,0x60,0x60,0x00,0x00],
"/": [0x20,0x10,0x08,0x04,0x02,0x00],
"0": [0x3E,0x41,0x49,0x41,0x3E,0x00],
"1": [0x00,0x42,0x7F,0x40,0x00,0x00],
"2": [0x72,0x49,0x49,0x49,0x46,0x00],
"3": [0x21,0x41,0x49,0x4D,0x32,0x00],
"4": [0x18,0x14,0x12,0x7F,0x10,0x00],
"5": [0x27,0x45,0x45,0x45,0x38,0x00],
"6": [0x3C,0x4A,0x49,0x49,0x31,0x00],
"7": [0x41,0x21,0x11,0x09,0x07,0x00],
"8": [0x36,0x49,0x49,0x49,0x36,0x00],
"9": [0x46,0x49,0x49,0x29,0x16,0x00],
":": [0x00,0x00,0x14,0x00,0x00,0x00],
";": [0x00,0x40,0x34,0x00,0x00,0x00],
"<": [0x00,0x08,0x14,0x22,0x41,0x00],
"=": [0x14,0x14,0x14,0x14,0x14,0x00],
">": [0x00,0x41,0x22,0x14,0x08,0x00],
"?": [0x02,0x01,0x59,0x09,0x06,0x00],
"@": [0x3E,0x41,0x5D,0x59,0x4E,0x00],
"A": [0x7C,0x12,0x11,0x12,0x7C,0x00],
"B": [0x7F,0x49,0x49,0x49,0x36,0x00],
"C": [0x3E,0x41,0x41,0x41,0x22,0x00],
"D": [0x7F,0x41,0x41,0x41,0x3E,0x00],
"E": [0x7F,0x49,0x49,0x49,0x41,0x00],
"F": [0x7F,0x09,0x09,0x09,0x01,0x00],
"G": [0x3E,0x41,0x41,0x51,0x73,0x00],
"H": [0x7F,0x08,0x08,0x08,0x7F,0x00],
"I": [0x00,0x41,0x7F,0x41,0x00,0x00],
"J": [0x20,0x40,0x41,0x3F,0x01,0x00],
"K": [0x7F,0x08,0x14,0x22,0x41,0x00],
"L": [0x7F,0x40,0x40,0x40,0x40,0x00],
"M": [0x7F,0x02,0x1C,0x02,0x7F,0x00],
"N": [0x7F,0x04,0x08,0x10,0x7F,0x00],
"O": [0x3E,0x41,0x41,0x41,0x3E,0x00],
"P": [0x7F,0x09,0x09,0x09,0x06,0x00],
"Q": [0x3E,0x41,0x51,0x21,0x5E,0x00],
"R": [0x7F,0x09,0x19,0x29,0x46,0x00],
"S": [0x26,0x49,0x49,0x49,0x32,0x00],
"T": [0x03,0x01,0x7F,0x01,0x03,0x00],
"U": [0x3F,0x40,0x40,0x40,0x3F,0x00],
"V": [0x1F,0x20,0x40,0x20,0x1F,0x00],
"W": [0x3F,0x40,0x38,0x40,0x3F,0x00],
"X": [0x63,0x14,0x08,0x14,0x63,0x00],
"Y": [0x03,0x04,0x78,0x04,0x03,0x00],
"Z": [0x61,0x59,0x49,0x4D,0x43,0x00],
"[": [0x00,0x7F,0x41,0x41,0x41,0x00],
"\\": [0x02,0x04,0x08,0x10,0x20,0x00],
"]": [0x00,0x41,0x41,0x41,0x7f,0x00],
"^": [0x04,0x02,0x01,0x02,0x04,0x00],
"_": [0x40,0x40,0x40,0x40,0x46,0x00],
"'": [0x00,0x03,0x07,0x08,0x00,0x00],
"a": [0x20,0x54,0x54,0x78,0x40,0x00],
"b": [0x7F,0x28,0x44,0x44,0x38,0x00],
"c": [0x38,0x44,0x44,0x44,0x28,0x00],
"d": [0x38,0x44,0x44,0x28,0x7F,0x00],
"e": [0x38,0x54,0x54,0x54,0x18,0x00],
"f": [0x00,0x08,0x7E,0x09,0x02,0x00],
"g": [0x38,0xA4,0xA4,0x9C,0x78,0x00],
"h": [0x7F,0x08,0x04,0x04,0x78,0x00],
"i": [0x00,0x44,0x7D,0x40,0x00,0x00],
"j": [0x20,0x40,0x40,0x3D,0x00,0x00],
"k": [0x7F,0x10,0x28,0x44,0x00,0x00],
"l": [0x00,0x41,0x7F,0x40,0x00,0x00],
"m": [0x7C,0x04,0x78,0x04,0x78,0x00],
"n": [0x7C,0x08,0x04,0x04,0x78,0x00],
"o": [0x38,0x44,0x44,0x44,0x38,0x00],
"p": [0xFC,0x18,0x24,0x24,0x18,0x00],
"q": [0x18,0x24,0x24,0x18,0xFC,0x00],
"r": [0x7C,0x08,0x04,0x04,0x08,0x00],
"s": [0x48,0x54,0x54,0x54,0x24,0x00],
"t": [0x04,0x04,0x3F,0x44,0x24,0x00],
"u": [0x3C,0x40,0x40,0x20,0x7C,0x00],
"v": [0x1C,0x20,0x40,0x20,0x1C,0x00],
"w": [0x3C,0x40,0x20,0x40,0x3C,0x00],
"x": [0x44,0x28,0x10,0x28,0x44,0x00],
"y": [0x4C,0x90,0x90,0x90,0x7C,0x00],
"z": [0x44,0x64,0x54,0x4C,0x44,0x00],
"{": [0x00,0x08,0x36,0x41,0x00,0x00],
"|": [0x00,0x00,0x77,0x00,0x00,0x00],
"}": [0x00,0x41,0x36,0x08,0x00,0x00],
"~": [0x02,0x01,0x02,0x04,0x02,0x00]
}
width = 6
height = 8
fmt = "LRMBLT"

View File

@@ -0,0 +1,101 @@
fonts = { # top to bottom, msb left, lsb right
" ": [0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00],
"!": [0x00,0x00,0x18,0x3C,0x3C,0x3C,0x18,0x18,0x18,0x00,0x18,0x18,0x00,0x00,0x00,0x00],
"\"": [0x00,0x63,0x63,0x63,0x22,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00],
"#": [0x00,0x00,0x00,0x36,0x36,0x7F,0x36,0x36,0x36,0x7F,0x36,0x36,0x00,0x00,0x00,0x00],
"$": [0x0C,0x0C,0x3E,0x63,0x61,0x60,0x3E,0x03,0x03,0x43,0x63,0x3E,0x0C,0x0C,0x00,0x00],
"%": [0x00,0x00,0x00,0x00,0x00,0x61,0x63,0x06,0x0C,0x18,0x33,0x63,0x00,0x00,0x00,0x00],
"&": [0x00,0x00,0x00,0x1C,0x36,0x36,0x1C,0x3B,0x6E,0x66,0x66,0x3B,0x00,0x00,0x00,0x00],
"'": [0x00,0x30,0x30,0x30,0x60,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00],
"(": [0x00,0x00,0x0C,0x18,0x18,0x30,0x30,0x30,0x30,0x18,0x18,0x0C,0x00,0x00,0x00,0x00],
")": [0x00,0x00,0x18,0x0C,0x0C,0x06,0x06,0x06,0x06,0x0C,0x0C,0x18,0x00,0x00,0x00,0x00],
"*": [0x00,0x00,0x00,0x00,0x42,0x66,0x3C,0xFF,0x3C,0x66,0x42,0x00,0x00,0x00,0x00,0x00],
"+": [0x00,0x00,0x00,0x00,0x18,0x18,0x18,0xFF,0x18,0x18,0x18,0x00,0x00,0x00,0x00,0x00],
",": [0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x18,0x18,0x18,0x30,0x00,0x00],
"-": [0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xFF,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00],
".": [0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x18,0x18,0x00,0x00,0x00,0x00],
"/": [0x00,0x00,0x01,0x03,0x07,0x0E,0x1C,0x38,0x70,0xE0,0xC0,0x80,0x00,0x00,0x00,0x00],
"0": [0x00,0x00,0x3E,0x63,0x63,0x63,0x6B,0x6B,0x63,0x63,0x63,0x3E,0x00,0x00,0x00,0x00],
"1": [0x00,0x00,0x0C,0x1C,0x3C,0x0C,0x0C,0x0C,0x0C,0x0C,0x0C,0x3F,0x00,0x00,0x00,0x00],
"2": [0x00,0x00,0x3E,0x63,0x03,0x06,0x0C,0x18,0x30,0x61,0x63,0x7F,0x00,0x00,0x00,0x00],
"3": [0x00,0x00,0x3E,0x63,0x03,0x03,0x1E,0x03,0x03,0x03,0x63,0x3E,0x00,0x00,0x00,0x00],
"4": [0x00,0x00,0x06,0x0E,0x1E,0x36,0x66,0x66,0x7F,0x06,0x06,0x0F,0x00,0x00,0x00,0x00],
"5": [0x00,0x00,0x7F,0x60,0x60,0x60,0x7E,0x03,0x03,0x63,0x73,0x3E,0x00,0x00,0x00,0x00],
"6": [0x00,0x00,0x1C,0x30,0x60,0x60,0x7E,0x63,0x63,0x63,0x63,0x3E,0x00,0x00,0x00,0x00],
"7": [0x00,0x00,0x7F,0x63,0x03,0x06,0x06,0x0C,0x0C,0x18,0x18,0x18,0x00,0x00,0x00,0x00],
"8": [0x00,0x00,0x3E,0x63,0x63,0x63,0x3E,0x63,0x63,0x63,0x63,0x3E,0x00,0x00,0x00,0x00],
"9": [0x00,0x00,0x3E,0x63,0x63,0x63,0x63,0x3F,0x03,0x03,0x06,0x3C,0x00,0x00,0x00,0x00],
":": [0x00,0x00,0x00,0x00,0x00,0x18,0x18,0x00,0x00,0x00,0x18,0x18,0x00,0x00,0x00,0x00],
";": [0x00,0x00,0x00,0x00,0x00,0x18,0x18,0x00,0x00,0x00,0x18,0x18,0x18,0x30,0x00,0x00],
"<": [0x00,0x00,0x00,0x06,0x0C,0x18,0x30,0x60,0x30,0x18,0x0C,0x06,0x00,0x00,0x00,0x00],
"=": [0x00,0x00,0x00,0x00,0x00,0x00,0x7E,0x00,0x00,0x7E,0x00,0x00,0x00,0x00,0x00,0x00],
">": [0x00,0x00,0x00,0x60,0x30,0x18,0x0C,0x06,0x0C,0x18,0x30,0x60,0x00,0x00,0x00,0x00],
"?": [0x00,0x00,0x3E,0x63,0x63,0x06,0x0C,0x0C,0x0C,0x00,0x0C,0x0C,0x00,0x00,0x00,0x00],
"@": [0x00,0x00,0x3E,0x63,0x63,0x6F,0x6B,0x6B,0x6E,0x60,0x60,0x3E,0x00,0x00,0x00,0x00],
"A": [0x00,0x00,0x08,0x1C,0x36,0x63,0x63,0x63,0x7F,0x63,0x63,0x63,0x00,0x00,0x00,0x00],
"B": [0x00,0x00,0x7E,0x33,0x33,0x33,0x3E,0x33,0x33,0x33,0x33,0x7E,0x00,0x00,0x00,0x00],
"C": [0x00,0x00,0x1E,0x33,0x61,0x60,0x60,0x60,0x60,0x61,0x33,0x1E,0x00,0x00,0x00,0x00],
"D": [0x00,0x00,0x7C,0x36,0x33,0x33,0x33,0x33,0x33,0x33,0x36,0x7C,0x00,0x00,0x00,0x00],
"E": [0x00,0x00,0x7F,0x33,0x31,0x34,0x3C,0x34,0x30,0x31,0x33,0x7F,0x00,0x00,0x00,0x00],
"F": [0x00,0x00,0x7F,0x33,0x31,0x34,0x3C,0x34,0x30,0x30,0x30,0x78,0x00,0x00,0x00,0x00],
"G": [0x00,0x00,0x1E,0x33,0x61,0x60,0x60,0x6F,0x63,0x63,0x37,0x1D,0x00,0x00,0x00,0x00],
"H": [0x00,0x00,0x63,0x63,0x63,0x63,0x7F,0x63,0x63,0x63,0x63,0x63,0x00,0x00,0x00,0x00],
"I": [0x00,0x00,0x3C,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x3C,0x00,0x00,0x00,0x00],
"J": [0x00,0x00,0x0F,0x06,0x06,0x06,0x06,0x06,0x06,0x66,0x66,0x3C,0x00,0x00,0x00,0x00],
"K": [0x00,0x00,0x73,0x33,0x36,0x36,0x3C,0x36,0x36,0x33,0x33,0x73,0x00,0x00,0x00,0x00],
"L": [0x00,0x00,0x78,0x30,0x30,0x30,0x30,0x30,0x30,0x31,0x33,0x7F,0x00,0x00,0x00,0x00],
"M": [0x00,0x00,0x63,0x77,0x7F,0x6B,0x63,0x63,0x63,0x63,0x63,0x63,0x00,0x00,0x00,0x00],
"N": [0x00,0x00,0x63,0x63,0x73,0x7B,0x7F,0x6F,0x67,0x63,0x63,0x63,0x00,0x00,0x00,0x00],
"O": [0x00,0x00,0x1C,0x36,0x63,0x63,0x63,0x63,0x63,0x63,0x36,0x1C,0x00,0x00,0x00,0x00],
"P": [0x00,0x00,0x7E,0x33,0x33,0x33,0x3E,0x30,0x30,0x30,0x30,0x78,0x00,0x00,0x00,0x00],
"Q": [0x00,0x00,0x3E,0x63,0x63,0x63,0x63,0x63,0x63,0x6B,0x6F,0x3E,0x06,0x07,0x00,0x00],
"R": [0x00,0x00,0x7E,0x33,0x33,0x33,0x3E,0x36,0x36,0x33,0x33,0x73,0x00,0x00,0x00,0x00],
"S": [0x00,0x00,0x3E,0x63,0x63,0x30,0x1C,0x06,0x03,0x63,0x63,0x3E,0x00,0x00,0x00,0x00],
"T": [0x00,0x00,0xFF,0xDB,0x99,0x18,0x18,0x18,0x18,0x18,0x18,0x3C,0x00,0x00,0x00,0x00],
"U": [0x00,0x00,0x63,0x63,0x63,0x63,0x63,0x63,0x63,0x63,0x63,0x3E,0x00,0x00,0x00,0x00],
"V": [0x00,0x00,0x63,0x63,0x63,0x63,0x63,0x63,0x63,0x36,0x1C,0x08,0x00,0x00,0x00,0x00],
"W": [0x00,0x00,0x63,0x63,0x63,0x63,0x63,0x6B,0x6B,0x7F,0x36,0x36,0x00,0x00,0x00,0x00],
"X": [0x00,0x00,0xC3,0xC3,0x66,0x3C,0x18,0x18,0x3C,0x66,0xC3,0xC3,0x00,0x00,0x00,0x00],
"Y": [0x00,0x00,0xC3,0xC3,0xC3,0x66,0x3C,0x18,0x18,0x18,0x18,0x3C,0x00,0x00,0x00,0x00],
"Z": [0x00,0x00,0x7F,0x63,0x43,0x06,0x0C,0x18,0x30,0x61,0x63,0x7F,0x00,0x00,0x00,0x00],
"[": [0x00,0x00,0x3C,0x30,0x30,0x30,0x30,0x30,0x30,0x30,0x30,0x3C,0x00,0x00,0x00,0x00],
"\\": [0x00,0x00,0x80,0xC0,0xE0,0x70,0x38,0x1C,0x0E,0x07,0x03,0x01,0x00,0x00,0x00,0x00],
"]": [0x00,0x00,0x3C,0x0C,0x0C,0x0C,0x0C,0x0C,0x0C,0x0C,0x0C,0x3C,0x00,0x00,0x00,0x00],
"^": [0x08,0x1C,0x36,0x63,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00],
"_": [0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xFF,0x00,0x00,0x00],
"'": [0x18,0x18,0x0C,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00],
"a": [0x00,0x00,0x00,0x00,0x00,0x3C,0x46,0x06,0x3E,0x66,0x66,0x3B,0x00,0x00,0x00,0x00],
"b": [0x00,0x00,0x70,0x30,0x30,0x3C,0x36,0x33,0x33,0x33,0x33,0x6E,0x00,0x00,0x00,0x00],
"c": [0x00,0x00,0x00,0x00,0x00,0x3E,0x63,0x60,0x60,0x60,0x63,0x3E,0x00,0x00,0x00,0x00],
"d": [0x00,0x00,0x0E,0x06,0x06,0x1E,0x36,0x66,0x66,0x66,0x66,0x3B,0x00,0x00,0x00,0x00],
"e": [0x00,0x00,0x00,0x00,0x00,0x3E,0x63,0x63,0x7E,0x60,0x63,0x3E,0x00,0x00,0x00,0x00],
"f": [0x00,0x00,0x1C,0x36,0x32,0x30,0x7C,0x30,0x30,0x30,0x30,0x78,0x00,0x00,0x00,0x00],
"g": [0x00,0x00,0x00,0x00,0x00,0x3B,0x66,0x66,0x66,0x66,0x3E,0x06,0x66,0x3C,0x00,0x00],
"h": [0x00,0x00,0x70,0x30,0x30,0x36,0x3B,0x33,0x33,0x33,0x33,0x73,0x00,0x00,0x00,0x00],
"i": [0x00,0x00,0x0C,0x0C,0x00,0x1C,0x0C,0x0C,0x0C,0x0C,0x0C,0x1E,0x00,0x00,0x00,0x00],
"j": [0x00,0x00,0x06,0x06,0x00,0x0E,0x06,0x06,0x06,0x06,0x06,0x66,0x66,0x3C,0x00,0x00],
"k": [0x00,0x00,0x70,0x30,0x30,0x33,0x33,0x36,0x3C,0x36,0x33,0x73,0x00,0x00,0x00,0x00],
"l": [0x00,0x00,0x1C,0x0C,0x0C,0x0C,0x0C,0x0C,0x0C,0x0C,0x0C,0x1E,0x00,0x00,0x00,0x00],
"m": [0x00,0x00,0x00,0x00,0x00,0x6E,0x7F,0x6B,0x6B,0x6B,0x6B,0x6B,0x00,0x00,0x00,0x00],
"n": [0x00,0x00,0x00,0x00,0x00,0x6E,0x33,0x33,0x33,0x33,0x33,0x33,0x00,0x00,0x00,0x00],
"o": [0x00,0x00,0x00,0x00,0x00,0x3E,0x63,0x63,0x63,0x63,0x63,0x3E,0x00,0x00,0x00,0x00],
"p": [0x00,0x00,0x00,0x00,0x00,0x6E,0x33,0x33,0x33,0x33,0x3E,0x30,0x30,0x78,0x00,0x00],
"q": [0x00,0x00,0x00,0x00,0x00,0x3B,0x66,0x66,0x66,0x66,0x3E,0x06,0x06,0x0F,0x00,0x00],
"r": [0x00,0x00,0x00,0x00,0x00,0x6E,0x3B,0x33,0x30,0x30,0x30,0x78,0x00,0x00,0x00,0x00],
"s": [0x00,0x00,0x00,0x00,0x00,0x3E,0x63,0x38,0x0E,0x03,0x63,0x3E,0x00,0x00,0x00,0x00],
"t": [0x00,0x00,0x08,0x18,0x18,0x7E,0x18,0x18,0x18,0x18,0x1B,0x0E,0x00,0x00,0x00,0x00],
"u": [0x00,0x00,0x00,0x00,0x00,0x66,0x66,0x66,0x66,0x66,0x66,0x3B,0x00,0x00,0x00,0x00],
"v": [0x00,0x00,0x00,0x00,0x00,0x63,0x63,0x36,0x36,0x1C,0x1C,0x08,0x00,0x00,0x00,0x00],
"w": [0x00,0x00,0x00,0x00,0x00,0x63,0x63,0x63,0x6B,0x6B,0x7F,0x36,0x00,0x00,0x00,0x00],
"x": [0x00,0x00,0x00,0x00,0x00,0x63,0x36,0x1C,0x1C,0x1C,0x36,0x63,0x00,0x00,0x00,0x00],
"y": [0x00,0x00,0x00,0x00,0x00,0x63,0x63,0x63,0x63,0x63,0x3F,0x03,0x06,0x3C,0x00,0x00],
"z": [0x00,0x00,0x00,0x00,0x00,0x7F,0x66,0x0C,0x18,0x30,0x63,0x7F,0x00,0x00,0x00,0x00],
"{": [0x00,0x00,0x0E,0x18,0x18,0x18,0x70,0x18,0x18,0x18,0x18,0x0E,0x00,0x00,0x00,0x00],
"|": [0x00,0x00,0x18,0x18,0x18,0x18,0x18,0x00,0x18,0x18,0x18,0x18,0x18,0x00,0x00,0x00],
"}": [0x00,0x00,0x70,0x18,0x18,0x18,0x0E,0x18,0x18,0x18,0x18,0x70,0x00,0x00,0x00,0x00],
"~": [0x00,0x00,0x3B,0x6E,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00]
}
width = 8
height = 16
fmt = "TBMLLR"

View File

@@ -0,0 +1,80 @@
# -*- coding:utf-8 -*-
'''
depends: freetype-py
'''
import freetype
import math
#import sys
#reload(sys)
#sys.setdefaultencoding("utf-8")
import importlib,sys
#importlib.reload(sys)
class Freetype_Helper:
def __init__(self, filePath):
self._face = freetype.Face(filePath)
self._width = 0
self._height = 0
self._fade = 96
def setFmt(self, width, height):
self._width = int(width)
self._height = int(height)
self._face.set_pixel_sizes(width, height)
def setDisLowerLimite(self, limite):
self._fade = limite
def getOne(self, ch):
self._face.load_char(ch)
bitmap = self._face.glyph.bitmap
originY = self._face.glyph.bitmap_top
width = bitmap.width
height = bitmap.rows
buffer = bitmap.buffer
rslt = []
# width = 4
# height = 4
# buffer = [0xff] * width * height
if height > self._height:
buffer = buffer[0: width * self._height]
height = self._height
if width > self._width:
for i in range(height):
rslt += buffer[i * width: i * width + self._width]
width = self._width
buffer = rslt
rslt = []
if (ord(ch) >= ord(" ") and ord(ch) <= ord("~")) or width <= (self._width // 2):
rslt = [0] * (((self._width - 1) // 16 + 1) * self._height + 1)
left = (self._width // 2 - width) // 2
lineDataLen = (self._width - 1) // 16 + 1
else:
rslt = [0] * (((self._width - 1) // 8 + 1) * self._height + 1)
left = (self._width - width) // 2
lineDataLen = (self._width - 1) // 8 + 1
if left < 0:
left = 0
# top = (self._height - height) * lineDataLen // 2
top = ((self._height * 8 + 5) // 10 - originY) * lineDataLen
if top < 0:
top = 0
for i in range(height):
for j in range(width):
if buffer[i * width + j] > self._fade:
try:
rslt[i * lineDataLen + (j + left) // 8 + top] |= 0x80 >> ((j + left) % 8)
except:
print("freetype_helper getOne err: width: %d, height: %d, top: %d, left: %d, rslt_len: %d, originY: %d" %(width, height, top, left, len(rslt), originY))
raise("err")
# rslt[i * lineDataLen + (j + left) // 8 + top] |= 0x80 >> ((j + left) % 8)
if (ord(ch) >= ord(" ") and ord(ch) <= ord("~")) or width < (self._width // 2):
return (rslt, self._width // 2, self._height, "TBMLLR")
else:
return (rslt, self._width, self._height, "TBMLLR")

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 44 KiB

View File

@@ -0,0 +1,2 @@
wqydkzh.ttf = 文泉驿等宽正黑.ttf GPL2 license </br>
zkklt.ttf = 站酷快乐体.ttf Chinese open source fonts file, use with freetype_helper.py

View File

@@ -0,0 +1,64 @@
# -*- coding:utf-8 -*-
import time
import RPi.GPIO as RPIGPIO
RPIGPIO.setmode(RPIGPIO.BCM)
RPIGPIO.setwarnings(False)
class GPIO:
HIGH = RPIGPIO.HIGH
LOW = RPIGPIO.LOW
OUT = RPIGPIO.OUT
IN = RPIGPIO.IN
RISING = RPIGPIO.RISING
FALLING = RPIGPIO.FALLING
BOTH = RPIGPIO.BOTH
def __init__(self, pin, mode, defaultOut = HIGH):
self._pin = pin
self._fInt = None
self._intDone = True
self._intMode = None
if mode == self.OUT:
RPIGPIO.setup(pin, mode)
if defaultOut == self.HIGH:
RPIGPIO.output(pin, defaultOut)
else:
RPIGPIO.output(pin, self.LOW)
else:
RPIGPIO.setup(pin, self.IN, pull_up_down = RPIGPIO.PUD_UP)
def setOut(self, level):
if level:
RPIGPIO.output(self._pin, self.HIGH)
else:
RPIGPIO.output(self._pin, self.LOW)
def _intCB(self, status):
if self._intDone:
self._intDone = False
time.sleep(0.02)
if self._intMode == self.BOTH:
self._fInt()
elif self._intMode == self.RISING and self.read() == self.HIGH:
self._fInt()
elif self._intMode == self.FALLING and self.read() == self.LOW:
self._fInt()
self._intDone = True
def setInterrupt(self, mode, cb):
if mode != self.RISING and mode != self.FALLING and mode != self.BOTH:
return
self._intMode = mode
RPIGPIO.add_event_detect(self._pin, mode, self._intCB)
self._fInt = cb
def read(self):
return RPIGPIO.input(self._pin)
def cleanup(self):
RPIGPIO.cleanup()

View File

@@ -0,0 +1,21 @@
# -*- coding:utf-8 -*-
'''
change i2c frequency on raspberry:
1. edit /etc/modprobe.d
2. add line:
options i2c_bcm2708 baudrate=400000
'''
import smbus
class I2C:
def __init__(self, port):
self._bus = smbus.SMBus(port)
def writeBytes(self, addr, reg, buf):
self._bus.write_block_data(addr, reg, buf)
def readBytes(self, addr, reg, length):
return self._bus.read_block_data(addr, reg, length)

View File

@@ -0,0 +1,21 @@
# -*- coding:utf-8 -*-
import spidev
class SPI:
MODE_1 = 1
MODE_2 = 2
MODE_3 = 3
MODE_4 = 4
def __init__(self, bus, dev, speed = 3900000, mode = MODE_4):
self._bus = spidev.SpiDev()
self._bus.open(0, 0)
self._bus.no_cs = True
self._bus.max_speed_hz = speed
def transfer(self, buf):
if len(buf):
return self._bus.xfer(buf)
return []

View File

@@ -9,7 +9,7 @@ frame_lock = Lock()
def update_frame(img):
global frame_lock, frame_path, frame_format
if not os.path.exists(os.path.basename(frame_path)):
os.makedirs(os.path.basename(frame_path))
if not os.path.exists(os.path.dirname(frame_path)):
os.makedirs(os.path.dirname(frame_path))
with frame_lock:
img.save(frame_path, format=frame_format)

View File

@@ -5,6 +5,37 @@
Plugins
{% endblock %}
{% block styles %}
{{ super() }}
<style>
.tooltip {
position: relative;
display: inline-block;
}
.tooltip .tooltiptext {
visibility: hidden;
width: 200px;
background-color: #3388cc;
color: #fff;
text-align: center;
border-radius: 10px;
border: 2px solid black;
padding: 20px 0;
position: absolute;
z-index: 1;
top: 100%;
left: 50%;
margin-left: -100px;
}
.tooltip:hover .tooltiptext {
visibility: visible;
}
</style>
{% endblock %}
{% block script %}
$(function(){
$('input[type=checkbox]').change(function(e) {
@@ -27,11 +58,19 @@ $(function(){
{% endblock %}
{% block content %}
<div id="container">
{% for name in database.keys() %}
{% for name in database.keys() | sort %}
{% set has_info = name in loaded and loaded[name].__description__ is defined %}
<div class="plugins-box">
<h4>
<a {% if name in loaded and loaded[name].on_webhook is defined %} href="/plugins/{{name}}" {% endif %}>{{name}}</a>
</h4>
<div class="tooltip">
<h4>
<a {% if name in loaded and loaded[name].on_webhook is defined %} href="/plugins/{{name}}" {% endif %}>{{name}}</a>
</h4>
{% if has_info %}
<span class="tooltiptext">{{ loaded[name].__description__ }}</span>
{% else %}
<span class="tooltiptext">Description can't be loaded yet.</span>
{% endif %}
</div>
<form method="POST" action="/plugins/toggle">
<input type="checkbox" data-role="flipswitch" name="enabled" id="flip-checkbox-{{name}}" data-on-text="Enabled" data-off-text="Disabled" data-wrapper-class="custom-size-flipswitch" {% if name in loaded %} checked {% endif %}>
<input type="hidden" name="csrf_token" value="{{ csrf_token() }}"/>

View File

@@ -257,8 +257,11 @@ def load_config(args):
elif config['ui']['display']['type'] in ('lcdhat',):
config['ui']['display']['type'] = 'lcdhat'
elif config['ui']['display']['type'] in ('dfrobot', 'df'):
config['ui']['display']['type'] = 'dfrobot'
elif config['ui']['display']['type'] in ('dfrobot_1', 'df1'):
config['ui']['display']['type'] = 'dfrobot_1'
elif config['ui']['display']['type'] in ('dfrobot_2', 'df2'):
config['ui']['display']['type'] = 'dfrobot_2'
elif config['ui']['display']['type'] in ('ws_154inch', 'ws154inch', 'waveshare_154inch', 'waveshare154inch'):
config['ui']['display']['type'] = 'waveshare154inch'