diff --git a/builder/data/etc/bash_completion.d/pwnagotchi_completion.sh b/builder/data/etc/bash_completion.d/pwnagotchi_completion.sh new file mode 100644 index 000000000..37c686424 --- /dev/null +++ b/builder/data/etc/bash_completion.d/pwnagotchi_completion.sh @@ -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 diff --git a/builder/data/usr/bin/bettercap-launcher b/builder/data/usr/bin/bettercap-launcher index 2598a25c2..3bc21855f 100755 --- a/builder/data/usr/bin/bettercap-launcher +++ b/builder/data/usr/bin/bettercap-launcher @@ -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 diff --git a/builder/data/usr/bin/decryption-webserver b/builder/data/usr/bin/decryption-webserver index 96e7ba6b4..7eb9c52fa 100755 --- a/builder/data/usr/bin/decryption-webserver +++ b/builder/data/usr/bin/decryption-webserver @@ -88,6 +88,27 @@ POST_RESPONSE = """ text-align: center; } + diff --git a/builder/data/usr/bin/pwnlib b/builder/data/usr/bin/pwnlib index cbf358c86..fd2d0dea8 100755 --- a/builder/data/usr/bin/pwnlib +++ b/builder/data/usr/bin/pwnlib @@ -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 @@ -158,7 +177,8 @@ EOF pkill wpa_supplicant pkill dnsmasq - kill "$(pgrep -f "decryption-webserver")" + pid="$(pgrep -f "decryption-webserver")" + [[ -n "$pid" ]] && kill "$pid" return 0 } diff --git a/pwnagotchi/_version.py b/pwnagotchi/_version.py index c3b384154..0bb84ff29 100644 --- a/pwnagotchi/_version.py +++ b/pwnagotchi/_version.py @@ -1 +1 @@ -__version__ = '1.5.2' +__version__ = '1.5.3' diff --git a/pwnagotchi/agent.py b/pwnagotchi/agent.py index bd884790c..7fe6a76db 100644 --- a/pwnagotchi/agent.py +++ b/pwnagotchi/agent.py @@ -307,7 +307,7 @@ def _load_recovery_data(self, delete=True, no_exceptions=True): 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 @@ def _fetch_stats(self): 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 @@ async def _on_event(self, msg): 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 @@ def _event_poller(self, loop): 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): diff --git a/pwnagotchi/bettercap.py b/pwnagotchi/bettercap.py index 422f9cde4..1f9217d0b 100644 --- a/pwnagotchi/bettercap.py +++ b/pwnagotchi/bettercap.py @@ -47,6 +47,8 @@ async def start_websocket(self, consumer): 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}) diff --git a/pwnagotchi/defaults.toml b/pwnagotchi/defaults.toml index 937cca19d..40a424075 100644 --- a/pwnagotchi/defaults.toml +++ b/pwnagotchi/defaults.toml @@ -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/" diff --git a/pwnagotchi/plugins/__init__.py b/pwnagotchi/plugins/__init__.py index 08d0b763f..53ee93424 100644 --- a/pwnagotchi/plugins/__init__.py +++ b/pwnagotchi/plugins/__init__.py @@ -58,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) diff --git a/pwnagotchi/plugins/cmd.py b/pwnagotchi/plugins/cmd.py index af8226c5b..eb87cb96a 100644 --- a/pwnagotchi/plugins/cmd.py +++ b/pwnagotchi/plugins/cmd.py @@ -1,6 +1,5 @@ # Handles the commandline stuff -import sys import os import logging import glob @@ -11,7 +10,6 @@ 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) + 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) + try: + os.makedirs(SAVE_DIR, exist_ok=True) + before_update = _analyse_dir(SAVE_DIR) - download_file(REPO_URL, os.path.join(SAVE_DIR, DEST)) + download_file(REPO_URL, os.path.join(SAVE_DIR, DEST)) - logging.info('Unzipping...') - unzip(DEST, SAVE_DIR, strip_dirs=1) + logging.info('Unzipping...') + unzip(DEST, SAVE_DIR, strip_dirs=1) - after_update = _analyse_dir(SAVE_DIR) + after_update = _analyse_dir(SAVE_DIR) - b_len = len(before_update) - a_len = len(after_update) + 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) + 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 + 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) + if changed: + logging.info('%d file(s) were changed.', changed) - return 0 - except Exception as ex: - logging.error('Error while updating plugins %s', ex) - return 1 + except Exception as ex: + logging.error('Error while updating plugins: %s', ex) + rc = 1 + return rc diff --git a/pwnagotchi/plugins/default/bt-tether.py b/pwnagotchi/plugins/default/bt-tether.py index 696ddf94c..c0a8ac22d 100644 --- a/pwnagotchi/plugins/default/bt-tether.py +++ b/pwnagotchi/plugins/default/bt-tether.py @@ -419,15 +419,19 @@ def interface(self): 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 @@ def on_loaded(self): 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 @@ def on_loaded(self): return logging.info("BT-TETHER: Successfully loaded ...") - self.ready = True - - def on_unload(self, ui): - 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): - if not self.ready: - return + while self.running: + time.sleep(1) - 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 @@ def on_ui_update(self, ui): 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 @@ def on_ui_update(self, ui): 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 @@ def on_ui_update(self, ui): 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 @@ def on_ui_update(self, ui): 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 @@ def on_ui_update(self, ui): 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) diff --git a/pwnagotchi/plugins/default/logtail.py b/pwnagotchi/plugins/default/logtail.py index 3f633e1af..83ff885c3 100644 --- a/pwnagotchi/plugins/default/logtail.py +++ b/pwnagotchi/plugins/default/logtail.py @@ -207,7 +207,6 @@ } document.body.style.cursor = 'default'; } - {% endblock %} {% block content %} @@ -254,6 +253,7 @@ def on_loaded(self): """ logging.info("Logtail plugin loaded.") + def on_webhook(self, path, request): if not self.ready: return "Plugin not ready" @@ -264,10 +264,7 @@ def on_webhook(self, path, request): if path == 'stream': def generate(): with open(self.config['main']['log']['path']) as f: - # https://stackoverflow.com/questions/39549426/read-multiple-lines-from-a-file-batch-by-batch/39549901#39549901 - n = 1024 - for n_lines in iter(lambda: ''.join(islice(f, n)), ''): - yield n_lines + yield ''.join(f.readlines()[-self.options.get('max-lines', 4096):]) while True: yield f.readline() diff --git a/pwnagotchi/plugins/default/onlinehashcrack.py b/pwnagotchi/plugins/default/onlinehashcrack.py index d2fd83726..1a7484f29 100644 --- a/pwnagotchi/plugins/default/onlinehashcrack.py +++ b/pwnagotchi/plugins/default/onlinehashcrack.py @@ -12,7 +12,7 @@ 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 @@ def _upload_to_ohc(self, path, timeout=30): 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 @@ def _download_cracked(self, save_file, timeout=120): 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 @@ def on_internet_available(self, agent): 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') diff --git a/pwnagotchi/plugins/default/watchdog.py b/pwnagotchi/plugins/default/watchdog.py new file mode 100644 index 000000000..6a370eb2d --- /dev/null +++ b/pwnagotchi/plugins/default/watchdog.py @@ -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) diff --git a/pwnagotchi/plugins/default/webgpsmap.py b/pwnagotchi/plugins/default/webgpsmap.py index be5361911..413f19a78 100644 --- a/pwnagotchi/plugins/default/webgpsmap.py +++ b/pwnagotchi/plugins/default/webgpsmap.py @@ -230,7 +230,7 @@ def load_gps_from_dir(self, gpsdir, newest_only=False): } # 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() diff --git a/pwnagotchi/plugins/default/wigle.py b/pwnagotchi/plugins/default/wigle.py index e344e066a..f28175773 100644 --- a/pwnagotchi/plugins/default/wigle.py +++ b/pwnagotchi/plugins/default/wigle.py @@ -106,7 +106,7 @@ def __init__(self): 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 @@ def on_internet_available(self, agent): 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 @@ def on_internet_available(self, agent): 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 @@ def on_internet_available(self, agent): 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) diff --git a/pwnagotchi/plugins/default/wpa-sec.py b/pwnagotchi/plugins/default/wpa-sec.py index 6d48479ac..5c14018b4 100644 --- a/pwnagotchi/plugins/default/wpa-sec.py +++ b/pwnagotchi/plugins/default/wpa-sec.py @@ -39,7 +39,7 @@ def _upload_to_wpasec(self, path, timeout=30): 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 @@ def on_loaded(self): 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 @@ def on_internet_available(self, agent): 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']: diff --git a/pwnagotchi/ui/display.py b/pwnagotchi/ui/display.py index 181ed769c..186030fa2 100644 --- a/pwnagotchi/ui/display.py +++ b/pwnagotchi/ui/display.py @@ -49,8 +49,11 @@ def is_oledhat(self): 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' diff --git a/pwnagotchi/ui/hw/__init__.py b/pwnagotchi/ui/hw/__init__.py index 432cb54c9..eb927dcbd 100644 --- a/pwnagotchi/ui/hw/__init__.py +++ b/pwnagotchi/ui/hw/__init__.py @@ -2,7 +2,8 @@ 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) diff --git a/pwnagotchi/ui/hw/dfrobot.py b/pwnagotchi/ui/hw/dfrobot1.py similarity index 84% rename from pwnagotchi/ui/hw/dfrobot.py rename to pwnagotchi/ui/hw/dfrobot1.py index 1d9c8ee34..732faf8e8 100644 --- a/pwnagotchi/ui/hw/dfrobot.py +++ b/pwnagotchi/ui/hw/dfrobot1.py @@ -3,9 +3,9 @@ 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 @@ def layout(self): 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): diff --git a/pwnagotchi/ui/hw/dfrobot2.py b/pwnagotchi/ui/hw/dfrobot2.py new file mode 100644 index 000000000..995afd387 --- /dev/null +++ b/pwnagotchi/ui/hw/dfrobot2.py @@ -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) diff --git a/pwnagotchi/ui/hw/libs/dfrobot/v1/__init__.py b/pwnagotchi/ui/hw/libs/dfrobot/v1/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/pwnagotchi/ui/hw/libs/dfrobot/dfrobot.py b/pwnagotchi/ui/hw/libs/dfrobot/v1/dfrobot.py similarity index 100% rename from pwnagotchi/ui/hw/libs/dfrobot/dfrobot.py rename to pwnagotchi/ui/hw/libs/dfrobot/v1/dfrobot.py diff --git a/pwnagotchi/ui/hw/libs/dfrobot/dfrobot_epaper.py b/pwnagotchi/ui/hw/libs/dfrobot/v1/dfrobot_epaper.py similarity index 100% rename from pwnagotchi/ui/hw/libs/dfrobot/dfrobot_epaper.py rename to pwnagotchi/ui/hw/libs/dfrobot/v1/dfrobot_epaper.py diff --git a/pwnagotchi/ui/hw/libs/dfrobot/gpio.py b/pwnagotchi/ui/hw/libs/dfrobot/v1/gpio.py similarity index 100% rename from pwnagotchi/ui/hw/libs/dfrobot/gpio.py rename to pwnagotchi/ui/hw/libs/dfrobot/v1/gpio.py diff --git a/pwnagotchi/ui/hw/libs/dfrobot/spi.py b/pwnagotchi/ui/hw/libs/dfrobot/v1/spi.py similarity index 100% rename from pwnagotchi/ui/hw/libs/dfrobot/spi.py rename to pwnagotchi/ui/hw/libs/dfrobot/v1/spi.py diff --git a/pwnagotchi/ui/hw/libs/dfrobot/v2/__init__.py b/pwnagotchi/ui/hw/libs/dfrobot/v2/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/pwnagotchi/ui/hw/libs/dfrobot/v2/dfrobot.py b/pwnagotchi/ui/hw/libs/dfrobot/v2/dfrobot.py new file mode 100644 index 000000000..02ebb8d6f --- /dev/null +++ b/pwnagotchi/ui/hw/libs/dfrobot/v2/dfrobot.py @@ -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) diff --git a/pwnagotchi/ui/hw/libs/dfrobot/v2/dfrobot_display/__init__.py b/pwnagotchi/ui/hw/libs/dfrobot/v2/dfrobot_display/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/pwnagotchi/ui/hw/libs/dfrobot/v2/dfrobot_display/__init__.pyc b/pwnagotchi/ui/hw/libs/dfrobot/v2/dfrobot_display/__init__.pyc new file mode 100644 index 000000000..d739fd191 Binary files /dev/null and b/pwnagotchi/ui/hw/libs/dfrobot/v2/dfrobot_display/__init__.pyc differ diff --git a/pwnagotchi/ui/hw/libs/dfrobot/v2/dfrobot_display/dfrobot_display.py b/pwnagotchi/ui/hw/libs/dfrobot/v2/dfrobot_display/dfrobot_display.py new file mode 100644 index 000000000..7a055f713 --- /dev/null +++ b/pwnagotchi/ui/hw/libs/dfrobot/v2/dfrobot_display/dfrobot_display.py @@ -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 diff --git a/pwnagotchi/ui/hw/libs/dfrobot/v2/dfrobot_display/dfrobot_display.pyc b/pwnagotchi/ui/hw/libs/dfrobot/v2/dfrobot_display/dfrobot_display.pyc new file mode 100644 index 000000000..d9bcc2660 Binary files /dev/null and b/pwnagotchi/ui/hw/libs/dfrobot/v2/dfrobot_display/dfrobot_display.pyc differ diff --git a/pwnagotchi/ui/hw/libs/dfrobot/v2/dfrobot_display/dfrobot_fonts.py b/pwnagotchi/ui/hw/libs/dfrobot/v2/dfrobot_display/dfrobot_fonts.py new file mode 100644 index 000000000..c85b9ae01 --- /dev/null +++ b/pwnagotchi/ui/hw/libs/dfrobot/v2/dfrobot_display/dfrobot_fonts.py @@ -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) diff --git a/pwnagotchi/ui/hw/libs/dfrobot/v2/dfrobot_display/dfrobot_fonts.pyc b/pwnagotchi/ui/hw/libs/dfrobot/v2/dfrobot_display/dfrobot_fonts.pyc new file mode 100644 index 000000000..274bbf43a Binary files /dev/null and b/pwnagotchi/ui/hw/libs/dfrobot/v2/dfrobot_display/dfrobot_fonts.pyc differ diff --git a/pwnagotchi/ui/hw/libs/dfrobot/v2/dfrobot_display/dfrobot_printString.py b/pwnagotchi/ui/hw/libs/dfrobot/v2/dfrobot_display/dfrobot_printString.py new file mode 100644 index 000000000..b32acf6e5 --- /dev/null +++ b/pwnagotchi/ui/hw/libs/dfrobot/v2/dfrobot_display/dfrobot_printString.py @@ -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") diff --git a/pwnagotchi/ui/hw/libs/dfrobot/v2/dfrobot_display/dfrobot_printString.pyc b/pwnagotchi/ui/hw/libs/dfrobot/v2/dfrobot_display/dfrobot_printString.pyc new file mode 100644 index 000000000..51dc169eb Binary files /dev/null and b/pwnagotchi/ui/hw/libs/dfrobot/v2/dfrobot_display/dfrobot_printString.pyc differ diff --git a/pwnagotchi/ui/hw/libs/dfrobot/v2/dfrobot_epaper.py b/pwnagotchi/ui/hw/libs/dfrobot/v2/dfrobot_epaper.py new file mode 100644 index 000000000..3f2b001cc --- /dev/null +++ b/pwnagotchi/ui/hw/libs/dfrobot/v2/dfrobot_epaper.py @@ -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() diff --git a/pwnagotchi/ui/hw/libs/dfrobot/v2/display_extension/__init__.py b/pwnagotchi/ui/hw/libs/dfrobot/v2/display_extension/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/pwnagotchi/ui/hw/libs/dfrobot/v2/display_extension/__init__.pyc b/pwnagotchi/ui/hw/libs/dfrobot/v2/display_extension/__init__.pyc new file mode 100644 index 000000000..38130c007 Binary files /dev/null and b/pwnagotchi/ui/hw/libs/dfrobot/v2/display_extension/__init__.pyc differ diff --git a/pwnagotchi/ui/hw/libs/dfrobot/v2/display_extension/fonts_6_8.py b/pwnagotchi/ui/hw/libs/dfrobot/v2/display_extension/fonts_6_8.py new file mode 100644 index 000000000..b1edc5e44 --- /dev/null +++ b/pwnagotchi/ui/hw/libs/dfrobot/v2/display_extension/fonts_6_8.py @@ -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" diff --git a/pwnagotchi/ui/hw/libs/dfrobot/v2/display_extension/fonts_8_16.py b/pwnagotchi/ui/hw/libs/dfrobot/v2/display_extension/fonts_8_16.py new file mode 100644 index 000000000..35331ce25 --- /dev/null +++ b/pwnagotchi/ui/hw/libs/dfrobot/v2/display_extension/fonts_8_16.py @@ -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" diff --git a/pwnagotchi/ui/hw/libs/dfrobot/v2/display_extension/fonts_8_16.pyc b/pwnagotchi/ui/hw/libs/dfrobot/v2/display_extension/fonts_8_16.pyc new file mode 100644 index 000000000..96d1b66ef Binary files /dev/null and b/pwnagotchi/ui/hw/libs/dfrobot/v2/display_extension/fonts_8_16.pyc differ diff --git a/pwnagotchi/ui/hw/libs/dfrobot/v2/display_extension/freetype_helper.py b/pwnagotchi/ui/hw/libs/dfrobot/v2/display_extension/freetype_helper.py new file mode 100644 index 000000000..ee7be9060 --- /dev/null +++ b/pwnagotchi/ui/hw/libs/dfrobot/v2/display_extension/freetype_helper.py @@ -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") diff --git a/pwnagotchi/ui/hw/libs/dfrobot/v2/display_extension/freetype_helper.pyc b/pwnagotchi/ui/hw/libs/dfrobot/v2/display_extension/freetype_helper.pyc new file mode 100644 index 000000000..b45f122f7 Binary files /dev/null and b/pwnagotchi/ui/hw/libs/dfrobot/v2/display_extension/freetype_helper.pyc differ diff --git a/pwnagotchi/ui/hw/libs/dfrobot/v2/display_extension/logo_colorbits1.bmp b/pwnagotchi/ui/hw/libs/dfrobot/v2/display_extension/logo_colorbits1.bmp new file mode 100644 index 000000000..0eace4ed6 Binary files /dev/null and b/pwnagotchi/ui/hw/libs/dfrobot/v2/display_extension/logo_colorbits1.bmp differ diff --git a/pwnagotchi/ui/hw/libs/dfrobot/v2/display_extension/logo_colorbits24.bmp b/pwnagotchi/ui/hw/libs/dfrobot/v2/display_extension/logo_colorbits24.bmp new file mode 100644 index 000000000..44bf65711 Binary files /dev/null and b/pwnagotchi/ui/hw/libs/dfrobot/v2/display_extension/logo_colorbits24.bmp differ diff --git a/pwnagotchi/ui/hw/libs/dfrobot/v2/display_extension/readme.md b/pwnagotchi/ui/hw/libs/dfrobot/v2/display_extension/readme.md new file mode 100644 index 000000000..15ffa7084 --- /dev/null +++ b/pwnagotchi/ui/hw/libs/dfrobot/v2/display_extension/readme.md @@ -0,0 +1,2 @@ +wqydkzh.ttf = 文泉驿等宽正黑.ttf GPL2 license
+zkklt.ttf = 站酷快乐体.ttf Chinese open source fonts file, use with freetype_helper.py \ No newline at end of file diff --git a/pwnagotchi/ui/hw/libs/dfrobot/v2/display_extension/wqydkzh.ttf b/pwnagotchi/ui/hw/libs/dfrobot/v2/display_extension/wqydkzh.ttf new file mode 100644 index 000000000..3b7649ad4 Binary files /dev/null and b/pwnagotchi/ui/hw/libs/dfrobot/v2/display_extension/wqydkzh.ttf differ diff --git a/pwnagotchi/ui/hw/libs/dfrobot/v2/display_extension/zkklt.ttf b/pwnagotchi/ui/hw/libs/dfrobot/v2/display_extension/zkklt.ttf new file mode 100644 index 000000000..ba2ad8a7d Binary files /dev/null and b/pwnagotchi/ui/hw/libs/dfrobot/v2/display_extension/zkklt.ttf differ diff --git a/pwnagotchi/ui/hw/libs/dfrobot/v2/gpio.py b/pwnagotchi/ui/hw/libs/dfrobot/v2/gpio.py new file mode 100644 index 000000000..3a6a597f1 --- /dev/null +++ b/pwnagotchi/ui/hw/libs/dfrobot/v2/gpio.py @@ -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() diff --git a/pwnagotchi/ui/hw/libs/dfrobot/v2/i2c.py b/pwnagotchi/ui/hw/libs/dfrobot/v2/i2c.py new file mode 100644 index 000000000..7b1048f50 --- /dev/null +++ b/pwnagotchi/ui/hw/libs/dfrobot/v2/i2c.py @@ -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) diff --git a/pwnagotchi/ui/hw/libs/dfrobot/v2/spi.py b/pwnagotchi/ui/hw/libs/dfrobot/v2/spi.py new file mode 100644 index 000000000..f1f39f016 --- /dev/null +++ b/pwnagotchi/ui/hw/libs/dfrobot/v2/spi.py @@ -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 [] diff --git a/pwnagotchi/ui/web/templates/plugins.html b/pwnagotchi/ui/web/templates/plugins.html index 5835c1808..41970fbd5 100644 --- a/pwnagotchi/ui/web/templates/plugins.html +++ b/pwnagotchi/ui/web/templates/plugins.html @@ -5,6 +5,37 @@ Plugins {% endblock %} +{% block styles %} + {{ super() }} + +{% endblock %} + {% block script %} $(function(){ $('input[type=checkbox]').change(function(e) { @@ -27,11 +58,19 @@ {% endblock %} {% block content %}
- {% for name in database.keys() %} + {% for name in database.keys() | sort %} + {% set has_info = name in loaded and loaded[name].__description__ is defined %}
-

- {{name}} -

+
+

+ {{name}} +

+ {% if has_info %} + {{ loaded[name].__description__ }} + {% else %} + Description can't be loaded yet. + {% endif %} +
diff --git a/pwnagotchi/utils.py b/pwnagotchi/utils.py index dee51dfda..a142e44cf 100644 --- a/pwnagotchi/utils.py +++ b/pwnagotchi/utils.py @@ -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'