Merge pull request #855 from dadav/develop

Bug fixes
This commit is contained in:
Simone Margaritelli 2020-04-20 11:13:25 +02:00 committed by GitHub
commit a3cf49244e
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
54 changed files with 1821 additions and 106 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

@ -88,6 +88,27 @@ POST_RESPONSE = """
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;">

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
@ -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
}

View File

@ -1 +1 @@
__version__ = '1.5.2'
__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/"

View File

@ -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)

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

@ -207,7 +207,6 @@ TEMPLATE = """
}
document.body.style.cursor = 'default';
}
{% endblock %}
{% block content %}
@ -254,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"
@ -264,10 +264,7 @@ class Logtail(plugins.Plugin):
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()

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

@ -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'