mirror of
https://github.com/jayofelony/pwnagotchi.git
synced 2025-07-01 18:37:27 -04:00
Compare commits
98 Commits
wpa-2-patc
...
v2.8.7.2
Author | SHA1 | Date | |
---|---|---|---|
7edd752664 | |||
cc550aa236 | |||
ff033d41d3 | |||
6d0a0d8d5f | |||
b33af167d4 | |||
9053762e71 | |||
26fef7dd99 | |||
5dd17291f7 | |||
bac79f3465 | |||
bb8dfe0244 | |||
1b16975031 | |||
dd2b559ebc | |||
93ba2a7f79 | |||
dd18072002 | |||
1eafbb841f | |||
aa817288ea | |||
43285eb2c0 | |||
4b29476c2f | |||
1a8ff930d9 | |||
8a65afdc8f | |||
6fcd1b0e23 | |||
e0b0f5d800 | |||
2243079bf6 | |||
e10eb31ed1 | |||
1c8114e444 | |||
91638a151f | |||
d4adaabcbd | |||
006cdb0fe3 | |||
29386fb945 | |||
4b4646d604 | |||
3fae6ec312 | |||
09a82aa0b4 | |||
541865a2eb | |||
928de2769d | |||
6987840da2 | |||
58de15ce2d | |||
b5ea3da619 | |||
6c68d4608f | |||
14a727954b | |||
aeada2ee6e | |||
ebb8fef3fc | |||
e531288369 | |||
2015b56c5d | |||
2497475057 | |||
0bdbbc23fd | |||
111787544f | |||
efa5d8b197 | |||
9e5fb49d7c | |||
cbbd7d5a6d | |||
90c5818123 | |||
eb76ef4c54 | |||
59e42daeb5 | |||
5b7014c68e | |||
42cc3136ee | |||
2a243870f7 | |||
8f043ff5ef | |||
255bbdbc08 | |||
6f57b66cf4 | |||
a4c25e9996 | |||
9495d55296 | |||
69e7503d67 | |||
25cb1f2175 | |||
54b7ce5d0d | |||
1f9afd541d | |||
87eae76a58 | |||
6691257036 | |||
2149c5dbdf | |||
77af772b4b | |||
14e4fc6d47 | |||
5be8580a59 | |||
f7a599ab8f | |||
5f907b236a | |||
bc92613700 | |||
501ec9ca2b | |||
e5e0180f3c | |||
ea60808700 | |||
a34db250b5 | |||
d29aca15a9 | |||
8531b89771 | |||
59d510d0e1 | |||
913b1a6e1d | |||
de2cdaa3c9 | |||
f2cf34a8b9 | |||
bbb46128fe | |||
46713b6e73 | |||
aa2b09fb21 | |||
9125e43b20 | |||
7e4d926b14 | |||
6417ef5a78 | |||
46c03063fe | |||
d5384d5a81 | |||
e800c66e57 | |||
e3a404cb39 | |||
6cb6aaeb81 | |||
5761dac073 | |||
0585fe75fe | |||
a7634a2b4a | |||
ca4feb895e |
2
.github/workflows/publish.yml
vendored
2
.github/workflows/publish.yml
vendored
@ -61,7 +61,7 @@ jobs:
|
|||||||
run: mv "pwnagotchi-64bit.img.xz" "pwnagotchi-$VERSION-64bit.img.xz"
|
run: mv "pwnagotchi-64bit.img.xz" "pwnagotchi-$VERSION-64bit.img.xz"
|
||||||
|
|
||||||
- name: Release
|
- name: Release
|
||||||
uses: softprops/action-gh-release@v1
|
uses: softprops/action-gh-release@v2
|
||||||
with:
|
with:
|
||||||
prerelease: true
|
prerelease: true
|
||||||
tag_name: v${{ env.VERSION }}
|
tag_name: v${{ env.VERSION }}
|
||||||
|
3
Makefile
3
Makefile
@ -46,13 +46,16 @@ packer: clean
|
|||||||
sudo mv /tmp/packer /usr/bin/packer
|
sudo mv /tmp/packer /usr/bin/packer
|
||||||
|
|
||||||
image: clean packer
|
image: clean packer
|
||||||
|
export LC_ALL=en_GB.UTF-8
|
||||||
cd builder && sudo /usr/bin/packer init combined.json.pkr.hcl && sudo $(UNSHARE) /usr/bin/packer build -var "pwn_hostname=$(PWN_HOSTNAME)" -var "pwn_version=$(PWN_VERSION)" combined.json.pkr.hcl
|
cd builder && sudo /usr/bin/packer init combined.json.pkr.hcl && sudo $(UNSHARE) /usr/bin/packer build -var "pwn_hostname=$(PWN_HOSTNAME)" -var "pwn_version=$(PWN_VERSION)" combined.json.pkr.hcl
|
||||||
|
|
||||||
bullseye: clean packer
|
bullseye: clean packer
|
||||||
|
export LC_ALL=en_GB.UTF-8
|
||||||
cd builder && sudo /usr/bin/packer init data/32bit/raspberrypi32.json.pkr.hcl && sudo $(UNSHARE) /usr/bin/packer build -var "pwn_hostname=$(PWN_HOSTNAME)" -var "pwn_version=$(PWN_VERSION)" data/32bit/raspberrypi32.json.pkr.hcl
|
cd builder && sudo /usr/bin/packer init data/32bit/raspberrypi32.json.pkr.hcl && sudo $(UNSHARE) /usr/bin/packer build -var "pwn_hostname=$(PWN_HOSTNAME)" -var "pwn_version=$(PWN_VERSION)" data/32bit/raspberrypi32.json.pkr.hcl
|
||||||
sudo pishrink -vaZ pwnagotchi-32bit.img
|
sudo pishrink -vaZ pwnagotchi-32bit.img
|
||||||
|
|
||||||
bookworm: clean packer
|
bookworm: clean packer
|
||||||
|
export LC_ALL=en_GB.UTF-8
|
||||||
cd builder && sudo /usr/bin/packer init data/64bit/raspberrypi64.json.pkr.hcl && sudo $(UNSHARE) /usr/bin/packer build -var "pwn_hostname=$(PWN_HOSTNAME)" -var "pwn_version=$(PWN_VERSION)" data/64bit/raspberrypi64.json.pkr.hcl
|
cd builder && sudo /usr/bin/packer init data/64bit/raspberrypi64.json.pkr.hcl && sudo $(UNSHARE) /usr/bin/packer build -var "pwn_hostname=$(PWN_HOSTNAME)" -var "pwn_version=$(PWN_VERSION)" data/64bit/raspberrypi64.json.pkr.hcl
|
||||||
sudo pishrink -vaZ pwnagotchi-64bit.img
|
sudo pishrink -vaZ pwnagotchi-64bit.img
|
||||||
|
|
||||||
|
251
bin/pwnagotchi
251
bin/pwnagotchi
@ -7,6 +7,7 @@ import sys
|
|||||||
import toml
|
import toml
|
||||||
import requests
|
import requests
|
||||||
import os
|
import os
|
||||||
|
import re
|
||||||
|
|
||||||
import pwnagotchi
|
import pwnagotchi
|
||||||
from pwnagotchi import utils
|
from pwnagotchi import utils
|
||||||
@ -18,85 +19,83 @@ from pwnagotchi import fs
|
|||||||
from pwnagotchi.utils import DottedTomlEncoder, parse_version as version_to_tuple
|
from pwnagotchi.utils import DottedTomlEncoder, parse_version as version_to_tuple
|
||||||
|
|
||||||
|
|
||||||
def do_clear(display):
|
def pwnagotchi_cli():
|
||||||
logging.info("clearing the display ...")
|
def do_clear(display):
|
||||||
display.clear()
|
logging.info("clearing the display ...")
|
||||||
sys.exit(0)
|
display.clear()
|
||||||
|
sys.exit(0)
|
||||||
|
|
||||||
|
def do_manual_mode(agent):
|
||||||
|
logging.info("entering manual mode ...")
|
||||||
|
|
||||||
def do_manual_mode(agent):
|
agent.mode = 'manual'
|
||||||
logging.info("entering manual mode ...")
|
agent.last_session.parse(agent.view(), args.skip_session)
|
||||||
|
if not args.skip_session:
|
||||||
agent.mode = 'manual'
|
logging.info(
|
||||||
agent.last_session.parse(agent.view(), args.skip_session)
|
"the last session lasted %s (%d completed epochs, trained for %d), average reward:%s (min:%s max:%s)" % (
|
||||||
if not args.skip_session:
|
agent.last_session.duration_human,
|
||||||
logging.info(
|
agent.last_session.epochs,
|
||||||
"the last session lasted %s (%d completed epochs, trained for %d), average reward:%s (min:%s max:%s)" % (
|
agent.last_session.train_epochs,
|
||||||
agent.last_session.duration_human,
|
agent.last_session.avg_reward,
|
||||||
agent.last_session.epochs,
|
agent.last_session.min_reward,
|
||||||
agent.last_session.train_epochs,
|
agent.last_session.max_reward))
|
||||||
agent.last_session.avg_reward,
|
|
||||||
agent.last_session.min_reward,
|
|
||||||
agent.last_session.max_reward))
|
|
||||||
|
|
||||||
while True:
|
|
||||||
display.on_manual_mode(agent.last_session)
|
|
||||||
time.sleep(5)
|
|
||||||
if grid.is_connected():
|
|
||||||
plugins.on('internet_available', agent)
|
|
||||||
|
|
||||||
|
|
||||||
def do_auto_mode(agent):
|
|
||||||
logging.info("entering auto mode ...")
|
|
||||||
|
|
||||||
agent.mode = 'auto'
|
|
||||||
agent.start()
|
|
||||||
|
|
||||||
while True:
|
|
||||||
try:
|
|
||||||
# recon on all channels
|
|
||||||
agent.recon()
|
|
||||||
# get nearby access points grouped by channel
|
|
||||||
channels = agent.get_access_points_by_channel()
|
|
||||||
# for each channel
|
|
||||||
for ch, aps in channels:
|
|
||||||
agent.set_channel(ch)
|
|
||||||
|
|
||||||
if not agent.is_stale() and agent.any_activity():
|
|
||||||
logging.info("%d access points on channel %d" % (len(aps), ch))
|
|
||||||
|
|
||||||
# for each ap on this channel
|
|
||||||
for ap in aps:
|
|
||||||
# send an association frame in order to get for a PMKID
|
|
||||||
agent.associate(ap)
|
|
||||||
# deauth all client stations in order to get a full handshake
|
|
||||||
for sta in ap['clients']:
|
|
||||||
agent.deauth(ap, sta)
|
|
||||||
time.sleep(1) # delay to not trigger nexmon firmware bugs
|
|
||||||
|
|
||||||
# An interesting effect of this:
|
|
||||||
#
|
|
||||||
# From Pwnagotchi's perspective, the more new access points
|
|
||||||
# and / or client stations nearby, the longer one epoch of
|
|
||||||
# its relative time will take ... basically, in Pwnagotchi's universe,
|
|
||||||
# Wi-Fi electromagnetic fields affect time like gravitational fields
|
|
||||||
# affect ours ... neat ^_^
|
|
||||||
agent.next_epoch()
|
|
||||||
|
|
||||||
|
while True:
|
||||||
|
display.on_manual_mode(agent.last_session)
|
||||||
|
time.sleep(5)
|
||||||
if grid.is_connected():
|
if grid.is_connected():
|
||||||
plugins.on('internet_available', agent)
|
plugins.on('internet_available', agent)
|
||||||
|
|
||||||
except Exception as e:
|
def do_auto_mode(agent):
|
||||||
if str(e).find("wifi.interface not set") > 0:
|
logging.info("entering auto mode ...")
|
||||||
logging.exception("main loop exception due to unavailable wifi device, likely programmatically disabled (%s)", e)
|
|
||||||
logging.info("sleeping 60 seconds then advancing to next epoch to allow for cleanup code to trigger")
|
agent.mode = 'auto'
|
||||||
time.sleep(60)
|
agent.start()
|
||||||
|
|
||||||
|
while True:
|
||||||
|
try:
|
||||||
|
# recon on all channels
|
||||||
|
agent.recon()
|
||||||
|
# get nearby access points grouped by channel
|
||||||
|
channels = agent.get_access_points_by_channel()
|
||||||
|
# for each channel
|
||||||
|
for ch, aps in channels:
|
||||||
|
time.sleep(0.2)
|
||||||
|
agent.set_channel(ch)
|
||||||
|
|
||||||
|
if not agent.is_stale() and agent.any_activity():
|
||||||
|
logging.info("%d access points on channel %d" % (len(aps), ch))
|
||||||
|
|
||||||
|
# for each ap on this channel
|
||||||
|
for ap in aps:
|
||||||
|
# send an association frame in order to get for a PMKID
|
||||||
|
agent.associate(ap)
|
||||||
|
# deauth all client stations in order to get a full handshake
|
||||||
|
for sta in ap['clients']:
|
||||||
|
agent.deauth(ap, sta)
|
||||||
|
time.sleep(1) # delay to not trigger nexmon firmware bugs
|
||||||
|
|
||||||
|
# An interesting effect of this:
|
||||||
|
#
|
||||||
|
# From Pwnagotchi's perspective, the more new access points
|
||||||
|
# and / or client stations nearby, the longer one epoch of
|
||||||
|
# its relative time will take ... basically, in Pwnagotchi's universe,
|
||||||
|
# Wi-Fi electromagnetic fields affect time like gravitational fields
|
||||||
|
# affect ours ... neat ^_^
|
||||||
agent.next_epoch()
|
agent.next_epoch()
|
||||||
else:
|
|
||||||
logging.exception("main loop exception (%s)", e)
|
|
||||||
|
|
||||||
|
if grid.is_connected():
|
||||||
|
plugins.on('internet_available', agent)
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
if str(e).find("wifi.interface not set") > 0:
|
||||||
|
logging.exception("main loop exception due to unavailable wifi device, likely programmatically disabled (%s)", e)
|
||||||
|
logging.info("sleeping 60 seconds then advancing to next epoch to allow for cleanup code to trigger")
|
||||||
|
time.sleep(60)
|
||||||
|
agent.next_epoch()
|
||||||
|
else:
|
||||||
|
logging.exception("main loop exception (%s)", e)
|
||||||
|
|
||||||
if __name__ == '__main__':
|
|
||||||
def add_parsers(parser):
|
def add_parsers(parser):
|
||||||
"""
|
"""
|
||||||
Adds the plugins and google subcommands
|
Adds the plugins and google subcommands
|
||||||
@ -133,6 +132,8 @@ if __name__ == '__main__':
|
|||||||
help="Print the configuration.")
|
help="Print the configuration.")
|
||||||
|
|
||||||
# Jayofelony added these
|
# Jayofelony added these
|
||||||
|
parser.add_argument('--wizard', dest="wizard", action="store_true", default=False,
|
||||||
|
help="Interactive installation of your personal configuration.")
|
||||||
parser.add_argument('--check-update', dest="check_update", action="store_true", default=False,
|
parser.add_argument('--check-update', dest="check_update", action="store_true", default=False,
|
||||||
help="Check for updates on Pwnagotchi. And tells current version.")
|
help="Check for updates on Pwnagotchi. And tells current version.")
|
||||||
parser.add_argument('--donate', dest="donate", action="store_true", default=False,
|
parser.add_argument('--donate', dest="donate", action="store_true", default=False,
|
||||||
@ -157,6 +158,111 @@ if __name__ == '__main__':
|
|||||||
print(pwnagotchi.__version__)
|
print(pwnagotchi.__version__)
|
||||||
sys.exit(0)
|
sys.exit(0)
|
||||||
|
|
||||||
|
if args.wizard:
|
||||||
|
def is_valid_hostname(hostname):
|
||||||
|
if len(hostname) > 255:
|
||||||
|
return False
|
||||||
|
if hostname[-1] == ".":
|
||||||
|
hostname = hostname[:-1] # strip exactly one dot from the right, if present
|
||||||
|
allowed = re.compile("(?!-)[A-Z\d-]{1,63}(?<!-)$", re.IGNORECASE)
|
||||||
|
return all(allowed.match(x) for x in hostname.split("."))
|
||||||
|
|
||||||
|
pwn_restore = input("Do you want to restore the previous configuration?\n\n"
|
||||||
|
"[Y/N]: ")
|
||||||
|
if pwn_restore in ('y', 'yes'):
|
||||||
|
os.system("cp -f /etc/pwnagotchi/config.toml.bak /etc/pwnagotchi/config.toml")
|
||||||
|
print("Your previous configuration is restored, and I will restart in 5 seconds.")
|
||||||
|
time.sleep(5)
|
||||||
|
os.system("service pwnagotchi restart")
|
||||||
|
else:
|
||||||
|
pwn_check = input("This will create a new configuration file and overwrite your current backup, are you sure?\n\n"
|
||||||
|
"[Y/N]: ")
|
||||||
|
if pwn_check.lower() in ('y', 'yes'):
|
||||||
|
os.system("mv -f /etc/pwnagotchi/config.toml /etc/pwnagotchi/config.toml.bak")
|
||||||
|
with open("/etc/pwnagotchi/config.toml", "a+") as f:
|
||||||
|
f.write("# Do not edit this file if you do not know what you are doing!!!\n\n")
|
||||||
|
# Set pwnagotchi name
|
||||||
|
print("Welcome to the interactive installation of your personal Pwnagotchi configuration!\n"
|
||||||
|
"My name is Jayofelony, how may I call you?\n\n")
|
||||||
|
pwn_name = input("Pwnagotchi name (no spaces): ")
|
||||||
|
if pwn_name == "":
|
||||||
|
pwn_name = "Pwnagotchi"
|
||||||
|
print("I shall go by Pwnagotchi from now on!")
|
||||||
|
pwn_name = f"main.name = \"{pwn_name}\"\n"
|
||||||
|
f.write(pwn_name)
|
||||||
|
else:
|
||||||
|
if is_valid_hostname(pwn_name):
|
||||||
|
print(f"I shall go by {pwn_name} from now on!")
|
||||||
|
pwn_name = f"main.name = \"{pwn_name}\"\n"
|
||||||
|
f.write(pwn_name)
|
||||||
|
else:
|
||||||
|
print("You have chosen an invalid name. Please start over.")
|
||||||
|
exit()
|
||||||
|
pwn_whitelist = input("How many networks do you want to whitelist? "
|
||||||
|
"We will also ask a MAC for each network?\n"
|
||||||
|
"Each SSID and BSSID count as 1 network. \n\n"
|
||||||
|
"Be sure to use digits as your answer.\n\n"
|
||||||
|
"Amount of networks: ")
|
||||||
|
if int(pwn_whitelist) > 0:
|
||||||
|
f.write("main.whitelist = [\n")
|
||||||
|
for x in range(int(pwn_whitelist)):
|
||||||
|
ssid = input("SSID (Name): ")
|
||||||
|
bssid = input("BSSID (MAC): ")
|
||||||
|
f.write(f"\t\"{ssid}\",\n")
|
||||||
|
f.write(f"\t\"{bssid}\",\n")
|
||||||
|
f.write("]\n")
|
||||||
|
# set bluetooth tether
|
||||||
|
pwn_bluetooth = input("Do you want to enable BT-Tether?\n\n"
|
||||||
|
"[Y/N] ")
|
||||||
|
if pwn_bluetooth.lower() in ('y', 'yes'):
|
||||||
|
f.write("main.plugins.bt-tether.enabled = true\n\n")
|
||||||
|
pwn_bluetooth_device = input("What device do you use? Android or iOS?\n\n"
|
||||||
|
"Device: ")
|
||||||
|
if pwn_bluetooth_device.lower() == "android":
|
||||||
|
f.write("main.plugins.bt-tether.devices.android-phone.enabled = true\n")
|
||||||
|
pwn_bluetooth_mac = input("What is the bluetooth MAC of your device?\n\n"
|
||||||
|
"MAC: ")
|
||||||
|
if pwn_bluetooth_mac != "":
|
||||||
|
f.write(f"main.plugins.bt-tether.devices.android-phone.mac = \"{pwn_bluetooth_mac}\"\n")
|
||||||
|
elif pwn_bluetooth_device.lower() == "ios":
|
||||||
|
f.write("main.plugins.bt-tether.devices.ios-phone.enabled = true\n")
|
||||||
|
pwn_bluetooth_mac = input("What is the bluetooth MAC of your device?\n\n"
|
||||||
|
"MAC: ")
|
||||||
|
if pwn_bluetooth_mac != "":
|
||||||
|
f.write(f"main.plugins.bt-tether.devices.ios-phone.mac = \"{pwn_bluetooth_mac}\"\n")
|
||||||
|
# set up display settings
|
||||||
|
pwn_display_enabled = input("Do you want to enable a display?\n\n"
|
||||||
|
"[Y/N]: ")
|
||||||
|
if pwn_display_enabled.lower() in ('y', 'yes'):
|
||||||
|
f.write("ui.display.enabled = true\n")
|
||||||
|
pwn_display_type = input("What display do you use?\n\n"
|
||||||
|
"Be sure to check for the correct display type @ \n"
|
||||||
|
"https://github.com/jayofelony/pwnagotchi/blob/master/pwnagotchi/utils.py#L240-L431\n\n"
|
||||||
|
"Display type: ")
|
||||||
|
if pwn_display_type != "":
|
||||||
|
f.write(f"ui.display.type = \"{pwn_display_type}\"\n")
|
||||||
|
pwn_display_invert = input("Do you want to invert the display colors?\n"
|
||||||
|
"N = Black background\n"
|
||||||
|
"Y = White background\n\n"
|
||||||
|
"[Y/N]: ")
|
||||||
|
if pwn_display_invert.lower() in ('y', 'yes'):
|
||||||
|
f.write("ui.invert = true\n")
|
||||||
|
f.close()
|
||||||
|
if pwn_bluetooth.lower() in ('y', 'yes'):
|
||||||
|
if pwn_bluetooth_device.lower == "android":
|
||||||
|
print("To visit the webui when connected with your phone, visit: http://192.168.44.44:8080\n"
|
||||||
|
"Your configuration is done, and I will restart in 5 seconds.")
|
||||||
|
elif pwn_bluetooth_device.lower == "ios":
|
||||||
|
print("To visit the webui when connected with your phone, visit: http://172.20.10.6:8080\n"
|
||||||
|
"Your configuration is done, and I will restart in 5 seconds.")
|
||||||
|
else:
|
||||||
|
print("Your configuration is done, and I will restart in 5 seconds.")
|
||||||
|
time.sleep(5)
|
||||||
|
os.system("service pwnagotchi restart")
|
||||||
|
else:
|
||||||
|
print("Ok, doing nothing.")
|
||||||
|
sys.exit(0)
|
||||||
|
|
||||||
if args.donate:
|
if args.donate:
|
||||||
print("Donations can made @ \n "
|
print("Donations can made @ \n "
|
||||||
"https://www.patreon.com/pwnagotchi_torch \n "
|
"https://www.patreon.com/pwnagotchi_torch \n "
|
||||||
@ -172,7 +278,7 @@ if __name__ == '__main__':
|
|||||||
local = version_to_tuple(pwnagotchi.__version__)
|
local = version_to_tuple(pwnagotchi.__version__)
|
||||||
remote = version_to_tuple(latest_ver)
|
remote = version_to_tuple(latest_ver)
|
||||||
if remote > local:
|
if remote > local:
|
||||||
user_input = input("There is a new version available! Update from v%s to v%s?\n[y(es)/n(o)]"
|
user_input = input("There is a new version available! Update from v%s to v%s?\n[Y/N] "
|
||||||
% (pwnagotchi.__version__, latest_ver))
|
% (pwnagotchi.__version__, latest_ver))
|
||||||
# input validation
|
# input validation
|
||||||
if user_input.lower() in ('y', 'yes'):
|
if user_input.lower() in ('y', 'yes'):
|
||||||
@ -227,3 +333,6 @@ if __name__ == '__main__':
|
|||||||
do_manual_mode(agent)
|
do_manual_mode(agent)
|
||||||
else:
|
else:
|
||||||
do_auto_mode(agent)
|
do_auto_mode(agent)
|
||||||
|
|
||||||
|
if __name__ == '__main__':
|
||||||
|
pwnagotchi_cli()
|
@ -20,8 +20,8 @@ variable "pwn_version" {
|
|||||||
}
|
}
|
||||||
|
|
||||||
source "arm" "rpi64-pwnagotchi" {
|
source "arm" "rpi64-pwnagotchi" {
|
||||||
file_checksum_url = "https://downloads.raspberrypi.org/raspios_lite_arm64/images/raspios_lite_arm64-2023-12-11/2023-12-11-raspios-bookworm-arm64-lite.img.xz.sha256"
|
file_checksum_url = "https://downloads.raspberrypi.com/raspios_lite_arm64/images/raspios_lite_arm64-2024-03-15/2024-03-15-raspios-bookworm-arm64-lite.img.xz.sha256"
|
||||||
file_urls = ["https://downloads.raspberrypi.org/raspios_lite_arm64/images/raspios_lite_arm64-2023-12-11/2023-12-11-raspios-bookworm-arm64-lite.img.xz"]
|
file_urls = ["https://downloads.raspberrypi.com/raspios_lite_arm64/images/raspios_lite_arm64-2024-03-15/2024-03-15-raspios-bookworm-arm64-lite.img.xz"]
|
||||||
file_checksum_type = "sha256"
|
file_checksum_type = "sha256"
|
||||||
file_target_extension = "xz"
|
file_target_extension = "xz"
|
||||||
file_unarchive_cmd = ["unxz", "$ARCHIVE_PATH"]
|
file_unarchive_cmd = ["unxz", "$ARCHIVE_PATH"]
|
||||||
@ -50,8 +50,8 @@ source "arm" "rpi64-pwnagotchi" {
|
|||||||
}
|
}
|
||||||
|
|
||||||
source "arm" "rpi32-pwnagotchi" {
|
source "arm" "rpi32-pwnagotchi" {
|
||||||
file_checksum_url = "https://downloads.raspberrypi.com/raspios_oldstable_lite_armhf/images/raspios_oldstable_lite_armhf-2023-12-06/2023-12-05-raspios-bullseye-armhf-lite.img.xz.sha256"
|
file_checksum_url = "https://downloads.raspberrypi.com/raspios_oldstable_lite_armhf/images/raspios_oldstable_lite_armhf-2024-03-12/2024-03-12-raspios-bullseye-armhf-lite.img.xz.sha256"
|
||||||
file_urls = ["https://downloads.raspberrypi.com/raspios_oldstable_lite_armhf/images/raspios_oldstable_lite_armhf-2023-12-06/2023-12-05-raspios-bullseye-armhf-lite.img.xz"]
|
file_urls = ["https://downloads.raspberrypi.com/raspios_oldstable_lite_armhf/images/raspios_oldstable_lite_armhf-2024-03-12/2024-03-12-raspios-bullseye-armhf-lite.img.xz"]
|
||||||
file_checksum_type = "sha256"
|
file_checksum_type = "sha256"
|
||||||
file_target_extension = "xz"
|
file_target_extension = "xz"
|
||||||
file_unarchive_cmd = ["unxz", "$ARCHIVE_PATH"]
|
file_unarchive_cmd = ["unxz", "$ARCHIVE_PATH"]
|
||||||
|
63
builder/data/32bit/boot/config.txt
Normal file
63
builder/data/32bit/boot/config.txt
Normal file
@ -0,0 +1,63 @@
|
|||||||
|
# For more options and information see
|
||||||
|
# http://rptl.io/configtxt
|
||||||
|
# Some settings may impact device functionality. See link above for details
|
||||||
|
|
||||||
|
# Uncomment some or all of these to enable the optional hardware interfaces
|
||||||
|
#dtparam=i2c_arm=on
|
||||||
|
#dtparam=i2s=on
|
||||||
|
#dtparam=spi=on
|
||||||
|
|
||||||
|
# Enable audio (loads snd_bcm2835)
|
||||||
|
dtparam=audio=on
|
||||||
|
|
||||||
|
# Additional overlays and parameters are documented
|
||||||
|
# /boot/overlays/README
|
||||||
|
|
||||||
|
# Automatically load overlays for detected cameras
|
||||||
|
camera_auto_detect=1
|
||||||
|
|
||||||
|
# Automatically load overlays for detected DSI displays
|
||||||
|
display_auto_detect=1
|
||||||
|
|
||||||
|
# Automatically load initramfs files, if found
|
||||||
|
auto_initramfs=1
|
||||||
|
|
||||||
|
# Enable DRM VC4 V3D driver
|
||||||
|
dtoverlay=vc4-kms-v3d
|
||||||
|
max_framebuffers=2
|
||||||
|
|
||||||
|
# Don't have the firmware create an initial video= setting in cmdline.txt.
|
||||||
|
# Use the kernel's default instead.
|
||||||
|
disable_fw_kms_setup=1
|
||||||
|
|
||||||
|
# Run in 64-bit mode
|
||||||
|
arm_64bit=0
|
||||||
|
|
||||||
|
# Disable compensation for displays with overscan
|
||||||
|
disable_overscan=1
|
||||||
|
|
||||||
|
# Run as fast as firmware / board allows
|
||||||
|
arm_boost=1
|
||||||
|
|
||||||
|
[cm4]
|
||||||
|
# Enable host mode on the 2711 built-in XHCI USB controller.
|
||||||
|
# This line should be removed if the legacy DWC2 controller is required
|
||||||
|
# (e.g. for USB device mode) or if USB support is not required.
|
||||||
|
otg_mode=1
|
||||||
|
|
||||||
|
[all]
|
||||||
|
dtoverlay=dwc2
|
||||||
|
dtparam=i2c1=on
|
||||||
|
dtparam=i2c_arm=on
|
||||||
|
dtparam=spi=on
|
||||||
|
gpu_mem=1
|
||||||
|
dtoverlay=dwc2
|
||||||
|
|
||||||
|
[pi0]
|
||||||
|
dtoverlay=spi1-3cs
|
||||||
|
|
||||||
|
[pi3]
|
||||||
|
dtoverlay=spi1-3cs
|
||||||
|
|
||||||
|
[pi4]
|
||||||
|
dtoverlay=spi1-3cs
|
6
builder/data/32bit/etc/modules-load.d/modules.conf
Normal file
6
builder/data/32bit/etc/modules-load.d/modules.conf
Normal file
@ -0,0 +1,6 @@
|
|||||||
|
# /etc/modules: kernel modules to load at boot time.
|
||||||
|
#
|
||||||
|
# This file contains the names of kernel modules that should be loaded
|
||||||
|
# at boot time, one per line. Lines beginning with "#" are ignored.
|
||||||
|
# Parameters can be specified after the module name.
|
||||||
|
i2c-dev
|
16
builder/data/32bit/etc/rc.local
Normal file
16
builder/data/32bit/etc/rc.local
Normal file
@ -0,0 +1,16 @@
|
|||||||
|
#!/bin/sh -e
|
||||||
|
#
|
||||||
|
# rc.local
|
||||||
|
#
|
||||||
|
# This script is executed at the end of each multiuser runlevel.
|
||||||
|
# Make sure that the script will "exit 0" on success or any other
|
||||||
|
# value on error.
|
||||||
|
#
|
||||||
|
# In order to enable or disable this script just change the execution
|
||||||
|
# bits.
|
||||||
|
#
|
||||||
|
# By default this script does nothing.
|
||||||
|
|
||||||
|
timedatectl set-ntp true
|
||||||
|
|
||||||
|
exit 0
|
@ -20,8 +20,8 @@ variable "pwn_version" {
|
|||||||
}
|
}
|
||||||
|
|
||||||
source "arm" "rpi32-pwnagotchi" {
|
source "arm" "rpi32-pwnagotchi" {
|
||||||
file_checksum_url = "https://downloads.raspberrypi.com/raspios_oldstable_lite_armhf/images/raspios_oldstable_lite_armhf-2023-12-06/2023-12-05-raspios-bullseye-armhf-lite.img.xz.sha256"
|
file_checksum_url = "https://downloads.raspberrypi.com/raspios_oldstable_lite_armhf/images/raspios_oldstable_lite_armhf-2024-03-12/2024-03-12-raspios-bullseye-armhf-lite.img.xz.sha256"
|
||||||
file_urls = ["https://downloads.raspberrypi.com/raspios_oldstable_lite_armhf/images/raspios_oldstable_lite_armhf-2023-12-06/2023-12-05-raspios-bullseye-armhf-lite.img.xz"]
|
file_urls = ["https://downloads.raspberrypi.com/raspios_oldstable_lite_armhf/images/raspios_oldstable_lite_armhf-2024-03-12/2024-03-12-raspios-bullseye-armhf-lite.img.xz"]
|
||||||
file_checksum_type = "sha256"
|
file_checksum_type = "sha256"
|
||||||
file_target_extension = "xz"
|
file_target_extension = "xz"
|
||||||
file_unarchive_cmd = ["unxz", "$ARCHIVE_PATH"]
|
file_unarchive_cmd = ["unxz", "$ARCHIVE_PATH"]
|
||||||
|
@ -39,26 +39,6 @@
|
|||||||
hostname: "{{ lookup('env', 'PWN_HOSTNAME') | default('pwnagotchi', true) }}"
|
hostname: "{{ lookup('env', 'PWN_HOSTNAME') | default('pwnagotchi', true) }}"
|
||||||
version: "{{ lookup('env', 'PWN_VERSION') | default('pwnagotchi-torch', true) }}"
|
version: "{{ lookup('env', 'PWN_VERSION') | default('pwnagotchi-torch', true) }}"
|
||||||
custom_plugin_dir: "/usr/local/share/pwnagotchi/custom-plugins"
|
custom_plugin_dir: "/usr/local/share/pwnagotchi/custom-plugins"
|
||||||
system:
|
|
||||||
boot_options:
|
|
||||||
- "#### pwnagotchi additions"
|
|
||||||
- "# this pwnagotchi image is 32-bit only no v8+ headers to build nexmon for 64 bit"
|
|
||||||
- "arm_64bit=0"
|
|
||||||
- "# dwc2 for RNDIS. comment out, and remove dwc2 and g_ether from cmdline.txt for X306 usb battery hat"
|
|
||||||
- "dtoverlay=dwc2"
|
|
||||||
- "dtoverlay=spi1-3cs"
|
|
||||||
- "dtparam=i2c1=on"
|
|
||||||
- "dtparam=i2c_arm=on"
|
|
||||||
- "dtparam=spi=on"
|
|
||||||
- "gpu_mem=16"
|
|
||||||
- "#### audio out on pins 18 and 19"
|
|
||||||
- "#dtoverlay=audremap,pins_18_19"
|
|
||||||
- "#### touchscreen on waveshare touch e-paper"
|
|
||||||
- "#dtoverlay=goodix,interrupt=27,reset=22"
|
|
||||||
- "#### for PWM backlighting on pimoroni displayhatmini"
|
|
||||||
- "dtoverlay=pwm-2chan,pin=12,func=4,pin2=13,func2=4"
|
|
||||||
modules:
|
|
||||||
- "i2c-dev"
|
|
||||||
services:
|
services:
|
||||||
enable:
|
enable:
|
||||||
- bettercap.service
|
- bettercap.service
|
||||||
@ -177,6 +157,7 @@
|
|||||||
- libusb-1.0-0-dev
|
- libusb-1.0-0-dev
|
||||||
- lsof
|
- lsof
|
||||||
- make
|
- make
|
||||||
|
- ntp
|
||||||
- python3-flask
|
- python3-flask
|
||||||
- python3-flask-cors
|
- python3-flask-cors
|
||||||
- python3-flaskext.wtf
|
- python3-flaskext.wtf
|
||||||
@ -228,6 +209,11 @@
|
|||||||
path: '{{ pwnagotchi.custom_plugin_dir }}'
|
path: '{{ pwnagotchi.custom_plugin_dir }}'
|
||||||
state: directory
|
state: directory
|
||||||
|
|
||||||
|
- name: remove current rc.local
|
||||||
|
file:
|
||||||
|
path: /etc/rc.local
|
||||||
|
state: absent
|
||||||
|
|
||||||
- name: update apt package cache
|
- name: update apt package cache
|
||||||
apt:
|
apt:
|
||||||
update_cache: yes
|
update_cache: yes
|
||||||
@ -383,6 +369,11 @@
|
|||||||
block: |
|
block: |
|
||||||
export GOPATH=$HOME/go
|
export GOPATH=$HOME/go
|
||||||
export PATH=/usr/local/go/bin:$PATH:$GOPATH/bin
|
export PATH=/usr/local/go/bin:$PATH:$GOPATH/bin
|
||||||
|
alias custom='cd /usr/local/share/pwnagotchi/custom-plugins/'
|
||||||
|
alias config='sudo nano /etc/pwnagotchi/config.toml'
|
||||||
|
alias pwnlog='tail -f -n300 /etc/pwnagotchi/log/pwn*.log | sed --unbuffered "s/,[[:digit:]]\\{3\\}\\]//g" | cut -d " " -f 2-'
|
||||||
|
alias pwnver='python3 -c "import pwnagotchi as p; print(p.__version__)"'
|
||||||
|
alias pwnkill='sudo killall -USR1 pwnagotchi'
|
||||||
when: golang.changed
|
when: golang.changed
|
||||||
|
|
||||||
- name: download pwngrid
|
- name: download pwngrid
|
||||||
@ -451,7 +442,7 @@
|
|||||||
# Add your configuration overrides on this file any configuration changes done to default.toml will be lost!
|
# Add your configuration overrides on this file any configuration changes done to default.toml will be lost!
|
||||||
# Example:
|
# Example:
|
||||||
# ui.display.enabled = true
|
# ui.display.enabled = true
|
||||||
# ui.display.type = "waveshare_2"
|
# ui.display.type = "waveshare_4"
|
||||||
when: not user_config.stat.exists
|
when: not user_config.stat.exists
|
||||||
|
|
||||||
- name: Delete motd 10-uname
|
- name: Delete motd 10-uname
|
||||||
@ -464,20 +455,6 @@
|
|||||||
path: /boot/ssh
|
path: /boot/ssh
|
||||||
state: touch
|
state: touch
|
||||||
|
|
||||||
- name: adjust /boot/config.txt
|
|
||||||
lineinfile:
|
|
||||||
dest: /boot/config.txt
|
|
||||||
insertafter: EOF
|
|
||||||
line: '{{ item }}'
|
|
||||||
with_items: "{{system.boot_options}}"
|
|
||||||
|
|
||||||
- name: adjust /etc/modules
|
|
||||||
lineinfile:
|
|
||||||
dest: /etc/modules
|
|
||||||
insertafter: EOF
|
|
||||||
line: '{{ item }}'
|
|
||||||
with_items: "{{system.modules}}"
|
|
||||||
|
|
||||||
- name: change root partition
|
- name: change root partition
|
||||||
replace:
|
replace:
|
||||||
dest: /boot/cmdline.txt
|
dest: /boot/cmdline.txt
|
||||||
@ -494,24 +471,6 @@
|
|||||||
regexp: '(.*)$'
|
regexp: '(.*)$'
|
||||||
line: '\1 modules-load=dwc2,g_ether'
|
line: '\1 modules-load=dwc2,g_ether'
|
||||||
|
|
||||||
- name: Add pwnlog alias
|
|
||||||
lineinfile:
|
|
||||||
dest: /home/pi/.bashrc
|
|
||||||
line: "\nalias pwnlog='tail -f -n300 /etc/pwnagotchi/log/pwn*.log | sed --unbuffered \"s/,[[:digit:]]\\{3\\}\\]//g\" | cut -d \" \" -f 2-'"
|
|
||||||
insertafter: EOF
|
|
||||||
|
|
||||||
- name: Add pwnver alias
|
|
||||||
lineinfile:
|
|
||||||
dest: /home/pi/.bashrc
|
|
||||||
line: "\nalias pwnver='python3 -c \"import pwnagotchi as p; print(p.__version__)\"'"
|
|
||||||
insertafter: EOF
|
|
||||||
|
|
||||||
- name: Add pwnkill alias to restart pwnagotchi with a signal
|
|
||||||
lineinfile:
|
|
||||||
dest: /home/pi/.bashrc
|
|
||||||
line: "\nalias pwnkill='sudo killall -USR1 pwnagotchi'"
|
|
||||||
insertafter: EOF
|
|
||||||
|
|
||||||
- name: add firmware packages to hold
|
- name: add firmware packages to hold
|
||||||
dpkg_selections:
|
dpkg_selections:
|
||||||
name: "{{ item }}"
|
name: "{{ item }}"
|
||||||
|
@ -11,6 +11,7 @@ fi
|
|||||||
|
|
||||||
if is_auto_mode; then
|
if is_auto_mode; then
|
||||||
/usr/local/bin/pwnagotchi
|
/usr/local/bin/pwnagotchi
|
||||||
|
systemctl restart bettercap
|
||||||
else
|
else
|
||||||
/usr/local/bin/pwnagotchi --manual
|
/usr/local/bin/pwnagotchi --manual
|
||||||
fi
|
fi
|
||||||
|
65
builder/data/64bit/boot/firmware/config.txt
Normal file
65
builder/data/64bit/boot/firmware/config.txt
Normal file
@ -0,0 +1,65 @@
|
|||||||
|
# For more options and information see
|
||||||
|
# http://rptl.io/configtxt
|
||||||
|
# Some settings may impact device functionality. See link above for details
|
||||||
|
|
||||||
|
# Uncomment some or all of these to enable the optional hardware interfaces
|
||||||
|
#dtparam=i2c_arm=on
|
||||||
|
#dtparam=i2s=on
|
||||||
|
#dtparam=spi=on
|
||||||
|
|
||||||
|
# Enable audio (loads snd_bcm2835)
|
||||||
|
dtparam=audio=on
|
||||||
|
|
||||||
|
# Additional overlays and parameters are documented
|
||||||
|
# /boot/firmware/overlays/README
|
||||||
|
|
||||||
|
# Automatically load overlays for detected cameras
|
||||||
|
camera_auto_detect=1
|
||||||
|
|
||||||
|
# Automatically load overlays for detected DSI displays
|
||||||
|
display_auto_detect=1
|
||||||
|
|
||||||
|
# Automatically load initramfs files, if found
|
||||||
|
auto_initramfs=1
|
||||||
|
|
||||||
|
# Enable DRM VC4 V3D driver
|
||||||
|
dtoverlay=vc4-kms-v3d
|
||||||
|
max_framebuffers=2
|
||||||
|
|
||||||
|
# Don't have the firmware create an initial video= setting in cmdline.txt.
|
||||||
|
# Use the kernel's default instead.
|
||||||
|
disable_fw_kms_setup=1
|
||||||
|
|
||||||
|
# Run in 64-bit mode
|
||||||
|
arm_64bit=1
|
||||||
|
|
||||||
|
# Disable compensation for displays with overscan
|
||||||
|
disable_overscan=1
|
||||||
|
|
||||||
|
# Run as fast as firmware / board allows
|
||||||
|
arm_boost=1
|
||||||
|
|
||||||
|
[cm4]
|
||||||
|
# Enable host mode on the 2711 built-in XHCI USB controller.
|
||||||
|
# This line should be removed if the legacy DWC2 controller is required
|
||||||
|
# (e.g. for USB device mode) or if USB support is not required.
|
||||||
|
otg_mode=1
|
||||||
|
|
||||||
|
[all]
|
||||||
|
dtparam=i2c1=on
|
||||||
|
dtparam=i2c_arm=on
|
||||||
|
dtparam=spi=on
|
||||||
|
gpu_mem=1
|
||||||
|
dtoverlay=dwc2
|
||||||
|
|
||||||
|
[pi0]
|
||||||
|
dtoverlay=spi0-0cs
|
||||||
|
|
||||||
|
[pi3]
|
||||||
|
dtoverlay=spi0-0cs
|
||||||
|
|
||||||
|
[pi4]
|
||||||
|
dtoverlay=spi0-0cs
|
||||||
|
|
||||||
|
[pi5]
|
||||||
|
dtoverlay=spi0-0cs
|
@ -2,7 +2,7 @@ _show_complete()
|
|||||||
{
|
{
|
||||||
local cur opts node_names all_options opt_line
|
local cur opts node_names all_options opt_line
|
||||||
all_options="
|
all_options="
|
||||||
pwnagotchi -h --help -C --config -U --user-config --manual --skip-session --clear --debug --version --print-config --check-update --donate {plugins,google}
|
pwnagotchi -h --help -C --config -U --user-config --manual --skip-session --clear --debug --version --print-config --wizard --check-update --donate {plugins,google}
|
||||||
pwnagotchi plugins -h --help {list,install,enable,disable,uninstall,update,upgrade}
|
pwnagotchi plugins -h --help {list,install,enable,disable,uninstall,update,upgrade}
|
||||||
pwnagotchi plugins list -i --installed -h --help
|
pwnagotchi plugins list -i --installed -h --help
|
||||||
pwnagotchi plugins install -h --help
|
pwnagotchi plugins install -h --help
|
||||||
|
6
builder/data/64bit/etc/modules-load.d/modules.conf
Normal file
6
builder/data/64bit/etc/modules-load.d/modules.conf
Normal file
@ -0,0 +1,6 @@
|
|||||||
|
# /etc/modules: kernel modules to load at boot time.
|
||||||
|
#
|
||||||
|
# This file contains the names of kernel modules that should be loaded
|
||||||
|
# at boot time, one per line. Lines beginning with "#" are ignored.
|
||||||
|
# Parameters can be specified after the module name.
|
||||||
|
i2c-dev
|
16
builder/data/64bit/etc/rc.local
Normal file
16
builder/data/64bit/etc/rc.local
Normal file
@ -0,0 +1,16 @@
|
|||||||
|
#!/bin/sh -e
|
||||||
|
#
|
||||||
|
# rc.local
|
||||||
|
#
|
||||||
|
# This script is executed at the end of each multiuser runlevel.
|
||||||
|
# Make sure that the script will "exit 0" on success or any other
|
||||||
|
# value on error.
|
||||||
|
#
|
||||||
|
# In order to enable or disable this script just change the execution
|
||||||
|
# bits.
|
||||||
|
#
|
||||||
|
# By default this script does nothing.
|
||||||
|
|
||||||
|
timedatectl set-ntp true
|
||||||
|
|
||||||
|
exit 0
|
@ -20,6 +20,7 @@ echo " I'm managed by systemd. Here are some basic commands."
|
|||||||
echo
|
echo
|
||||||
echo " If you want to know what I'm doing, you can check my logs with the command"
|
echo " If you want to know what I'm doing, you can check my logs with the command"
|
||||||
echo " - pwnlog"
|
echo " - pwnlog"
|
||||||
|
echo " - sudo pwnagotchi --wizard, to help set up a config.toml"
|
||||||
echo " - sudo pwnagotchi --version, to check the current version"
|
echo " - sudo pwnagotchi --version, to check the current version"
|
||||||
echo " - sudo pwnagotchi --donate, to see how you can donate to this project"
|
echo " - sudo pwnagotchi --donate, to see how you can donate to this project"
|
||||||
echo " - sudo pwnagotchi --check-update, to see if there is a new version available"
|
echo " - sudo pwnagotchi --check-update, to see if there is a new version available"
|
||||||
|
@ -20,8 +20,8 @@ variable "pwn_version" {
|
|||||||
}
|
}
|
||||||
|
|
||||||
source "arm" "rpi64-pwnagotchi" {
|
source "arm" "rpi64-pwnagotchi" {
|
||||||
file_checksum_url = "https://downloads.raspberrypi.org/raspios_lite_arm64/images/raspios_lite_arm64-2023-12-11/2023-12-11-raspios-bookworm-arm64-lite.img.xz.sha256"
|
file_checksum_url = "https://downloads.raspberrypi.com/raspios_lite_arm64/images/raspios_lite_arm64-2024-03-15/2024-03-15-raspios-bookworm-arm64-lite.img.xz.sha256"
|
||||||
file_urls = ["https://downloads.raspberrypi.org/raspios_lite_arm64/images/raspios_lite_arm64-2023-12-11/2023-12-11-raspios-bookworm-arm64-lite.img.xz"]
|
file_urls = ["https://downloads.raspberrypi.com/raspios_lite_arm64/images/raspios_lite_arm64-2024-03-15/2024-03-15-raspios-bookworm-arm64-lite.img.xz"]
|
||||||
file_checksum_type = "sha256"
|
file_checksum_type = "sha256"
|
||||||
file_target_extension = "xz"
|
file_target_extension = "xz"
|
||||||
file_unarchive_cmd = ["unxz", "$ARCHIVE_PATH"]
|
file_unarchive_cmd = ["unxz", "$ARCHIVE_PATH"]
|
||||||
|
@ -5,22 +5,12 @@
|
|||||||
become: true
|
become: true
|
||||||
vars:
|
vars:
|
||||||
kernel:
|
kernel:
|
||||||
min: "6.1"
|
min: "6.6"
|
||||||
full: "6.1.0-rpi8-rpi-v8"
|
full: "6.6.20+rpt-rpi-v8"
|
||||||
full_pi5: "6.1.0-rpi8-rpi-2712"
|
full_pi5: "6.6.20+rpt-rpi-2712"
|
||||||
pwnagotchi:
|
pwnagotchi:
|
||||||
hostname: "{{ lookup('env', 'PWN_HOSTNAME') | default('pwnagotchi', true) }}"
|
hostname: "{{ lookup('env', 'PWN_HOSTNAME') | default('pwnagotchi', true) }}"
|
||||||
version: "{{ lookup('env', 'PWN_VERSION') | default('pwnagotchi', true) }}"
|
version: "{{ lookup('env', 'PWN_VERSION') | default('pwnagotchi', true) }}"
|
||||||
system:
|
|
||||||
boot_options:
|
|
||||||
- "dtoverlay=dwc2"
|
|
||||||
- "dtoverlay=spi1-3cs"
|
|
||||||
- "dtparam=i2c1=on"
|
|
||||||
- "dtparam=i2c_arm=on"
|
|
||||||
- "dtparam=spi=on"
|
|
||||||
- "gpu_mem=16"
|
|
||||||
modules:
|
|
||||||
- "i2c-dev"
|
|
||||||
services:
|
services:
|
||||||
enable:
|
enable:
|
||||||
- bettercap.service
|
- bettercap.service
|
||||||
@ -137,6 +127,7 @@
|
|||||||
- libusb-1.0-0-dev
|
- libusb-1.0-0-dev
|
||||||
- lsof
|
- lsof
|
||||||
- make
|
- make
|
||||||
|
- ntp
|
||||||
- python3-dbus
|
- python3-dbus
|
||||||
- python3-flask
|
- python3-flask
|
||||||
- python3-flask-cors
|
- python3-flask-cors
|
||||||
@ -180,6 +171,12 @@
|
|||||||
update_cache: yes
|
update_cache: yes
|
||||||
install_recommends: false
|
install_recommends: false
|
||||||
|
|
||||||
|
- name: update pip3, setuptools, wheel
|
||||||
|
shell: "python3 -m pip install --upgrade pip setuptools wheel --break-system-packages"
|
||||||
|
args:
|
||||||
|
executable: /bin/bash
|
||||||
|
chdir: /usr/local/src
|
||||||
|
|
||||||
# Now we set up /boot/firmware
|
# Now we set up /boot/firmware
|
||||||
- name: Create pi user
|
- name: Create pi user
|
||||||
copy:
|
copy:
|
||||||
@ -192,12 +189,10 @@
|
|||||||
path: /boot/firmware/ssh
|
path: /boot/firmware/ssh
|
||||||
state: touch
|
state: touch
|
||||||
|
|
||||||
- name: adjust /boot/firmware/config.txt
|
- name: remove current rc.local
|
||||||
lineinfile:
|
file:
|
||||||
dest: /boot/firmware/config.txt
|
path: /etc/rc.local
|
||||||
insertafter: EOF
|
state: absent
|
||||||
line: '{{ item }}'
|
|
||||||
with_items: "{{ system.boot_options }}"
|
|
||||||
|
|
||||||
- name: change root partition
|
- name: change root partition
|
||||||
replace:
|
replace:
|
||||||
@ -491,6 +486,11 @@
|
|||||||
block: |
|
block: |
|
||||||
export GOPATH=$HOME/go
|
export GOPATH=$HOME/go
|
||||||
export PATH=/usr/local/go/bin:$PATH:$GOPATH/bin
|
export PATH=/usr/local/go/bin:$PATH:$GOPATH/bin
|
||||||
|
alias custom='cd /usr/local/share/pwnagotchi/custom-plugins/'
|
||||||
|
alias config='sudo nano /etc/pwnagotchi/config.toml'
|
||||||
|
alias pwnlog='tail -f -n300 /etc/pwnagotchi/log/pwn*.log | sed --unbuffered "s/,[[:digit:]]\\{3\\}\\]//g" | cut -d " " -f 2-'
|
||||||
|
alias pwnver='python3 -c "import pwnagotchi as p; print(p.__version__)"'
|
||||||
|
alias pwnkill='sudo killall -USR1 pwnagotchi'
|
||||||
when: golang.changed
|
when: golang.changed
|
||||||
|
|
||||||
- name: download pwngrid
|
- name: download pwngrid
|
||||||
@ -579,7 +579,7 @@
|
|||||||
# Add your configuration overrides on this file any configuration changes done to default.toml will be lost!
|
# Add your configuration overrides on this file any configuration changes done to default.toml will be lost!
|
||||||
# Example:
|
# Example:
|
||||||
# ui.display.enabled = true
|
# ui.display.enabled = true
|
||||||
# ui.display.type = "waveshare_2"
|
# ui.display.type = "waveshare_4"
|
||||||
when: not user_config.stat.exists
|
when: not user_config.stat.exists
|
||||||
|
|
||||||
- name: Delete motd
|
- name: Delete motd
|
||||||
@ -592,24 +592,6 @@
|
|||||||
state: absent
|
state: absent
|
||||||
path: /etc/update-motd.d/10-uname
|
path: /etc/update-motd.d/10-uname
|
||||||
|
|
||||||
- name: Add pwnlog alias
|
|
||||||
lineinfile:
|
|
||||||
dest: /home/pi/.bashrc
|
|
||||||
line: "\nalias pwnlog='tail -f -n300 /etc/pwnagotchi/log/pwn*.log | sed --unbuffered \"s/,[[:digit:]]\\{3\\}\\]//g\" | cut -d \" \" -f 2-'"
|
|
||||||
insertafter: EOF
|
|
||||||
|
|
||||||
- name: Add pwnver alias
|
|
||||||
lineinfile:
|
|
||||||
dest: /home/pi/.bashrc
|
|
||||||
line: "\nalias pwnver='python3 -c \"import pwnagotchi as p; print(p.__version__)\"'"
|
|
||||||
insertafter: EOF
|
|
||||||
|
|
||||||
- name: Add pwnkill alias to restart pwnagotchi with a signal
|
|
||||||
lineinfile:
|
|
||||||
dest: /home/pi/.bashrc
|
|
||||||
line: "\nalias pwnkill='sudo killall -USR1 pwnagotchi'"
|
|
||||||
insertafter: EOF
|
|
||||||
|
|
||||||
- name: add firmware packages to hold
|
- name: add firmware packages to hold
|
||||||
dpkg_selections:
|
dpkg_selections:
|
||||||
name: "{{ item }}"
|
name: "{{ item }}"
|
||||||
|
@ -11,6 +11,7 @@ fi
|
|||||||
|
|
||||||
if is_auto_mode; then
|
if is_auto_mode; then
|
||||||
/usr/local/bin/pwnagotchi
|
/usr/local/bin/pwnagotchi
|
||||||
|
systemctl restart bettercap
|
||||||
else
|
else
|
||||||
/usr/local/bin/pwnagotchi --manual
|
/usr/local/bin/pwnagotchi --manual
|
||||||
fi
|
fi
|
||||||
|
@ -1,18 +1,5 @@
|
|||||||
#!/usr/bin/env bash
|
#!/usr/bin/env bash
|
||||||
|
|
||||||
# well ... it blinks the led
|
|
||||||
blink_led() {
|
|
||||||
# shellcheck disable=SC2034
|
|
||||||
for i in $(seq 1 "$1"); do
|
|
||||||
echo 0 >/sys/class/leds/led0/brightness
|
|
||||||
sleep 0.3
|
|
||||||
echo 1 >/sys/class/leds/led0/brightness
|
|
||||||
sleep 0.3
|
|
||||||
done
|
|
||||||
echo 0 >/sys/class/leds/led0/brightness
|
|
||||||
sleep 0.3
|
|
||||||
}
|
|
||||||
|
|
||||||
# reload mod
|
# reload mod
|
||||||
reload_brcm() {
|
reload_brcm() {
|
||||||
if ! modprobe -r brcmfmac; then
|
if ! modprobe -r brcmfmac; then
|
||||||
|
@ -1 +1 @@
|
|||||||
__version__ = '2.8.5'
|
__version__ = '2.8.7.2'
|
||||||
|
@ -24,30 +24,27 @@ main.plugins.auto-update.interval = 1
|
|||||||
|
|
||||||
main.plugins.bt-tether.enabled = false
|
main.plugins.bt-tether.enabled = false
|
||||||
|
|
||||||
# Configuration for Android Phone
|
main.plugins.bt-tether.devices.android-phone.enabled = false
|
||||||
main.plugins.bt-tether.devices.android-phone.enabled = false
|
|
||||||
main.plugins.bt-tether.devices.android-phone.search_order = 1
|
main.plugins.bt-tether.devices.android-phone.search_order = 1
|
||||||
main.plugins.bt-tether.devices.android-phone.mac = "" # Bluetooth MAC address of the Android phone
|
main.plugins.bt-tether.devices.android-phone.mac = ""
|
||||||
main.plugins.bt-tether.devices.android-phone.ip = "192.168.44.44" # Static IP of the Pwnagotchi
|
main.plugins.bt-tether.devices.android-phone.ip = "192.168.44.44"
|
||||||
main.plugins.bt-tether.devices.android-phone.netmask = 24 # Netmask of the PAN
|
main.plugins.bt-tether.devices.android-phone.netmask = 24
|
||||||
main.plugins.bt-tether.devices.android-phone.interval = 1 # Search interval in minutes
|
main.plugins.bt-tether.devices.android-phone.interval = 1
|
||||||
main.plugins.bt-tether.devices.android-phone.scantime = 10 # Duration of each search in seconds
|
main.plugins.bt-tether.devices.android-phone.scantime = 10
|
||||||
main.plugins.bt-tether.devices.android-phone.max_tries = 10 # Maximum attempts to find the phone
|
main.plugins.bt-tether.devices.android-phone.max_tries = 10
|
||||||
main.plugins.bt-tether.devices.android-phone.share_internet = false # Enable internet sharing via Bluetooth
|
main.plugins.bt-tether.devices.android-phone.share_internet = true
|
||||||
main.plugins.bt-tether.devices.android-phone.priority = 1 # Priority level for tethering
|
main.plugins.bt-tether.devices.android-phone.priority = 1
|
||||||
|
|
||||||
# Configuration for iOS Phone
|
|
||||||
main.plugins.bt-tether.devices.ios-phone.enabled = false
|
|
||||||
main.plugins.bt-tether.devices.ios-phone.search_order = 1
|
|
||||||
main.plugins.bt-tether.devices.ios-phone.mac = "" # Bluetooth MAC address of the iOS phone
|
|
||||||
main.plugins.bt-tether.devices.ios-phone.ip = "" # Static IP of the Pwnagotchi when tethered to iOS
|
|
||||||
main.plugins.bt-tether.devices.ios-phone.netmask = 24 # Netmask of the PAN
|
|
||||||
main.plugins.bt-tether.devices.ios-phone.interval = 1 # Search interval in minutes
|
|
||||||
main.plugins.bt-tether.devices.ios-phone.scantime = 10 # Duration of each search in seconds
|
|
||||||
main.plugins.bt-tether.devices.ios-phone.max_tries = 10 # Maximum attempts to find the phone
|
|
||||||
main.plugins.bt-tether.devices.ios-phone.share_internet = false # Enable internet sharing via Bluetooth
|
|
||||||
main.plugins.bt-tether.devices.ios-phone.priority = 1 # Priority level for tethering (edited)
|
|
||||||
|
|
||||||
|
main.plugins.bt-tether.devices.ios-phone.enabled = false
|
||||||
|
main.plugins.bt-tether.devices.ios-phone.search_order = 2
|
||||||
|
main.plugins.bt-tether.devices.ios-phone.mac = ""
|
||||||
|
main.plugins.bt-tether.devices.ios-phone.ip = "172.20.10.6"
|
||||||
|
main.plugins.bt-tether.devices.ios-phone.netmask = 24
|
||||||
|
main.plugins.bt-tether.devices.ios-phone.interval = 5
|
||||||
|
main.plugins.bt-tether.devices.ios-phone.scantime = 20
|
||||||
|
main.plugins.bt-tether.devices.ios-phone.max_tries = 0
|
||||||
|
main.plugins.bt-tether.devices.ios-phone.share_internet = true
|
||||||
|
main.plugins.bt-tether.devices.ios-phone.priority = 999
|
||||||
|
|
||||||
main.plugins.fix_services.enabled = true
|
main.plugins.fix_services.enabled = true
|
||||||
|
|
||||||
@ -157,6 +154,8 @@ personality.throttle_d = 0.9
|
|||||||
|
|
||||||
personality.clear_on_exit = true # clear display when shutting down cleanly
|
personality.clear_on_exit = true # clear display when shutting down cleanly
|
||||||
|
|
||||||
|
ui.invert = false # false = black background, true = white background
|
||||||
|
|
||||||
ui.fps = 0.0
|
ui.fps = 0.0
|
||||||
ui.font.name = "DejaVuSansMono" # for japanese: fonts-japanese-gothic
|
ui.font.name = "DejaVuSansMono" # for japanese: fonts-japanese-gothic
|
||||||
ui.font.size_offset = 0 # will be added to the font size
|
ui.font.size_offset = 0 # will be added to the font size
|
||||||
|
@ -199,6 +199,9 @@ def list_plugins(args, config, pattern='*'):
|
|||||||
available_not_installed = set(available.keys()) - set(installed.keys())
|
available_not_installed = set(available.keys()) - set(installed.keys())
|
||||||
|
|
||||||
max_len_list = available_and_installed if args.installed else available_not_installed
|
max_len_list = available_and_installed if args.installed else available_not_installed
|
||||||
|
if not max_len_list:
|
||||||
|
print('Maybe try: sudo pwnagotchi plugins update')
|
||||||
|
return 1
|
||||||
max_len = max(map(len, max_len_list))
|
max_len = max(map(len, max_len_list))
|
||||||
header = line.format(name='Plugin', width=max_len, version='Version', enabled='Active', status='Status')
|
header = line.format(name='Plugin', width=max_len, version='Version', enabled='Active', status='Status')
|
||||||
line_length = max(max_len, len('Plugin')) + len(header) - len('Plugin') - 12 # lol
|
line_length = max(max_len, len('Plugin')) + len(header) - len('Plugin') - 12 # lol
|
||||||
@ -239,7 +242,7 @@ def list_plugins(args, config, pattern='*'):
|
|||||||
print('-' * line_length)
|
print('-' * line_length)
|
||||||
|
|
||||||
if not found:
|
if not found:
|
||||||
logging.info('Maybe try: pwnagotchi plugins update')
|
print('Maybe try: sudo pwnagotchi plugins update')
|
||||||
return 1
|
return 1
|
||||||
return 0
|
return 0
|
||||||
|
|
||||||
|
@ -29,6 +29,7 @@ def check(version, repo, native=True):
|
|||||||
latest = resp.json()
|
latest = resp.json()
|
||||||
info['available'] = latest_ver = latest['tag_name'].replace('v', '')
|
info['available'] = latest_ver = latest['tag_name'].replace('v', '')
|
||||||
is_armhf = info['arch'].startswith('arm')
|
is_armhf = info['arch'].startswith('arm')
|
||||||
|
is_aarch = info['arch'].startswith('aarch')
|
||||||
|
|
||||||
local = version_to_tuple(info['current'])
|
local = version_to_tuple(info['current'])
|
||||||
remote = version_to_tuple(latest_ver)
|
remote = version_to_tuple(latest_ver)
|
||||||
@ -44,6 +45,14 @@ def check(version, repo, native=True):
|
|||||||
(info['arch'] in download_url or (is_armhf and 'armhf' in download_url))):
|
(info['arch'] in download_url or (is_armhf and 'armhf' in download_url))):
|
||||||
info['url'] = download_url
|
info['url'] = download_url
|
||||||
break
|
break
|
||||||
|
elif is_aarch:
|
||||||
|
# check if this release is compatible with arm64/aarch64
|
||||||
|
for asset in latest['assets']:
|
||||||
|
download_url = asset['browser_download_url']
|
||||||
|
if (download_url.endswith('.zip') and
|
||||||
|
(info['arch'] in download_url or (is_aarch and 'aarch' in download_url))):
|
||||||
|
info['url'] = download_url
|
||||||
|
break
|
||||||
|
|
||||||
return info
|
return info
|
||||||
|
|
||||||
|
@ -35,14 +35,11 @@ class FixServices(plugins.Plugin):
|
|||||||
self.isReloadingMon = False
|
self.isReloadingMon = False
|
||||||
self.connection = None
|
self.connection = None
|
||||||
self.LASTTRY = 0
|
self.LASTTRY = 0
|
||||||
self._status = "--"
|
|
||||||
self._count = 0
|
|
||||||
|
|
||||||
def on_loaded(self):
|
def on_loaded(self):
|
||||||
"""
|
"""
|
||||||
Gets called when the plugin gets loaded
|
Gets called when the plugin gets loaded
|
||||||
"""
|
"""
|
||||||
self._status = "ld"
|
|
||||||
logging.info("[Fix_Services] plugin loaded.")
|
logging.info("[Fix_Services] plugin loaded.")
|
||||||
|
|
||||||
def on_ready(self, agent):
|
def on_ready(self, agent):
|
||||||
@ -50,31 +47,27 @@ class FixServices(plugins.Plugin):
|
|||||||
stdout=subprocess.PIPE).stdout))[-10:])
|
stdout=subprocess.PIPE).stdout))[-10:])
|
||||||
try:
|
try:
|
||||||
cmd_output = subprocess.check_output("ip link show wlan0mon", shell=True)
|
cmd_output = subprocess.check_output("ip link show wlan0mon", shell=True)
|
||||||
logging.info("[Fix_Services ip link show wlan0mon]: %s" % repr(cmd_output))
|
logging.debug("[Fix_Services ip link show wlan0mon]: %s" % repr(cmd_output))
|
||||||
if ",UP," in str(cmd_output):
|
if ",UP," in str(cmd_output):
|
||||||
logging.info("wlan0mon is up.")
|
logging.debug("wlan0mon is up.")
|
||||||
self._status = "up"
|
|
||||||
|
|
||||||
if len(self.pattern.findall(last_lines)) >= 3:
|
if len(self.pattern.findall(last_lines)) >= 3:
|
||||||
self._status = "XX"
|
|
||||||
if hasattr(agent, 'view'):
|
if hasattr(agent, 'view'):
|
||||||
display = agent.view()
|
display = agent.view()
|
||||||
display.set('status', 'Blind-Bug detected. Restarting.')
|
display.set('status', 'Blind-Bug detected. Restarting.')
|
||||||
display.update(force=True)
|
display.update(force=True)
|
||||||
logging.info('[Fix_Services] Blind-Bug detected. Restarting.')
|
logging.debug('[Fix_Services] Blind-Bug detected. Restarting.')
|
||||||
try:
|
try:
|
||||||
self._tryTurningItOffAndOnAgain(agent)
|
self._tryTurningItOffAndOnAgain(agent)
|
||||||
except Exception as err:
|
except Exception as err:
|
||||||
logging.warning("[Fix_Services turnOffAndOn] %s" % repr(err))
|
logging.warning("[Fix_Services turnOffAndOn] %s" % repr(err))
|
||||||
|
|
||||||
else:
|
else:
|
||||||
logging.info("[Fix_Services] Logs look good!")
|
logging.debug("[Fix_Services] Logs look good!")
|
||||||
self._status = ""
|
|
||||||
|
|
||||||
except Exception as err:
|
except Exception as err:
|
||||||
logging.error("[Fix_Services ip link show wlan0mon]: %s" % repr(err))
|
logging.error("[Fix_Services ip link show wlan0mon]: %s" % repr(err))
|
||||||
try:
|
try:
|
||||||
self._status = "xx"
|
|
||||||
self._tryTurningItOffAndOnAgain(agent)
|
self._tryTurningItOffAndOnAgain(agent)
|
||||||
except Exception as err:
|
except Exception as err:
|
||||||
logging.error("[Fix_Services OffNOn]: %s" % repr(err))
|
logging.error("[Fix_Services OffNOn]: %s" % repr(err))
|
||||||
@ -84,12 +77,12 @@ class FixServices(plugins.Plugin):
|
|||||||
# apparently this only gets messages from bettercap going to syslog, not from syslog
|
# apparently this only gets messages from bettercap going to syslog, not from syslog
|
||||||
def on_bcap_sys_log(self, agent, event):
|
def on_bcap_sys_log(self, agent, event):
|
||||||
if re.search('wifi error while hopping to channel', event['data']['Message']):
|
if re.search('wifi error while hopping to channel', event['data']['Message']):
|
||||||
logging.info("[Fix_Services]SYSLOG MATCH: %s" % event['data']['Message'])
|
logging.debug("[Fix_Services]SYSLOG MATCH: %s" % event['data']['Message'])
|
||||||
logging.info("[Fix_Services]**** restarting wifi.recon")
|
logging.debug("[Fix_Services]**** restarting wifi.recon")
|
||||||
try:
|
try:
|
||||||
result = agent.run("wifi.recon off; wifi.recon on")
|
result = agent.run("wifi.recon off; wifi.recon on")
|
||||||
if result["success"]:
|
if result["success"]:
|
||||||
logging.info("[Fix_Services] wifi.recon flip: success!")
|
logging.debug("[Fix_Services] wifi.recon flip: success!")
|
||||||
if hasattr(agent, 'view'):
|
if hasattr(agent, 'view'):
|
||||||
display = agent.view()
|
display = agent.view()
|
||||||
if display:
|
if display:
|
||||||
@ -121,12 +114,12 @@ class FixServices(plugins.Plugin):
|
|||||||
|
|
||||||
# Look for pattern 1
|
# Look for pattern 1
|
||||||
if len(self.pattern.findall(last_lines)) >= 3:
|
if len(self.pattern.findall(last_lines)) >= 3:
|
||||||
logging.info("[Fix_Services]**** Should trigger a reload of the wlan0mon device:\n%s" % last_lines)
|
logging.debug("[Fix_Services]**** Should trigger a reload of the wlan0mon device:\n%s" % last_lines)
|
||||||
if hasattr(agent, 'view'):
|
if hasattr(agent, 'view'):
|
||||||
display = agent.view()
|
display = agent.view()
|
||||||
display.set('status', 'Blind-Bug detected. Restarting.')
|
display.set('status', 'Blind-Bug detected. Restarting.')
|
||||||
display.update(force=True)
|
display.update(force=True)
|
||||||
logging.info('[Fix_Services] Blind-Bug detected. Restarting.')
|
logging.debug('[Fix_Services] Blind-Bug detected. Restarting.')
|
||||||
try:
|
try:
|
||||||
self._tryTurningItOffAndOnAgain(agent)
|
self._tryTurningItOffAndOnAgain(agent)
|
||||||
except Exception as err:
|
except Exception as err:
|
||||||
@ -134,20 +127,19 @@ class FixServices(plugins.Plugin):
|
|||||||
|
|
||||||
# Look for pattern 2
|
# Look for pattern 2
|
||||||
elif len(self.pattern2.findall(other_last_lines)) >= 5:
|
elif len(self.pattern2.findall(other_last_lines)) >= 5:
|
||||||
logging.info("[Fix_Services]**** Should trigger a reload of the wlan0mon device:\n%s" % last_lines)
|
logging.debug("[Fix_Services]**** Should trigger a reload of the wlan0mon device:\n%s" % last_lines)
|
||||||
if hasattr(agent, 'view'):
|
if hasattr(agent, 'view'):
|
||||||
display = agent.view()
|
display = agent.view()
|
||||||
display.set('status', 'Wifi channel stuck. Restarting recon.')
|
display.set('status', 'Wifi channel stuck. Restarting recon.')
|
||||||
display.update(force=True)
|
display.update(force=True)
|
||||||
logging.info('[Fix_Services] Wifi channel stuck. Restarting recon.')
|
logging.debug('[Fix_Services] Wifi channel stuck. Restarting recon.')
|
||||||
|
|
||||||
try:
|
try:
|
||||||
result = agent.run("wifi.recon off; wifi.recon on")
|
result = agent.run("wifi.recon off; wifi.recon on")
|
||||||
if result["success"]:
|
if result["success"]:
|
||||||
logging.info("[Fix_Services] wifi.recon flip: success!")
|
logging.debug("[Fix_Services] wifi.recon flip: success!")
|
||||||
if display:
|
if display:
|
||||||
display.update(force=True, new_data={"status": "Wifi recon flipped!",
|
display.update(force=True, new_data={"status": "Wifi recon flipped!",
|
||||||
"brcmfmac_status": self._status,
|
|
||||||
"face": faces.COOL})
|
"face": faces.COOL})
|
||||||
else:
|
else:
|
||||||
print("Wifi recon flipped\nthat was easy!")
|
print("Wifi recon flipped\nthat was easy!")
|
||||||
@ -159,7 +151,7 @@ class FixServices(plugins.Plugin):
|
|||||||
|
|
||||||
# Look for pattern 3
|
# Look for pattern 3
|
||||||
elif len(self.pattern3.findall(other_last_lines)) >= 1:
|
elif len(self.pattern3.findall(other_last_lines)) >= 1:
|
||||||
logging.info("[Fix_Services] Firmware has halted or crashed. Restarting wlan0mon.")
|
logging.debug("[Fix_Services] Firmware has halted or crashed. Restarting wlan0mon.")
|
||||||
if hasattr(agent, 'view'):
|
if hasattr(agent, 'view'):
|
||||||
display = agent.view()
|
display = agent.view()
|
||||||
display.set('status', 'Firmware has halted or crashed. Restarting wlan0mon.')
|
display.set('status', 'Firmware has halted or crashed. Restarting wlan0mon.')
|
||||||
@ -167,13 +159,13 @@ class FixServices(plugins.Plugin):
|
|||||||
try:
|
try:
|
||||||
# Run the monstart command to restart wlan0mon
|
# Run the monstart command to restart wlan0mon
|
||||||
cmd_output = subprocess.check_output("monstart", shell=True)
|
cmd_output = subprocess.check_output("monstart", shell=True)
|
||||||
logging.info("[Fix_Services monstart]: %s" % repr(cmd_output))
|
logging.debug("[Fix_Services monstart]: %s" % repr(cmd_output))
|
||||||
except Exception as err:
|
except Exception as err:
|
||||||
logging.error("[Fix_Services monstart]: %s" % repr(err))
|
logging.error("[Fix_Services monstart]: %s" % repr(err))
|
||||||
|
|
||||||
# Look for pattern 4
|
# Look for pattern 4
|
||||||
elif len(self.pattern4.findall(other_other_last_lines)) >= 3:
|
elif len(self.pattern4.findall(other_other_last_lines)) >= 3:
|
||||||
logging.info("[Fix_Services] wlan0 is down!")
|
logging.debug("[Fix_Services] wlan0 is down!")
|
||||||
if hasattr(agent, 'view'):
|
if hasattr(agent, 'view'):
|
||||||
display = agent.view()
|
display = agent.view()
|
||||||
display.set('status', 'Restarting wlan0 now!')
|
display.set('status', 'Restarting wlan0 now!')
|
||||||
@ -181,7 +173,7 @@ class FixServices(plugins.Plugin):
|
|||||||
try:
|
try:
|
||||||
# Run the monstart command to restart wlan0mon
|
# Run the monstart command to restart wlan0mon
|
||||||
cmd_output = subprocess.check_output("monstart", shell=True)
|
cmd_output = subprocess.check_output("monstart", shell=True)
|
||||||
logging.info("[Fix_Services monstart]: %s" % repr(cmd_output))
|
logging.debug("[Fix_Services monstart]: %s" % repr(cmd_output))
|
||||||
except Exception as err:
|
except Exception as err:
|
||||||
logging.error("[Fix_Services monstart]: %s" % repr(err))
|
logging.error("[Fix_Services monstart]: %s" % repr(err))
|
||||||
|
|
||||||
@ -197,7 +189,7 @@ class FixServices(plugins.Plugin):
|
|||||||
elif level == "debug":
|
elif level == "debug":
|
||||||
logging.debug(message)
|
logging.debug(message)
|
||||||
else:
|
else:
|
||||||
logging.info(message)
|
logging.debug(message)
|
||||||
|
|
||||||
if ui:
|
if ui:
|
||||||
ui.update(force=force, new_data=displayData)
|
ui.update(force=force, new_data=displayData)
|
||||||
@ -212,17 +204,16 @@ class FixServices(plugins.Plugin):
|
|||||||
# avoid overlapping restarts, but allow it if it's been a while
|
# avoid overlapping restarts, but allow it if it's been a while
|
||||||
# (in case the last attempt failed before resetting "isReloadingMon")
|
# (in case the last attempt failed before resetting "isReloadingMon")
|
||||||
if self.isReloadingMon and (time.time() - self.LASTTRY) < 180:
|
if self.isReloadingMon and (time.time() - self.LASTTRY) < 180:
|
||||||
logging.info("[Fix_Services] Duplicate attempt ignored")
|
logging.debug("[Fix_Services] Duplicate attempt ignored")
|
||||||
else:
|
else:
|
||||||
self.isReloadingMon = True
|
self.isReloadingMon = True
|
||||||
self.LASTTRY = time.time()
|
self.LASTTRY = time.time()
|
||||||
|
|
||||||
self._status = "BL"
|
|
||||||
if hasattr(connection, 'view'):
|
if hasattr(connection, 'view'):
|
||||||
display = connection.view()
|
display = connection.view()
|
||||||
if display:
|
if display:
|
||||||
display.update(force=True, new_data={"status": "I'm blind! Try turning it off and on again",
|
display.update(force=True, new_data={"status": "I'm blind! Try turning it off and on again",
|
||||||
"brcmfmac_status": self._status, "face": faces.BORED})
|
"face": faces.BORED})
|
||||||
else:
|
else:
|
||||||
display = None
|
display = None
|
||||||
|
|
||||||
@ -238,9 +229,9 @@ class FixServices(plugins.Plugin):
|
|||||||
# is it up?
|
# is it up?
|
||||||
try:
|
try:
|
||||||
cmd_output = subprocess.check_output("ip link show wlan0mon", shell=True)
|
cmd_output = subprocess.check_output("ip link show wlan0mon", shell=True)
|
||||||
logging.info("[Fix_Services ip link show wlan0mon]: %s" % repr(cmd_output))
|
logging.debug("[Fix_Services ip link show wlan0mon]: %s" % repr(cmd_output))
|
||||||
if ",UP," in str(cmd_output):
|
if ",UP," in str(cmd_output):
|
||||||
logging.info("wlan0mon is up. Skip reset?")
|
logging.debug("wlan0mon is up. Skip reset?")
|
||||||
# not reliable, so don't skip just yet
|
# not reliable, so don't skip just yet
|
||||||
# print("wlan0mon is up. Skipping reset.")
|
# print("wlan0mon is up. Skipping reset.")
|
||||||
# self.isReloadingMon = False
|
# self.isReloadingMon = False
|
||||||
@ -261,11 +252,10 @@ class FixServices(plugins.Plugin):
|
|||||||
except Exception as err:
|
except Exception as err:
|
||||||
logging.error("[Fix_Services wifi.recon off] error %s" % (repr(err)))
|
logging.error("[Fix_Services wifi.recon off] error %s" % (repr(err)))
|
||||||
|
|
||||||
logging.info("[Fix_Services] recon paused. Now trying wlan0mon reload")
|
logging.debug("[Fix_Services] recon paused. Now trying wlan0mon reload")
|
||||||
|
|
||||||
try:
|
try:
|
||||||
cmd_output = subprocess.check_output("monstop", shell=True)
|
cmd_output = subprocess.check_output("monstop", shell=True)
|
||||||
self._status = "dn"
|
|
||||||
self.logPrintView("info", "[Fix_Services] wlan0mon down and deleted: %s" % cmd_output,
|
self.logPrintView("info", "[Fix_Services] wlan0mon down and deleted: %s" % cmd_output,
|
||||||
display, {"status": "wlan0mon d-d-d-down!", "face": faces.BORED})
|
display, {"status": "wlan0mon d-d-d-down!", "face": faces.BORED})
|
||||||
except Exception as nope:
|
except Exception as nope:
|
||||||
@ -285,7 +275,6 @@ class FixServices(plugins.Plugin):
|
|||||||
cmd_output = subprocess.check_output("sudo modprobe -r brcmfmac", shell=True)
|
cmd_output = subprocess.check_output("sudo modprobe -r brcmfmac", shell=True)
|
||||||
self.logPrintView("info", "[Fix_Services] unloaded brcmfmac", display,
|
self.logPrintView("info", "[Fix_Services] unloaded brcmfmac", display,
|
||||||
{"status": "Turning it off #%s" % tries, "face": faces.SMART})
|
{"status": "Turning it off #%s" % tries, "face": faces.SMART})
|
||||||
self._status = "ul"
|
|
||||||
|
|
||||||
# reload the module
|
# reload the module
|
||||||
try:
|
try:
|
||||||
@ -293,28 +282,24 @@ class FixServices(plugins.Plugin):
|
|||||||
cmd_output = subprocess.check_output("sudo modprobe brcmfmac", shell=True)
|
cmd_output = subprocess.check_output("sudo modprobe brcmfmac", shell=True)
|
||||||
|
|
||||||
self.logPrintView("info", "[Fix_Services] reloaded brcmfmac")
|
self.logPrintView("info", "[Fix_Services] reloaded brcmfmac")
|
||||||
self._status = "rl"
|
|
||||||
|
|
||||||
# success! now make the mon0
|
# success! now make the mon0
|
||||||
try:
|
try:
|
||||||
cmd_output = subprocess.check_output("monstart", shell=True)
|
cmd_output = subprocess.check_output("monstart", shell=True)
|
||||||
self.logPrintView("info", "[Fix_Services interface add wlan0mon worked #%s: %s"
|
self.logPrintView("info", "[Fix_Services interface add wlan0mon worked #%s: %s"
|
||||||
% (tries, cmd_output))
|
% (tries, cmd_output))
|
||||||
self._status = "up"
|
|
||||||
try:
|
try:
|
||||||
# try accessing mon0 in bettercap
|
# try accessing mon0 in bettercap
|
||||||
result = connection.run("set wifi.interface wlan0mon")
|
result = connection.run("set wifi.interface wlan0mon")
|
||||||
if "success" in result:
|
if "success" in result:
|
||||||
logging.info("[Fix_Services set wifi.interface wlan0mon worked!")
|
logging.debug("[Fix_Services set wifi.interface wlan0mon worked!")
|
||||||
self._status = ""
|
|
||||||
self._count = self._count + 1
|
|
||||||
# stop looping and get back to recon
|
# stop looping and get back to recon
|
||||||
break
|
break
|
||||||
else:
|
else:
|
||||||
logging.info(
|
logging.debug(
|
||||||
"[Fix_Services set wifi.interfaceface wlan0mon] failed? %s" % repr(result))
|
"[Fix_Services set wifi.interfaceface wlan0mon] failed? %s" % repr(result))
|
||||||
except Exception as err:
|
except Exception as err:
|
||||||
logging.info(
|
logging.debug(
|
||||||
"[Fix_Services set wifi.interface wlan0mon] except: %s" % repr(err))
|
"[Fix_Services set wifi.interface wlan0mon] except: %s" % repr(err))
|
||||||
except Exception as cerr: #
|
except Exception as cerr: #
|
||||||
if not display:
|
if not display:
|
||||||
@ -333,41 +318,38 @@ class FixServices(plugins.Plugin):
|
|||||||
|
|
||||||
tries = tries + 1
|
tries = tries + 1
|
||||||
if tries < 3:
|
if tries < 3:
|
||||||
logging.info("[Fix_Services] wlan0mon didn't make it. trying again")
|
logging.debug("[Fix_Services] wlan0mon didn't make it. trying again")
|
||||||
if not display:
|
if not display:
|
||||||
print(" wlan0mon didn't make it. trying again")
|
print(" wlan0mon didn't make it. trying again")
|
||||||
else:
|
else:
|
||||||
logging.info("[Fix_Services] wlan0mon loading failed, no choice but to reboot ..")
|
logging.debug("[Fix_Services] wlan0mon loading failed, no choice but to reboot ..")
|
||||||
pwnagotchi.reboot()
|
pwnagotchi.reboot()
|
||||||
|
|
||||||
# exited the loop, so hopefully it loaded
|
# exited the loop, so hopefully it loaded
|
||||||
if tries < 3:
|
if tries < 3:
|
||||||
if display:
|
if display:
|
||||||
display.update(force=True, new_data={"status": "And back on again...",
|
display.update(force=True, new_data={"status": "And back on again...",
|
||||||
"brcmfmac_status": self._status,
|
|
||||||
"face": faces.INTENSE})
|
"face": faces.INTENSE})
|
||||||
else:
|
else:
|
||||||
print("And back on again...")
|
print("And back on again...")
|
||||||
logging.info("[Fix_Services] wlan0mon back up")
|
logging.debug("[Fix_Services] wlan0mon back up")
|
||||||
else:
|
else:
|
||||||
self.LASTTRY = time.time()
|
self.LASTTRY = time.time()
|
||||||
|
|
||||||
time.sleep(8 + tries * 2) # give it a bit before restarting recon in bettercap
|
time.sleep(8 + tries * 2) # give it a bit before restarting recon in bettercap
|
||||||
self.isReloadingMon = False
|
self.isReloadingMon = False
|
||||||
|
|
||||||
logging.info("[Fix_Services] re-enable recon")
|
logging.debug("[Fix_Services] re-enable recon")
|
||||||
try:
|
try:
|
||||||
result = connection.run("wifi.clear; wifi.recon on")
|
result = connection.run("wifi.clear; wifi.recon on")
|
||||||
|
|
||||||
if "success" in result: # and result["success"] is True:
|
if "success" in result: # and result["success"] is True:
|
||||||
self._status = ""
|
|
||||||
if display:
|
if display:
|
||||||
display.update(force=True, new_data={"status": "I can see again! (probably)",
|
display.update(force=True, new_data={"status": "I can see again! (probably)",
|
||||||
"brcmfmac_status": self._status,
|
|
||||||
"face": faces.HAPPY})
|
"face": faces.HAPPY})
|
||||||
else:
|
else:
|
||||||
print("I can see again")
|
print("I can see again")
|
||||||
logging.info("[Fix_Services] wifi.recon on")
|
logging.debug("[Fix_Services] wifi.recon on")
|
||||||
self.LASTTRY = time.time() + 120 # 2-minute pause until next time.
|
self.LASTTRY = time.time() + 120 # 2-minute pause until next time.
|
||||||
else:
|
else:
|
||||||
logging.error("[Fix_Services] wifi.recon did not start up")
|
logging.error("[Fix_Services] wifi.recon did not start up")
|
||||||
@ -388,25 +370,14 @@ class FixServices(plugins.Plugin):
|
|||||||
else:
|
else:
|
||||||
pos = (ui.width() / 2 + 35, ui.height() - 11)
|
pos = (ui.width() / 2 + 35, ui.height() - 11)
|
||||||
|
|
||||||
logging.info("Got here")
|
logging.debug("Got here")
|
||||||
ui.add_element('brcmfmac_status', Text(color=BLACK, value='--', position=pos, font=fonts.Small))
|
|
||||||
|
|
||||||
# called when the ui is updated
|
# called when the ui is updated
|
||||||
def on_ui_update(self, ui):
|
def on_ui_update(self, ui):
|
||||||
# update those elements
|
return
|
||||||
if self._status:
|
|
||||||
ui.set('brcmfmac_status', "wlan0mon %s" % self._status)
|
|
||||||
else:
|
|
||||||
ui.set('brcmfmac_status', "rst#%s" % self._count)
|
|
||||||
|
|
||||||
def on_unload(self, ui):
|
def on_unload(self, ui):
|
||||||
with ui._lock:
|
return
|
||||||
try:
|
|
||||||
ui.remove_element('brcmfmac_status')
|
|
||||||
logging.info("[Fix_Services] unloaded")
|
|
||||||
except Exception as err:
|
|
||||||
logging.info("[Fix_Services] unload err %s " % repr(err))
|
|
||||||
pass
|
|
||||||
|
|
||||||
|
|
||||||
# run from command line to brute force a reload
|
# run from command line to brute force a reload
|
||||||
|
@ -98,7 +98,7 @@ class GPS(plugins.Plugin):
|
|||||||
lat_pos = (127, 74)
|
lat_pos = (127, 74)
|
||||||
lon_pos = (122, 84)
|
lon_pos = (122, 84)
|
||||||
alt_pos = (127, 94)
|
alt_pos = (127, 94)
|
||||||
elif ui.is_waveshare27inch():
|
elif ui.is_waveshare2in7():
|
||||||
lat_pos = (6, 120)
|
lat_pos = (6, 120)
|
||||||
lon_pos = (1, 135)
|
lon_pos = (1, 135)
|
||||||
alt_pos = (6, 150)
|
alt_pos = (6, 150)
|
||||||
|
@ -5,7 +5,6 @@ import glob
|
|||||||
import re
|
import re
|
||||||
|
|
||||||
import pwnagotchi.grid as grid
|
import pwnagotchi.grid as grid
|
||||||
import pwnagotchi.plugins
|
|
||||||
import pwnagotchi.plugins as plugins
|
import pwnagotchi.plugins as plugins
|
||||||
from pwnagotchi.utils import StatusFile, WifiInfo, extract_from_pcap
|
from pwnagotchi.utils import StatusFile, WifiInfo, extract_from_pcap
|
||||||
from threading import Lock
|
from threading import Lock
|
||||||
@ -87,7 +86,7 @@ class Grid(plugins.Plugin):
|
|||||||
agent.view().on_unread_messages(self.unread_messages, self.total_messages)
|
agent.view().on_unread_messages(self.unread_messages, self.total_messages)
|
||||||
|
|
||||||
def check_handshakes(self, agent):
|
def check_handshakes(self, agent):
|
||||||
logging.debug("checking pcaps")
|
logging.debug("checking pcap's")
|
||||||
config = agent.config()
|
config = agent.config()
|
||||||
|
|
||||||
pcap_files = glob.glob(os.path.join(agent.config()['bettercap']['handshakes'], "*.pcap"))
|
pcap_files = glob.glob(os.path.join(agent.config()['bettercap']['handshakes'], "*.pcap"))
|
||||||
|
@ -142,6 +142,6 @@ class OnlineHashCrack(plugins.Plugin):
|
|||||||
for row in csv.DictReader(cracked_list):
|
for row in csv.DictReader(cracked_list):
|
||||||
if row['password']:
|
if row['password']:
|
||||||
filename = re.sub(r'[^a-zA-Z0-9]', '', row['ESSID']) + '_' + row['BSSID'].replace(':','')
|
filename = re.sub(r'[^a-zA-Z0-9]', '', row['ESSID']) + '_' + row['BSSID'].replace(':','')
|
||||||
if os.path.exists( os.path.join(handshake_dir, filename+'.pcap') ):
|
if os.path.exists( os.path.join(handshake_dir, filename+'.pcap')):
|
||||||
with open(os.path.join(handshake_dir, filename+'.pcap.cracked'), 'w') as f:
|
with open(os.path.join(handshake_dir, filename+'.pcap.cracked'), 'w') as f:
|
||||||
f.write(row['password'])
|
f.write(row['password'])
|
||||||
|
@ -1,42 +0,0 @@
|
|||||||
import logging
|
|
||||||
import requests
|
|
||||||
import pwnagotchi.plugins as plugins
|
|
||||||
|
|
||||||
'''
|
|
||||||
You need an bluetooth connection to your android phone which is running PAW server with the GPS "hack" from Systemik and edited by shaynemk
|
|
||||||
GUIDE HERE: https://community.pwnagotchi.ai/t/setting-up-paw-gps-on-android
|
|
||||||
'''
|
|
||||||
|
|
||||||
|
|
||||||
class PawGPS(plugins.Plugin):
|
|
||||||
__author__ = 'leont'
|
|
||||||
__version__ = '1.0.1'
|
|
||||||
__name__ = 'pawgps'
|
|
||||||
__license__ = 'GPL3'
|
|
||||||
__description__ = 'Saves GPS coordinates whenever an handshake is captured. The GPS data is get from PAW on android.'
|
|
||||||
|
|
||||||
def __init__(self):
|
|
||||||
self.options = dict()
|
|
||||||
|
|
||||||
def on_loaded(self):
|
|
||||||
logging.info("[paw-gps] plugin loaded")
|
|
||||||
if 'ip' not in self.options or ('ip' in self.options and self.options['ip'] is None) or (len('ip' in self.options and self.options['ip']) is 0):
|
|
||||||
logging.info("[paw-gps] no IP Address defined in the config file, will uses paw server default (192.168.44.1:8080)")
|
|
||||||
|
|
||||||
def on_handshake(self, agent, filename, access_point, client_station):
|
|
||||||
if 'ip' not in self.options or ('ip' in self.options and self.options['ip'] is None or (len('ip' in self.options and self.options['ip']) is 0)):
|
|
||||||
ip = "192.168.44.1:8080"
|
|
||||||
else:
|
|
||||||
ip = self.options['ip']
|
|
||||||
|
|
||||||
try:
|
|
||||||
gps = requests.get('http://' + ip + '/gps.xhtml')
|
|
||||||
try:
|
|
||||||
gps_filename = filename.replace('.pcap', '.paw-gps.json')
|
|
||||||
logging.info("[paw-gps] saving GPS data to %s" % (gps_filename))
|
|
||||||
with open(gps_filename, 'w+t') as f:
|
|
||||||
f.write(gps.text)
|
|
||||||
except Exception as error:
|
|
||||||
logging.error(f"[paw-gps] encountered error while saving gps data: {error}")
|
|
||||||
except Exception as error:
|
|
||||||
logging.error(f"[paw-gps] encountered error while getting gps data: {error}")
|
|
@ -14,7 +14,7 @@ from dateutil.parser import parse
|
|||||||
|
|
||||||
the plugin does the following:
|
the plugin does the following:
|
||||||
- search for *.pcap files in your /handshakes/ dir
|
- search for *.pcap files in your /handshakes/ dir
|
||||||
- for every found .pcap file it looks for a .geo.json or .gps.json or .paw-gps.json file with
|
- for every found .pcap file it looks for a .geo.json or .gps.json or file with
|
||||||
latitude+longitude data inside and shows this position on the map
|
latitude+longitude data inside and shows this position on the map
|
||||||
- if also an .cracked file with a plaintext password inside exist, it reads the content and shows the
|
- if also an .cracked file with a plaintext password inside exist, it reads the content and shows the
|
||||||
position as green instead of red and the password inside the infopox of the position
|
position as green instead of red and the password inside the infopox of the position
|
||||||
@ -87,7 +87,8 @@ class Webgpsmap(plugins.Plugin):
|
|||||||
# returns all positions
|
# returns all positions
|
||||||
try:
|
try:
|
||||||
self.ALREADY_SENT = list()
|
self.ALREADY_SENT = list()
|
||||||
response_data = bytes(json.dumps(self.load_gps_from_dir(self.config['bettercap']['handshakes'])), "utf-8")
|
response_data = bytes(
|
||||||
|
json.dumps(self.load_gps_from_dir(self.config['bettercap']['handshakes'])), "utf-8")
|
||||||
response_status = 200
|
response_status = 200
|
||||||
response_mimetype = "application/json"
|
response_mimetype = "application/json"
|
||||||
response_header_contenttype = 'application/json'
|
response_header_contenttype = 'application/json'
|
||||||
@ -100,7 +101,8 @@ class Webgpsmap(plugins.Plugin):
|
|||||||
self.ALREADY_SENT = list()
|
self.ALREADY_SENT = list()
|
||||||
json_data = json.dumps(self.load_gps_from_dir(self.config['bettercap']['handshakes']))
|
json_data = json.dumps(self.load_gps_from_dir(self.config['bettercap']['handshakes']))
|
||||||
html_data = self.get_html()
|
html_data = self.get_html()
|
||||||
html_data = html_data.replace('var positions = [];', 'var positions = ' + json_data + ';positionsLoaded=true;drawPositions();')
|
html_data = html_data.replace('var positions = [];',
|
||||||
|
'var positions = ' + json_data + ';positionsLoaded=true;drawPositions();')
|
||||||
response_data = bytes(html_data, "utf-8")
|
response_data = bytes(html_data, "utf-8")
|
||||||
response_status = 200
|
response_status = 200
|
||||||
response_mimetype = "application/xhtml+xml"
|
response_mimetype = "application/xhtml+xml"
|
||||||
@ -163,7 +165,8 @@ class Webgpsmap(plugins.Plugin):
|
|||||||
|
|
||||||
all_files = os.listdir(handshake_dir)
|
all_files = os.listdir(handshake_dir)
|
||||||
# print(all_files)
|
# print(all_files)
|
||||||
all_pcap_files = [os.path.join(handshake_dir, filename) for filename in all_files if filename.endswith('.pcap')]
|
all_pcap_files = [os.path.join(handshake_dir, filename) for filename in all_files if
|
||||||
|
filename.endswith('.pcap')]
|
||||||
all_geo_or_gps_files = []
|
all_geo_or_gps_files = []
|
||||||
for filename_pcap in all_pcap_files:
|
for filename_pcap in all_pcap_files:
|
||||||
filename_base = filename_pcap[:-5] # remove ".pcap"
|
filename_base = filename_pcap[:-5] # remove ".pcap"
|
||||||
@ -180,22 +183,18 @@ class Webgpsmap(plugins.Plugin):
|
|||||||
if check_for in all_files:
|
if check_for in all_files:
|
||||||
filename_position = str(os.path.join(handshake_dir, check_for))
|
filename_position = str(os.path.join(handshake_dir, check_for))
|
||||||
|
|
||||||
logging.debug("[webgpsmap] search for .paw-gps.json")
|
|
||||||
check_for = os.path.basename(filename_base) + ".paw-gps.json"
|
|
||||||
if check_for in all_files:
|
|
||||||
filename_position = str(os.path.join(handshake_dir, check_for))
|
|
||||||
|
|
||||||
logging.debug(f"[webgpsmap] end search for position data files and use {filename_position}")
|
logging.debug(f"[webgpsmap] end search for position data files and use {filename_position}")
|
||||||
|
|
||||||
if filename_position is not None:
|
if filename_position is not None:
|
||||||
all_geo_or_gps_files.append(filename_position)
|
all_geo_or_gps_files.append(filename_position)
|
||||||
|
|
||||||
# all_geo_or_gps_files = set(all_geo_or_gps_files) - set(SKIP) # remove skipped networks? No!
|
# all_geo_or_gps_files = set(all_geo_or_gps_files) - set(SKIP) # remove skipped networks? No!
|
||||||
|
|
||||||
if newest_only:
|
if newest_only:
|
||||||
all_geo_or_gps_files = set(all_geo_or_gps_files) - set(self.ALREADY_SENT)
|
all_geo_or_gps_files = set(all_geo_or_gps_files) - set(self.ALREADY_SENT)
|
||||||
|
|
||||||
logging.info(f"[webgpsmap] Found {len(all_geo_or_gps_files)} position-data files from {len(all_pcap_files)} handshakes. Fetching positions ...")
|
logging.info(
|
||||||
|
f"[webgpsmap] Found {len(all_geo_or_gps_files)} position-data files from {len(all_pcap_files)} handshakes. Fetching positions ...")
|
||||||
|
|
||||||
for pos_file in all_geo_or_gps_files:
|
for pos_file in all_geo_or_gps_files:
|
||||||
try:
|
try:
|
||||||
@ -213,9 +212,7 @@ class Webgpsmap(plugins.Plugin):
|
|||||||
pos_type = 'gps'
|
pos_type = 'gps'
|
||||||
elif pos.type() == PositionFile.GEO:
|
elif pos.type() == PositionFile.GEO:
|
||||||
pos_type = 'geo'
|
pos_type = 'geo'
|
||||||
elif pos.type() == PositionFile.PAWGPS:
|
gps_data[ssid + "_" + mac] = {
|
||||||
pos_type = 'paw'
|
|
||||||
gps_data[ssid+"_"+mac] = {
|
|
||||||
'ssid': ssid,
|
'ssid': ssid,
|
||||||
'mac': mac,
|
'mac': mac,
|
||||||
'type': pos_type,
|
'type': pos_type,
|
||||||
@ -224,7 +221,7 @@ class Webgpsmap(plugins.Plugin):
|
|||||||
'acc': pos.accuracy(),
|
'acc': pos.accuracy(),
|
||||||
'ts_first': pos.timestamp_first(),
|
'ts_first': pos.timestamp_first(),
|
||||||
'ts_last': pos.timestamp_last(),
|
'ts_last': pos.timestamp_last(),
|
||||||
}
|
}
|
||||||
|
|
||||||
# get ap password if exist
|
# get ap password if exist
|
||||||
check_for = os.path.basename(pos_file).split(".")[0] + ".pcap.cracked"
|
check_for = os.path.basename(pos_file).split(".")[0] + ".pcap.cracked"
|
||||||
@ -265,7 +262,6 @@ class PositionFile:
|
|||||||
"""
|
"""
|
||||||
GPS = 1
|
GPS = 1
|
||||||
GEO = 2
|
GEO = 2
|
||||||
PAWGPS = 3
|
|
||||||
|
|
||||||
def __init__(self, path):
|
def __init__(self, path):
|
||||||
self._file = path
|
self._file = path
|
||||||
@ -282,7 +278,7 @@ class PositionFile:
|
|||||||
"""
|
"""
|
||||||
Returns the mac from filename
|
Returns the mac from filename
|
||||||
"""
|
"""
|
||||||
parsed_mac = re.search(r'.*_?([a-zA-Z0-9]{12})\.(?:gps|geo|paw-gps)\.json', self._filename)
|
parsed_mac = re.search(r'.*_?([a-zA-Z0-9]{12})\.(?:gps|geo)\.json', self._filename)
|
||||||
if parsed_mac:
|
if parsed_mac:
|
||||||
mac = parsed_mac.groups()[0]
|
mac = parsed_mac.groups()[0]
|
||||||
return mac
|
return mac
|
||||||
@ -292,7 +288,7 @@ class PositionFile:
|
|||||||
"""
|
"""
|
||||||
Returns the ssid from filename
|
Returns the ssid from filename
|
||||||
"""
|
"""
|
||||||
parsed_ssid = re.search(r'(.+)_[a-zA-Z0-9]{12}\.(?:gps|geo|paw-gps)\.json', self._filename)
|
parsed_ssid = re.search(r'(.+)_[a-zA-Z0-9]{12}\.(?:gps|geo)\.json', self._filename)
|
||||||
if parsed_ssid:
|
if parsed_ssid:
|
||||||
return parsed_ssid.groups()[0]
|
return parsed_ssid.groups()[0]
|
||||||
return None
|
return None
|
||||||
@ -354,8 +350,6 @@ class PositionFile:
|
|||||||
return PositionFile.GPS
|
return PositionFile.GPS
|
||||||
if self._file.endswith('.geo.json'):
|
if self._file.endswith('.geo.json'):
|
||||||
return PositionFile.GEO
|
return PositionFile.GEO
|
||||||
if self._file.endswith('.paw-gps.json'):
|
|
||||||
return PositionFile.PAWGPS
|
|
||||||
return None
|
return None
|
||||||
|
|
||||||
def lat(self):
|
def lat(self):
|
||||||
@ -402,9 +396,7 @@ class PositionFile:
|
|||||||
|
|
||||||
def accuracy(self):
|
def accuracy(self):
|
||||||
if self.type() == PositionFile.GPS:
|
if self.type() == PositionFile.GPS:
|
||||||
return 50.0 # a default
|
return 50.0 # a default
|
||||||
if self.type() == PositionFile.PAWGPS:
|
|
||||||
return 50.0 # a default
|
|
||||||
if self.type() == PositionFile.GEO:
|
if self.type() == PositionFile.GEO:
|
||||||
try:
|
try:
|
||||||
return self._json['accuracy']
|
return self._json['accuracy']
|
||||||
|
@ -118,6 +118,9 @@ class Display(View):
|
|||||||
def is_waveshare2in66g(self):
|
def is_waveshare2in66g(self):
|
||||||
return self._implementation.name == 'waveshare2in66g'
|
return self._implementation.name == 'waveshare2in66g'
|
||||||
|
|
||||||
|
def is_weact2in9(self):
|
||||||
|
return self._implementation.name == 'weact2in9'
|
||||||
|
|
||||||
def is_waveshare3in0g(self):
|
def is_waveshare3in0g(self):
|
||||||
return self._implementation.name == 'waveshare3in0g'
|
return self._implementation.name == 'waveshare3in0g'
|
||||||
|
|
||||||
@ -151,6 +154,12 @@ class Display(View):
|
|||||||
def is_waveshare5in65f(self):
|
def is_waveshare5in65f(self):
|
||||||
return self._implementation.name == 'waveshare5in65f'
|
return self._implementation.name == 'waveshare5in65f'
|
||||||
|
|
||||||
|
def is_waveshare5in79(self):
|
||||||
|
return self._implementation.name == 'waveshare5in79'
|
||||||
|
|
||||||
|
def is_waveshare5in79b(self):
|
||||||
|
return self._implementation.name == 'waveshare5in79b'
|
||||||
|
|
||||||
def is_waveshare5in83(self):
|
def is_waveshare5in83(self):
|
||||||
return self._implementation.name == 'waveshare5in83'
|
return self._implementation.name == 'waveshare5in83'
|
||||||
|
|
||||||
@ -208,6 +217,18 @@ class Display(View):
|
|||||||
def is_displayhatmini(self):
|
def is_displayhatmini(self):
|
||||||
return self._implementation.name == 'displayhatmini'
|
return self._implementation.name == 'displayhatmini'
|
||||||
|
|
||||||
|
def is_pirateaudio(self):
|
||||||
|
return self._implementation.name == 'pirateaudio'
|
||||||
|
|
||||||
|
def is_pitft(self):
|
||||||
|
return self._implementation.name == 'pitft'
|
||||||
|
|
||||||
|
def is_tftbonnet(self):
|
||||||
|
return self._implementation.name == 'tftbonnet'
|
||||||
|
|
||||||
|
def is_waveshareoledlcd(self):
|
||||||
|
return self._implementation.name == 'waveshareoledlcd'
|
||||||
|
|
||||||
def is_waveshare35lcd(self):
|
def is_waveshare35lcd(self):
|
||||||
return self._implementation.name == 'waveshare35lcd'
|
return self._implementation.name == 'waveshare35lcd'
|
||||||
|
|
||||||
|
@ -21,6 +21,10 @@ from pwnagotchi.ui.hw.waveshare2in13b_V4 import Waveshare213bV4
|
|||||||
from pwnagotchi.ui.hw.waveshare3in5lcd import Waveshare35lcd
|
from pwnagotchi.ui.hw.waveshare3in5lcd import Waveshare35lcd
|
||||||
from pwnagotchi.ui.hw.spotpear24in import Spotpear24inch
|
from pwnagotchi.ui.hw.spotpear24in import Spotpear24inch
|
||||||
from pwnagotchi.ui.hw.displayhatmini import DisplayHatMini
|
from pwnagotchi.ui.hw.displayhatmini import DisplayHatMini
|
||||||
|
from pwnagotchi.ui.hw.pirateaudio import PirateAudio
|
||||||
|
from pwnagotchi.ui.hw.pitft import Pitft
|
||||||
|
from pwnagotchi.ui.hw.tftbonnet import TftBonnet
|
||||||
|
from pwnagotchi.ui.hw.waveshareoledlcd import Waveshareoledlcd
|
||||||
from pwnagotchi.ui.hw.waveshare1in02 import Waveshare1in02
|
from pwnagotchi.ui.hw.waveshare1in02 import Waveshare1in02
|
||||||
from pwnagotchi.ui.hw.waveshare1in54 import Waveshare154
|
from pwnagotchi.ui.hw.waveshare1in54 import Waveshare154
|
||||||
from pwnagotchi.ui.hw.waveshare1in54_V2 import Waveshare154V2
|
from pwnagotchi.ui.hw.waveshare1in54_V2 import Waveshare154V2
|
||||||
@ -49,6 +53,8 @@ from pwnagotchi.ui.hw.waveshare4in2bc import Waveshare4in2bc
|
|||||||
from pwnagotchi.ui.hw.waveshare4in26 import Waveshare4in26
|
from pwnagotchi.ui.hw.waveshare4in26 import Waveshare4in26
|
||||||
from pwnagotchi.ui.hw.waveshare4in37g import Waveshare4in37g
|
from pwnagotchi.ui.hw.waveshare4in37g import Waveshare4in37g
|
||||||
from pwnagotchi.ui.hw.waveshare5in65f import Waveshare5in65f
|
from pwnagotchi.ui.hw.waveshare5in65f import Waveshare5in65f
|
||||||
|
from pwnagotchi.ui.hw.waveshare5in79 import Waveshare5in79
|
||||||
|
from pwnagotchi.ui.hw.waveshare5in79b import Waveshare5in79b
|
||||||
from pwnagotchi.ui.hw.waveshare5in83 import Waveshare5in83
|
from pwnagotchi.ui.hw.waveshare5in83 import Waveshare5in83
|
||||||
from pwnagotchi.ui.hw.waveshare5in83_V2 import Waveshare5in83V2
|
from pwnagotchi.ui.hw.waveshare5in83_V2 import Waveshare5in83V2
|
||||||
from pwnagotchi.ui.hw.waveshare5in83b_V2 import Waveshare5in83bV2
|
from pwnagotchi.ui.hw.waveshare5in83b_V2 import Waveshare5in83bV2
|
||||||
@ -95,7 +101,19 @@ def display_for(config):
|
|||||||
|
|
||||||
elif config['ui']['display']['type'] == 'displayhatmini':
|
elif config['ui']['display']['type'] == 'displayhatmini':
|
||||||
return DisplayHatMini(config)
|
return DisplayHatMini(config)
|
||||||
|
|
||||||
|
elif config['ui']['display']['type'] == 'pirateaudio':
|
||||||
|
return PirateAudio(config)
|
||||||
|
|
||||||
|
elif config['ui']['display']['type'] == 'pitft':
|
||||||
|
return Pitft(config)
|
||||||
|
|
||||||
|
elif config['ui']['display']['type'] == 'tftbonnet':
|
||||||
|
return TftBonnet(config)
|
||||||
|
|
||||||
|
elif config['ui']['display']['type'] == 'waveshareoledlcd':
|
||||||
|
return Waveshareoledlcd(config)
|
||||||
|
|
||||||
elif config['ui']['display']['type'] == 'waveshare1in02':
|
elif config['ui']['display']['type'] == 'waveshare1in02':
|
||||||
return Waveshare1in02(config)
|
return Waveshare1in02(config)
|
||||||
|
|
||||||
@ -219,6 +237,12 @@ def display_for(config):
|
|||||||
elif config['ui']['display']['type'] == 'waveshare5in65f':
|
elif config['ui']['display']['type'] == 'waveshare5in65f':
|
||||||
return Waveshare5in65f(config)
|
return Waveshare5in65f(config)
|
||||||
|
|
||||||
|
elif config['ui']['display']['type'] == 'waveshare5in79':
|
||||||
|
return Waveshare5in79(config)
|
||||||
|
|
||||||
|
elif config['ui']['display']['type'] == 'waveshare5in79b':
|
||||||
|
return Waveshare5in79b(config)
|
||||||
|
|
||||||
elif config['ui']['display']['type'] == 'waveshare5in83':
|
elif config['ui']['display']['type'] == 'waveshare5in83':
|
||||||
return Waveshare5in83(config)
|
return Waveshare5in83(config)
|
||||||
|
|
||||||
|
362
pwnagotchi/ui/hw/libs/adafruit/pitft/ILI9341.py
Normal file
362
pwnagotchi/ui/hw/libs/adafruit/pitft/ILI9341.py
Normal file
@ -0,0 +1,362 @@
|
|||||||
|
# Copyright (c) 2014 Adafruit Industries
|
||||||
|
# Author: Tony DiCola
|
||||||
|
#
|
||||||
|
# Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
|
# of this software and associated documentation files (the "Software"), to deal
|
||||||
|
# in the Software without restriction, including without limitation the rights
|
||||||
|
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
|
# copies of the Software, and to permit persons to whom the Software is
|
||||||
|
# furnished to do so, subject to the following conditions:
|
||||||
|
#
|
||||||
|
# The above copyright notice and this permission notice shall be included in
|
||||||
|
# all copies or substantial portions of the Software.
|
||||||
|
#
|
||||||
|
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
|
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||||
|
# THE SOFTWARE.
|
||||||
|
|
||||||
|
# Modified for Pwnagotchi by RasTacsko
|
||||||
|
# Based on ST7899 driver for pimoroni displayhatmini by Do-Ki
|
||||||
|
|
||||||
|
import numbers
|
||||||
|
import time
|
||||||
|
import numpy as np
|
||||||
|
|
||||||
|
from PIL import Image
|
||||||
|
from PIL import ImageDraw
|
||||||
|
|
||||||
|
import spidev
|
||||||
|
import RPi.GPIO as GPIO
|
||||||
|
|
||||||
|
__version__ = '0.0.1'
|
||||||
|
|
||||||
|
# Constants for interacting with display registers.
|
||||||
|
ILI9341_TFTWIDTH = 320
|
||||||
|
ILI9341_TFTHEIGHT = 240
|
||||||
|
|
||||||
|
ILI9341_NOP = 0x00
|
||||||
|
ILI9341_SWRESET = 0x01
|
||||||
|
ILI9341_RDDID = 0x04
|
||||||
|
ILI9341_RDDST = 0x09
|
||||||
|
|
||||||
|
ILI9341_SLPIN = 0x10
|
||||||
|
ILI9341_SLPOUT = 0x11
|
||||||
|
ILI9341_PTLON = 0x12
|
||||||
|
ILI9341_NORON = 0x13
|
||||||
|
|
||||||
|
ILI9341_RDMODE = 0x0A
|
||||||
|
ILI9341_RDMADCTL = 0x0B
|
||||||
|
ILI9341_RDPIXFMT = 0x0C
|
||||||
|
ILI9341_RDIMGFMT = 0x0A
|
||||||
|
ILI9341_RDSELFDIAG = 0x0F
|
||||||
|
|
||||||
|
ILI9341_INVOFF = 0x20
|
||||||
|
ILI9341_INVON = 0x21
|
||||||
|
ILI9341_GAMMASET = 0x26
|
||||||
|
ILI9341_DISPOFF = 0x28
|
||||||
|
ILI9341_DISPON = 0x29
|
||||||
|
|
||||||
|
ILI9341_CASET = 0x2A
|
||||||
|
ILI9341_PASET = 0x2B
|
||||||
|
ILI9341_RAMWR = 0x2C
|
||||||
|
ILI9341_RAMRD = 0x2E
|
||||||
|
|
||||||
|
ILI9341_PTLAR = 0x30
|
||||||
|
ILI9341_MADCTL = 0x36
|
||||||
|
ILI9341_PIXFMT = 0x3A
|
||||||
|
|
||||||
|
ILI9341_FRMCTR1 = 0xB1
|
||||||
|
ILI9341_FRMCTR2 = 0xB2
|
||||||
|
ILI9341_FRMCTR3 = 0xB3
|
||||||
|
ILI9341_INVCTR = 0xB4
|
||||||
|
ILI9341_DFUNCTR = 0xB6
|
||||||
|
|
||||||
|
ILI9341_PWCTR1 = 0xC0
|
||||||
|
ILI9341_PWCTR2 = 0xC1
|
||||||
|
ILI9341_PWCTR3 = 0xC2
|
||||||
|
ILI9341_PWCTR4 = 0xC3
|
||||||
|
ILI9341_PWCTR5 = 0xC4
|
||||||
|
ILI9341_VMCTR1 = 0xC5
|
||||||
|
ILI9341_VMCTR2 = 0xC7
|
||||||
|
|
||||||
|
ILI9341_RDID1 = 0xDA
|
||||||
|
ILI9341_RDID2 = 0xDB
|
||||||
|
ILI9341_RDID3 = 0xDC
|
||||||
|
ILI9341_RDID4 = 0xDD
|
||||||
|
|
||||||
|
ILI9341_GMCTRP1 = 0xE0
|
||||||
|
ILI9341_GMCTRN1 = 0xE1
|
||||||
|
|
||||||
|
ILI9341_PWCTR6 = 0xFC
|
||||||
|
|
||||||
|
class ILI9341(object):
|
||||||
|
"""Representation of an ILI9341 TFT LCD."""
|
||||||
|
|
||||||
|
def __init__(self, port, cs, dc, backlight, rst=None,
|
||||||
|
width=ILI9341_TFTWIDTH, height=ILI9341_TFTHEIGHT,
|
||||||
|
rotation=270, invert=False, spi_speed_hz=64000000,
|
||||||
|
offset_left=0, offset_top=0):
|
||||||
|
"""Create an instance of the display using SPI communication.
|
||||||
|
Must provide the GPIO pin number for the D/C pin and the SPI driver.
|
||||||
|
Can optionally provide the GPIO pin number for the reset pin as the rst parameter.
|
||||||
|
:param port: SPI port number -> 0
|
||||||
|
:param cs: SPI chip-select number (0 or 1 for BCM) -> 1
|
||||||
|
:param backlight: Pin for controlling backlight -> 18
|
||||||
|
:param rst: Reset pin for ILI9341 -> 24?
|
||||||
|
:param width: Width of display connected to ILI9341 -> 240
|
||||||
|
:param height: Height of display connected to ILI9341 -> 320
|
||||||
|
:param rotation: Rotation of display connected to ILI9341
|
||||||
|
:param invert: Invert display
|
||||||
|
:param spi_speed_hz: SPI speed (in Hz)
|
||||||
|
"""
|
||||||
|
|
||||||
|
if rotation not in [0, 90, 180, 270]:
|
||||||
|
raise ValueError("Invalid rotation {}".format(rotation))
|
||||||
|
|
||||||
|
GPIO.setwarnings(False)
|
||||||
|
GPIO.setmode(GPIO.BCM)
|
||||||
|
|
||||||
|
self._spi = spidev.SpiDev(port, cs)
|
||||||
|
self._spi.mode = 0
|
||||||
|
self._spi.lsbfirst = False
|
||||||
|
self._spi.max_speed_hz = spi_speed_hz
|
||||||
|
|
||||||
|
self._dc = dc
|
||||||
|
self._rst = rst
|
||||||
|
self._width = width
|
||||||
|
self._height = height
|
||||||
|
self._rotation = rotation
|
||||||
|
self._invert = invert
|
||||||
|
|
||||||
|
self._offset_left = offset_left
|
||||||
|
self._offset_top = offset_top
|
||||||
|
|
||||||
|
# Set DC as output.
|
||||||
|
GPIO.setup(dc, GPIO.OUT)
|
||||||
|
|
||||||
|
# Setup backlight as output (if provided).
|
||||||
|
self._backlight = backlight
|
||||||
|
if backlight is not None:
|
||||||
|
GPIO.setup(backlight, GPIO.OUT)
|
||||||
|
GPIO.output(backlight, GPIO.LOW)
|
||||||
|
time.sleep(0.05)
|
||||||
|
GPIO.output(backlight, GPIO.HIGH)
|
||||||
|
|
||||||
|
# Setup reset as output (if provided).
|
||||||
|
if rst is not None:
|
||||||
|
GPIO.setup(self._rst, GPIO.OUT)
|
||||||
|
self.reset()
|
||||||
|
|
||||||
|
# Create an image buffer.
|
||||||
|
self.buffer = Image.new('RGB', (width, height))
|
||||||
|
|
||||||
|
self._init()
|
||||||
|
|
||||||
|
def send(self, data, is_data=True, chunk_size=4096):
|
||||||
|
"""Write a byte or array of bytes to the display. Is_data parameter
|
||||||
|
controls if byte should be interpreted as display data (True) or command
|
||||||
|
data (False). Chunk_size is an optional size of bytes to write in a
|
||||||
|
single SPI transaction, with a default of 4096.
|
||||||
|
"""
|
||||||
|
# Set DC low for command, high for data.
|
||||||
|
GPIO.output(self._dc, is_data)
|
||||||
|
# Convert scalar argument to list so either can be passed as parameter.
|
||||||
|
if isinstance(data, numbers.Number):
|
||||||
|
data = [data & 0xFF]
|
||||||
|
# Write data a chunk at a time.
|
||||||
|
for start in range(0, len(data), chunk_size):
|
||||||
|
end = min(start+chunk_size, len(data))
|
||||||
|
self._spi.xfer(data[start:end])
|
||||||
|
|
||||||
|
def set_backlight(self, value):
|
||||||
|
"""Set the backlight on/off."""
|
||||||
|
if self._backlight is not None:
|
||||||
|
GPIO.output(self._backlight, value)
|
||||||
|
|
||||||
|
@property
|
||||||
|
def width(self):
|
||||||
|
return self._width if self._rotation == 0 or self._rotation == 180 else self._height
|
||||||
|
|
||||||
|
@property
|
||||||
|
def height(self):
|
||||||
|
return self._height if self._rotation == 0 or self._rotation == 180 else self._width
|
||||||
|
|
||||||
|
def command(self, data):
|
||||||
|
"""Write a byte or array of bytes to the display as command data."""
|
||||||
|
self.send(data, False)
|
||||||
|
|
||||||
|
def data(self, data):
|
||||||
|
"""Write a byte or array of bytes to the display as display data."""
|
||||||
|
self.send(data, True)
|
||||||
|
|
||||||
|
def reset(self):
|
||||||
|
"""Reset the display, if reset pin is connected."""
|
||||||
|
if self._rst is not None:
|
||||||
|
GPIO.output(self._rst, 1)
|
||||||
|
time.sleep(0.005)
|
||||||
|
GPIO.output(self._rst, 0)
|
||||||
|
time.sleep(0.02)
|
||||||
|
GPIO.output(self._rst, 1)
|
||||||
|
time.sleep(0.150)
|
||||||
|
|
||||||
|
def _init(self):
|
||||||
|
# Initialize the display. Broken out as a separate function so it can
|
||||||
|
# be overridden by other displays in the future.
|
||||||
|
self.command(0xEF)
|
||||||
|
self.data(0x03)
|
||||||
|
self.data(0x80)
|
||||||
|
self.data(0x02)
|
||||||
|
self.command(0xCF)
|
||||||
|
self.data(0x00)
|
||||||
|
self.data(0XC1)
|
||||||
|
self.data(0X30)
|
||||||
|
self.command(0xED)
|
||||||
|
self.data(0x64)
|
||||||
|
self.data(0x03)
|
||||||
|
self.data(0X12)
|
||||||
|
self.data(0X81)
|
||||||
|
self.command(0xE8)
|
||||||
|
self.data(0x85)
|
||||||
|
self.data(0x00)
|
||||||
|
self.data(0x78)
|
||||||
|
self.command(0xCB)
|
||||||
|
self.data(0x39)
|
||||||
|
self.data(0x2C)
|
||||||
|
self.data(0x00)
|
||||||
|
self.data(0x34)
|
||||||
|
self.data(0x02)
|
||||||
|
self.command(0xF7)
|
||||||
|
self.data(0x20)
|
||||||
|
self.command(0xEA)
|
||||||
|
self.data(0x00)
|
||||||
|
self.data(0x00)
|
||||||
|
self.command(ILI9341_PWCTR1) # Power control
|
||||||
|
self.data(0x23) # VRH[5:0]
|
||||||
|
self.command(ILI9341_PWCTR2) # Power control
|
||||||
|
self.data(0x10) # SAP[2:0];BT[3:0]
|
||||||
|
self.command(ILI9341_VMCTR1) # VCM control
|
||||||
|
self.data(0x3e)
|
||||||
|
self.data(0x28)
|
||||||
|
self.command(ILI9341_VMCTR2) # VCM control2
|
||||||
|
self.data(0x86) # --
|
||||||
|
self.command(ILI9341_MADCTL) # Memory Access Control
|
||||||
|
self.data(0x48)
|
||||||
|
self.command(ILI9341_PIXFMT)
|
||||||
|
self.data(0x55)
|
||||||
|
self.command(ILI9341_FRMCTR1)
|
||||||
|
self.data(0x00)
|
||||||
|
self.data(0x18)
|
||||||
|
self.command(ILI9341_DFUNCTR) # Display Function Control
|
||||||
|
self.data(0x08)
|
||||||
|
self.data(0x82)
|
||||||
|
self.data(0x27)
|
||||||
|
self.command(0xF2) # 3Gamma Function Disable
|
||||||
|
self.data(0x00)
|
||||||
|
self.command(ILI9341_GAMMASET) # Gamma curve selected
|
||||||
|
self.data(0x01)
|
||||||
|
self.command(ILI9341_GMCTRP1) # Set Gamma
|
||||||
|
self.data(0x0F)
|
||||||
|
self.data(0x31)
|
||||||
|
self.data(0x2B)
|
||||||
|
self.data(0x0C)
|
||||||
|
self.data(0x0E)
|
||||||
|
self.data(0x08)
|
||||||
|
self.data(0x4E)
|
||||||
|
self.data(0xF1)
|
||||||
|
self.data(0x37)
|
||||||
|
self.data(0x07)
|
||||||
|
self.data(0x10)
|
||||||
|
self.data(0x03)
|
||||||
|
self.data(0x0E)
|
||||||
|
self.data(0x09)
|
||||||
|
self.data(0x00)
|
||||||
|
self.command(ILI9341_GMCTRN1) # Set Gamma
|
||||||
|
self.data(0x00)
|
||||||
|
self.data(0x0E)
|
||||||
|
self.data(0x14)
|
||||||
|
self.data(0x03)
|
||||||
|
self.data(0x11)
|
||||||
|
self.data(0x07)
|
||||||
|
self.data(0x31)
|
||||||
|
self.data(0xC1)
|
||||||
|
self.data(0x48)
|
||||||
|
self.data(0x08)
|
||||||
|
self.data(0x0F)
|
||||||
|
self.data(0x0C)
|
||||||
|
self.data(0x31)
|
||||||
|
self.data(0x36)
|
||||||
|
self.data(0x0F)
|
||||||
|
if self._invert:
|
||||||
|
self.command(ILI9341_INVON) # Invert display
|
||||||
|
else:
|
||||||
|
self.command(ILI9341_INVOFF) # Don't invert display
|
||||||
|
self.command(ILI9341_SLPOUT) # Exit Sleep
|
||||||
|
time.sleep(0.120)
|
||||||
|
self.command(ILI9341_DISPON) # Display on
|
||||||
|
|
||||||
|
def begin(self):
|
||||||
|
"""Set up the display deprecated.
|
||||||
|
Included in __init__. """
|
||||||
|
pass
|
||||||
|
|
||||||
|
def set_window(self, x0=0, y0=0, x1=None, y1=None):
|
||||||
|
"""Set the pixel address window for proceeding drawing commands. x0 and
|
||||||
|
x1 should define the minimum and maximum x pixel bounds. y0 and y1
|
||||||
|
should define the minimum and maximum y pixel bound. If no parameters
|
||||||
|
are specified the default will be to update the entire display from 0,0
|
||||||
|
to 239,319.
|
||||||
|
"""
|
||||||
|
if x1 is None:
|
||||||
|
x1 = self.width-1
|
||||||
|
if y1 is None:
|
||||||
|
y1 = self.height-1
|
||||||
|
|
||||||
|
self.command(ILI9341_CASET) # Column addr set
|
||||||
|
self.data(x0 >> 8)
|
||||||
|
self.data(x0 & 0xFF) # XSTART
|
||||||
|
self.data(x1 >> 8)
|
||||||
|
self.data(x1 & 0xFF) # XEND
|
||||||
|
self.command(ILI9341_PASET) # Row addr set
|
||||||
|
self.data(y0 >> 8)
|
||||||
|
self.data(y0 & 0xFF) # YSTART
|
||||||
|
self.data(y1 >> 8)
|
||||||
|
self.data(y1 & 0xFF) # YEND
|
||||||
|
self.command(ILI9341_RAMWR) # write to RAM
|
||||||
|
|
||||||
|
def display(self, image):
|
||||||
|
"""Write the provided image to the hardware.
|
||||||
|
:param image: Should be RGB format and the same dimensions as the display hardware.
|
||||||
|
"""
|
||||||
|
# Set address bounds to entire display.
|
||||||
|
self.set_window()
|
||||||
|
|
||||||
|
# Convert image to 16bit RGB565 format and
|
||||||
|
# flatten into bytes.
|
||||||
|
pixelbytes = self.image_to_data(image, self._rotation)
|
||||||
|
|
||||||
|
# Write data to hardware.
|
||||||
|
for i in range(0, len(pixelbytes), 4096):
|
||||||
|
self.data(pixelbytes[i:i + 4096])
|
||||||
|
|
||||||
|
def image_to_data(self, image, rotation=0):
|
||||||
|
if not isinstance(image, np.ndarray):
|
||||||
|
image = np.array(image.convert('RGB'))
|
||||||
|
|
||||||
|
# Rotate the image
|
||||||
|
pb = np.rot90(image, rotation // 90).astype('uint16')
|
||||||
|
|
||||||
|
|
||||||
|
# Mask and shift the 888 RGB into 565 RGB
|
||||||
|
red = (pb[..., [0]] & 0xf8) << 8
|
||||||
|
green = (pb[..., [1]] & 0xfc) << 3
|
||||||
|
blue = (pb[..., [2]] & 0xf8) >> 3
|
||||||
|
|
||||||
|
# Stick 'em together
|
||||||
|
result = red | green | blue
|
||||||
|
|
||||||
|
# Output the raw bytes
|
||||||
|
return result.byteswap().tobytes()
|
360
pwnagotchi/ui/hw/libs/adafruit/tftbonnet/ST7789.py
Normal file
360
pwnagotchi/ui/hw/libs/adafruit/tftbonnet/ST7789.py
Normal file
@ -0,0 +1,360 @@
|
|||||||
|
# Copyright (c) 2014 Adafruit Industries
|
||||||
|
# Author: Tony DiCola
|
||||||
|
#
|
||||||
|
# Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
|
# of this software and associated documentation files (the "Software"), to deal
|
||||||
|
# in the Software without restriction, including without limitation the rights
|
||||||
|
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
|
# copies of the Software, and to permit persons to whom the Software is
|
||||||
|
# furnished to do so, subject to the following conditions:
|
||||||
|
#
|
||||||
|
# The above copyright notice and this permission notice shall be included in
|
||||||
|
# all copies or substantial portions of the Software.
|
||||||
|
#
|
||||||
|
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
|
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||||
|
# THE SOFTWARE.
|
||||||
|
import numbers
|
||||||
|
import time
|
||||||
|
import numpy as np
|
||||||
|
|
||||||
|
import spidev
|
||||||
|
import RPi.GPIO as GPIO
|
||||||
|
|
||||||
|
|
||||||
|
__version__ = '0.0.4'
|
||||||
|
|
||||||
|
BG_SPI_CS_BACK = 0
|
||||||
|
BG_SPI_CS_FRONT = 1
|
||||||
|
|
||||||
|
SPI_CLOCK_HZ = 16000000
|
||||||
|
|
||||||
|
ST7789_NOP = 0x00
|
||||||
|
ST7789_SWRESET = 0x01
|
||||||
|
ST7789_RDDID = 0x04
|
||||||
|
ST7789_RDDST = 0x09
|
||||||
|
|
||||||
|
ST7789_SLPIN = 0x10
|
||||||
|
ST7789_SLPOUT = 0x11
|
||||||
|
ST7789_PTLON = 0x12
|
||||||
|
ST7789_NORON = 0x13
|
||||||
|
|
||||||
|
ST7789_INVOFF = 0x20
|
||||||
|
ST7789_INVON = 0x21
|
||||||
|
ST7789_DISPOFF = 0x28
|
||||||
|
ST7789_DISPON = 0x29
|
||||||
|
|
||||||
|
ST7789_CASET = 0x2A
|
||||||
|
ST7789_RASET = 0x2B
|
||||||
|
ST7789_RAMWR = 0x2C
|
||||||
|
ST7789_RAMRD = 0x2E
|
||||||
|
|
||||||
|
ST7789_PTLAR = 0x30
|
||||||
|
ST7789_MADCTL = 0x36
|
||||||
|
ST7789_COLMOD = 0x3A
|
||||||
|
|
||||||
|
ST7789_FRMCTR1 = 0xB1
|
||||||
|
ST7789_FRMCTR2 = 0xB2
|
||||||
|
ST7789_FRMCTR3 = 0xB3
|
||||||
|
ST7789_INVCTR = 0xB4
|
||||||
|
ST7789_DISSET5 = 0xB6
|
||||||
|
|
||||||
|
ST7789_GCTRL = 0xB7
|
||||||
|
ST7789_GTADJ = 0xB8
|
||||||
|
ST7789_VCOMS = 0xBB
|
||||||
|
|
||||||
|
ST7789_LCMCTRL = 0xC0
|
||||||
|
ST7789_IDSET = 0xC1
|
||||||
|
ST7789_VDVVRHEN = 0xC2
|
||||||
|
ST7789_VRHS = 0xC3
|
||||||
|
ST7789_VDVS = 0xC4
|
||||||
|
ST7789_VMCTR1 = 0xC5
|
||||||
|
ST7789_FRCTRL2 = 0xC6
|
||||||
|
ST7789_CABCCTRL = 0xC7
|
||||||
|
|
||||||
|
ST7789_RDID1 = 0xDA
|
||||||
|
ST7789_RDID2 = 0xDB
|
||||||
|
ST7789_RDID3 = 0xDC
|
||||||
|
ST7789_RDID4 = 0xDD
|
||||||
|
|
||||||
|
ST7789_GMCTRP1 = 0xE0
|
||||||
|
ST7789_GMCTRN1 = 0xE1
|
||||||
|
|
||||||
|
ST7789_PWCTR6 = 0xFC
|
||||||
|
|
||||||
|
|
||||||
|
class ST7789(object):
|
||||||
|
"""Representation of an ST7789 TFT LCD."""
|
||||||
|
|
||||||
|
def __init__(self, port, cs, dc, backlight, rst=None, width=240,
|
||||||
|
height=240, rotation=90, invert=True, spi_speed_hz=60 * 1000 * 1000,
|
||||||
|
offset_left=0,
|
||||||
|
offset_top=0):
|
||||||
|
"""Create an instance of the display using SPI communication.
|
||||||
|
|
||||||
|
Must provide the GPIO pin number for the D/C pin and the SPI driver.
|
||||||
|
|
||||||
|
Can optionally provide the GPIO pin number for the reset pin as the rst parameter.
|
||||||
|
|
||||||
|
:param port: SPI port number
|
||||||
|
:param cs: SPI chip-select number (0 or 1 for BCM
|
||||||
|
:param backlight: Pin for controlling backlight
|
||||||
|
:param rst: Reset pin for ST7789
|
||||||
|
:param width: Width of display connected to ST7789
|
||||||
|
:param height: Height of display connected to ST7789
|
||||||
|
:param rotation: Rotation of display connected to ST7789
|
||||||
|
:param invert: Invert display
|
||||||
|
:param spi_speed_hz: SPI speed (in Hz)
|
||||||
|
|
||||||
|
"""
|
||||||
|
if rotation not in [0, 90, 180, 270]:
|
||||||
|
raise ValueError("Invalid rotation {}".format(rotation))
|
||||||
|
|
||||||
|
if width != height and rotation in [90, 270]:
|
||||||
|
raise ValueError("Invalid rotation {} for {}x{} resolution".format(rotation, width, height))
|
||||||
|
|
||||||
|
GPIO.setwarnings(False)
|
||||||
|
GPIO.setmode(GPIO.BCM)
|
||||||
|
|
||||||
|
self._spi = spidev.SpiDev(port, cs)
|
||||||
|
self._spi.mode = 0
|
||||||
|
self._spi.lsbfirst = False
|
||||||
|
self._spi.max_speed_hz = spi_speed_hz
|
||||||
|
|
||||||
|
self._dc = dc
|
||||||
|
self._rst = rst
|
||||||
|
self._width = width
|
||||||
|
self._height = height
|
||||||
|
self._rotation = rotation
|
||||||
|
self._invert = invert
|
||||||
|
|
||||||
|
self._offset_left = offset_left
|
||||||
|
self._offset_top = offset_top
|
||||||
|
|
||||||
|
# Set DC as output.
|
||||||
|
GPIO.setup(dc, GPIO.OUT)
|
||||||
|
|
||||||
|
# Setup backlight as output (if provided).
|
||||||
|
self._backlight = backlight
|
||||||
|
if backlight is not None:
|
||||||
|
GPIO.setup(backlight, GPIO.OUT)
|
||||||
|
GPIO.output(backlight, GPIO.LOW)
|
||||||
|
time.sleep(0.1)
|
||||||
|
GPIO.output(backlight, GPIO.HIGH)
|
||||||
|
|
||||||
|
# Setup reset as output (if provided).
|
||||||
|
if rst is not None:
|
||||||
|
GPIO.setup(self._rst, GPIO.OUT)
|
||||||
|
self.reset()
|
||||||
|
self._init()
|
||||||
|
|
||||||
|
def send(self, data, is_data=True, chunk_size=4096):
|
||||||
|
"""Write a byte or array of bytes to the display. Is_data parameter
|
||||||
|
controls if byte should be interpreted as display data (True) or command
|
||||||
|
data (False). Chunk_size is an optional size of bytes to write in a
|
||||||
|
single SPI transaction, with a default of 4096.
|
||||||
|
"""
|
||||||
|
# Set DC low for command, high for data.
|
||||||
|
GPIO.output(self._dc, is_data)
|
||||||
|
# Convert scalar argument to list so either can be passed as parameter.
|
||||||
|
if isinstance(data, numbers.Number):
|
||||||
|
data = [data & 0xFF]
|
||||||
|
# Write data a chunk at a time.
|
||||||
|
for start in range(0, len(data), chunk_size):
|
||||||
|
end = min(start + chunk_size, len(data))
|
||||||
|
self._spi.xfer(data[start:end])
|
||||||
|
|
||||||
|
def set_backlight(self, value):
|
||||||
|
"""Set the backlight on/off."""
|
||||||
|
if self._backlight is not None:
|
||||||
|
GPIO.output(self._backlight, value)
|
||||||
|
|
||||||
|
@property
|
||||||
|
def width(self):
|
||||||
|
return self._width if self._rotation == 0 or self._rotation == 180 else self._height
|
||||||
|
|
||||||
|
@property
|
||||||
|
def height(self):
|
||||||
|
return self._height if self._rotation == 0 or self._rotation == 180 else self._width
|
||||||
|
|
||||||
|
def command(self, data):
|
||||||
|
"""Write a byte or array of bytes to the display as command data."""
|
||||||
|
self.send(data, False)
|
||||||
|
|
||||||
|
def data(self, data):
|
||||||
|
"""Write a byte or array of bytes to the display as display data."""
|
||||||
|
self.send(data, True)
|
||||||
|
|
||||||
|
def reset(self):
|
||||||
|
"""Reset the display, if reset pin is connected."""
|
||||||
|
if self._rst is not None:
|
||||||
|
GPIO.output(self._rst, 1)
|
||||||
|
time.sleep(0.500)
|
||||||
|
GPIO.output(self._rst, 0)
|
||||||
|
time.sleep(0.500)
|
||||||
|
GPIO.output(self._rst, 1)
|
||||||
|
time.sleep(0.500)
|
||||||
|
|
||||||
|
def _init(self):
|
||||||
|
# Initialize the display.
|
||||||
|
|
||||||
|
self.command(ST7789_SWRESET) # Software reset
|
||||||
|
time.sleep(0.150) # delay 150 ms
|
||||||
|
|
||||||
|
self.command(ST7789_MADCTL)
|
||||||
|
self.data(0x70)
|
||||||
|
|
||||||
|
self.command(ST7789_FRMCTR2) # Frame rate ctrl - idle mode
|
||||||
|
self.data(0x0C)
|
||||||
|
self.data(0x0C)
|
||||||
|
self.data(0x00)
|
||||||
|
self.data(0x33)
|
||||||
|
self.data(0x33)
|
||||||
|
|
||||||
|
self.command(ST7789_COLMOD)
|
||||||
|
self.data(0x05)
|
||||||
|
|
||||||
|
self.command(ST7789_GCTRL)
|
||||||
|
self.data(0x14)
|
||||||
|
|
||||||
|
self.command(ST7789_VCOMS)
|
||||||
|
self.data(0x37)
|
||||||
|
|
||||||
|
self.command(ST7789_LCMCTRL) # Power control
|
||||||
|
self.data(0x2C)
|
||||||
|
|
||||||
|
self.command(ST7789_VDVVRHEN) # Power control
|
||||||
|
self.data(0x01)
|
||||||
|
|
||||||
|
self.command(ST7789_VRHS) # Power control
|
||||||
|
self.data(0x12)
|
||||||
|
|
||||||
|
self.command(ST7789_VDVS) # Power control
|
||||||
|
self.data(0x20)
|
||||||
|
|
||||||
|
self.command(0xD0)
|
||||||
|
self.data(0xA4)
|
||||||
|
self.data(0xA1)
|
||||||
|
|
||||||
|
self.command(ST7789_FRCTRL2)
|
||||||
|
self.data(0x0F)
|
||||||
|
|
||||||
|
self.command(ST7789_GMCTRP1) # Set Gamma
|
||||||
|
self.data(0xD0)
|
||||||
|
self.data(0x04)
|
||||||
|
self.data(0x0D)
|
||||||
|
self.data(0x11)
|
||||||
|
self.data(0x13)
|
||||||
|
self.data(0x2B)
|
||||||
|
self.data(0x3F)
|
||||||
|
self.data(0x54)
|
||||||
|
self.data(0x4C)
|
||||||
|
self.data(0x18)
|
||||||
|
self.data(0x0D)
|
||||||
|
self.data(0x0B)
|
||||||
|
self.data(0x1F)
|
||||||
|
self.data(0x23)
|
||||||
|
|
||||||
|
self.command(ST7789_GMCTRN1) # Set Gamma
|
||||||
|
self.data(0xD0)
|
||||||
|
self.data(0x04)
|
||||||
|
self.data(0x0C)
|
||||||
|
self.data(0x11)
|
||||||
|
self.data(0x13)
|
||||||
|
self.data(0x2C)
|
||||||
|
self.data(0x3F)
|
||||||
|
self.data(0x44)
|
||||||
|
self.data(0x51)
|
||||||
|
self.data(0x2F)
|
||||||
|
self.data(0x1F)
|
||||||
|
self.data(0x1F)
|
||||||
|
self.data(0x20)
|
||||||
|
self.data(0x23)
|
||||||
|
|
||||||
|
if self._invert:
|
||||||
|
self.command(ST7789_INVON) # Invert display
|
||||||
|
else:
|
||||||
|
self.command(ST7789_INVOFF) # Don't invert display
|
||||||
|
|
||||||
|
self.command(ST7789_SLPOUT)
|
||||||
|
|
||||||
|
self.command(ST7789_DISPON) # Display on
|
||||||
|
time.sleep(0.100) # 100 ms
|
||||||
|
|
||||||
|
def begin(self):
|
||||||
|
"""Set up the display
|
||||||
|
|
||||||
|
Deprecated. Included in __init__.
|
||||||
|
|
||||||
|
"""
|
||||||
|
pass
|
||||||
|
|
||||||
|
def set_window(self, x0=0, y0=0, x1=None, y1=None):
|
||||||
|
"""Set the pixel address window for proceeding drawing commands. x0 and
|
||||||
|
x1 should define the minimum and maximum x pixel bounds. y0 and y1
|
||||||
|
should define the minimum and maximum y pixel bound. If no parameters
|
||||||
|
are specified the default will be to update the entire display from 0,0
|
||||||
|
to width-1,height-1.
|
||||||
|
"""
|
||||||
|
if x1 is None:
|
||||||
|
x1 = self._width - 1
|
||||||
|
|
||||||
|
if y1 is None:
|
||||||
|
y1 = self._height - 1
|
||||||
|
|
||||||
|
y0 += self._offset_top
|
||||||
|
y1 += self._offset_top
|
||||||
|
|
||||||
|
x0 += self._offset_left
|
||||||
|
x1 += self._offset_left
|
||||||
|
|
||||||
|
self.command(ST7789_CASET) # Column addr set
|
||||||
|
self.data(x0 >> 8)
|
||||||
|
self.data(x0 & 0xFF) # XSTART
|
||||||
|
self.data(x1 >> 8)
|
||||||
|
self.data(x1 & 0xFF) # XEND
|
||||||
|
self.command(ST7789_RASET) # Row addr set
|
||||||
|
self.data(y0 >> 8)
|
||||||
|
self.data(y0 & 0xFF) # YSTART
|
||||||
|
self.data(y1 >> 8)
|
||||||
|
self.data(y1 & 0xFF) # YEND
|
||||||
|
self.command(ST7789_RAMWR) # write to RAM
|
||||||
|
|
||||||
|
def display(self, image):
|
||||||
|
"""Write the provided image to the hardware.
|
||||||
|
|
||||||
|
:param image: Should be RGB format and the same dimensions as the display hardware.
|
||||||
|
|
||||||
|
"""
|
||||||
|
# Set address bounds to entire display.
|
||||||
|
self.set_window()
|
||||||
|
|
||||||
|
# Convert image to 16bit RGB565 format and
|
||||||
|
# flatten into bytes.
|
||||||
|
pixelbytes = self.image_to_data(image, self._rotation)
|
||||||
|
|
||||||
|
# Write data to hardware.
|
||||||
|
for i in range(0, len(pixelbytes), 4096):
|
||||||
|
self.data(pixelbytes[i:i + 4096])
|
||||||
|
|
||||||
|
def image_to_data(self, image, rotation=0):
|
||||||
|
if not isinstance(image, np.ndarray):
|
||||||
|
image = np.array(image.convert('RGB'))
|
||||||
|
|
||||||
|
# Rotate the image
|
||||||
|
pb = np.rot90(image, rotation // 90).astype('uint16')
|
||||||
|
|
||||||
|
# Mask and shift the 888 RGB into 565 RGB
|
||||||
|
red = (pb[..., [0]] & 0xf8) << 8
|
||||||
|
green = (pb[..., [1]] & 0xfc) << 3
|
||||||
|
blue = (pb[..., [2]] & 0xf8) >> 3
|
||||||
|
|
||||||
|
# Stick 'em together
|
||||||
|
result = red | green | blue
|
||||||
|
|
||||||
|
# Output the raw bytes
|
||||||
|
return result.byteswap().tobytes()
|
360
pwnagotchi/ui/hw/libs/pimoroni/pirateaudio/ST7789.py
Normal file
360
pwnagotchi/ui/hw/libs/pimoroni/pirateaudio/ST7789.py
Normal file
@ -0,0 +1,360 @@
|
|||||||
|
# Copyright (c) 2014 Adafruit Industries
|
||||||
|
# Author: Tony DiCola
|
||||||
|
#
|
||||||
|
# Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
|
# of this software and associated documentation files (the "Software"), to deal
|
||||||
|
# in the Software without restriction, including without limitation the rights
|
||||||
|
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
|
# copies of the Software, and to permit persons to whom the Software is
|
||||||
|
# furnished to do so, subject to the following conditions:
|
||||||
|
#
|
||||||
|
# The above copyright notice and this permission notice shall be included in
|
||||||
|
# all copies or substantial portions of the Software.
|
||||||
|
#
|
||||||
|
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
|
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||||
|
# THE SOFTWARE.
|
||||||
|
import numbers
|
||||||
|
import time
|
||||||
|
import numpy as np
|
||||||
|
|
||||||
|
import spidev
|
||||||
|
import RPi.GPIO as GPIO
|
||||||
|
|
||||||
|
|
||||||
|
__version__ = '0.0.4'
|
||||||
|
|
||||||
|
BG_SPI_CS_BACK = 0
|
||||||
|
BG_SPI_CS_FRONT = 1
|
||||||
|
|
||||||
|
SPI_CLOCK_HZ = 16000000
|
||||||
|
|
||||||
|
ST7789_NOP = 0x00
|
||||||
|
ST7789_SWRESET = 0x01
|
||||||
|
ST7789_RDDID = 0x04
|
||||||
|
ST7789_RDDST = 0x09
|
||||||
|
|
||||||
|
ST7789_SLPIN = 0x10
|
||||||
|
ST7789_SLPOUT = 0x11
|
||||||
|
ST7789_PTLON = 0x12
|
||||||
|
ST7789_NORON = 0x13
|
||||||
|
|
||||||
|
ST7789_INVOFF = 0x20
|
||||||
|
ST7789_INVON = 0x21
|
||||||
|
ST7789_DISPOFF = 0x28
|
||||||
|
ST7789_DISPON = 0x29
|
||||||
|
|
||||||
|
ST7789_CASET = 0x2A
|
||||||
|
ST7789_RASET = 0x2B
|
||||||
|
ST7789_RAMWR = 0x2C
|
||||||
|
ST7789_RAMRD = 0x2E
|
||||||
|
|
||||||
|
ST7789_PTLAR = 0x30
|
||||||
|
ST7789_MADCTL = 0x36
|
||||||
|
ST7789_COLMOD = 0x3A
|
||||||
|
|
||||||
|
ST7789_FRMCTR1 = 0xB1
|
||||||
|
ST7789_FRMCTR2 = 0xB2
|
||||||
|
ST7789_FRMCTR3 = 0xB3
|
||||||
|
ST7789_INVCTR = 0xB4
|
||||||
|
ST7789_DISSET5 = 0xB6
|
||||||
|
|
||||||
|
ST7789_GCTRL = 0xB7
|
||||||
|
ST7789_GTADJ = 0xB8
|
||||||
|
ST7789_VCOMS = 0xBB
|
||||||
|
|
||||||
|
ST7789_LCMCTRL = 0xC0
|
||||||
|
ST7789_IDSET = 0xC1
|
||||||
|
ST7789_VDVVRHEN = 0xC2
|
||||||
|
ST7789_VRHS = 0xC3
|
||||||
|
ST7789_VDVS = 0xC4
|
||||||
|
ST7789_VMCTR1 = 0xC5
|
||||||
|
ST7789_FRCTRL2 = 0xC6
|
||||||
|
ST7789_CABCCTRL = 0xC7
|
||||||
|
|
||||||
|
ST7789_RDID1 = 0xDA
|
||||||
|
ST7789_RDID2 = 0xDB
|
||||||
|
ST7789_RDID3 = 0xDC
|
||||||
|
ST7789_RDID4 = 0xDD
|
||||||
|
|
||||||
|
ST7789_GMCTRP1 = 0xE0
|
||||||
|
ST7789_GMCTRN1 = 0xE1
|
||||||
|
|
||||||
|
ST7789_PWCTR6 = 0xFC
|
||||||
|
|
||||||
|
|
||||||
|
class ST7789(object):
|
||||||
|
"""Representation of an ST7789 TFT LCD."""
|
||||||
|
|
||||||
|
def __init__(self, port, cs, dc, backlight, rst=None, width=240,
|
||||||
|
height=240, rotation=0, invert=True, spi_speed_hz=60 * 1000 * 1000,
|
||||||
|
offset_left=0,
|
||||||
|
offset_top=0):
|
||||||
|
"""Create an instance of the display using SPI communication.
|
||||||
|
|
||||||
|
Must provide the GPIO pin number for the D/C pin and the SPI driver.
|
||||||
|
|
||||||
|
Can optionally provide the GPIO pin number for the reset pin as the rst parameter.
|
||||||
|
|
||||||
|
:param port: SPI port number
|
||||||
|
:param cs: SPI chip-select number (0 or 1 for BCM
|
||||||
|
:param backlight: Pin for controlling backlight
|
||||||
|
:param rst: Reset pin for ST7789
|
||||||
|
:param width: Width of display connected to ST7789
|
||||||
|
:param height: Height of display connected to ST7789
|
||||||
|
:param rotation: Rotation of display connected to ST7789
|
||||||
|
:param invert: Invert display
|
||||||
|
:param spi_speed_hz: SPI speed (in Hz)
|
||||||
|
|
||||||
|
"""
|
||||||
|
if rotation not in [0, 90, 180, 270]:
|
||||||
|
raise ValueError("Invalid rotation {}".format(rotation))
|
||||||
|
|
||||||
|
if width != height and rotation in [90, 270]:
|
||||||
|
raise ValueError("Invalid rotation {} for {}x{} resolution".format(rotation, width, height))
|
||||||
|
|
||||||
|
GPIO.setwarnings(False)
|
||||||
|
GPIO.setmode(GPIO.BCM)
|
||||||
|
|
||||||
|
self._spi = spidev.SpiDev(port, cs)
|
||||||
|
self._spi.mode = 0
|
||||||
|
self._spi.lsbfirst = False
|
||||||
|
self._spi.max_speed_hz = spi_speed_hz
|
||||||
|
|
||||||
|
self._dc = dc
|
||||||
|
self._rst = rst
|
||||||
|
self._width = width
|
||||||
|
self._height = height
|
||||||
|
self._rotation = rotation
|
||||||
|
self._invert = invert
|
||||||
|
|
||||||
|
self._offset_left = offset_left
|
||||||
|
self._offset_top = offset_top
|
||||||
|
|
||||||
|
# Set DC as output.
|
||||||
|
GPIO.setup(dc, GPIO.OUT)
|
||||||
|
|
||||||
|
# Setup backlight as output (if provided).
|
||||||
|
self._backlight = backlight
|
||||||
|
if backlight is not None:
|
||||||
|
GPIO.setup(backlight, GPIO.OUT)
|
||||||
|
GPIO.output(backlight, GPIO.LOW)
|
||||||
|
time.sleep(0.1)
|
||||||
|
GPIO.output(backlight, GPIO.HIGH)
|
||||||
|
|
||||||
|
# Setup reset as output (if provided).
|
||||||
|
if rst is not None:
|
||||||
|
GPIO.setup(self._rst, GPIO.OUT)
|
||||||
|
self.reset()
|
||||||
|
self._init()
|
||||||
|
|
||||||
|
def send(self, data, is_data=True, chunk_size=4096):
|
||||||
|
"""Write a byte or array of bytes to the display. Is_data parameter
|
||||||
|
controls if byte should be interpreted as display data (True) or command
|
||||||
|
data (False). Chunk_size is an optional size of bytes to write in a
|
||||||
|
single SPI transaction, with a default of 4096.
|
||||||
|
"""
|
||||||
|
# Set DC low for command, high for data.
|
||||||
|
GPIO.output(self._dc, is_data)
|
||||||
|
# Convert scalar argument to list so either can be passed as parameter.
|
||||||
|
if isinstance(data, numbers.Number):
|
||||||
|
data = [data & 0xFF]
|
||||||
|
# Write data a chunk at a time.
|
||||||
|
for start in range(0, len(data), chunk_size):
|
||||||
|
end = min(start + chunk_size, len(data))
|
||||||
|
self._spi.xfer(data[start:end])
|
||||||
|
|
||||||
|
def set_backlight(self, value):
|
||||||
|
"""Set the backlight on/off."""
|
||||||
|
if self._backlight is not None:
|
||||||
|
GPIO.output(self._backlight, value)
|
||||||
|
|
||||||
|
@property
|
||||||
|
def width(self):
|
||||||
|
return self._width if self._rotation == 0 or self._rotation == 180 else self._height
|
||||||
|
|
||||||
|
@property
|
||||||
|
def height(self):
|
||||||
|
return self._height if self._rotation == 0 or self._rotation == 180 else self._width
|
||||||
|
|
||||||
|
def command(self, data):
|
||||||
|
"""Write a byte or array of bytes to the display as command data."""
|
||||||
|
self.send(data, False)
|
||||||
|
|
||||||
|
def data(self, data):
|
||||||
|
"""Write a byte or array of bytes to the display as display data."""
|
||||||
|
self.send(data, True)
|
||||||
|
|
||||||
|
def reset(self):
|
||||||
|
"""Reset the display, if reset pin is connected."""
|
||||||
|
if self._rst is not None:
|
||||||
|
GPIO.output(self._rst, 1)
|
||||||
|
time.sleep(0.500)
|
||||||
|
GPIO.output(self._rst, 0)
|
||||||
|
time.sleep(0.500)
|
||||||
|
GPIO.output(self._rst, 1)
|
||||||
|
time.sleep(0.500)
|
||||||
|
|
||||||
|
def _init(self):
|
||||||
|
# Initialize the display.
|
||||||
|
|
||||||
|
self.command(ST7789_SWRESET) # Software reset
|
||||||
|
time.sleep(0.150) # delay 150 ms
|
||||||
|
|
||||||
|
self.command(ST7789_MADCTL)
|
||||||
|
self.data(0x70)
|
||||||
|
|
||||||
|
self.command(ST7789_FRMCTR2) # Frame rate ctrl - idle mode
|
||||||
|
self.data(0x0C)
|
||||||
|
self.data(0x0C)
|
||||||
|
self.data(0x00)
|
||||||
|
self.data(0x33)
|
||||||
|
self.data(0x33)
|
||||||
|
|
||||||
|
self.command(ST7789_COLMOD)
|
||||||
|
self.data(0x05)
|
||||||
|
|
||||||
|
self.command(ST7789_GCTRL)
|
||||||
|
self.data(0x14)
|
||||||
|
|
||||||
|
self.command(ST7789_VCOMS)
|
||||||
|
self.data(0x37)
|
||||||
|
|
||||||
|
self.command(ST7789_LCMCTRL) # Power control
|
||||||
|
self.data(0x2C)
|
||||||
|
|
||||||
|
self.command(ST7789_VDVVRHEN) # Power control
|
||||||
|
self.data(0x01)
|
||||||
|
|
||||||
|
self.command(ST7789_VRHS) # Power control
|
||||||
|
self.data(0x12)
|
||||||
|
|
||||||
|
self.command(ST7789_VDVS) # Power control
|
||||||
|
self.data(0x20)
|
||||||
|
|
||||||
|
self.command(0xD0)
|
||||||
|
self.data(0xA4)
|
||||||
|
self.data(0xA1)
|
||||||
|
|
||||||
|
self.command(ST7789_FRCTRL2)
|
||||||
|
self.data(0x0F)
|
||||||
|
|
||||||
|
self.command(ST7789_GMCTRP1) # Set Gamma
|
||||||
|
self.data(0xD0)
|
||||||
|
self.data(0x04)
|
||||||
|
self.data(0x0D)
|
||||||
|
self.data(0x11)
|
||||||
|
self.data(0x13)
|
||||||
|
self.data(0x2B)
|
||||||
|
self.data(0x3F)
|
||||||
|
self.data(0x54)
|
||||||
|
self.data(0x4C)
|
||||||
|
self.data(0x18)
|
||||||
|
self.data(0x0D)
|
||||||
|
self.data(0x0B)
|
||||||
|
self.data(0x1F)
|
||||||
|
self.data(0x23)
|
||||||
|
|
||||||
|
self.command(ST7789_GMCTRN1) # Set Gamma
|
||||||
|
self.data(0xD0)
|
||||||
|
self.data(0x04)
|
||||||
|
self.data(0x0C)
|
||||||
|
self.data(0x11)
|
||||||
|
self.data(0x13)
|
||||||
|
self.data(0x2C)
|
||||||
|
self.data(0x3F)
|
||||||
|
self.data(0x44)
|
||||||
|
self.data(0x51)
|
||||||
|
self.data(0x2F)
|
||||||
|
self.data(0x1F)
|
||||||
|
self.data(0x1F)
|
||||||
|
self.data(0x20)
|
||||||
|
self.data(0x23)
|
||||||
|
|
||||||
|
if self._invert:
|
||||||
|
self.command(ST7789_INVON) # Invert display
|
||||||
|
else:
|
||||||
|
self.command(ST7789_INVOFF) # Don't invert display
|
||||||
|
|
||||||
|
self.command(ST7789_SLPOUT)
|
||||||
|
|
||||||
|
self.command(ST7789_DISPON) # Display on
|
||||||
|
time.sleep(0.100) # 100 ms
|
||||||
|
|
||||||
|
def begin(self):
|
||||||
|
"""Set up the display
|
||||||
|
|
||||||
|
Deprecated. Included in __init__.
|
||||||
|
|
||||||
|
"""
|
||||||
|
pass
|
||||||
|
|
||||||
|
def set_window(self, x0=0, y0=0, x1=None, y1=None):
|
||||||
|
"""Set the pixel address window for proceeding drawing commands. x0 and
|
||||||
|
x1 should define the minimum and maximum x pixel bounds. y0 and y1
|
||||||
|
should define the minimum and maximum y pixel bound. If no parameters
|
||||||
|
are specified the default will be to update the entire display from 0,0
|
||||||
|
to width-1,height-1.
|
||||||
|
"""
|
||||||
|
if x1 is None:
|
||||||
|
x1 = self._width - 1
|
||||||
|
|
||||||
|
if y1 is None:
|
||||||
|
y1 = self._height - 1
|
||||||
|
|
||||||
|
y0 += self._offset_top
|
||||||
|
y1 += self._offset_top
|
||||||
|
|
||||||
|
x0 += self._offset_left
|
||||||
|
x1 += self._offset_left
|
||||||
|
|
||||||
|
self.command(ST7789_CASET) # Column addr set
|
||||||
|
self.data(x0 >> 8)
|
||||||
|
self.data(x0 & 0xFF) # XSTART
|
||||||
|
self.data(x1 >> 8)
|
||||||
|
self.data(x1 & 0xFF) # XEND
|
||||||
|
self.command(ST7789_RASET) # Row addr set
|
||||||
|
self.data(y0 >> 8)
|
||||||
|
self.data(y0 & 0xFF) # YSTART
|
||||||
|
self.data(y1 >> 8)
|
||||||
|
self.data(y1 & 0xFF) # YEND
|
||||||
|
self.command(ST7789_RAMWR) # write to RAM
|
||||||
|
|
||||||
|
def display(self, image):
|
||||||
|
"""Write the provided image to the hardware.
|
||||||
|
|
||||||
|
:param image: Should be RGB format and the same dimensions as the display hardware.
|
||||||
|
|
||||||
|
"""
|
||||||
|
# Set address bounds to entire display.
|
||||||
|
self.set_window()
|
||||||
|
|
||||||
|
# Convert image to 16bit RGB565 format and
|
||||||
|
# flatten into bytes.
|
||||||
|
pixelbytes = self.image_to_data(image, self._rotation)
|
||||||
|
|
||||||
|
# Write data to hardware.
|
||||||
|
for i in range(0, len(pixelbytes), 4096):
|
||||||
|
self.data(pixelbytes[i:i + 4096])
|
||||||
|
|
||||||
|
def image_to_data(self, image, rotation=0):
|
||||||
|
if not isinstance(image, np.ndarray):
|
||||||
|
image = np.array(image.convert('RGB'))
|
||||||
|
|
||||||
|
# Rotate the image
|
||||||
|
pb = np.rot90(image, rotation // 90).astype('uint16')
|
||||||
|
|
||||||
|
# Mask and shift the 888 RGB into 565 RGB
|
||||||
|
red = (pb[..., [0]] & 0xf8) << 8
|
||||||
|
green = (pb[..., [1]] & 0xfc) << 3
|
||||||
|
blue = (pb[..., [2]] & 0xf8) >> 3
|
||||||
|
|
||||||
|
# Stick 'em together
|
||||||
|
result = red | green | blue
|
||||||
|
|
||||||
|
# Output the raw bytes
|
||||||
|
return result.byteswap().tobytes()
|
@ -31,6 +31,7 @@ import os
|
|||||||
import logging
|
import logging
|
||||||
import sys
|
import sys
|
||||||
import time
|
import time
|
||||||
|
import subprocess
|
||||||
|
|
||||||
logger = logging.getLogger(__name__)
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
@ -45,16 +46,48 @@ class RaspberryPi:
|
|||||||
|
|
||||||
def __init__(self):
|
def __init__(self):
|
||||||
import spidev
|
import spidev
|
||||||
import RPi.GPIO
|
import gpiozero
|
||||||
|
|
||||||
self.GPIO = RPi.GPIO
|
|
||||||
self.SPI = spidev.SpiDev()
|
self.SPI = spidev.SpiDev()
|
||||||
|
self.GPIO_RST_PIN = gpiozero.LED(self.RST_PIN)
|
||||||
|
self.GPIO_DC_PIN = gpiozero.LED(self.DC_PIN)
|
||||||
|
self.GPIO_CS_PIN = gpiozero.LED(self.CS_PIN)
|
||||||
|
self.GPIO_PWR_PIN = gpiozero.LED(self.PWR_PIN)
|
||||||
|
self.GPIO_BUSY_PIN = gpiozero.Button(self.BUSY_PIN, pull_up=False)
|
||||||
|
|
||||||
def digital_write(self, pin, value):
|
def digital_write(self, pin, value):
|
||||||
self.GPIO.output(pin, value)
|
if pin == self.RST_PIN:
|
||||||
|
if value:
|
||||||
|
self.GPIO_RST_PIN.on()
|
||||||
|
else:
|
||||||
|
self.GPIO_RST_PIN.off()
|
||||||
|
elif pin == self.DC_PIN:
|
||||||
|
if value:
|
||||||
|
self.GPIO_DC_PIN.on()
|
||||||
|
else:
|
||||||
|
self.GPIO_DC_PIN.off()
|
||||||
|
elif pin == self.CS_PIN:
|
||||||
|
if value:
|
||||||
|
self.GPIO_CS_PIN.on()
|
||||||
|
else:
|
||||||
|
self.GPIO_CS_PIN.off()
|
||||||
|
elif pin == self.PWR_PIN:
|
||||||
|
if value:
|
||||||
|
self.GPIO_PWR_PIN.on()
|
||||||
|
else:
|
||||||
|
self.GPIO_PWR_PIN.off()
|
||||||
|
|
||||||
def digital_read(self, pin):
|
def digital_read(self, pin):
|
||||||
return self.GPIO.input(pin)
|
if pin == self.BUSY_PIN:
|
||||||
|
return self.GPIO_BUSY_PIN.value
|
||||||
|
elif pin == self.RST_PIN:
|
||||||
|
return self.RST_PIN.value
|
||||||
|
elif pin == self.DC_PIN:
|
||||||
|
return self.DC_PIN.value
|
||||||
|
elif pin == self.CS_PIN:
|
||||||
|
return self.CS_PIN.value
|
||||||
|
elif pin == self.PWR_PIN:
|
||||||
|
return self.PWR_PIN.value
|
||||||
|
|
||||||
def delay_ms(self, delaytime):
|
def delay_ms(self, delaytime):
|
||||||
time.sleep(delaytime / 1000.0)
|
time.sleep(delaytime / 1000.0)
|
||||||
@ -66,15 +99,7 @@ class RaspberryPi:
|
|||||||
self.SPI.writebytes2(data)
|
self.SPI.writebytes2(data)
|
||||||
|
|
||||||
def module_init(self):
|
def module_init(self):
|
||||||
self.GPIO.setmode(self.GPIO.BCM)
|
self.GPIO_PWR_PIN.on()
|
||||||
self.GPIO.setwarnings(False)
|
|
||||||
self.GPIO.setup(self.RST_PIN, self.GPIO.OUT)
|
|
||||||
self.GPIO.setup(self.DC_PIN, self.GPIO.OUT)
|
|
||||||
self.GPIO.setup(self.CS_PIN, self.GPIO.OUT)
|
|
||||||
self.GPIO.setup(self.PWR_PIN, self.GPIO.OUT)
|
|
||||||
self.GPIO.setup(self.BUSY_PIN, self.GPIO.IN)
|
|
||||||
|
|
||||||
self.GPIO.output(self.PWR_PIN, 1)
|
|
||||||
|
|
||||||
# SPI device, bus = 0, device = 0
|
# SPI device, bus = 0, device = 0
|
||||||
self.SPI.open(0, 0)
|
self.SPI.open(0, 0)
|
||||||
@ -82,160 +107,24 @@ class RaspberryPi:
|
|||||||
self.SPI.mode = 0b00
|
self.SPI.mode = 0b00
|
||||||
return 0
|
return 0
|
||||||
|
|
||||||
def module_exit(self):
|
def module_exit(self, cleanup=False):
|
||||||
logger.debug("spi end")
|
logger.debug("spi end")
|
||||||
self.SPI.close()
|
self.SPI.close()
|
||||||
|
|
||||||
|
self.GPIO_RST_PIN.off()
|
||||||
|
self.GPIO_DC_PIN.off()
|
||||||
|
self.GPIO_PWR_PIN.off()
|
||||||
logger.debug("close 5V, Module enters 0 power consumption ...")
|
logger.debug("close 5V, Module enters 0 power consumption ...")
|
||||||
self.GPIO.output(self.RST_PIN, 0)
|
|
||||||
self.GPIO.output(self.DC_PIN, 0)
|
|
||||||
self.GPIO.output(self.PWR_PIN, 0)
|
|
||||||
|
|
||||||
self.GPIO.cleanup([self.RST_PIN, self.DC_PIN, self.CS_PIN, self.BUSY_PIN, self.PWR_PIN])
|
if cleanup:
|
||||||
|
self.GPIO_RST_PIN.close()
|
||||||
|
self.GPIO_DC_PIN.close()
|
||||||
|
self.GPIO_CS_PIN.close()
|
||||||
|
self.GPIO_PWR_PIN.close()
|
||||||
|
self.GPIO_BUSY_PIN.close()
|
||||||
|
|
||||||
|
|
||||||
class JetsonNano:
|
implementation = RaspberryPi()
|
||||||
# Pin definition
|
|
||||||
RST_PIN = 17
|
|
||||||
DC_PIN = 25
|
|
||||||
CS_PIN = 8
|
|
||||||
BUSY_PIN = 24
|
|
||||||
PWR_PIN = 18
|
|
||||||
|
|
||||||
def __init__(self):
|
|
||||||
import ctypes
|
|
||||||
find_dirs = [
|
|
||||||
os.path.dirname(os.path.realpath(__file__)),
|
|
||||||
'/usr/local/lib',
|
|
||||||
'/usr/lib',
|
|
||||||
]
|
|
||||||
self.SPI = None
|
|
||||||
for find_dir in find_dirs:
|
|
||||||
so_filename = os.path.join(find_dir, 'sysfs_software_spi.so')
|
|
||||||
if os.path.exists(so_filename):
|
|
||||||
self.SPI = ctypes.cdll.LoadLibrary(so_filename)
|
|
||||||
break
|
|
||||||
if self.SPI is None:
|
|
||||||
raise RuntimeError('Cannot find sysfs_software_spi.so')
|
|
||||||
|
|
||||||
import Jetson.GPIO
|
|
||||||
self.GPIO = Jetson.GPIO
|
|
||||||
|
|
||||||
def digital_write(self, pin, value):
|
|
||||||
self.GPIO.output(pin, value)
|
|
||||||
|
|
||||||
def digital_read(self, pin):
|
|
||||||
return self.GPIO.input(self.BUSY_PIN)
|
|
||||||
|
|
||||||
def delay_ms(self, delaytime):
|
|
||||||
time.sleep(delaytime / 1000.0)
|
|
||||||
|
|
||||||
def spi_writebyte(self, data):
|
|
||||||
self.SPI.SYSFS_software_spi_transfer(data[0])
|
|
||||||
|
|
||||||
def spi_writebyte2(self, data):
|
|
||||||
for i in range(len(data)):
|
|
||||||
self.SPI.SYSFS_software_spi_transfer(data[i])
|
|
||||||
|
|
||||||
def module_init(self):
|
|
||||||
self.GPIO.setmode(self.GPIO.BCM)
|
|
||||||
self.GPIO.setwarnings(False)
|
|
||||||
self.GPIO.setup(self.RST_PIN, self.GPIO.OUT)
|
|
||||||
self.GPIO.setup(self.DC_PIN, self.GPIO.OUT)
|
|
||||||
self.GPIO.setup(self.CS_PIN, self.GPIO.OUT)
|
|
||||||
self.GPIO.setup(self.PWR_PIN, self.GPIO.OUT)
|
|
||||||
self.GPIO.setup(self.BUSY_PIN, self.GPIO.IN)
|
|
||||||
|
|
||||||
self.GPIO.output(self.PWR_PIN, 1)
|
|
||||||
|
|
||||||
self.SPI.SYSFS_software_spi_begin()
|
|
||||||
return 0
|
|
||||||
|
|
||||||
def module_exit(self):
|
|
||||||
logger.debug("spi end")
|
|
||||||
self.SPI.SYSFS_software_spi_end()
|
|
||||||
|
|
||||||
logger.debug("close 5V, Module enters 0 power consumption ...")
|
|
||||||
self.GPIO.output(self.RST_PIN, 0)
|
|
||||||
self.GPIO.output(self.DC_PIN, 0)
|
|
||||||
self.GPIO.output(self.PWR_PIN, 0)
|
|
||||||
|
|
||||||
self.GPIO.cleanup([self.RST_PIN, self.DC_PIN, self.CS_PIN, self.BUSY_PIN, self.PWR_PIN])
|
|
||||||
|
|
||||||
|
|
||||||
class SunriseX3:
|
|
||||||
# Pin definition
|
|
||||||
RST_PIN = 17
|
|
||||||
DC_PIN = 25
|
|
||||||
CS_PIN = 8
|
|
||||||
BUSY_PIN = 24
|
|
||||||
PWR_PIN = 18
|
|
||||||
Flag = 0
|
|
||||||
|
|
||||||
def __init__(self):
|
|
||||||
import spidev
|
|
||||||
import Hobot.GPIO
|
|
||||||
|
|
||||||
self.GPIO = Hobot.GPIO
|
|
||||||
self.SPI = spidev.SpiDev()
|
|
||||||
|
|
||||||
def digital_write(self, pin, value):
|
|
||||||
self.GPIO.output(pin, value)
|
|
||||||
|
|
||||||
def digital_read(self, pin):
|
|
||||||
return self.GPIO.input(pin)
|
|
||||||
|
|
||||||
def delay_ms(self, delaytime):
|
|
||||||
time.sleep(delaytime / 1000.0)
|
|
||||||
|
|
||||||
def spi_writebyte(self, data):
|
|
||||||
self.SPI.writebytes(data)
|
|
||||||
|
|
||||||
def spi_writebyte2(self, data):
|
|
||||||
# for i in range(len(data)):
|
|
||||||
# self.SPI.writebytes([data[i]])
|
|
||||||
self.SPI.xfer3(data)
|
|
||||||
|
|
||||||
def module_init(self):
|
|
||||||
if self.Flag == 0:
|
|
||||||
self.Flag = 1
|
|
||||||
self.GPIO.setmode(self.GPIO.BCM)
|
|
||||||
self.GPIO.setwarnings(False)
|
|
||||||
self.GPIO.setup(self.RST_PIN, self.GPIO.OUT)
|
|
||||||
self.GPIO.setup(self.DC_PIN, self.GPIO.OUT)
|
|
||||||
self.GPIO.setup(self.CS_PIN, self.GPIO.OUT)
|
|
||||||
self.GPIO.setup(self.PWR_PIN, self.GPIO.OUT)
|
|
||||||
self.GPIO.setup(self.BUSY_PIN, self.GPIO.IN)
|
|
||||||
|
|
||||||
self.GPIO.output(self.PWR_PIN, 1)
|
|
||||||
|
|
||||||
# SPI device, bus = 0, device = 0
|
|
||||||
self.SPI.open(2, 0)
|
|
||||||
self.SPI.max_speed_hz = 4000000
|
|
||||||
self.SPI.mode = 0b00
|
|
||||||
return 0
|
|
||||||
else:
|
|
||||||
return 0
|
|
||||||
|
|
||||||
def module_exit(self):
|
|
||||||
logger.debug("spi end")
|
|
||||||
self.SPI.close()
|
|
||||||
|
|
||||||
logger.debug("close 5V, Module enters 0 power consumption ...")
|
|
||||||
self.Flag = 0
|
|
||||||
self.GPIO.output(self.RST_PIN, 0)
|
|
||||||
self.GPIO.output(self.DC_PIN, 0)
|
|
||||||
self.GPIO.output(self.PWR_PIN, 0)
|
|
||||||
|
|
||||||
self.GPIO.cleanup([self.RST_PIN, self.DC_PIN, self.CS_PIN, self.BUSY_PIN], self.PWR_PIN)
|
|
||||||
|
|
||||||
|
|
||||||
if os.path.exists('/sys/bus/platform/drivers/gpiomem-bcm2835'):
|
|
||||||
implementation = RaspberryPi()
|
|
||||||
elif os.path.exists('/sys/bus/platform/drivers/gpio-x3'):
|
|
||||||
implementation = SunriseX3()
|
|
||||||
else:
|
|
||||||
implementation = RaspberryPi()
|
|
||||||
|
|
||||||
for func in [x for x in dir(implementation) if not x.startswith('_')]:
|
for func in [x for x in dir(implementation) if not x.startswith('_')]:
|
||||||
setattr(sys.modules[__name__], func, getattr(implementation, func))
|
setattr(sys.modules[__name__], func, getattr(implementation, func))
|
||||||
|
360
pwnagotchi/ui/hw/libs/waveshare/oledlcd/ST7789.py
Normal file
360
pwnagotchi/ui/hw/libs/waveshare/oledlcd/ST7789.py
Normal file
@ -0,0 +1,360 @@
|
|||||||
|
# Copyright (c) 2014 Adafruit Industries
|
||||||
|
# Author: Tony DiCola
|
||||||
|
#
|
||||||
|
# Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
|
# of this software and associated documentation files (the "Software"), to deal
|
||||||
|
# in the Software without restriction, including without limitation the rights
|
||||||
|
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
|
# copies of the Software, and to permit persons to whom the Software is
|
||||||
|
# furnished to do so, subject to the following conditions:
|
||||||
|
#
|
||||||
|
# The above copyright notice and this permission notice shall be included in
|
||||||
|
# all copies or substantial portions of the Software.
|
||||||
|
#
|
||||||
|
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
|
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||||
|
# THE SOFTWARE.
|
||||||
|
import numbers
|
||||||
|
import time
|
||||||
|
import numpy as np
|
||||||
|
|
||||||
|
import spidev
|
||||||
|
import RPi.GPIO as GPIO
|
||||||
|
|
||||||
|
|
||||||
|
__version__ = '0.0.4'
|
||||||
|
|
||||||
|
BG_SPI_CS_BACK = 0
|
||||||
|
BG_SPI_CS_FRONT = 1
|
||||||
|
|
||||||
|
SPI_CLOCK_HZ = 16000000
|
||||||
|
|
||||||
|
ST7789_NOP = 0x00
|
||||||
|
ST7789_SWRESET = 0x01
|
||||||
|
ST7789_RDDID = 0x04
|
||||||
|
ST7789_RDDST = 0x09
|
||||||
|
|
||||||
|
ST7789_SLPIN = 0x10
|
||||||
|
ST7789_SLPOUT = 0x11
|
||||||
|
ST7789_PTLON = 0x12
|
||||||
|
ST7789_NORON = 0x13
|
||||||
|
|
||||||
|
ST7789_INVOFF = 0x20
|
||||||
|
ST7789_INVON = 0x21
|
||||||
|
ST7789_DISPOFF = 0x28
|
||||||
|
ST7789_DISPON = 0x29
|
||||||
|
|
||||||
|
ST7789_CASET = 0x2A
|
||||||
|
ST7789_RASET = 0x2B
|
||||||
|
ST7789_RAMWR = 0x2C
|
||||||
|
ST7789_RAMRD = 0x2E
|
||||||
|
|
||||||
|
ST7789_PTLAR = 0x30
|
||||||
|
ST7789_MADCTL = 0x36
|
||||||
|
ST7789_COLMOD = 0x3A
|
||||||
|
|
||||||
|
ST7789_FRMCTR1 = 0xB1
|
||||||
|
ST7789_FRMCTR2 = 0xB2
|
||||||
|
ST7789_FRMCTR3 = 0xB3
|
||||||
|
ST7789_INVCTR = 0xB4
|
||||||
|
ST7789_DISSET5 = 0xB6
|
||||||
|
|
||||||
|
ST7789_GCTRL = 0xB7
|
||||||
|
ST7789_GTADJ = 0xB8
|
||||||
|
ST7789_VCOMS = 0xBB
|
||||||
|
|
||||||
|
ST7789_LCMCTRL = 0xC0
|
||||||
|
ST7789_IDSET = 0xC1
|
||||||
|
ST7789_VDVVRHEN = 0xC2
|
||||||
|
ST7789_VRHS = 0xC3
|
||||||
|
ST7789_VDVS = 0xC4
|
||||||
|
ST7789_VMCTR1 = 0xC5
|
||||||
|
ST7789_FRCTRL2 = 0xC6
|
||||||
|
ST7789_CABCCTRL = 0xC7
|
||||||
|
|
||||||
|
ST7789_RDID1 = 0xDA
|
||||||
|
ST7789_RDID2 = 0xDB
|
||||||
|
ST7789_RDID3 = 0xDC
|
||||||
|
ST7789_RDID4 = 0xDD
|
||||||
|
|
||||||
|
ST7789_GMCTRP1 = 0xE0
|
||||||
|
ST7789_GMCTRN1 = 0xE1
|
||||||
|
|
||||||
|
ST7789_PWCTR6 = 0xFC
|
||||||
|
|
||||||
|
|
||||||
|
class ST7789(object):
|
||||||
|
"""Representation of an ST7789 TFT LCD."""
|
||||||
|
|
||||||
|
def __init__(self, port, cs, dc, backlight, rst=None, width=320,
|
||||||
|
height=240, rotation=180, invert=True, spi_speed_hz=60 * 1000 * 1000,
|
||||||
|
offset_left=0,
|
||||||
|
offset_top=0):
|
||||||
|
"""Create an instance of the display using SPI communication.
|
||||||
|
|
||||||
|
Must provide the GPIO pin number for the D/C pin and the SPI driver.
|
||||||
|
|
||||||
|
Can optionally provide the GPIO pin number for the reset pin as the rst parameter.
|
||||||
|
|
||||||
|
:param port: SPI port number
|
||||||
|
:param cs: SPI chip-select number (0 or 1 for BCM
|
||||||
|
:param backlight: Pin for controlling backlight
|
||||||
|
:param rst: Reset pin for ST7789
|
||||||
|
:param width: Width of display connected to ST7789
|
||||||
|
:param height: Height of display connected to ST7789
|
||||||
|
:param rotation: Rotation of display connected to ST7789
|
||||||
|
:param invert: Invert display
|
||||||
|
:param spi_speed_hz: SPI speed (in Hz)
|
||||||
|
|
||||||
|
"""
|
||||||
|
if rotation not in [0, 90, 180, 270]:
|
||||||
|
raise ValueError("Invalid rotation {}".format(rotation))
|
||||||
|
|
||||||
|
if width != height and rotation in [90, 270]:
|
||||||
|
raise ValueError("Invalid rotation {} for {}x{} resolution".format(rotation, width, height))
|
||||||
|
|
||||||
|
GPIO.setwarnings(False)
|
||||||
|
GPIO.setmode(GPIO.BCM)
|
||||||
|
|
||||||
|
self._spi = spidev.SpiDev(port, cs)
|
||||||
|
self._spi.mode = 0
|
||||||
|
self._spi.lsbfirst = False
|
||||||
|
self._spi.max_speed_hz = spi_speed_hz
|
||||||
|
|
||||||
|
self._dc = dc
|
||||||
|
self._rst = rst
|
||||||
|
self._width = width
|
||||||
|
self._height = height
|
||||||
|
self._rotation = rotation
|
||||||
|
self._invert = invert
|
||||||
|
|
||||||
|
self._offset_left = offset_left
|
||||||
|
self._offset_top = offset_top
|
||||||
|
|
||||||
|
# Set DC as output.
|
||||||
|
GPIO.setup(dc, GPIO.OUT)
|
||||||
|
|
||||||
|
# Setup backlight as output (if provided).
|
||||||
|
self._backlight = backlight
|
||||||
|
if backlight is not None:
|
||||||
|
GPIO.setup(backlight, GPIO.OUT)
|
||||||
|
GPIO.output(backlight, GPIO.LOW)
|
||||||
|
time.sleep(0.1)
|
||||||
|
GPIO.output(backlight, GPIO.HIGH)
|
||||||
|
|
||||||
|
# Setup reset as output (if provided).
|
||||||
|
if rst is not None:
|
||||||
|
GPIO.setup(self._rst, GPIO.OUT)
|
||||||
|
self.reset()
|
||||||
|
self._init()
|
||||||
|
|
||||||
|
def send(self, data, is_data=True, chunk_size=4096):
|
||||||
|
"""Write a byte or array of bytes to the display. Is_data parameter
|
||||||
|
controls if byte should be interpreted as display data (True) or command
|
||||||
|
data (False). Chunk_size is an optional size of bytes to write in a
|
||||||
|
single SPI transaction, with a default of 4096.
|
||||||
|
"""
|
||||||
|
# Set DC low for command, high for data.
|
||||||
|
GPIO.output(self._dc, is_data)
|
||||||
|
# Convert scalar argument to list so either can be passed as parameter.
|
||||||
|
if isinstance(data, numbers.Number):
|
||||||
|
data = [data & 0xFF]
|
||||||
|
# Write data a chunk at a time.
|
||||||
|
for start in range(0, len(data), chunk_size):
|
||||||
|
end = min(start + chunk_size, len(data))
|
||||||
|
self._spi.xfer(data[start:end])
|
||||||
|
|
||||||
|
def set_backlight(self, value):
|
||||||
|
"""Set the backlight on/off."""
|
||||||
|
if self._backlight is not None:
|
||||||
|
GPIO.output(self._backlight, value)
|
||||||
|
|
||||||
|
@property
|
||||||
|
def width(self):
|
||||||
|
return self._width if self._rotation == 0 or self._rotation == 180 else self._height
|
||||||
|
|
||||||
|
@property
|
||||||
|
def height(self):
|
||||||
|
return self._height if self._rotation == 0 or self._rotation == 180 else self._width
|
||||||
|
|
||||||
|
def command(self, data):
|
||||||
|
"""Write a byte or array of bytes to the display as command data."""
|
||||||
|
self.send(data, False)
|
||||||
|
|
||||||
|
def data(self, data):
|
||||||
|
"""Write a byte or array of bytes to the display as display data."""
|
||||||
|
self.send(data, True)
|
||||||
|
|
||||||
|
def reset(self):
|
||||||
|
"""Reset the display, if reset pin is connected."""
|
||||||
|
if self._rst is not None:
|
||||||
|
GPIO.output(self._rst, 1)
|
||||||
|
time.sleep(0.500)
|
||||||
|
GPIO.output(self._rst, 0)
|
||||||
|
time.sleep(0.500)
|
||||||
|
GPIO.output(self._rst, 1)
|
||||||
|
time.sleep(0.500)
|
||||||
|
|
||||||
|
def _init(self):
|
||||||
|
# Initialize the display.
|
||||||
|
|
||||||
|
self.command(ST7789_SWRESET) # Software reset
|
||||||
|
time.sleep(0.150) # delay 150 ms
|
||||||
|
|
||||||
|
self.command(ST7789_MADCTL)
|
||||||
|
self.data(0x70)
|
||||||
|
|
||||||
|
self.command(ST7789_FRMCTR2) # Frame rate ctrl - idle mode
|
||||||
|
self.data(0x0C)
|
||||||
|
self.data(0x0C)
|
||||||
|
self.data(0x00)
|
||||||
|
self.data(0x33)
|
||||||
|
self.data(0x33)
|
||||||
|
|
||||||
|
self.command(ST7789_COLMOD)
|
||||||
|
self.data(0x05)
|
||||||
|
|
||||||
|
self.command(ST7789_GCTRL)
|
||||||
|
self.data(0x14)
|
||||||
|
|
||||||
|
self.command(ST7789_VCOMS)
|
||||||
|
self.data(0x37)
|
||||||
|
|
||||||
|
self.command(ST7789_LCMCTRL) # Power control
|
||||||
|
self.data(0x2C)
|
||||||
|
|
||||||
|
self.command(ST7789_VDVVRHEN) # Power control
|
||||||
|
self.data(0x01)
|
||||||
|
|
||||||
|
self.command(ST7789_VRHS) # Power control
|
||||||
|
self.data(0x12)
|
||||||
|
|
||||||
|
self.command(ST7789_VDVS) # Power control
|
||||||
|
self.data(0x20)
|
||||||
|
|
||||||
|
self.command(0xD0)
|
||||||
|
self.data(0xA4)
|
||||||
|
self.data(0xA1)
|
||||||
|
|
||||||
|
self.command(ST7789_FRCTRL2)
|
||||||
|
self.data(0x0F)
|
||||||
|
|
||||||
|
self.command(ST7789_GMCTRP1) # Set Gamma
|
||||||
|
self.data(0xD0)
|
||||||
|
self.data(0x04)
|
||||||
|
self.data(0x0D)
|
||||||
|
self.data(0x11)
|
||||||
|
self.data(0x13)
|
||||||
|
self.data(0x2B)
|
||||||
|
self.data(0x3F)
|
||||||
|
self.data(0x54)
|
||||||
|
self.data(0x4C)
|
||||||
|
self.data(0x18)
|
||||||
|
self.data(0x0D)
|
||||||
|
self.data(0x0B)
|
||||||
|
self.data(0x1F)
|
||||||
|
self.data(0x23)
|
||||||
|
|
||||||
|
self.command(ST7789_GMCTRN1) # Set Gamma
|
||||||
|
self.data(0xD0)
|
||||||
|
self.data(0x04)
|
||||||
|
self.data(0x0C)
|
||||||
|
self.data(0x11)
|
||||||
|
self.data(0x13)
|
||||||
|
self.data(0x2C)
|
||||||
|
self.data(0x3F)
|
||||||
|
self.data(0x44)
|
||||||
|
self.data(0x51)
|
||||||
|
self.data(0x2F)
|
||||||
|
self.data(0x1F)
|
||||||
|
self.data(0x1F)
|
||||||
|
self.data(0x20)
|
||||||
|
self.data(0x23)
|
||||||
|
|
||||||
|
if self._invert:
|
||||||
|
self.command(ST7789_INVON) # Invert display
|
||||||
|
else:
|
||||||
|
self.command(ST7789_INVOFF) # Don't invert display
|
||||||
|
|
||||||
|
self.command(ST7789_SLPOUT)
|
||||||
|
|
||||||
|
self.command(ST7789_DISPON) # Display on
|
||||||
|
time.sleep(0.100) # 100 ms
|
||||||
|
|
||||||
|
def begin(self):
|
||||||
|
"""Set up the display
|
||||||
|
|
||||||
|
Deprecated. Included in __init__.
|
||||||
|
|
||||||
|
"""
|
||||||
|
pass
|
||||||
|
|
||||||
|
def set_window(self, x0=0, y0=0, x1=None, y1=None):
|
||||||
|
"""Set the pixel address window for proceeding drawing commands. x0 and
|
||||||
|
x1 should define the minimum and maximum x pixel bounds. y0 and y1
|
||||||
|
should define the minimum and maximum y pixel bound. If no parameters
|
||||||
|
are specified the default will be to update the entire display from 0,0
|
||||||
|
to width-1,height-1.
|
||||||
|
"""
|
||||||
|
if x1 is None:
|
||||||
|
x1 = self._width - 1
|
||||||
|
|
||||||
|
if y1 is None:
|
||||||
|
y1 = self._height - 1
|
||||||
|
|
||||||
|
y0 += self._offset_top
|
||||||
|
y1 += self._offset_top
|
||||||
|
|
||||||
|
x0 += self._offset_left
|
||||||
|
x1 += self._offset_left
|
||||||
|
|
||||||
|
self.command(ST7789_CASET) # Column addr set
|
||||||
|
self.data(x0 >> 8)
|
||||||
|
self.data(x0 & 0xFF) # XSTART
|
||||||
|
self.data(x1 >> 8)
|
||||||
|
self.data(x1 & 0xFF) # XEND
|
||||||
|
self.command(ST7789_RASET) # Row addr set
|
||||||
|
self.data(y0 >> 8)
|
||||||
|
self.data(y0 & 0xFF) # YSTART
|
||||||
|
self.data(y1 >> 8)
|
||||||
|
self.data(y1 & 0xFF) # YEND
|
||||||
|
self.command(ST7789_RAMWR) # write to RAM
|
||||||
|
|
||||||
|
def display(self, image):
|
||||||
|
"""Write the provided image to the hardware.
|
||||||
|
|
||||||
|
:param image: Should be RGB format and the same dimensions as the display hardware.
|
||||||
|
|
||||||
|
"""
|
||||||
|
# Set address bounds to entire display.
|
||||||
|
self.set_window()
|
||||||
|
|
||||||
|
# Convert image to 16bit RGB565 format and
|
||||||
|
# flatten into bytes.
|
||||||
|
pixelbytes = self.image_to_data(image, self._rotation)
|
||||||
|
|
||||||
|
# Write data to hardware.
|
||||||
|
for i in range(0, len(pixelbytes), 4096):
|
||||||
|
self.data(pixelbytes[i:i + 4096])
|
||||||
|
|
||||||
|
def image_to_data(self, image, rotation=0):
|
||||||
|
if not isinstance(image, np.ndarray):
|
||||||
|
image = np.array(image.convert('RGB'))
|
||||||
|
|
||||||
|
# Rotate the image
|
||||||
|
pb = np.rot90(image, rotation // 90).astype('uint16')
|
||||||
|
|
||||||
|
# Mask and shift the 888 RGB into 565 RGB
|
||||||
|
red = (pb[..., [0]] & 0xf8) << 8
|
||||||
|
green = (pb[..., [1]] & 0xfc) << 3
|
||||||
|
blue = (pb[..., [2]] & 0xf8) >> 3
|
||||||
|
|
||||||
|
# Stick 'em together
|
||||||
|
result = red | green | blue
|
||||||
|
|
||||||
|
# Output the raw bytes
|
||||||
|
return result.byteswap().tobytes()
|
675
pwnagotchi/ui/hw/libs/waveshare/v5in79/epd5in79.py
Normal file
675
pwnagotchi/ui/hw/libs/waveshare/v5in79/epd5in79.py
Normal file
@ -0,0 +1,675 @@
|
|||||||
|
# *****************************************************************************
|
||||||
|
# * | File : epd5in79.py
|
||||||
|
# * | Author : Waveshare team
|
||||||
|
# * | Function : Electronic paper driver
|
||||||
|
# * | Info :
|
||||||
|
# *----------------
|
||||||
|
# * | This version: V1.0
|
||||||
|
# * | Date : 2024-03-05
|
||||||
|
# # | Info : python demo
|
||||||
|
# -----------------------------------------------------------------------------
|
||||||
|
# Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
|
# of this software and associated documnetation files (the "Software"), to deal
|
||||||
|
# in the Software without restriction, including without limitation the rights
|
||||||
|
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
|
# copies of the Software, and to permit persons to whom the Software is
|
||||||
|
# furished to do so, subject to the following conditions:
|
||||||
|
#
|
||||||
|
# The above copyright notice and this permission notice shall be included in
|
||||||
|
# all copies or substantial portions of the Software.
|
||||||
|
#
|
||||||
|
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
# FITNESS OR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
# LIABILITY WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
|
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||||
|
# THE SOFTWARE.
|
||||||
|
#
|
||||||
|
|
||||||
|
|
||||||
|
import logging
|
||||||
|
from .. import epdconfig
|
||||||
|
|
||||||
|
# Display resolution
|
||||||
|
EPD_WIDTH = 792
|
||||||
|
EPD_HEIGHT = 272
|
||||||
|
|
||||||
|
GRAY1 = 0xff # white
|
||||||
|
GRAY2 = 0xC0
|
||||||
|
GRAY3 = 0x80 # gray
|
||||||
|
GRAY4 = 0x00 # Blackest
|
||||||
|
|
||||||
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
|
class EPD:
|
||||||
|
def __init__(self):
|
||||||
|
self.reset_pin = epdconfig.RST_PIN
|
||||||
|
self.dc_pin = epdconfig.DC_PIN
|
||||||
|
self.busy_pin = epdconfig.BUSY_PIN
|
||||||
|
self.cs_pin = epdconfig.CS_PIN
|
||||||
|
self.width = EPD_WIDTH
|
||||||
|
self.height = EPD_HEIGHT
|
||||||
|
self.GRAY1 = GRAY1 # white
|
||||||
|
self.GRAY2 = GRAY2
|
||||||
|
self.GRAY3 = GRAY3 # gray
|
||||||
|
self.GRAY4 = GRAY4 # Blackest
|
||||||
|
|
||||||
|
self.LUT_DATA_4Gray = [
|
||||||
|
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, 0x00, 0x00, 0x00,
|
||||||
|
|
||||||
|
0x01, 0x4A, 0x00, 0x00, 0x00, 0x01, 0x00,
|
||||||
|
0x01, 0x82, 0x42, 0x00, 0x00, 0x10, 0x00,
|
||||||
|
0x01, 0x8A, 0x00, 0x00, 0x00, 0x01, 0x00,
|
||||||
|
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||||
|
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||||
|
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||||
|
|
||||||
|
0x01, 0x41, 0x00, 0x00, 0x00, 0x01, 0x00,
|
||||||
|
0x01, 0x82, 0x42, 0x00, 0x00, 0x10, 0x00,
|
||||||
|
0x01, 0x81, 0x00, 0x00, 0x00, 0x01, 0x00,
|
||||||
|
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||||
|
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||||
|
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||||
|
|
||||||
|
0x01, 0x81, 0x00, 0x00, 0x00, 0x01, 0x00,
|
||||||
|
0x01, 0x82, 0x42, 0x00, 0x00, 0x10, 0x00,
|
||||||
|
0x01, 0x41, 0x00, 0x00, 0x00, 0x01, 0x00,
|
||||||
|
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||||
|
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||||
|
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||||
|
|
||||||
|
0x01, 0x8A, 0x00, 0x00, 0x00, 0x01, 0x00,
|
||||||
|
0x01, 0x82, 0x42, 0x00, 0x00, 0x10, 0x00,
|
||||||
|
0x01, 0x4A, 0x00, 0x00, 0x00, 0x01, 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,
|
||||||
|
|
||||||
|
0x02, 0x00, 0x00,
|
||||||
|
0x22, 0x17, 0x41, 0xA8, 0x32, 0x40, ]
|
||||||
|
|
||||||
|
# Hardware reset
|
||||||
|
def reset(self):
|
||||||
|
epdconfig.digital_write(self.reset_pin, 1)
|
||||||
|
epdconfig.delay_ms(200)
|
||||||
|
epdconfig.digital_write(self.reset_pin, 0)
|
||||||
|
epdconfig.delay_ms(1)
|
||||||
|
epdconfig.digital_write(self.reset_pin, 1)
|
||||||
|
epdconfig.delay_ms(200)
|
||||||
|
|
||||||
|
def send_command(self, command):
|
||||||
|
epdconfig.digital_write(self.dc_pin, 0)
|
||||||
|
epdconfig.digital_write(self.cs_pin, 0)
|
||||||
|
epdconfig.spi_writebyte([command])
|
||||||
|
epdconfig.digital_write(self.cs_pin, 1)
|
||||||
|
|
||||||
|
def send_data(self, data):
|
||||||
|
epdconfig.digital_write(self.dc_pin, 1)
|
||||||
|
epdconfig.digital_write(self.cs_pin, 0)
|
||||||
|
epdconfig.spi_writebyte([data])
|
||||||
|
epdconfig.digital_write(self.cs_pin, 1)
|
||||||
|
|
||||||
|
# send a lot of data
|
||||||
|
def send_data2(self, data):
|
||||||
|
epdconfig.digital_write(self.dc_pin, 1)
|
||||||
|
epdconfig.digital_write(self.cs_pin, 0)
|
||||||
|
epdconfig.spi_writebyte2(data)
|
||||||
|
epdconfig.digital_write(self.cs_pin, 1)
|
||||||
|
|
||||||
|
def ReadBusy(self):
|
||||||
|
logger.debug("e-Paper busy")
|
||||||
|
while (epdconfig.digital_read(self.busy_pin) == 1): # 0: idle, 1: busy
|
||||||
|
epdconfig.delay_ms(200)
|
||||||
|
logger.debug("e-Paper busy release")
|
||||||
|
|
||||||
|
def TurnOnDisplay(self):
|
||||||
|
self.send_command(0x22)
|
||||||
|
self.send_data(0xF7)
|
||||||
|
self.send_command(0x20) # DISPLAY REFRESH
|
||||||
|
epdconfig.delay_ms(100) # The delay here is necessary, 200uS at least!!!
|
||||||
|
self.ReadBusy() # waiting for the electronic paper IC to release the idle signal
|
||||||
|
|
||||||
|
def TurnOnDisplay_Fast(self):
|
||||||
|
self.send_command(0x22)
|
||||||
|
self.send_data(0xC7)
|
||||||
|
self.send_command(0x20) # DISPLAY REFRESH
|
||||||
|
epdconfig.delay_ms(100) # The delay here is necessary, 200uS at least!!!
|
||||||
|
self.ReadBusy() # waiting for the electronic paper IC to release the idle signal
|
||||||
|
|
||||||
|
def TurnOnDisplay_Partial(self):
|
||||||
|
self.send_command(0x22)
|
||||||
|
self.send_data(0xFF)
|
||||||
|
self.send_command(0x20) # DISPLAY REFRESH
|
||||||
|
epdconfig.delay_ms(100) # The delay here is necessary, 200uS at least!!!
|
||||||
|
self.ReadBusy() # waiting for the electronic paper IC to release the idle signal
|
||||||
|
|
||||||
|
def TurnOnDisplay_4GRAY(self):
|
||||||
|
self.send_command(0x22)
|
||||||
|
self.send_data(0xCF)
|
||||||
|
self.send_command(0x20) # DISPLAY REFRESH
|
||||||
|
epdconfig.delay_ms(100) # The delay here is necessary, 200uS at least!!!
|
||||||
|
self.ReadBusy() # waiting for the electronic paper IC to release the idle signal
|
||||||
|
|
||||||
|
def EPD_5in79_Lut(self):
|
||||||
|
self.send_command(0x32)
|
||||||
|
self.send_data2(self.LUT_DATA_4Gray[:227])
|
||||||
|
self.send_command(0x3f)
|
||||||
|
self.send_data(self.LUT_DATA_4Gray[227])
|
||||||
|
|
||||||
|
self.send_command(0x03)
|
||||||
|
self.send_data(self.LUT_DATA_4Gray[228])
|
||||||
|
|
||||||
|
self.send_command(0x04)
|
||||||
|
self.send_data(self.LUT_DATA_4Gray[229])
|
||||||
|
self.send_data(self.LUT_DATA_4Gray[230])
|
||||||
|
self.send_data(self.LUT_DATA_4Gray[231])
|
||||||
|
|
||||||
|
self.send_command(0x2C)
|
||||||
|
self.send_data(self.LUT_DATA_4Gray[232])
|
||||||
|
|
||||||
|
def init(self):
|
||||||
|
if (epdconfig.module_init() != 0):
|
||||||
|
return -1
|
||||||
|
|
||||||
|
self.reset()
|
||||||
|
self.ReadBusy() # waiting for the electronic paper IC to release the idle signal
|
||||||
|
self.send_command(0x12) # POWER ON
|
||||||
|
self.ReadBusy() # waiting for the electronic paper IC to release the idle signal
|
||||||
|
|
||||||
|
self.send_command(0x11)
|
||||||
|
self.send_data(0x01)
|
||||||
|
|
||||||
|
self.send_command(0x44) # Set Ram X- address Start / End position
|
||||||
|
self.send_data(0x00) # XStart, POR = 00h
|
||||||
|
self.send_data(0x31) # 400/8-1
|
||||||
|
self.send_command(0x45) # Set Ram Y- address Start / End position
|
||||||
|
self.send_data(0x0f)
|
||||||
|
self.send_data(0x01) # 300-1
|
||||||
|
self.send_data(0x00) # YEnd L
|
||||||
|
self.send_data(0x00) # YEnd H
|
||||||
|
|
||||||
|
self.send_command(0x4e)
|
||||||
|
self.send_data(0x00)
|
||||||
|
self.send_command(0x4f)
|
||||||
|
self.send_data(0x0f)
|
||||||
|
self.send_data(0x01)
|
||||||
|
|
||||||
|
self.ReadBusy()
|
||||||
|
|
||||||
|
self.send_command(0x91)
|
||||||
|
self.send_data(0x00)
|
||||||
|
|
||||||
|
self.send_command(0xC4) # Set Ram X- address Start / End position
|
||||||
|
self.send_data(0x31) # XStart, POR = 00h
|
||||||
|
self.send_data(0x00) # 400/8-1
|
||||||
|
self.send_command(0xC5) # Set Ram Y- address Start / End position
|
||||||
|
self.send_data(0x0f)
|
||||||
|
self.send_data(0x01) # 300-1
|
||||||
|
self.send_data(0x00) # YEnd L
|
||||||
|
self.send_data(0x00) # YEnd H
|
||||||
|
|
||||||
|
self.send_command(0xCE)
|
||||||
|
self.send_data(0x31)
|
||||||
|
self.send_command(0xCF)
|
||||||
|
self.send_data(0x0f)
|
||||||
|
self.send_data(0x01)
|
||||||
|
|
||||||
|
self.ReadBusy()
|
||||||
|
|
||||||
|
return 0
|
||||||
|
|
||||||
|
def init_Fast(self):
|
||||||
|
if (epdconfig.module_init() != 0):
|
||||||
|
return -1
|
||||||
|
|
||||||
|
self.reset()
|
||||||
|
self.ReadBusy()
|
||||||
|
self.send_command(0x12)
|
||||||
|
self.ReadBusy()
|
||||||
|
|
||||||
|
self.send_command(0x18)
|
||||||
|
self.send_data(0x80)
|
||||||
|
|
||||||
|
self.send_command(0x22)
|
||||||
|
self.send_data(0xB1)
|
||||||
|
self.send_command(0x20)
|
||||||
|
self.ReadBusy()
|
||||||
|
|
||||||
|
self.send_command(0x1A)
|
||||||
|
self.send_data(0x64)
|
||||||
|
self.send_data(0x00)
|
||||||
|
|
||||||
|
self.send_command(0x22)
|
||||||
|
self.send_data(0x91)
|
||||||
|
self.send_command(0x20)
|
||||||
|
self.ReadBusy()
|
||||||
|
|
||||||
|
self.send_command(0x11)
|
||||||
|
self.send_data(0x01)
|
||||||
|
|
||||||
|
self.send_command(0x44)
|
||||||
|
self.send_data(0x00)
|
||||||
|
self.send_data(0x31)
|
||||||
|
self.send_command(0x45)
|
||||||
|
self.send_data(0x0f)
|
||||||
|
self.send_data(0x01)
|
||||||
|
self.send_data(0x00)
|
||||||
|
self.send_data(0x00)
|
||||||
|
|
||||||
|
self.send_command(0x4e)
|
||||||
|
self.send_data(0x00)
|
||||||
|
self.send_command(0x4f)
|
||||||
|
self.send_data(0x0f)
|
||||||
|
self.send_data(0x01)
|
||||||
|
|
||||||
|
self.ReadBusy()
|
||||||
|
|
||||||
|
self.send_command(0x91)
|
||||||
|
self.send_data(0x00)
|
||||||
|
|
||||||
|
self.send_command(0xC4)
|
||||||
|
self.send_data(0x31)
|
||||||
|
self.send_data(0x00)
|
||||||
|
self.send_command(0xC5)
|
||||||
|
self.send_data(0x0f)
|
||||||
|
self.send_data(0x01)
|
||||||
|
self.send_data(0x00)
|
||||||
|
self.send_data(0x00)
|
||||||
|
|
||||||
|
self.send_command(0xCe)
|
||||||
|
self.send_data(0x31)
|
||||||
|
self.send_command(0xCf)
|
||||||
|
self.send_data(0x0f)
|
||||||
|
self.send_data(0x01)
|
||||||
|
|
||||||
|
self.ReadBusy()
|
||||||
|
|
||||||
|
return 0
|
||||||
|
|
||||||
|
def init_Partial(self):
|
||||||
|
if (epdconfig.module_init() != 0):
|
||||||
|
return -1
|
||||||
|
|
||||||
|
self.reset()
|
||||||
|
self.ReadBusy()
|
||||||
|
self.send_command(0x12)
|
||||||
|
self.ReadBusy()
|
||||||
|
|
||||||
|
self.send_command(0x3C)
|
||||||
|
self.send_data(0x80)
|
||||||
|
|
||||||
|
return 0
|
||||||
|
|
||||||
|
def init_4Gray(self):
|
||||||
|
if (epdconfig.module_init() != 0):
|
||||||
|
return -1
|
||||||
|
|
||||||
|
self.reset()
|
||||||
|
self.ReadBusy()
|
||||||
|
self.send_command(0x12)
|
||||||
|
self.ReadBusy()
|
||||||
|
|
||||||
|
self.send_command(0x0C)
|
||||||
|
self.send_data(0x8B)
|
||||||
|
self.send_data(0x9C)
|
||||||
|
self.send_data(0xA6)
|
||||||
|
self.send_data(0x0F)
|
||||||
|
|
||||||
|
self.send_command(0x3C)
|
||||||
|
self.send_data(0x81)
|
||||||
|
|
||||||
|
self.ReadBusy()
|
||||||
|
|
||||||
|
self.send_command(0x11)
|
||||||
|
self.send_data(0x01)
|
||||||
|
|
||||||
|
self.send_command(0x44)
|
||||||
|
self.send_data(0x00)
|
||||||
|
self.send_data(0x31)
|
||||||
|
self.send_command(0x45)
|
||||||
|
self.send_data(0x0f)
|
||||||
|
self.send_data(0x01)
|
||||||
|
self.send_data(0x00)
|
||||||
|
self.send_data(0x00)
|
||||||
|
|
||||||
|
self.send_command(0x4e)
|
||||||
|
self.send_data(0x00)
|
||||||
|
self.send_command(0x4f)
|
||||||
|
self.send_data(0x0f)
|
||||||
|
self.send_data(0x01)
|
||||||
|
|
||||||
|
self.ReadBusy()
|
||||||
|
|
||||||
|
self.send_command(0x91)
|
||||||
|
self.send_data(0x00)
|
||||||
|
|
||||||
|
self.send_command(0xC4)
|
||||||
|
self.send_data(0x31)
|
||||||
|
self.send_data(0x00)
|
||||||
|
self.send_command(0xC5)
|
||||||
|
self.send_data(0x0f)
|
||||||
|
self.send_data(0x01)
|
||||||
|
self.send_data(0x00)
|
||||||
|
self.send_data(0x00)
|
||||||
|
|
||||||
|
self.send_command(0xCe)
|
||||||
|
self.send_data(0x31)
|
||||||
|
self.send_command(0xCf)
|
||||||
|
self.send_data(0x0f)
|
||||||
|
self.send_data(0x01)
|
||||||
|
|
||||||
|
self.EPD_5in79_Lut()
|
||||||
|
return 0
|
||||||
|
|
||||||
|
def getbuffer(self, image):
|
||||||
|
# logger.debug("bufsiz = ",int(self.width/8) * self.height)
|
||||||
|
buf = [0xFF] * (int(self.width / 8) * self.height)
|
||||||
|
image_monocolor = image.convert('1')
|
||||||
|
imwidth, imheight = image_monocolor.size
|
||||||
|
pixels = image_monocolor.load()
|
||||||
|
# logger.debug("imwidth = %d, imheight = %d",imwidth,imheight)
|
||||||
|
if imwidth == self.width and imheight == self.height:
|
||||||
|
logger.debug("Horizontal")
|
||||||
|
for y in range(imheight):
|
||||||
|
for x in range(imwidth):
|
||||||
|
# Set the bits for the column of pixels at the current position.
|
||||||
|
if pixels[x, y] == 0:
|
||||||
|
buf[int((x + y * self.width) / 8)] &= ~(0x80 >> (x % 8))
|
||||||
|
elif imwidth == self.height and imheight == self.width:
|
||||||
|
logger.debug("Vertical")
|
||||||
|
for y in range(imheight):
|
||||||
|
for x in range(imwidth):
|
||||||
|
newx = y
|
||||||
|
newy = self.height - x - 1
|
||||||
|
if pixels[x, y] == 0:
|
||||||
|
buf[int((newx + newy * self.width) / 8)] &= ~(0x80 >> (y % 8))
|
||||||
|
return buf
|
||||||
|
|
||||||
|
def getbuffer_4Gray(self, image):
|
||||||
|
# logger.debug("bufsiz = ",int(self.width/8) * self.height)
|
||||||
|
buf = [0xFF] * (int(self.width / 4) * self.height)
|
||||||
|
image_monocolor = image.convert('L')
|
||||||
|
imwidth, imheight = image_monocolor.size
|
||||||
|
pixels = image_monocolor.load()
|
||||||
|
i = 0
|
||||||
|
# logger.debug("imwidth = %d, imheight = %d",imwidth,imheight)
|
||||||
|
if (imwidth == self.width and imheight == self.height):
|
||||||
|
logger.debug("Vertical")
|
||||||
|
for y in range(imheight):
|
||||||
|
for x in range(imwidth):
|
||||||
|
# Set the bits for the column of pixels at the current position.
|
||||||
|
if (pixels[x, y] == 0xC0):
|
||||||
|
pixels[x, y] = 0x80
|
||||||
|
elif (pixels[x, y] == 0x80):
|
||||||
|
pixels[x, y] = 0x40
|
||||||
|
i = i + 1
|
||||||
|
if (i % 4 == 0):
|
||||||
|
buf[int((x + (y * self.width)) / 4)] = (
|
||||||
|
(pixels[x - 3, y] & 0xc0) | (pixels[x - 2, y] & 0xc0) >> 2 | (
|
||||||
|
pixels[x - 1, y] & 0xc0) >> 4 | (pixels[x, y] & 0xc0) >> 6)
|
||||||
|
|
||||||
|
elif (imwidth == self.height and imheight == self.width):
|
||||||
|
logger.debug("Horizontal")
|
||||||
|
for x in range(imwidth):
|
||||||
|
for y in range(imheight):
|
||||||
|
newx = y
|
||||||
|
newy = self.height - x - 1
|
||||||
|
if (pixels[x, y] == 0xC0):
|
||||||
|
pixels[x, y] = 0x80
|
||||||
|
elif (pixels[x, y] == 0x80):
|
||||||
|
pixels[x, y] = 0x40
|
||||||
|
i = i + 1
|
||||||
|
if (i % 4 == 0):
|
||||||
|
buf[int((newx + (newy * self.width)) / 4)] = (
|
||||||
|
(pixels[x, y - 3] & 0xc0) | (pixels[x, y - 2] & 0xc0) >> 2 | (
|
||||||
|
pixels[x, y - 1] & 0xc0) >> 4 | (pixels[x, y] & 0xc0) >> 6)
|
||||||
|
return buf
|
||||||
|
|
||||||
|
def display(self, imageblack):
|
||||||
|
Width = int(self.width / 16) + 1
|
||||||
|
Width1 = int(self.width / 8)
|
||||||
|
|
||||||
|
self.send_command(0x24)
|
||||||
|
for i in range(self.height):
|
||||||
|
self.send_data2(imageblack[i * Width1: i * Width1 + Width])
|
||||||
|
self.send_command(0X26)
|
||||||
|
self.send_data2([0x00] * 13600)
|
||||||
|
|
||||||
|
self.send_command(0xA4)
|
||||||
|
for i in range(self.height):
|
||||||
|
self.send_data2(imageblack[i * Width1 + Width - 1: i * Width1 + Width * 2 - 1])
|
||||||
|
self.send_command(0xA6)
|
||||||
|
self.send_data2([0x00] * 13600)
|
||||||
|
|
||||||
|
self.TurnOnDisplay()
|
||||||
|
|
||||||
|
def display_Base(self, imageblack):
|
||||||
|
Width = int(self.width / 16) + 1
|
||||||
|
Width1 = int(self.width / 8)
|
||||||
|
|
||||||
|
self.send_command(0x24)
|
||||||
|
for i in range(self.height):
|
||||||
|
self.send_data2(imageblack[i * Width1: i * Width1 + Width])
|
||||||
|
self.send_command(0X26)
|
||||||
|
self.send_data2([0x00] * 13600)
|
||||||
|
|
||||||
|
self.send_command(0xA4)
|
||||||
|
for i in range(self.height):
|
||||||
|
self.send_data2(imageblack[i * Width1 + Width - 1: i * Width1 + Width * 2 - 1])
|
||||||
|
self.send_command(0xA6)
|
||||||
|
self.send_data2([0x00] * 13600)
|
||||||
|
|
||||||
|
self.TurnOnDisplay()
|
||||||
|
|
||||||
|
self.send_command(0x26)
|
||||||
|
for i in range(self.height):
|
||||||
|
self.send_data2(imageblack[i * Width1: i * Width1 + Width])
|
||||||
|
|
||||||
|
self.send_command(0xA6)
|
||||||
|
for i in range(self.height):
|
||||||
|
self.send_data2(imageblack[i * Width1 + Width - 1: i * Width1 + Width * 2 - 1])
|
||||||
|
|
||||||
|
def display_Base_color(self, color):
|
||||||
|
Width = int(self.width / 16) + 1
|
||||||
|
Width1 = int(self.width / 8)
|
||||||
|
|
||||||
|
self.send_command(0x24)
|
||||||
|
self.send_data2([color] * 13600)
|
||||||
|
self.send_command(0X26)
|
||||||
|
self.send_data2([0x00] * 13600)
|
||||||
|
|
||||||
|
self.send_command(0xA4)
|
||||||
|
self.send_data2([color] * 13600)
|
||||||
|
self.send_command(0xA6)
|
||||||
|
self.send_data2([0x00] * 13600)
|
||||||
|
|
||||||
|
self.TurnOnDisplay()
|
||||||
|
|
||||||
|
self.send_command(0x26)
|
||||||
|
self.send_data2([color] * 13600)
|
||||||
|
|
||||||
|
self.send_command(0xA6)
|
||||||
|
self.send_data2([color] * 13600)
|
||||||
|
|
||||||
|
def display_Fast(self, imageblack):
|
||||||
|
Width = int(self.width / 16) + 1
|
||||||
|
Width1 = int(self.width / 8)
|
||||||
|
|
||||||
|
self.send_command(0x24)
|
||||||
|
for i in range(self.height):
|
||||||
|
self.send_data2(imageblack[i * Width1: i * Width1 + Width])
|
||||||
|
self.send_command(0X26)
|
||||||
|
self.send_data2([0x00] * 13600)
|
||||||
|
|
||||||
|
self.send_command(0xA4)
|
||||||
|
for i in range(self.height):
|
||||||
|
self.send_data2(imageblack[i * Width1 + Width - 1: i * Width1 + Width * 2 - 1])
|
||||||
|
self.send_command(0xA6)
|
||||||
|
self.send_data2([0x00] * 13600)
|
||||||
|
|
||||||
|
self.TurnOnDisplay_Fast()
|
||||||
|
|
||||||
|
def display_Partial(self, Image):
|
||||||
|
Width = int(self.width / 16) + 1
|
||||||
|
Width1 = int(self.width / 8)
|
||||||
|
Height = self.height
|
||||||
|
self.send_command(0x44)
|
||||||
|
self.send_data(0x00)
|
||||||
|
self.send_data(0x31)
|
||||||
|
self.send_command(0x45)
|
||||||
|
self.send_data(0x0f)
|
||||||
|
self.send_data(0x01) # 300-1
|
||||||
|
self.send_data(0x00) # YEnd L
|
||||||
|
self.send_data(0x00) # YEnd H
|
||||||
|
|
||||||
|
self.send_command(0x4e)
|
||||||
|
self.send_data(0x00)
|
||||||
|
self.send_command(0x4f)
|
||||||
|
self.send_data(0x0f)
|
||||||
|
self.send_data(0x01)
|
||||||
|
|
||||||
|
self.send_command(0x24)
|
||||||
|
for i in range(Height):
|
||||||
|
self.send_data2(Image[i * Width1: i * Width1 + Width])
|
||||||
|
|
||||||
|
self.send_command(0xC4) # Set Ram X- address Start / End position
|
||||||
|
self.send_data(0x31) # XStart, POR = 00h
|
||||||
|
self.send_data(0x00) # 400/8-1
|
||||||
|
self.send_command(0xC5) # Set Ram Y- address Start / End position
|
||||||
|
self.send_data(0x0f)
|
||||||
|
self.send_data(0x01) # 300-1
|
||||||
|
self.send_data(0x00) # YEnd L
|
||||||
|
self.send_data(0x00) # YEnd H
|
||||||
|
|
||||||
|
self.send_command(0xCe)
|
||||||
|
self.send_data(0x31)
|
||||||
|
self.send_command(0xCf)
|
||||||
|
self.send_data(0x0f)
|
||||||
|
self.send_data(0x01)
|
||||||
|
|
||||||
|
self.send_command(0xA4)
|
||||||
|
for i in range(Height):
|
||||||
|
self.send_data2(Image[i * Width1 + Width - 1: i * Width1 + Width * 2 - 1])
|
||||||
|
|
||||||
|
self.TurnOnDisplay_Partial()
|
||||||
|
|
||||||
|
def display_4Gray(self, image):
|
||||||
|
Width = int(self.width / 16) + 1
|
||||||
|
Width1 = int(self.width / 8)
|
||||||
|
|
||||||
|
self.send_command(0x24)
|
||||||
|
for j in range(self.height):
|
||||||
|
for i in range(Width):
|
||||||
|
temp3 = 0
|
||||||
|
for o in range(0, 2):
|
||||||
|
temp1 = image[(j * Width1 + i) * 2 + o]
|
||||||
|
for k in range(0, 4):
|
||||||
|
temp2 = temp1 & 0xC0
|
||||||
|
if (temp2 == 0xC0):
|
||||||
|
temp3 |= 0x01
|
||||||
|
elif (temp2 == 0x00):
|
||||||
|
temp3 |= 0x00
|
||||||
|
elif (temp2 == 0x80):
|
||||||
|
temp3 |= 0x00
|
||||||
|
else: # 0x40
|
||||||
|
temp3 |= 0x01
|
||||||
|
if (o != 1 or k != 3):
|
||||||
|
temp3 <<= 1
|
||||||
|
temp1 <<= 2
|
||||||
|
self.send_data(temp3)
|
||||||
|
|
||||||
|
self.send_command(0x26)
|
||||||
|
for j in range(self.height):
|
||||||
|
for i in range(Width):
|
||||||
|
temp3 = 0
|
||||||
|
for o in range(0, 2):
|
||||||
|
temp1 = image[(j * Width1 + i) * 2 + o]
|
||||||
|
for k in range(0, 4):
|
||||||
|
temp2 = temp1 & 0xC0
|
||||||
|
if (temp2 == 0xC0):
|
||||||
|
temp3 |= 0x01
|
||||||
|
elif (temp2 == 0x00):
|
||||||
|
temp3 |= 0x00
|
||||||
|
elif (temp2 == 0x80):
|
||||||
|
temp3 |= 0x01
|
||||||
|
else: # 0x40
|
||||||
|
temp3 |= 0x00
|
||||||
|
if (o != 1 or k != 3):
|
||||||
|
temp3 <<= 1
|
||||||
|
temp1 <<= 2
|
||||||
|
self.send_data(temp3)
|
||||||
|
|
||||||
|
self.send_command(0xA4)
|
||||||
|
for j in range(self.height):
|
||||||
|
for i in range(Width):
|
||||||
|
temp3 = 0
|
||||||
|
for o in range(0, 2):
|
||||||
|
temp1 = image[(j * Width1 + i + Width - 1) * 2 + o]
|
||||||
|
for k in range(0, 4):
|
||||||
|
temp2 = temp1 & 0xC0
|
||||||
|
if (temp2 == 0xC0):
|
||||||
|
temp3 |= 0x01
|
||||||
|
elif (temp2 == 0x00):
|
||||||
|
temp3 |= 0x00
|
||||||
|
elif (temp2 == 0x80):
|
||||||
|
temp3 |= 0x00
|
||||||
|
else: # 0x40
|
||||||
|
temp3 |= 0x01
|
||||||
|
if (o != 1 or k != 3):
|
||||||
|
temp3 <<= 1
|
||||||
|
temp1 <<= 2
|
||||||
|
self.send_data(temp3)
|
||||||
|
|
||||||
|
self.send_command(0xA6)
|
||||||
|
for j in range(self.height):
|
||||||
|
for i in range(Width):
|
||||||
|
temp3 = 0
|
||||||
|
for o in range(0, 2):
|
||||||
|
temp1 = image[(j * Width1 + i + Width - 1) * 2 + o]
|
||||||
|
for k in range(0, 4):
|
||||||
|
temp2 = temp1 & 0xC0
|
||||||
|
if (temp2 == 0xC0):
|
||||||
|
temp3 |= 0x01
|
||||||
|
elif (temp2 == 0x00):
|
||||||
|
temp3 |= 0x00
|
||||||
|
elif (temp2 == 0x80):
|
||||||
|
temp3 |= 0x01
|
||||||
|
else: # 0x40
|
||||||
|
temp3 |= 0x00
|
||||||
|
if (o != 1 or k != 3):
|
||||||
|
temp3 <<= 1
|
||||||
|
temp1 <<= 2
|
||||||
|
self.send_data(temp3)
|
||||||
|
|
||||||
|
self.TurnOnDisplay_4GRAY()
|
||||||
|
|
||||||
|
def Clear(self):
|
||||||
|
self.send_command(0x24)
|
||||||
|
self.send_data2([0xFF] * 13600)
|
||||||
|
self.send_command(0X26)
|
||||||
|
self.send_data2([0x00] * 13600)
|
||||||
|
|
||||||
|
self.send_command(0xA4)
|
||||||
|
self.send_data2([0xFF] * 13600)
|
||||||
|
self.send_command(0xA6)
|
||||||
|
self.send_data2([0x00] * 13600)
|
||||||
|
|
||||||
|
self.TurnOnDisplay()
|
||||||
|
|
||||||
|
def sleep(self):
|
||||||
|
self.send_command(0X10) # deep sleep
|
||||||
|
self.send_data(0x03)
|
||||||
|
|
||||||
|
epdconfig.delay_ms(2000)
|
||||||
|
epdconfig.module_exit()
|
||||||
|
### END OF FILE ###
|
205
pwnagotchi/ui/hw/libs/waveshare/v5in79b/epd5in79b.py
Normal file
205
pwnagotchi/ui/hw/libs/waveshare/v5in79b/epd5in79b.py
Normal file
@ -0,0 +1,205 @@
|
|||||||
|
# *****************************************************************************
|
||||||
|
# * | File : epd5in79b.py
|
||||||
|
# * | Author : Waveshare team
|
||||||
|
# * | Function : Electronic paper driver
|
||||||
|
# * | Info :
|
||||||
|
# *----------------
|
||||||
|
# * | This version: V1.0
|
||||||
|
# * | Date : 2024-03-05
|
||||||
|
# # | Info : python demo
|
||||||
|
# -----------------------------------------------------------------------------
|
||||||
|
# Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
|
# of this software and associated documnetation files (the "Software"), to deal
|
||||||
|
# in the Software without restriction, including without limitation the rights
|
||||||
|
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
|
# copies of the Software, and to permit persons to whom the Software is
|
||||||
|
# furished to do so, subject to the following conditions:
|
||||||
|
#
|
||||||
|
# The above copyright notice and this permission notice shall be included in
|
||||||
|
# all copies or substantial portions of the Software.
|
||||||
|
#
|
||||||
|
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
# FITNESS OR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
# LIABILITY WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
|
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||||
|
# THE SOFTWARE.
|
||||||
|
#
|
||||||
|
|
||||||
|
|
||||||
|
import logging
|
||||||
|
from .. import epdconfig
|
||||||
|
|
||||||
|
# Display resolution
|
||||||
|
EPD_WIDTH = 792
|
||||||
|
EPD_HEIGHT = 272
|
||||||
|
|
||||||
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
|
class EPD:
|
||||||
|
def __init__(self):
|
||||||
|
self.reset_pin = epdconfig.RST_PIN
|
||||||
|
self.dc_pin = epdconfig.DC_PIN
|
||||||
|
self.busy_pin = epdconfig.BUSY_PIN
|
||||||
|
self.cs_pin = epdconfig.CS_PIN
|
||||||
|
self.width = EPD_WIDTH
|
||||||
|
self.height = EPD_HEIGHT
|
||||||
|
|
||||||
|
# Hardware reset
|
||||||
|
def reset(self):
|
||||||
|
epdconfig.digital_write(self.reset_pin, 1)
|
||||||
|
epdconfig.delay_ms(200)
|
||||||
|
epdconfig.digital_write(self.reset_pin, 0)
|
||||||
|
epdconfig.delay_ms(1)
|
||||||
|
epdconfig.digital_write(self.reset_pin, 1)
|
||||||
|
epdconfig.delay_ms(200)
|
||||||
|
|
||||||
|
def send_command(self, command):
|
||||||
|
epdconfig.digital_write(self.dc_pin, 0)
|
||||||
|
epdconfig.digital_write(self.cs_pin, 0)
|
||||||
|
epdconfig.spi_writebyte([command])
|
||||||
|
epdconfig.digital_write(self.cs_pin, 1)
|
||||||
|
|
||||||
|
def send_data(self, data):
|
||||||
|
epdconfig.digital_write(self.dc_pin, 1)
|
||||||
|
epdconfig.digital_write(self.cs_pin, 0)
|
||||||
|
epdconfig.spi_writebyte([data])
|
||||||
|
epdconfig.digital_write(self.cs_pin, 1)
|
||||||
|
|
||||||
|
# send a lot of data
|
||||||
|
def send_data2(self, data):
|
||||||
|
epdconfig.digital_write(self.dc_pin, 1)
|
||||||
|
epdconfig.digital_write(self.cs_pin, 0)
|
||||||
|
epdconfig.spi_writebyte2(data)
|
||||||
|
epdconfig.digital_write(self.cs_pin, 1)
|
||||||
|
|
||||||
|
def ReadBusy(self):
|
||||||
|
logger.debug("e-Paper busy")
|
||||||
|
while (epdconfig.digital_read(self.busy_pin) == 1): # 0: idle, 1: busy
|
||||||
|
epdconfig.delay_ms(200)
|
||||||
|
logger.debug("e-Paper busy release")
|
||||||
|
|
||||||
|
def TurnOnDisplay(self):
|
||||||
|
self.send_command(0x22)
|
||||||
|
self.send_data(0xF7) # 24s # 0xD7 16s Probability refresh bad, probability damage ink screen
|
||||||
|
self.send_command(0x20) # DISPLAY REFRESH
|
||||||
|
epdconfig.delay_ms(100) # The delay here is necessary, 200uS at least!!!
|
||||||
|
self.ReadBusy() # waiting for the electronic paper IC to release the idle signal
|
||||||
|
|
||||||
|
def init(self):
|
||||||
|
if (epdconfig.module_init() != 0):
|
||||||
|
return -1
|
||||||
|
|
||||||
|
self.reset()
|
||||||
|
|
||||||
|
self.ReadBusy() # waiting for the electronic paper IC to release the idle signal
|
||||||
|
self.send_command(0x12) # POWER ON
|
||||||
|
self.ReadBusy() # waiting for the electronic paper IC to release the idle signal
|
||||||
|
|
||||||
|
self.send_command(0x11)
|
||||||
|
self.send_data(0x01)
|
||||||
|
self.send_command(0x44) # Set Ram X- address Start / End position
|
||||||
|
self.send_data(0x00) # XStart, POR = 00h
|
||||||
|
self.send_data(0x31) # 400/8-1
|
||||||
|
self.send_command(0x45) # Set Ram Y- address Start / End position
|
||||||
|
self.send_data(0x0f)
|
||||||
|
self.send_data(0x01) # 300-1
|
||||||
|
self.send_data(0x00) # YEnd L
|
||||||
|
self.send_data(0x00) # YEnd H
|
||||||
|
|
||||||
|
self.send_command(0x4e)
|
||||||
|
self.send_data(0x00)
|
||||||
|
self.send_command(0x4f)
|
||||||
|
self.send_data(0x0f)
|
||||||
|
self.send_data(0x01)
|
||||||
|
|
||||||
|
self.send_command(0x91)
|
||||||
|
self.send_data(0x00)
|
||||||
|
|
||||||
|
self.send_command(0xC4) # Set Ram X- address Start / End position
|
||||||
|
self.send_data(0x31) # XStart, POR = 00h
|
||||||
|
self.send_data(0x00) # 400/8-1
|
||||||
|
self.send_command(0xC5) # Set Ram Y- address Start / End position
|
||||||
|
self.send_data(0x0f)
|
||||||
|
self.send_data(0x01) # 300-1
|
||||||
|
self.send_data(0x00) # YEnd L
|
||||||
|
self.send_data(0x00) # YEnd H
|
||||||
|
|
||||||
|
self.send_command(0xCe)
|
||||||
|
self.send_data(0x31)
|
||||||
|
self.send_command(0xCf)
|
||||||
|
self.send_data(0x0f)
|
||||||
|
self.send_data(0x01)
|
||||||
|
return 0
|
||||||
|
|
||||||
|
def getbuffer(self, image):
|
||||||
|
# logger.debug("bufsiz = ",int(self.width/8) * self.height)
|
||||||
|
buf = [0xFF] * (int(self.width / 8) * self.height)
|
||||||
|
image_monocolor = image.convert('1')
|
||||||
|
imwidth, imheight = image_monocolor.size
|
||||||
|
pixels = image_monocolor.load()
|
||||||
|
# logger.debug("imwidth = %d, imheight = %d",imwidth,imheight)
|
||||||
|
if (imwidth == self.width and imheight == self.height):
|
||||||
|
logger.debug("Vertical")
|
||||||
|
for y in range(imheight):
|
||||||
|
for x in range(imwidth):
|
||||||
|
# Set the bits for the column of pixels at the current position.
|
||||||
|
if pixels[x, y] == 0:
|
||||||
|
buf[int((x + y * self.width) / 8)] &= ~(0x80 >> (x % 8))
|
||||||
|
elif (imwidth == self.height and imheight == self.width):
|
||||||
|
logger.debug("Horizontal")
|
||||||
|
for y in range(imheight):
|
||||||
|
for x in range(imwidth):
|
||||||
|
newx = y
|
||||||
|
newy = self.height - x - 1
|
||||||
|
if pixels[x, y] == 0:
|
||||||
|
buf[int((newx + newy * self.width) / 8)] &= ~(0x80 >> (y % 8))
|
||||||
|
return buf
|
||||||
|
|
||||||
|
def display(self, imageblack, imagered):
|
||||||
|
buf = [0x00] * int(self.width * self.height / 8)
|
||||||
|
for i in range(0, int(self.width * self.height / 8)):
|
||||||
|
buf[i] = ~imagered[i]
|
||||||
|
|
||||||
|
Width = int(self.width / 16) + 1
|
||||||
|
Width1 = int(self.width / 8)
|
||||||
|
Height = self.height
|
||||||
|
|
||||||
|
self.send_command(0x24)
|
||||||
|
for i in range(Height):
|
||||||
|
self.send_data2(imageblack[i * Width1: i * Width1 + Width])
|
||||||
|
self.send_command(0X26)
|
||||||
|
for i in range(self.height):
|
||||||
|
self.send_data2(buf[i * Width1: i * Width1 + Width])
|
||||||
|
|
||||||
|
self.send_command(0xA4)
|
||||||
|
for i in range(self.height):
|
||||||
|
self.send_data2(imageblack[i * Width1 + Width - 1: i * Width1 + Width * 2 - 1])
|
||||||
|
self.send_command(0xA6)
|
||||||
|
for i in range(self.height):
|
||||||
|
self.send_data2(buf[i * Width1 + Width - 1: i * Width1 + Width * 2 - 1])
|
||||||
|
|
||||||
|
self.TurnOnDisplay()
|
||||||
|
|
||||||
|
def Clear(self):
|
||||||
|
self.send_command(0x24)
|
||||||
|
self.send_data2([0xFF] * 13600)
|
||||||
|
self.send_command(0X26)
|
||||||
|
self.send_data2([0x00] * 13600)
|
||||||
|
|
||||||
|
self.send_command(0xA4)
|
||||||
|
self.send_data2([0xFF] * 13600)
|
||||||
|
self.send_command(0xA6)
|
||||||
|
self.send_data2([0x00] * 13600)
|
||||||
|
|
||||||
|
self.TurnOnDisplay()
|
||||||
|
|
||||||
|
def sleep(self):
|
||||||
|
self.send_command(0X10) # deep sleep
|
||||||
|
self.send_data(0x03)
|
||||||
|
|
||||||
|
epdconfig.delay_ms(2000)
|
||||||
|
epdconfig.module_exit()
|
||||||
|
### END OF FILE ###
|
243
pwnagotchi/ui/hw/libs/weact/epdconfig.py
Normal file
243
pwnagotchi/ui/hw/libs/weact/epdconfig.py
Normal file
@ -0,0 +1,243 @@
|
|||||||
|
# /*****************************************************************************
|
||||||
|
# * | File : epdconfig.py
|
||||||
|
# * | Author : Waveshare team
|
||||||
|
# * | Function : Hardware underlying interface
|
||||||
|
# * | Info :
|
||||||
|
# *----------------
|
||||||
|
# * | This version: V1.2
|
||||||
|
# * | Date : 2022-10-29
|
||||||
|
# * | Info :
|
||||||
|
# ******************************************************************************
|
||||||
|
# Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
|
# of this software and associated documnetation files (the "Software"), to deal
|
||||||
|
# in the Software without restriction, including without limitation the rights
|
||||||
|
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
|
# copies of the Software, and to permit persons to whom the Software is
|
||||||
|
# furished to do so, subject to the following conditions:
|
||||||
|
#
|
||||||
|
# The above copyright notice and this permission notice shall be included in
|
||||||
|
# all copies or substantial portions of the Software.
|
||||||
|
#
|
||||||
|
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
# FITNESS OR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
# LIABILITY WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
|
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||||
|
# THE SOFTWARE.
|
||||||
|
#
|
||||||
|
|
||||||
|
import os
|
||||||
|
import logging
|
||||||
|
import sys
|
||||||
|
import time
|
||||||
|
|
||||||
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
|
class RaspberryPi:
|
||||||
|
# Pin definition
|
||||||
|
RST_PIN = 17
|
||||||
|
DC_PIN = 25
|
||||||
|
CS_PIN = 8
|
||||||
|
BUSY_PIN = 24
|
||||||
|
PWR_PIN = 18
|
||||||
|
|
||||||
|
def __init__(self):
|
||||||
|
import spidev
|
||||||
|
import RPi.GPIO
|
||||||
|
|
||||||
|
self.GPIO = RPi.GPIO
|
||||||
|
self.SPI = spidev.SpiDev()
|
||||||
|
|
||||||
|
def digital_write(self, pin, value):
|
||||||
|
self.GPIO.output(pin, value)
|
||||||
|
|
||||||
|
def digital_read(self, pin):
|
||||||
|
return self.GPIO.input(pin)
|
||||||
|
|
||||||
|
def delay_ms(self, delaytime):
|
||||||
|
time.sleep(delaytime / 1000.0)
|
||||||
|
|
||||||
|
def spi_writebyte(self, data):
|
||||||
|
self.SPI.writebytes(data)
|
||||||
|
|
||||||
|
def spi_writebyte2(self, data):
|
||||||
|
self.SPI.writebytes2(data)
|
||||||
|
|
||||||
|
def module_init(self):
|
||||||
|
self.GPIO.setmode(self.GPIO.BCM)
|
||||||
|
self.GPIO.setwarnings(False)
|
||||||
|
self.GPIO.setup(self.RST_PIN, self.GPIO.OUT)
|
||||||
|
self.GPIO.setup(self.DC_PIN, self.GPIO.OUT)
|
||||||
|
self.GPIO.setup(self.CS_PIN, self.GPIO.OUT)
|
||||||
|
self.GPIO.setup(self.PWR_PIN, self.GPIO.OUT)
|
||||||
|
self.GPIO.setup(self.BUSY_PIN, self.GPIO.IN)
|
||||||
|
|
||||||
|
self.GPIO.output(self.PWR_PIN, 1)
|
||||||
|
|
||||||
|
# SPI device, bus = 0, device = 0
|
||||||
|
self.SPI.open(0, 0)
|
||||||
|
self.SPI.max_speed_hz = 4000000
|
||||||
|
self.SPI.mode = 0b00
|
||||||
|
return 0
|
||||||
|
|
||||||
|
def module_exit(self):
|
||||||
|
logger.debug("spi end")
|
||||||
|
self.SPI.close()
|
||||||
|
|
||||||
|
logger.debug("close 5V, Module enters 0 power consumption ...")
|
||||||
|
self.GPIO.output(self.RST_PIN, 0)
|
||||||
|
self.GPIO.output(self.DC_PIN, 0)
|
||||||
|
self.GPIO.output(self.PWR_PIN, 0)
|
||||||
|
|
||||||
|
self.GPIO.cleanup([self.RST_PIN, self.DC_PIN, self.CS_PIN, self.BUSY_PIN, self.PWR_PIN])
|
||||||
|
|
||||||
|
|
||||||
|
class JetsonNano:
|
||||||
|
# Pin definition
|
||||||
|
RST_PIN = 17
|
||||||
|
DC_PIN = 25
|
||||||
|
CS_PIN = 8
|
||||||
|
BUSY_PIN = 24
|
||||||
|
PWR_PIN = 18
|
||||||
|
|
||||||
|
def __init__(self):
|
||||||
|
import ctypes
|
||||||
|
find_dirs = [
|
||||||
|
os.path.dirname(os.path.realpath(__file__)),
|
||||||
|
'/usr/local/lib',
|
||||||
|
'/usr/lib',
|
||||||
|
]
|
||||||
|
self.SPI = None
|
||||||
|
for find_dir in find_dirs:
|
||||||
|
so_filename = os.path.join(find_dir, 'sysfs_software_spi.so')
|
||||||
|
if os.path.exists(so_filename):
|
||||||
|
self.SPI = ctypes.cdll.LoadLibrary(so_filename)
|
||||||
|
break
|
||||||
|
if self.SPI is None:
|
||||||
|
raise RuntimeError('Cannot find sysfs_software_spi.so')
|
||||||
|
|
||||||
|
import Jetson.GPIO
|
||||||
|
self.GPIO = Jetson.GPIO
|
||||||
|
|
||||||
|
def digital_write(self, pin, value):
|
||||||
|
self.GPIO.output(pin, value)
|
||||||
|
|
||||||
|
def digital_read(self, pin):
|
||||||
|
return self.GPIO.input(self.BUSY_PIN)
|
||||||
|
|
||||||
|
def delay_ms(self, delaytime):
|
||||||
|
time.sleep(delaytime / 1000.0)
|
||||||
|
|
||||||
|
def spi_writebyte(self, data):
|
||||||
|
self.SPI.SYSFS_software_spi_transfer(data[0])
|
||||||
|
|
||||||
|
def spi_writebyte2(self, data):
|
||||||
|
for i in range(len(data)):
|
||||||
|
self.SPI.SYSFS_software_spi_transfer(data[i])
|
||||||
|
|
||||||
|
def module_init(self):
|
||||||
|
self.GPIO.setmode(self.GPIO.BCM)
|
||||||
|
self.GPIO.setwarnings(False)
|
||||||
|
self.GPIO.setup(self.RST_PIN, self.GPIO.OUT)
|
||||||
|
self.GPIO.setup(self.DC_PIN, self.GPIO.OUT)
|
||||||
|
self.GPIO.setup(self.CS_PIN, self.GPIO.OUT)
|
||||||
|
self.GPIO.setup(self.PWR_PIN, self.GPIO.OUT)
|
||||||
|
self.GPIO.setup(self.BUSY_PIN, self.GPIO.IN)
|
||||||
|
|
||||||
|
self.GPIO.output(self.PWR_PIN, 1)
|
||||||
|
|
||||||
|
self.SPI.SYSFS_software_spi_begin()
|
||||||
|
return 0
|
||||||
|
|
||||||
|
def module_exit(self):
|
||||||
|
logger.debug("spi end")
|
||||||
|
self.SPI.SYSFS_software_spi_end()
|
||||||
|
|
||||||
|
logger.debug("close 5V, Module enters 0 power consumption ...")
|
||||||
|
self.GPIO.output(self.RST_PIN, 0)
|
||||||
|
self.GPIO.output(self.DC_PIN, 0)
|
||||||
|
self.GPIO.output(self.PWR_PIN, 0)
|
||||||
|
|
||||||
|
self.GPIO.cleanup([self.RST_PIN, self.DC_PIN, self.CS_PIN, self.BUSY_PIN, self.PWR_PIN])
|
||||||
|
|
||||||
|
|
||||||
|
class SunriseX3:
|
||||||
|
# Pin definition
|
||||||
|
RST_PIN = 17
|
||||||
|
DC_PIN = 25
|
||||||
|
CS_PIN = 8
|
||||||
|
BUSY_PIN = 24
|
||||||
|
PWR_PIN = 18
|
||||||
|
Flag = 0
|
||||||
|
|
||||||
|
def __init__(self):
|
||||||
|
import spidev
|
||||||
|
import Hobot.GPIO
|
||||||
|
|
||||||
|
self.GPIO = Hobot.GPIO
|
||||||
|
self.SPI = spidev.SpiDev()
|
||||||
|
|
||||||
|
def digital_write(self, pin, value):
|
||||||
|
self.GPIO.output(pin, value)
|
||||||
|
|
||||||
|
def digital_read(self, pin):
|
||||||
|
return self.GPIO.input(pin)
|
||||||
|
|
||||||
|
def delay_ms(self, delaytime):
|
||||||
|
time.sleep(delaytime / 1000.0)
|
||||||
|
|
||||||
|
def spi_writebyte(self, data):
|
||||||
|
self.SPI.writebytes(data)
|
||||||
|
|
||||||
|
def spi_writebyte2(self, data):
|
||||||
|
# for i in range(len(data)):
|
||||||
|
# self.SPI.writebytes([data[i]])
|
||||||
|
self.SPI.xfer3(data)
|
||||||
|
|
||||||
|
def module_init(self):
|
||||||
|
if self.Flag == 0:
|
||||||
|
self.Flag = 1
|
||||||
|
self.GPIO.setmode(self.GPIO.BCM)
|
||||||
|
self.GPIO.setwarnings(False)
|
||||||
|
self.GPIO.setup(self.RST_PIN, self.GPIO.OUT)
|
||||||
|
self.GPIO.setup(self.DC_PIN, self.GPIO.OUT)
|
||||||
|
self.GPIO.setup(self.CS_PIN, self.GPIO.OUT)
|
||||||
|
self.GPIO.setup(self.PWR_PIN, self.GPIO.OUT)
|
||||||
|
self.GPIO.setup(self.BUSY_PIN, self.GPIO.IN)
|
||||||
|
|
||||||
|
self.GPIO.output(self.PWR_PIN, 1)
|
||||||
|
|
||||||
|
# SPI device, bus = 0, device = 0
|
||||||
|
self.SPI.open(2, 0)
|
||||||
|
self.SPI.max_speed_hz = 4000000
|
||||||
|
self.SPI.mode = 0b00
|
||||||
|
return 0
|
||||||
|
else:
|
||||||
|
return 0
|
||||||
|
|
||||||
|
def module_exit(self):
|
||||||
|
logger.debug("spi end")
|
||||||
|
self.SPI.close()
|
||||||
|
|
||||||
|
logger.debug("close 5V, Module enters 0 power consumption ...")
|
||||||
|
self.Flag = 0
|
||||||
|
self.GPIO.output(self.RST_PIN, 0)
|
||||||
|
self.GPIO.output(self.DC_PIN, 0)
|
||||||
|
self.GPIO.output(self.PWR_PIN, 0)
|
||||||
|
|
||||||
|
self.GPIO.cleanup([self.RST_PIN, self.DC_PIN, self.CS_PIN, self.BUSY_PIN], self.PWR_PIN)
|
||||||
|
|
||||||
|
|
||||||
|
if os.path.exists('/sys/bus/platform/drivers/gpiomem-bcm2835'):
|
||||||
|
implementation = RaspberryPi()
|
||||||
|
elif os.path.exists('/sys/bus/platform/drivers/gpio-x3'):
|
||||||
|
implementation = SunriseX3()
|
||||||
|
else:
|
||||||
|
implementation = RaspberryPi()
|
||||||
|
|
||||||
|
for func in [x for x in dir(implementation) if not x.startswith('_')]:
|
||||||
|
setattr(sys.modules[__name__], func, getattr(implementation, func))
|
||||||
|
|
||||||
|
### END OF FILE ###
|
374
pwnagotchi/ui/hw/libs/weact/v2in9/epd2in9.py
Normal file
374
pwnagotchi/ui/hw/libs/weact/v2in9/epd2in9.py
Normal file
@ -0,0 +1,374 @@
|
|||||||
|
import logging
|
||||||
|
from .. import epdconfig
|
||||||
|
|
||||||
|
# Display resolution
|
||||||
|
EPD_WIDTH = 296
|
||||||
|
EPD_HEIGHT = 128
|
||||||
|
|
||||||
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
|
class EPD:
|
||||||
|
def __init__(self):
|
||||||
|
self.reset_pin = epdconfig.RST_PIN
|
||||||
|
self.dc_pin = epdconfig.DC_PIN
|
||||||
|
self.busy_pin = epdconfig.BUSY_PIN
|
||||||
|
self.cs_pin = epdconfig.CS_PIN
|
||||||
|
self.width = EPD_WIDTH
|
||||||
|
self.height = EPD_HEIGHT
|
||||||
|
|
||||||
|
lut_partial_update = [
|
||||||
|
0x0, 0x40, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
|
||||||
|
0x80, 0x80, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
|
||||||
|
0x40, 0x40, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
|
||||||
|
0x0, 0x80, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
|
||||||
|
0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
|
||||||
|
0x14, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
|
||||||
|
0x1, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
|
||||||
|
0x1, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
|
||||||
|
0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
|
||||||
|
0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
|
||||||
|
0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
|
||||||
|
0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
|
||||||
|
0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
|
||||||
|
0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
|
||||||
|
0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
|
||||||
|
0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
|
||||||
|
0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
|
||||||
|
0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x0, 0x0, 0x0,
|
||||||
|
0x22, 0x17, 0x41, 0x00, 0x32, 0x36,
|
||||||
|
]
|
||||||
|
|
||||||
|
lut_full_update = [
|
||||||
|
0x80, 0x4A, 0x40, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
|
||||||
|
0x40, 0x4A, 0x80, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
|
||||||
|
0x80, 0x4A, 0x40, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
|
||||||
|
0x40, 0x4A, 0x80, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
|
||||||
|
0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
|
||||||
|
0xF, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
|
||||||
|
0xF, 0x0, 0x0, 0xF, 0x0, 0x0, 0x2,
|
||||||
|
0xF, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
|
||||||
|
0x1, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
|
||||||
|
0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
|
||||||
|
0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
|
||||||
|
0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
|
||||||
|
0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
|
||||||
|
0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
|
||||||
|
0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
|
||||||
|
0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
|
||||||
|
0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
|
||||||
|
0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x0, 0x0, 0x0,
|
||||||
|
0x22, 0x17, 0x41, 0x0, 0x32, 0x36,
|
||||||
|
]
|
||||||
|
|
||||||
|
'''
|
||||||
|
function :Hardware reset
|
||||||
|
parameter:
|
||||||
|
'''
|
||||||
|
|
||||||
|
def reset(self):
|
||||||
|
epdconfig.digital_write(self.reset_pin, 1)
|
||||||
|
epdconfig.delay_ms(20)
|
||||||
|
epdconfig.digital_write(self.reset_pin, 0)
|
||||||
|
epdconfig.delay_ms(2)
|
||||||
|
epdconfig.digital_write(self.reset_pin, 1)
|
||||||
|
epdconfig.delay_ms(20)
|
||||||
|
|
||||||
|
'''
|
||||||
|
function :send command
|
||||||
|
parameter:
|
||||||
|
command : Command register
|
||||||
|
'''
|
||||||
|
|
||||||
|
def send_command(self, command):
|
||||||
|
epdconfig.digital_write(self.dc_pin, 0)
|
||||||
|
epdconfig.digital_write(self.cs_pin, 0)
|
||||||
|
epdconfig.spi_writebyte([command])
|
||||||
|
epdconfig.digital_write(self.cs_pin, 1)
|
||||||
|
|
||||||
|
'''
|
||||||
|
function :send data
|
||||||
|
parameter:
|
||||||
|
data : Write data
|
||||||
|
'''
|
||||||
|
|
||||||
|
def send_data(self, data):
|
||||||
|
epdconfig.digital_write(self.dc_pin, 1)
|
||||||
|
epdconfig.digital_write(self.cs_pin, 0)
|
||||||
|
epdconfig.spi_writebyte([data])
|
||||||
|
epdconfig.digital_write(self.cs_pin, 1)
|
||||||
|
|
||||||
|
# send a lot of data
|
||||||
|
def send_data2(self, data):
|
||||||
|
epdconfig.digital_write(self.dc_pin, 1)
|
||||||
|
epdconfig.digital_write(self.cs_pin, 0)
|
||||||
|
epdconfig.spi_writebyte2(data)
|
||||||
|
epdconfig.digital_write(self.cs_pin, 1)
|
||||||
|
|
||||||
|
'''
|
||||||
|
function :Wait until the busy_pin goes LOW
|
||||||
|
parameter:
|
||||||
|
'''
|
||||||
|
|
||||||
|
def ReadBusy(self):
|
||||||
|
logger.debug("e-Paper busy")
|
||||||
|
while (epdconfig.digital_read(self.busy_pin) == 1): # 0: idle, 1: busy
|
||||||
|
epdconfig.delay_ms(10)
|
||||||
|
logger.debug("e-Paper busy release")
|
||||||
|
|
||||||
|
'''
|
||||||
|
function : Turn On Display
|
||||||
|
parameter:
|
||||||
|
'''
|
||||||
|
|
||||||
|
def TurnOnDisplay(self):
|
||||||
|
self.send_command(0x22) # Display Update Control
|
||||||
|
self.send_data(0xC7)
|
||||||
|
self.send_command(0x20) # Activate Display Update Sequence
|
||||||
|
self.ReadBusy()
|
||||||
|
|
||||||
|
'''
|
||||||
|
function : Turn On Display Part
|
||||||
|
parameter:
|
||||||
|
'''
|
||||||
|
|
||||||
|
def TurnOnDisplayPart(self):
|
||||||
|
self.send_command(0x22) # Display Update Control
|
||||||
|
self.send_data(0x0f) # fast:0x0c, quality:0x0f, 0xcf
|
||||||
|
self.send_command(0x20) # Activate Display Update Sequence
|
||||||
|
self.ReadBusy()
|
||||||
|
|
||||||
|
'''
|
||||||
|
function : Set lut
|
||||||
|
parameter:
|
||||||
|
lut : lut data
|
||||||
|
'''
|
||||||
|
|
||||||
|
def Lut(self, lut):
|
||||||
|
self.send_command(0x32)
|
||||||
|
for i in range(0, 153):
|
||||||
|
self.send_data(lut[i])
|
||||||
|
self.ReadBusy()
|
||||||
|
|
||||||
|
'''
|
||||||
|
function : Send lut data and configuration
|
||||||
|
parameter:
|
||||||
|
lut : lut data
|
||||||
|
'''
|
||||||
|
|
||||||
|
def SetLut(self, lut):
|
||||||
|
self.Lut(lut)
|
||||||
|
self.send_command(0x3f)
|
||||||
|
self.send_data(lut[153])
|
||||||
|
self.send_command(0x03) # gate voltage
|
||||||
|
self.send_data(lut[154])
|
||||||
|
self.send_command(0x04) # source voltage
|
||||||
|
self.send_data(lut[155]) # VSH
|
||||||
|
self.send_data(lut[156]) # VSH2
|
||||||
|
self.send_data(lut[157]) # VSL
|
||||||
|
self.send_command(0x2c) # VCOM
|
||||||
|
self.send_data(lut[158])
|
||||||
|
|
||||||
|
'''
|
||||||
|
function : Setting the display window
|
||||||
|
parameter:
|
||||||
|
xstart : X-axis starting position
|
||||||
|
ystart : Y-axis starting position
|
||||||
|
xend : End position of X-axis
|
||||||
|
yend : End position of Y-axis
|
||||||
|
'''
|
||||||
|
|
||||||
|
def SetWindow(self, x_start, y_start, x_end, y_end):
|
||||||
|
self.send_command(0x44) # SET_RAM_X_ADDRESS_START_END_POSITION
|
||||||
|
# x point must be the multiple of 8 or the last 3 bits will be ignored
|
||||||
|
self.send_data((x_start >> 3) & 0xFF)
|
||||||
|
self.send_data((x_end >> 3) & 0xFF)
|
||||||
|
|
||||||
|
self.send_command(0x45) # SET_RAM_Y_ADDRESS_START_END_POSITION
|
||||||
|
self.send_data(y_start & 0xFF)
|
||||||
|
self.send_data((y_start >> 8) & 0xFF)
|
||||||
|
self.send_data(y_end & 0xFF)
|
||||||
|
self.send_data((y_end >> 8) & 0xFF)
|
||||||
|
|
||||||
|
'''
|
||||||
|
function : Set Cursor
|
||||||
|
parameter:
|
||||||
|
x : X-axis starting position
|
||||||
|
y : Y-axis starting position
|
||||||
|
'''
|
||||||
|
|
||||||
|
def SetCursor(self, x, y):
|
||||||
|
self.send_command(0x4E) # SET_RAM_X_ADDRESS_COUNTER
|
||||||
|
# x point must be the multiple of 8 or the last 3 bits will be ignored
|
||||||
|
self.send_data(x & 0xFF)
|
||||||
|
|
||||||
|
self.send_command(0x4F) # SET_RAM_Y_ADDRESS_COUNTER
|
||||||
|
self.send_data(y & 0xFF)
|
||||||
|
self.send_data((y >> 8) & 0xFF)
|
||||||
|
|
||||||
|
'''
|
||||||
|
function : Initialize the e-Paper register
|
||||||
|
parameter:
|
||||||
|
'''
|
||||||
|
|
||||||
|
def init(self):
|
||||||
|
if (epdconfig.module_init() != 0):
|
||||||
|
return -1
|
||||||
|
# EPD hardware init start
|
||||||
|
self.reset()
|
||||||
|
|
||||||
|
self.ReadBusy()
|
||||||
|
self.send_command(0x12) # SWRESET
|
||||||
|
self.ReadBusy()
|
||||||
|
|
||||||
|
self.send_command(0x01) # Driver output control
|
||||||
|
self.send_data(0xf9)
|
||||||
|
self.send_data(0x00)
|
||||||
|
self.send_data(0x00)
|
||||||
|
|
||||||
|
self.send_command(0x11) # data entry mode
|
||||||
|
self.send_data(0x03)
|
||||||
|
|
||||||
|
self.SetWindow(0, 0, self.width - 1, self.height - 1)
|
||||||
|
self.SetCursor(0, 0)
|
||||||
|
|
||||||
|
self.send_command(0x3c)
|
||||||
|
self.send_data(0x05)
|
||||||
|
|
||||||
|
self.send_command(0x21) # Display update control
|
||||||
|
self.send_data(0x00)
|
||||||
|
self.send_data(0x80)
|
||||||
|
|
||||||
|
self.send_command(0x18)
|
||||||
|
self.send_data(0x80)
|
||||||
|
|
||||||
|
self.ReadBusy()
|
||||||
|
|
||||||
|
self.SetLut(self.lut_full_update)
|
||||||
|
return 0
|
||||||
|
|
||||||
|
'''
|
||||||
|
function : Display images
|
||||||
|
parameter:
|
||||||
|
image : Image data
|
||||||
|
'''
|
||||||
|
|
||||||
|
def getbuffer(self, image):
|
||||||
|
img = image
|
||||||
|
imwidth, imheight = img.size
|
||||||
|
if (imwidth == self.width and imheight == self.height):
|
||||||
|
img = img.convert('1')
|
||||||
|
elif (imwidth == self.height and imheight == self.width):
|
||||||
|
# image has correct dimensions, but needs to be rotated
|
||||||
|
img = img.rotate(90, expand=True).convert('1')
|
||||||
|
else:
|
||||||
|
logger.warning("Wrong image dimensions: must be " + str(self.width) + "x" + str(self.height))
|
||||||
|
# return a blank buffer
|
||||||
|
return [0x00] * (int(self.width / 8) * self.height)
|
||||||
|
|
||||||
|
buf = bytearray(img.tobytes('raw'))
|
||||||
|
return buf
|
||||||
|
|
||||||
|
'''
|
||||||
|
function : Sends the image buffer in RAM to e-Paper and displays
|
||||||
|
parameter:
|
||||||
|
image : Image data
|
||||||
|
'''
|
||||||
|
|
||||||
|
def display(self, image):
|
||||||
|
if self.width % 8 == 0:
|
||||||
|
linewidth = int(self.width / 8)
|
||||||
|
else:
|
||||||
|
linewidth = int(self.width / 8) + 1
|
||||||
|
|
||||||
|
self.send_command(0x24)
|
||||||
|
for j in range(0, self.height):
|
||||||
|
for i in range(0, linewidth):
|
||||||
|
self.send_data(image[i + j * linewidth])
|
||||||
|
self.TurnOnDisplay()
|
||||||
|
|
||||||
|
'''
|
||||||
|
function : Sends the image buffer in RAM to e-Paper and partial refresh
|
||||||
|
parameter:
|
||||||
|
image : Image data
|
||||||
|
'''
|
||||||
|
|
||||||
|
def displayPartial(self, image):
|
||||||
|
epdconfig.digital_write(self.reset_pin, 0)
|
||||||
|
epdconfig.delay_ms(1)
|
||||||
|
epdconfig.digital_write(self.reset_pin, 1)
|
||||||
|
|
||||||
|
self.SetLut(self.lut_partial_update)
|
||||||
|
self.send_command(0x37)
|
||||||
|
self.send_data(0x00)
|
||||||
|
self.send_data(0x00)
|
||||||
|
self.send_data(0x00)
|
||||||
|
self.send_data(0x00)
|
||||||
|
self.send_data(0x00)
|
||||||
|
self.send_data(0x40)
|
||||||
|
self.send_data(0x00)
|
||||||
|
self.send_data(0x00)
|
||||||
|
self.send_data(0x00)
|
||||||
|
self.send_data(0x00)
|
||||||
|
|
||||||
|
self.send_command(0x3C) # BorderWavefrom
|
||||||
|
self.send_data(0x80)
|
||||||
|
|
||||||
|
self.send_command(0x22)
|
||||||
|
self.send_data(0xC0)
|
||||||
|
self.send_command(0x20)
|
||||||
|
self.ReadBusy()
|
||||||
|
|
||||||
|
self.SetWindow(0, 0, self.width - 1, self.height - 1)
|
||||||
|
self.SetCursor(0, 0)
|
||||||
|
|
||||||
|
self.send_command(0x24) # WRITE_RAM
|
||||||
|
# for j in range(0, self.height):
|
||||||
|
# for i in range(0, linewidth):
|
||||||
|
# self.send_data(image[i + j * linewidth])
|
||||||
|
self.send_data2(image)
|
||||||
|
self.TurnOnDisplayPart()
|
||||||
|
|
||||||
|
'''
|
||||||
|
function : Refresh a base image
|
||||||
|
parameter:
|
||||||
|
image : Image data
|
||||||
|
'''
|
||||||
|
|
||||||
|
def displayPartBaseImage(self, image):
|
||||||
|
self.send_command(0x24)
|
||||||
|
self.send_data2(image)
|
||||||
|
|
||||||
|
self.send_command(0x26)
|
||||||
|
self.send_data2(image)
|
||||||
|
self.TurnOnDisplay()
|
||||||
|
|
||||||
|
'''
|
||||||
|
function : Clear screen
|
||||||
|
parameter:
|
||||||
|
'''
|
||||||
|
|
||||||
|
def Clear(self, color=0xFF):
|
||||||
|
if self.width % 8 == 0:
|
||||||
|
linewidth = int(self.width / 8)
|
||||||
|
else:
|
||||||
|
linewidth = int(self.width / 8) + 1
|
||||||
|
# logger.debug(linewidth)
|
||||||
|
|
||||||
|
self.send_command(0x24)
|
||||||
|
self.send_data2([color] * int(self.height * linewidth))
|
||||||
|
self.TurnOnDisplay()
|
||||||
|
|
||||||
|
'''
|
||||||
|
function : Enter sleep mode
|
||||||
|
parameter:
|
||||||
|
'''
|
||||||
|
|
||||||
|
def sleep(self):
|
||||||
|
self.send_command(0x10) # enter deep sleep
|
||||||
|
self.send_data(0x01)
|
||||||
|
|
||||||
|
epdconfig.delay_ms(2000)
|
||||||
|
epdconfig.module_exit()
|
||||||
|
|
||||||
|
### END OF FILE ###
|
55
pwnagotchi/ui/hw/pirateaudio.py
Normal file
55
pwnagotchi/ui/hw/pirateaudio.py
Normal file
@ -0,0 +1,55 @@
|
|||||||
|
# board GPIO:
|
||||||
|
# A: GPIO5
|
||||||
|
# B: GPIO6
|
||||||
|
# X: GPIO16
|
||||||
|
# Y: GPIO20 / GPIO24 (refer to the pimoroni site or pinout.xyz)
|
||||||
|
# I2S pins for the DAC: GPIO 18 GPIO19 GPIO21
|
||||||
|
#
|
||||||
|
# HW datasheet: https://shop.pimoroni.com/products/pirate-audio-line-out?variant=31189750546515
|
||||||
|
# pinout xyz: https://pinout.xyz/pinout/pirate_audio_line_out
|
||||||
|
|
||||||
|
import logging
|
||||||
|
|
||||||
|
import pwnagotchi.ui.fonts as fonts
|
||||||
|
from pwnagotchi.ui.hw.base import DisplayImpl
|
||||||
|
|
||||||
|
|
||||||
|
class PirateAudio(DisplayImpl):
|
||||||
|
def __init__(self, config):
|
||||||
|
super(PirateAudio, self).__init__(config, 'pirateaudio')
|
||||||
|
self._display = None
|
||||||
|
|
||||||
|
def layout(self):
|
||||||
|
fonts.setup(10, 9, 10, 35, 25, 9)
|
||||||
|
self._layout['width'] = 240
|
||||||
|
self._layout['height'] = 240
|
||||||
|
self._layout['face'] = (0, 40)
|
||||||
|
self._layout['name'] = (5, 20)
|
||||||
|
self._layout['channel'] = (0, 0)
|
||||||
|
self._layout['aps'] = (28, 0)
|
||||||
|
self._layout['uptime'] = (175, 0)
|
||||||
|
self._layout['line1'] = [0, 14, 240, 14]
|
||||||
|
self._layout['line2'] = [0, 108, 240, 108]
|
||||||
|
self._layout['friend_face'] = (0, 92)
|
||||||
|
self._layout['friend_name'] = (40, 94)
|
||||||
|
self._layout['shakes'] = (0, 109)
|
||||||
|
self._layout['mode'] = (215, 109)
|
||||||
|
self._layout['status'] = {
|
||||||
|
'pos': (125, 20),
|
||||||
|
'font': fonts.status_font(fonts.Medium),
|
||||||
|
'max': 20
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
return self._layout
|
||||||
|
|
||||||
|
def initialize(self):
|
||||||
|
logging.info("Initializing PirateAudio - display only")
|
||||||
|
from pwnagotchi.ui.hw.libs.pimoroni.pirateaudio.ST7789 import ST7789
|
||||||
|
self._display = ST7789(0,1,9,13)
|
||||||
|
|
||||||
|
def render(self, canvas):
|
||||||
|
self._display.display(canvas.rotate(90))
|
||||||
|
|
||||||
|
def clear(self):
|
||||||
|
self._display.clear()
|
62
pwnagotchi/ui/hw/pitft.py
Normal file
62
pwnagotchi/ui/hw/pitft.py
Normal file
@ -0,0 +1,62 @@
|
|||||||
|
# board GPIO:
|
||||||
|
# Key1 (for2,8"): GPIO17
|
||||||
|
# Key2 (for2,8"): GPIO22
|
||||||
|
# Key3 (for2,8"): GPIO23
|
||||||
|
# Key4 (for2,8"): GPIO27
|
||||||
|
# Key5 (for2,4"): GPIO27
|
||||||
|
#
|
||||||
|
# Key1 (for2,4"): GPIO16
|
||||||
|
# Key2 (for2,4"): GPIO13
|
||||||
|
# Key3 (for2,4"): GPIO12
|
||||||
|
# Key4 (for2,4"): GPIO6
|
||||||
|
# Key5 (for2,4"): GPIO5
|
||||||
|
#
|
||||||
|
# Touch chipset: STMPE610
|
||||||
|
# HW datasheet 2,8": https://learn.adafruit.com/adafruit-pitft-28-inch-resistive-touchscreen-display-raspberry-pi/overview
|
||||||
|
# HW datasheet 2,4": https://learn.adafruit.com/adafruit-2-4-pitft-hat-with-resistive-touchscreen-mini-kit/overview
|
||||||
|
|
||||||
|
import logging
|
||||||
|
|
||||||
|
import pwnagotchi.ui.fonts as fonts
|
||||||
|
from pwnagotchi.ui.hw.base import DisplayImpl
|
||||||
|
|
||||||
|
import os,time
|
||||||
|
|
||||||
|
class Pitft(DisplayImpl):
|
||||||
|
def __init__(self, config):
|
||||||
|
super(Pitft, self).__init__(config, 'pitft')
|
||||||
|
self._display = None
|
||||||
|
|
||||||
|
def layout(self):
|
||||||
|
fonts.setup(12, 10, 12, 70, 25, 9)
|
||||||
|
self._layout['width'] = 320
|
||||||
|
self._layout['height'] = 240
|
||||||
|
self._layout['face'] = (0, 36)
|
||||||
|
self._layout['name'] = (150, 36)
|
||||||
|
self._layout['channel'] = (0, 0)
|
||||||
|
self._layout['aps'] = (40, 0)
|
||||||
|
self._layout['uptime'] = (240, 0)
|
||||||
|
self._layout['line1'] = [0, 14, 320, 14]
|
||||||
|
self._layout['line2'] = [0, 220, 320, 220]
|
||||||
|
self._layout['friend_face'] = (0, 130)
|
||||||
|
self._layout['friend_name'] = (40, 135)
|
||||||
|
self._layout['shakes'] = (0, 220)
|
||||||
|
self._layout['mode'] = (280, 220)
|
||||||
|
self._layout['status'] = {
|
||||||
|
'pos': (150, 48),
|
||||||
|
'font': fonts.status_font(fonts.Medium),
|
||||||
|
'max': 20
|
||||||
|
}
|
||||||
|
|
||||||
|
return self._layout
|
||||||
|
|
||||||
|
def initialize(self):
|
||||||
|
logging.info("Initializing adafruit pitft 320x240 screen")
|
||||||
|
from pwnagotchi.ui.hw.libs.adafruit.pitft.ILI9341 import ILI9341
|
||||||
|
self._display = ILI9341(0,0,25,18)
|
||||||
|
|
||||||
|
def render(self, canvas):
|
||||||
|
self._display.display(canvas)
|
||||||
|
|
||||||
|
def clear(self):
|
||||||
|
self._display.clear()
|
54
pwnagotchi/ui/hw/tftbonnet.py
Normal file
54
pwnagotchi/ui/hw/tftbonnet.py
Normal file
@ -0,0 +1,54 @@
|
|||||||
|
# board GPIO:
|
||||||
|
# UP: GPIO17
|
||||||
|
# DOWN: GPIO22
|
||||||
|
# LEFT: GPIO27
|
||||||
|
# RIGHT: GPIO23
|
||||||
|
# CENTER: GPIO4
|
||||||
|
# A: GPIO5
|
||||||
|
# B: GPIO6
|
||||||
|
#
|
||||||
|
# HW datasheet: https://learn.adafruit.com/adafruit-1-3-color-tft-bonnet-for-raspberry-pi/overview
|
||||||
|
|
||||||
|
import logging
|
||||||
|
|
||||||
|
import pwnagotchi.ui.fonts as fonts
|
||||||
|
from pwnagotchi.ui.hw.base import DisplayImpl
|
||||||
|
|
||||||
|
|
||||||
|
class TftBonnet(DisplayImpl):
|
||||||
|
def __init__(self, config):
|
||||||
|
super(TftBonnet, self).__init__(config, 'tftbonnet')
|
||||||
|
|
||||||
|
def layout(self):
|
||||||
|
fonts.setup(10, 9, 10, 35, 25, 9)
|
||||||
|
self._layout['width'] = 240
|
||||||
|
self._layout['height'] = 240
|
||||||
|
self._layout['face'] = (0, 40)
|
||||||
|
self._layout['name'] = (5, 20)
|
||||||
|
self._layout['channel'] = (0, 0)
|
||||||
|
self._layout['aps'] = (28, 0)
|
||||||
|
self._layout['uptime'] = (175, 0)
|
||||||
|
self._layout['line1'] = [0, 14, 240, 14]
|
||||||
|
self._layout['line2'] = [0, 108, 240, 108]
|
||||||
|
self._layout['friend_face'] = (0, 92)
|
||||||
|
self._layout['friend_name'] = (40, 94)
|
||||||
|
self._layout['shakes'] = (0, 109)
|
||||||
|
self._layout['mode'] = (215, 109)
|
||||||
|
self._layout['status'] = {
|
||||||
|
'pos': (125, 20),
|
||||||
|
'font': fonts.status_font(fonts.Medium),
|
||||||
|
'max': 20
|
||||||
|
}
|
||||||
|
|
||||||
|
return self._layout
|
||||||
|
|
||||||
|
def initialize(self):
|
||||||
|
logging.info("initializing Adafruit Tft Bonnet")
|
||||||
|
from pwnagotchi.ui.hw.libs.adafruit.tftbonnet.ST7789 import ST7789
|
||||||
|
self._display = ST7789(0,0,25,26)
|
||||||
|
|
||||||
|
def render(self, canvas):
|
||||||
|
self._display.display(canvas)
|
||||||
|
|
||||||
|
def clear(self):
|
||||||
|
self._display.clear()
|
@ -34,9 +34,7 @@ class Waveshare154(DisplayImpl):
|
|||||||
logging.info("initializing waveshare v1in54 display")
|
logging.info("initializing waveshare v1in54 display")
|
||||||
from pwnagotchi.ui.hw.libs.waveshare.v1in54.epd1in54 import EPD
|
from pwnagotchi.ui.hw.libs.waveshare.v1in54.epd1in54 import EPD
|
||||||
self._display = EPD()
|
self._display = EPD()
|
||||||
self._display.init(0)
|
self._display.init(self._display.lut_partial_update)
|
||||||
self._display.Clear()
|
|
||||||
self._display.init(1)
|
|
||||||
self._display.Clear()
|
self._display.Clear()
|
||||||
|
|
||||||
def render(self, canvas):
|
def render(self, canvas):
|
||||||
|
@ -9,81 +9,38 @@ class WaveshareV1(DisplayImpl):
|
|||||||
super(WaveshareV1, self).__init__(config, 'waveshare_1')
|
super(WaveshareV1, self).__init__(config, 'waveshare_1')
|
||||||
|
|
||||||
def layout(self):
|
def layout(self):
|
||||||
if self.config['color'] == 'black':
|
fonts.setup(10, 8, 10, 35, 25, 9)
|
||||||
fonts.setup(10, 9, 10, 35, 25, 9)
|
self._layout['width'] = 250
|
||||||
self._layout['width'] = 250
|
self._layout['height'] = 122
|
||||||
self._layout['height'] = 122
|
self._layout['face'] = (0, 40)
|
||||||
self._layout['face'] = (0, 40)
|
self._layout['name'] = (5, 20)
|
||||||
self._layout['name'] = (5, 20)
|
self._layout['channel'] = (0, 0)
|
||||||
self._layout['channel'] = (0, 0)
|
self._layout['aps'] = (28, 0)
|
||||||
self._layout['aps'] = (28, 0)
|
self._layout['uptime'] = (185, 0)
|
||||||
self._layout['uptime'] = (185, 0)
|
self._layout['line1'] = [0, 14, 250, 14]
|
||||||
self._layout['line1'] = [0, 14, 250, 14]
|
self._layout['line2'] = [0, 108, 250, 108]
|
||||||
self._layout['line2'] = [0, 108, 250, 108]
|
self._layout['friend_face'] = (0, 92)
|
||||||
self._layout['friend_face'] = (0, 92)
|
self._layout['friend_name'] = (40, 94)
|
||||||
self._layout['friend_name'] = (40, 94)
|
self._layout['shakes'] = (0, 109)
|
||||||
self._layout['shakes'] = (0, 109)
|
self._layout['mode'] = (225, 109)
|
||||||
self._layout['mode'] = (225, 109)
|
self._layout['status'] = {
|
||||||
self._layout['status'] = {
|
'pos': (125, 20),
|
||||||
'pos': (125, 20),
|
'font': fonts.status_font(fonts.Medium),
|
||||||
'font': fonts.status_font(fonts.Medium),
|
'max': 20
|
||||||
'max': 20
|
}
|
||||||
}
|
|
||||||
else:
|
|
||||||
fonts.setup(10, 8, 10, 25, 25, 9)
|
|
||||||
self._layout['width'] = 212
|
|
||||||
self._layout['height'] = 104
|
|
||||||
self._layout['face'] = (0, 26)
|
|
||||||
self._layout['name'] = (5, 15)
|
|
||||||
self._layout['channel'] = (0, 0)
|
|
||||||
self._layout['aps'] = (28, 0)
|
|
||||||
self._layout['uptime'] = (147, 0)
|
|
||||||
self._layout['line1'] = [0, 12, 212, 12]
|
|
||||||
self._layout['line2'] = [0, 92, 212, 92]
|
|
||||||
self._layout['friend_face'] = (0, 76)
|
|
||||||
self._layout['friend_name'] = (40, 78)
|
|
||||||
self._layout['shakes'] = (0, 93)
|
|
||||||
self._layout['mode'] = (187, 93)
|
|
||||||
self._layout['status'] = {
|
|
||||||
'pos': (91, 15),
|
|
||||||
'font': fonts.status_font(fonts.Medium),
|
|
||||||
'max': 20
|
|
||||||
}
|
|
||||||
return self._layout
|
return self._layout
|
||||||
|
|
||||||
def initialize(self):
|
def initialize(self):
|
||||||
if self.config['color'] == 'black':
|
logging.info("initializing waveshare v2in13_V1 display in monochromatic mode")
|
||||||
logging.info("initializing waveshare v2in13_V1 display in monochromatic mode")
|
from pwnagotchi.ui.hw.libs.waveshare.v2in13_V1.epd2in13 import EPD
|
||||||
from pwnagotchi.ui.hw.libs.waveshare.v2in13_V1.epd2in13 import EPD
|
self._display = EPD()
|
||||||
self._display = EPD()
|
self._display.init(self._display.lut_full_update)
|
||||||
self._display.init(self._display.lut_full_update)
|
self._display.Clear(0xFF)
|
||||||
self._display.Clear(0xFF)
|
self._display.init(self._display.lut_partial_update)
|
||||||
self._display.init(self._display.lut_partial_update)
|
|
||||||
elif self.config['color'] == 'fastAndFurious':
|
|
||||||
logging.info("initializing waveshare v2in13_V1 3-color display in FAST MODE")
|
|
||||||
logging.info("THIS MAY BE POTENTIALLY DANGEROUS. NO WARRANTY IS PROVIDED")
|
|
||||||
logging.info("USE THIS DISPLAY IN THIS MODE AT YOUR OWN RISK")
|
|
||||||
from pwnagotchi.ui.hw.libs.waveshare.v2in13_V1.epd2in13bcFAST import EPD
|
|
||||||
self._display = EPD()
|
|
||||||
self._display.init()
|
|
||||||
self._display.Clear()
|
|
||||||
else:
|
|
||||||
logging.info("initializing waveshare v2in13_V1 display 3-color mode")
|
|
||||||
from pwnagotchi.ui.hw.libs.waveshare.v2in13_V1.epd2in13bc import EPD
|
|
||||||
self._display = EPD()
|
|
||||||
self._display.init()
|
|
||||||
self._display.Clear()
|
|
||||||
|
|
||||||
def render(self, canvas):
|
def render(self, canvas):
|
||||||
if self.config['color'] == 'black':
|
buf = self._display.getbuffer(canvas)
|
||||||
buf = self._display.getbuffer(canvas)
|
self._display.display(buf)
|
||||||
self._display.display(buf)
|
|
||||||
elif self.config['color'] == 'fastAndFurious':
|
|
||||||
buf_black = self._display.getbuffer(canvas)
|
|
||||||
self._display.DisplayPartial(buf_black)
|
|
||||||
else:
|
|
||||||
buf_black = self._display.getbuffer(canvas)
|
|
||||||
self._display.displayBlack(buf_black)
|
|
||||||
|
|
||||||
def clear(self):
|
def clear(self):
|
||||||
self._display.Clear(0xff)
|
self._display.Clear(0xff)
|
||||||
|
@ -9,47 +9,25 @@ class WaveshareV2(DisplayImpl):
|
|||||||
super(WaveshareV2, self).__init__(config, 'waveshare_2')
|
super(WaveshareV2, self).__init__(config, 'waveshare_2')
|
||||||
|
|
||||||
def layout(self):
|
def layout(self):
|
||||||
if self.config['color'] == 'black':
|
fonts.setup(10, 8, 10, 35, 25, 9)
|
||||||
fonts.setup(10, 9, 10, 35, 25, 9)
|
self._layout['width'] = 250
|
||||||
self._layout['width'] = 250
|
self._layout['height'] = 122
|
||||||
self._layout['height'] = 122
|
self._layout['face'] = (0, 40)
|
||||||
self._layout['face'] = (0, 40)
|
self._layout['name'] = (5, 20)
|
||||||
self._layout['name'] = (5, 20)
|
self._layout['channel'] = (0, 0)
|
||||||
self._layout['channel'] = (0, 0)
|
self._layout['aps'] = (28, 0)
|
||||||
self._layout['aps'] = (28, 0)
|
self._layout['uptime'] = (185, 0)
|
||||||
self._layout['uptime'] = (185, 0)
|
self._layout['line1'] = [0, 14, 250, 14]
|
||||||
self._layout['line1'] = [0, 14, 250, 14]
|
self._layout['line2'] = [0, 108, 250, 108]
|
||||||
self._layout['line2'] = [0, 108, 250, 108]
|
self._layout['friend_face'] = (0, 92)
|
||||||
self._layout['friend_face'] = (0, 92)
|
self._layout['friend_name'] = (40, 94)
|
||||||
self._layout['friend_name'] = (40, 94)
|
self._layout['shakes'] = (0, 109)
|
||||||
self._layout['shakes'] = (0, 109)
|
self._layout['mode'] = (225, 109)
|
||||||
self._layout['mode'] = (225, 109)
|
self._layout['status'] = {
|
||||||
self._layout['status'] = {
|
'pos': (125, 20),
|
||||||
'pos': (125, 20),
|
'font': fonts.status_font(fonts.Medium),
|
||||||
'font': fonts.status_font(fonts.Medium),
|
'max': 20
|
||||||
'max': 20
|
}
|
||||||
}
|
|
||||||
else:
|
|
||||||
fonts.setup(10, 8, 10, 25, 25, 9)
|
|
||||||
self._layout['width'] = 212
|
|
||||||
self._layout['height'] = 104
|
|
||||||
self._layout['face'] = (0, 26)
|
|
||||||
self._layout['name'] = (5, 15)
|
|
||||||
self._layout['channel'] = (0, 0)
|
|
||||||
self._layout['aps'] = (28, 0)
|
|
||||||
self._layout['status'] = (91, 15)
|
|
||||||
self._layout['uptime'] = (147, 0)
|
|
||||||
self._layout['line1'] = [0, 12, 212, 12]
|
|
||||||
self._layout['line2'] = [0, 92, 212, 92]
|
|
||||||
self._layout['friend_face'] = (0, 76)
|
|
||||||
self._layout['friend_name'] = (40, 78)
|
|
||||||
self._layout['shakes'] = (0, 93)
|
|
||||||
self._layout['mode'] = (187, 93)
|
|
||||||
self._layout['status'] = {
|
|
||||||
'pos': (125, 20),
|
|
||||||
'font': fonts.status_font(fonts.Medium),
|
|
||||||
'max': 14
|
|
||||||
}
|
|
||||||
return self._layout
|
return self._layout
|
||||||
|
|
||||||
def initialize(self):
|
def initialize(self):
|
||||||
|
@ -10,47 +10,25 @@ class Waveshare2in13bV3(DisplayImpl):
|
|||||||
super(Waveshare2in13bV3, self).__init__(config, 'waveshare2in13b_v3')
|
super(Waveshare2in13bV3, self).__init__(config, 'waveshare2in13b_v3')
|
||||||
|
|
||||||
def layout(self):
|
def layout(self):
|
||||||
if self.config['color'] == 'black':
|
fonts.setup(10, 9, 10, 35, 25, 9)
|
||||||
fonts.setup(10, 9, 10, 35, 25, 9)
|
self._layout['width'] = 250
|
||||||
self._layout['width'] = 250
|
self._layout['height'] = 122
|
||||||
self._layout['height'] = 122
|
self._layout['face'] = (0, 40)
|
||||||
self._layout['face'] = (0, 40)
|
self._layout['name'] = (5, 20)
|
||||||
self._layout['name'] = (5, 20)
|
self._layout['channel'] = (0, 0)
|
||||||
self._layout['channel'] = (0, 0)
|
self._layout['aps'] = (28, 0)
|
||||||
self._layout['aps'] = (28, 0)
|
self._layout['uptime'] = (185, 0)
|
||||||
self._layout['uptime'] = (185, 0)
|
self._layout['line1'] = [0, 14, 250, 14]
|
||||||
self._layout['line1'] = [0, 14, 250, 14]
|
self._layout['line2'] = [0, 108, 250, 108]
|
||||||
self._layout['line2'] = [0, 108, 250, 108]
|
self._layout['friend_face'] = (0, 92)
|
||||||
self._layout['friend_face'] = (0, 92)
|
self._layout['friend_name'] = (40, 94)
|
||||||
self._layout['friend_name'] = (40, 94)
|
self._layout['shakes'] = (0, 109)
|
||||||
self._layout['shakes'] = (0, 109)
|
self._layout['mode'] = (225, 109)
|
||||||
self._layout['mode'] = (225, 109)
|
self._layout['status'] = {
|
||||||
self._layout['status'] = {
|
'pos': (125, 20),
|
||||||
'pos': (125, 20),
|
'font': fonts.status_font(fonts.Medium),
|
||||||
'font': fonts.status_font(fonts.Medium),
|
'max': 20
|
||||||
'max': 20
|
}
|
||||||
}
|
|
||||||
else:
|
|
||||||
fonts.setup(10, 8, 10, 25, 25, 9)
|
|
||||||
self._layout['width'] = 212
|
|
||||||
self._layout['height'] = 104
|
|
||||||
self._layout['face'] = (0, 26)
|
|
||||||
self._layout['name'] = (5, 15)
|
|
||||||
self._layout['channel'] = (0, 0)
|
|
||||||
self._layout['aps'] = (28, 0)
|
|
||||||
self._layout['status'] = (91, 15)
|
|
||||||
self._layout['uptime'] = (147, 0)
|
|
||||||
self._layout['line1'] = [0, 12, 212, 12]
|
|
||||||
self._layout['line2'] = [0, 92, 212, 92]
|
|
||||||
self._layout['friend_face'] = (0, 76)
|
|
||||||
self._layout['friend_name'] = (40, 78)
|
|
||||||
self._layout['shakes'] = (0, 93)
|
|
||||||
self._layout['mode'] = (187, 93)
|
|
||||||
self._layout['status'] = {
|
|
||||||
'pos': (125, 20),
|
|
||||||
'font': fonts.status_font(fonts.Medium),
|
|
||||||
'max': 14
|
|
||||||
}
|
|
||||||
return self._layout
|
return self._layout
|
||||||
|
|
||||||
def initialize(self):
|
def initialize(self):
|
||||||
|
@ -10,47 +10,25 @@ class Waveshare213bV4(DisplayImpl):
|
|||||||
super(Waveshare213bV4, self).__init__(config, 'waveshare2in13b_v4')
|
super(Waveshare213bV4, self).__init__(config, 'waveshare2in13b_v4')
|
||||||
|
|
||||||
def layout(self):
|
def layout(self):
|
||||||
if self.config['color'] == 'black':
|
fonts.setup(10, 9, 10, 35, 25, 9)
|
||||||
fonts.setup(10, 9, 10, 35, 25, 9)
|
self._layout['width'] = 250
|
||||||
self._layout['width'] = 250
|
self._layout['height'] = 122
|
||||||
self._layout['height'] = 122
|
self._layout['face'] = (0, 40)
|
||||||
self._layout['face'] = (0, 40)
|
self._layout['name'] = (5, 20)
|
||||||
self._layout['name'] = (5, 20)
|
self._layout['channel'] = (0, 0)
|
||||||
self._layout['channel'] = (0, 0)
|
self._layout['aps'] = (28, 0)
|
||||||
self._layout['aps'] = (28, 0)
|
self._layout['uptime'] = (185, 0)
|
||||||
self._layout['uptime'] = (185, 0)
|
self._layout['line1'] = [0, 14, 250, 14]
|
||||||
self._layout['line1'] = [0, 14, 250, 14]
|
self._layout['line2'] = [0, 108, 250, 108]
|
||||||
self._layout['line2'] = [0, 108, 250, 108]
|
self._layout['friend_face'] = (0, 92)
|
||||||
self._layout['friend_face'] = (0, 92)
|
self._layout['friend_name'] = (40, 94)
|
||||||
self._layout['friend_name'] = (40, 94)
|
self._layout['shakes'] = (0, 109)
|
||||||
self._layout['shakes'] = (0, 109)
|
self._layout['mode'] = (225, 109)
|
||||||
self._layout['mode'] = (225, 109)
|
self._layout['status'] = {
|
||||||
self._layout['status'] = {
|
'pos': (125, 20),
|
||||||
'pos': (125, 20),
|
'font': fonts.status_font(fonts.Medium),
|
||||||
'font': fonts.status_font(fonts.Medium),
|
'max': 20
|
||||||
'max': 20
|
}
|
||||||
}
|
|
||||||
else:
|
|
||||||
fonts.setup(10, 8, 10, 25, 25, 9)
|
|
||||||
self._layout['width'] = 212
|
|
||||||
self._layout['height'] = 104
|
|
||||||
self._layout['face'] = (0, 26)
|
|
||||||
self._layout['name'] = (5, 15)
|
|
||||||
self._layout['channel'] = (0, 0)
|
|
||||||
self._layout['aps'] = (28, 0)
|
|
||||||
self._layout['status'] = (91, 15)
|
|
||||||
self._layout['uptime'] = (147, 0)
|
|
||||||
self._layout['line1'] = [0, 12, 212, 12]
|
|
||||||
self._layout['line2'] = [0, 92, 212, 92]
|
|
||||||
self._layout['friend_face'] = (0, 76)
|
|
||||||
self._layout['friend_name'] = (40, 78)
|
|
||||||
self._layout['shakes'] = (0, 93)
|
|
||||||
self._layout['mode'] = (187, 93)
|
|
||||||
self._layout['status'] = {
|
|
||||||
'pos': (125, 20),
|
|
||||||
'font': fonts.status_font(fonts.Medium),
|
|
||||||
'max': 14
|
|
||||||
}
|
|
||||||
return self._layout
|
return self._layout
|
||||||
|
|
||||||
def initialize(self):
|
def initialize(self):
|
||||||
|
@ -35,8 +35,6 @@ class Waveshare27inchV2(DisplayImpl):
|
|||||||
from pwnagotchi.ui.hw.libs.waveshare.v2in7_v2.epd2in7_V2 import EPD
|
from pwnagotchi.ui.hw.libs.waveshare.v2in7_v2.epd2in7_V2 import EPD
|
||||||
self._display = EPD()
|
self._display = EPD()
|
||||||
self._display.init()
|
self._display.init()
|
||||||
# this must have changed by waveshare
|
|
||||||
# remove the 0xFF(Clear(0xFF)) other wise it errors. can't pass oxff and self
|
|
||||||
self._display.Clear()
|
self._display.Clear()
|
||||||
|
|
||||||
def render(self, canvas):
|
def render(self, canvas):
|
||||||
|
@ -9,22 +9,22 @@ class Waveshare3in52(DisplayImpl):
|
|||||||
super(Waveshare3in52, self).__init__(config, 'waveshare3in52')
|
super(Waveshare3in52, self).__init__(config, 'waveshare3in52')
|
||||||
|
|
||||||
def layout(self):
|
def layout(self):
|
||||||
fonts.setup(16, 14, 16, 100, 31, 15)
|
fonts.setup(16, 14, 16, 50, 31, 15)
|
||||||
self._layout['width'] = 360
|
self._layout['width'] = 360
|
||||||
self._layout['height'] = 240
|
self._layout['height'] = 240
|
||||||
self._layout['face'] = (0, 40)
|
self._layout['face'] = (0, 85)
|
||||||
self._layout['name'] = (0, 0)
|
self._layout['name'] = (10, 30)
|
||||||
self._layout['channel'] = (300, 0)
|
self._layout['channel'] = (1, 4)
|
||||||
self._layout['aps'] = (0, 220)
|
self._layout['aps'] = (45, 4)
|
||||||
self._layout['uptime'] = (120, 0)
|
self._layout['uptime'] = (250, 4)
|
||||||
self._layout['line1'] = [0, 24, 360, 24]
|
self._layout['line1'] = [0, 24, 360, 24]
|
||||||
self._layout['line2'] = [0, 220, 360, 220]
|
self._layout['line2'] = [0, 220, 360, 220]
|
||||||
self._layout['friend_face'] = (0, 195)
|
self._layout['friend_face'] = (0, 180)
|
||||||
self._layout['friend_name'] = (0, 185)
|
self._layout['friend_name'] = (0, 170)
|
||||||
self._layout['shakes'] = (100, 220)
|
self._layout['shakes'] = (1, 223)
|
||||||
self._layout['mode'] = (0,200)
|
self._layout['mode'] = (320, 222)
|
||||||
self._layout['status'] = {
|
self._layout['status'] = {
|
||||||
'pos': (3, 170),
|
'pos': (185, 50),
|
||||||
'font': fonts.status_font(fonts.Small),
|
'font': fonts.status_font(fonts.Small),
|
||||||
'max': 100
|
'max': 100
|
||||||
}
|
}
|
||||||
|
45
pwnagotchi/ui/hw/waveshare5in79.py
Normal file
45
pwnagotchi/ui/hw/waveshare5in79.py
Normal file
@ -0,0 +1,45 @@
|
|||||||
|
import logging
|
||||||
|
|
||||||
|
import pwnagotchi.ui.fonts as fonts
|
||||||
|
from pwnagotchi.ui.hw.base import DisplayImpl
|
||||||
|
|
||||||
|
|
||||||
|
class Waveshare5in79(DisplayImpl):
|
||||||
|
def __init__(self, config):
|
||||||
|
super(Waveshare5in79, self).__init__(config, 'waveshare5in79')
|
||||||
|
|
||||||
|
def layout(self):
|
||||||
|
fonts.setup(10, 8, 10, 18, 25, 9)
|
||||||
|
self._layout['width'] = 792
|
||||||
|
self._layout['height'] = 272
|
||||||
|
self._layout['face'] = (0, 43)
|
||||||
|
self._layout['name'] = (0, 14)
|
||||||
|
self._layout['channel'] = (0, 0)
|
||||||
|
self._layout['aps'] = (0, 71)
|
||||||
|
self._layout['uptime'] = (0, 25)
|
||||||
|
self._layout['line1'] = [0, 12, 600, 12]
|
||||||
|
self._layout['line2'] = [0, 116, 600, 116]
|
||||||
|
self._layout['friend_face'] = (12, 88)
|
||||||
|
self._layout['friend_name'] = (1, 103)
|
||||||
|
self._layout['shakes'] = (26, 117)
|
||||||
|
self._layout['mode'] = (0, 117)
|
||||||
|
self._layout['status'] = {
|
||||||
|
'pos': (65, 26),
|
||||||
|
'font': fonts.status_font(fonts.Small),
|
||||||
|
'max': 12
|
||||||
|
}
|
||||||
|
return self._layout
|
||||||
|
|
||||||
|
def initialize(self):
|
||||||
|
logging.info("initializing waveshare 5.79 inch lcd display")
|
||||||
|
from pwnagotchi.ui.hw.libs.waveshare.v5in79.epd5in79 import EPD
|
||||||
|
self._display = EPD()
|
||||||
|
self._display.init()
|
||||||
|
self._display.Clear()
|
||||||
|
|
||||||
|
def render(self, canvas):
|
||||||
|
buf = self._display.getbuffer(canvas)
|
||||||
|
self._display.display_Partial(buf)
|
||||||
|
|
||||||
|
def clear(self):
|
||||||
|
self._display.Clear()
|
45
pwnagotchi/ui/hw/waveshare5in79b.py
Normal file
45
pwnagotchi/ui/hw/waveshare5in79b.py
Normal file
@ -0,0 +1,45 @@
|
|||||||
|
import logging
|
||||||
|
|
||||||
|
import pwnagotchi.ui.fonts as fonts
|
||||||
|
from pwnagotchi.ui.hw.base import DisplayImpl
|
||||||
|
|
||||||
|
|
||||||
|
class Waveshare5in79b(DisplayImpl):
|
||||||
|
def __init__(self, config):
|
||||||
|
super(Waveshare5in79b, self).__init__(config, 'waveshare5in79b')
|
||||||
|
|
||||||
|
def layout(self):
|
||||||
|
fonts.setup(10, 8, 10, 18, 25, 9)
|
||||||
|
self._layout['width'] = 792
|
||||||
|
self._layout['height'] = 272
|
||||||
|
self._layout['face'] = (0, 43)
|
||||||
|
self._layout['name'] = (0, 14)
|
||||||
|
self._layout['channel'] = (0, 0)
|
||||||
|
self._layout['aps'] = (0, 71)
|
||||||
|
self._layout['uptime'] = (0, 25)
|
||||||
|
self._layout['line1'] = [0, 12, 600, 12]
|
||||||
|
self._layout['line2'] = [0, 116, 600, 116]
|
||||||
|
self._layout['friend_face'] = (12, 88)
|
||||||
|
self._layout['friend_name'] = (1, 103)
|
||||||
|
self._layout['shakes'] = (26, 117)
|
||||||
|
self._layout['mode'] = (0, 117)
|
||||||
|
self._layout['status'] = {
|
||||||
|
'pos': (65, 26),
|
||||||
|
'font': fonts.status_font(fonts.Small),
|
||||||
|
'max': 12
|
||||||
|
}
|
||||||
|
return self._layout
|
||||||
|
|
||||||
|
def initialize(self):
|
||||||
|
logging.info("initializing waveshare 5.79b inch lcd display")
|
||||||
|
from pwnagotchi.ui.hw.libs.waveshare.v5in79b.epd5in79b import EPD
|
||||||
|
self._display = EPD()
|
||||||
|
self._display.init()
|
||||||
|
self._display.Clear()
|
||||||
|
|
||||||
|
def render(self, canvas):
|
||||||
|
buf = self._display.getbuffer(canvas)
|
||||||
|
self._display.display(buf, None)
|
||||||
|
|
||||||
|
def clear(self):
|
||||||
|
self._display.Clear()
|
59
pwnagotchi/ui/hw/waveshareoledlcd.py
Normal file
59
pwnagotchi/ui/hw/waveshareoledlcd.py
Normal file
@ -0,0 +1,59 @@
|
|||||||
|
# workinprogress based on the displayhatmini driver
|
||||||
|
# LCD support OK
|
||||||
|
# OLED support ongoing
|
||||||
|
# board GPIO:
|
||||||
|
# Key1: GPIO4 / pin7
|
||||||
|
# Key2: GPIO17 / pin11
|
||||||
|
# Key3: GPIO23 / pin16
|
||||||
|
# Key4: GPIO24 / pin18
|
||||||
|
# OLED SDA: GPIO2 / pin3
|
||||||
|
# OLED SCL: GPIO3 / pin5
|
||||||
|
# OLED info:
|
||||||
|
# driver: SSD1315 (I2C)
|
||||||
|
# resolution: 128x64
|
||||||
|
# I2C address: 0x3C 0x3D
|
||||||
|
# HW datasheet: https://www.waveshare.com/wiki/OLED/LCD_HAT_(A)
|
||||||
|
|
||||||
|
import logging
|
||||||
|
|
||||||
|
import pwnagotchi.ui.fonts as fonts
|
||||||
|
from pwnagotchi.ui.hw.base import DisplayImpl
|
||||||
|
|
||||||
|
|
||||||
|
class Waveshareoledlcd(DisplayImpl):
|
||||||
|
def __init__(self, config):
|
||||||
|
super(Waveshareoledlcd, self).__init__(config, 'waveshareoledlcd')
|
||||||
|
|
||||||
|
def layout(self):
|
||||||
|
fonts.setup(12, 10, 12, 70, 25, 9)
|
||||||
|
self._layout['width'] = 320
|
||||||
|
self._layout['height'] = 240
|
||||||
|
self._layout['face'] = (0, 36)
|
||||||
|
self._layout['name'] = (150, 36)
|
||||||
|
self._layout['channel'] = (0, 0)
|
||||||
|
self._layout['aps'] = (40, 0)
|
||||||
|
self._layout['uptime'] = (240, 0)
|
||||||
|
self._layout['line1'] = [0, 14, 320, 14]
|
||||||
|
self._layout['line2'] = [0, 220, 320, 220]
|
||||||
|
self._layout['friend_face'] = (0, 130)
|
||||||
|
self._layout['friend_name'] = (40, 135)
|
||||||
|
self._layout['shakes'] = (0, 220)
|
||||||
|
self._layout['mode'] = (280, 220)
|
||||||
|
self._layout['status'] = {
|
||||||
|
'pos': (150, 48),
|
||||||
|
'font': fonts.status_font(fonts.Medium),
|
||||||
|
'max': 20
|
||||||
|
}
|
||||||
|
|
||||||
|
return self._layout
|
||||||
|
|
||||||
|
def initialize(self):
|
||||||
|
logging.info("initializing Waveshare OLED/LCD hat")
|
||||||
|
from pwnagotchi.ui.hw.libs.waveshare.oledlcd.ST7789 import ST7789
|
||||||
|
self._display = ST7789(0,0,22,18)
|
||||||
|
|
||||||
|
def render(self, canvas):
|
||||||
|
self._display.display(canvas)
|
||||||
|
|
||||||
|
def clear(self):
|
||||||
|
self._display.clear()
|
47
pwnagotchi/ui/hw/weact_2in9.py
Normal file
47
pwnagotchi/ui/hw/weact_2in9.py
Normal file
@ -0,0 +1,47 @@
|
|||||||
|
import logging
|
||||||
|
|
||||||
|
import pwnagotchi.ui.fonts as fonts
|
||||||
|
from pwnagotchi.ui.hw.base import DisplayImpl
|
||||||
|
|
||||||
|
|
||||||
|
class WeAct2in9(DisplayImpl):
|
||||||
|
def __init__(self, config):
|
||||||
|
super(WeAct2in9, self).__init__(config, 'weact2in9')
|
||||||
|
|
||||||
|
def layout(self):
|
||||||
|
fonts.setup(10, 8, 10, 35, 25, 9)
|
||||||
|
self._layout['width'] = 296
|
||||||
|
self._layout['height'] = 128
|
||||||
|
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 WeAct 2.9 inch display")
|
||||||
|
from pwnagotchi.ui.hw.libs.weact.v2in9.epd2in9 import EPD
|
||||||
|
self._display = EPD()
|
||||||
|
self._display.init()
|
||||||
|
self._display.Clear(0xFF)
|
||||||
|
|
||||||
|
def render(self, canvas):
|
||||||
|
buf = self._display.getbuffer(canvas)
|
||||||
|
self._display.displayPartial(buf)
|
||||||
|
|
||||||
|
def clear(self):
|
||||||
|
#pass
|
||||||
|
self._display.Clear(0xFF)
|
@ -23,7 +23,21 @@ ROOT = None
|
|||||||
|
|
||||||
class View(object):
|
class View(object):
|
||||||
def __init__(self, config, impl, state=None):
|
def __init__(self, config, impl, state=None):
|
||||||
global ROOT
|
global ROOT, BLACK, WHITE
|
||||||
|
|
||||||
|
self.invert = 0
|
||||||
|
self._black = 0xFF
|
||||||
|
self._white = 0x00
|
||||||
|
if 'invert' in config['ui'] and config['ui']['invert'] == True:
|
||||||
|
logging.debug("INVERT BLACK/WHITES:" + str(config['ui']['invert']))
|
||||||
|
self.invert = 1
|
||||||
|
BLACK = 0x00
|
||||||
|
WHITE - 0xFF
|
||||||
|
self._black = 0x00
|
||||||
|
self._white = 0xFF
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
# setup faces from the configuration in case the user customized them
|
# setup faces from the configuration in case the user customized them
|
||||||
faces.load_from_config(config['ui']['faces'])
|
faces.load_from_config(config['ui']['faces'])
|
||||||
@ -57,7 +71,7 @@ class View(object):
|
|||||||
'face': Text(value=faces.SLEEP, position=(config['ui']['faces']['position_x'], config['ui']['faces']['position_y']), color=BLACK, font=fonts.Huge, png=config['ui']['faces']['png']),
|
'face': Text(value=faces.SLEEP, position=(config['ui']['faces']['position_x'], config['ui']['faces']['position_y']), color=BLACK, font=fonts.Huge, png=config['ui']['faces']['png']),
|
||||||
|
|
||||||
# 'friend_face': Text(value=None, position=self._layout['friend_face'], font=fonts.Bold, color=BLACK),
|
# 'friend_face': Text(value=None, position=self._layout['friend_face'], font=fonts.Bold, color=BLACK),
|
||||||
'friend_name': Text(value=None, position=self._layout['friend_name'], font=fonts.BoldSmall, color=BLACK),
|
'friend_name': Text(value=None, position=self._layout['friend_face'], font=fonts.BoldSmall, color=BLACK),
|
||||||
|
|
||||||
'name': Text(value='%s>' % 'pwnagotchi', position=self._layout['name'], color=BLACK, font=fonts.Bold),
|
'name': Text(value='%s>' % 'pwnagotchi', position=self._layout['name'], color=BLACK, font=fonts.Bold),
|
||||||
|
|
||||||
@ -98,6 +112,11 @@ class View(object):
|
|||||||
self._state.has_element(key)
|
self._state.has_element(key)
|
||||||
|
|
||||||
def add_element(self, key, elem):
|
def add_element(self, key, elem):
|
||||||
|
if self.invert is 1 and elem.color:
|
||||||
|
if elem.color == 0xff:
|
||||||
|
elem.color = 0x00
|
||||||
|
elif elem.color == 0x00:
|
||||||
|
elem.color = 0xff
|
||||||
self._state.add_element(key, elem)
|
self._state.add_element(key, elem)
|
||||||
|
|
||||||
def remove_element(self, key):
|
def remove_element(self, key):
|
||||||
@ -371,7 +390,7 @@ class View(object):
|
|||||||
state = self._state
|
state = self._state
|
||||||
changes = state.changes(ignore=self._ignore_changes)
|
changes = state.changes(ignore=self._ignore_changes)
|
||||||
if force or len(changes):
|
if force or len(changes):
|
||||||
self._canvas = Image.new('1', (self._width, self._height), WHITE)
|
self._canvas = Image.new('1', (self._width, self._height), self._white)
|
||||||
drawer = ImageDraw.Draw(self._canvas)
|
drawer = ImageDraw.Draw(self._canvas)
|
||||||
|
|
||||||
plugins.on('ui_update', self)
|
plugins.on('ui_update', self)
|
||||||
|
@ -47,6 +47,6 @@ class Server:
|
|||||||
formatServerIpAddress = '[::]' if self._address == '::' else self._address
|
formatServerIpAddress = '[::]' if self._address == '::' else self._address
|
||||||
logging.info("web ui available at http://%s:%d/" % (formatServerIpAddress, self._port))
|
logging.info("web ui available at http://%s:%d/" % (formatServerIpAddress, self._port))
|
||||||
|
|
||||||
app.run(host=self._address, port=self._port, debug=False)
|
app.run(host=self._address, port=self._port)
|
||||||
else:
|
else:
|
||||||
logging.info("could not get ip of usb0, video server not starting")
|
logging.info("could not get ip of usb0, video server not starting")
|
||||||
|
@ -238,14 +238,72 @@ def load_config(args):
|
|||||||
config = merge_config(additional_config, config)
|
config = merge_config(additional_config, config)
|
||||||
|
|
||||||
# the very first step is to normalize the display name, so we don't need dozens of if/elif around
|
# the very first step is to normalize the display name, so we don't need dozens of if/elif around
|
||||||
|
# NON E-INK DISPLAYS---------------------------------------------------------------
|
||||||
if config['ui']['display']['type'] in ('inky', 'inkyphat'):
|
if config['ui']['display']['type'] in ('inky', 'inkyphat'):
|
||||||
config['ui']['display']['type'] = 'inky'
|
config['ui']['display']['type'] = 'inky'
|
||||||
|
|
||||||
elif config['ui']['display']['type'] in ('papirus', 'papi'):
|
elif config['ui']['display']['type'] in ('papirus', 'papi'):
|
||||||
config['ui']['display']['type'] = 'papirus'
|
config['ui']['display']['type'] = 'papirus'
|
||||||
|
|
||||||
elif config['ui']['display']['type'] in 'oledhat':
|
elif config['ui']['display']['type'] in 'oledhat':
|
||||||
config['ui']['display']['type'] = 'oledhat'
|
config['ui']['display']['type'] = 'oledhat'
|
||||||
|
|
||||||
|
elif config['ui']['display']['type'] in 'lcdhat':
|
||||||
|
config['ui']['display']['type'] = 'lcdhat'
|
||||||
|
|
||||||
|
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 ('waveshare144lcd', 'ws_144', 'ws144', 'waveshare_144', 'waveshare144'):
|
||||||
|
config['ui']['display']['type'] = 'waveshare144lcd'
|
||||||
|
|
||||||
|
elif config['ui']['display']['type'] in ('spotpear24inch'):
|
||||||
|
config['ui']['display']['type'] = 'spotpear24inch'
|
||||||
|
|
||||||
|
elif config['ui']['display']['type'] in ('displayhatmini'):
|
||||||
|
config['ui']['display']['type'] = 'displayhatmini'
|
||||||
|
|
||||||
|
elif config['ui']['display']['type'] in ('pirateaudio'):
|
||||||
|
config['ui']['display']['type'] = 'pirateaudio'
|
||||||
|
|
||||||
|
elif config['ui']['display']['type'] in ('pitft'):
|
||||||
|
config['ui']['display']['type'] = 'pitft'
|
||||||
|
|
||||||
|
elif config['ui']['display']['type'] in ('tftbonnet'):
|
||||||
|
config['ui']['display']['type'] = 'tftbonnet'
|
||||||
|
|
||||||
|
elif config['ui']['display']['type'] in ('waveshareoledlcd'):
|
||||||
|
config['ui']['display']['type'] = 'waveshareoledlcd'
|
||||||
|
|
||||||
|
elif config['ui']['display']['type'] in ('waveshare35lcd'):
|
||||||
|
config['ui']['display']['type'] = 'waveshare35lcd'
|
||||||
|
|
||||||
|
# E-INK DISPLAYS ------------------------------------------------------------------------
|
||||||
|
|
||||||
|
elif config['ui']['display']['type'] in ('waveshare1in02', 'ws1in02', 'ws102', 'waveshare_102', 'waveshare_1in02'):
|
||||||
|
config['ui']['display']['type'] = 'waveshare1in02'
|
||||||
|
|
||||||
|
elif config['ui']['display']['type'] in ('ws_154inch', 'waveshare1in54', 'ws154inch', 'waveshare_154', 'waveshare154'):
|
||||||
|
config['ui']['display']['type'] = 'waveshare1in54'
|
||||||
|
|
||||||
|
elif config['ui']['display']['type'] in ('ws_154inchb', 'waveshare1in54b', 'ws154inchb', 'waveshare_154b', 'waveshare154b'):
|
||||||
|
config['ui']['display']['type'] = 'waveshare1in54b'
|
||||||
|
|
||||||
|
elif config['ui']['display']['type'] in ('waveshare1in54c', 'ws1in54c', 'ws154c', 'waveshare_154c', 'waveshare_1in54c'):
|
||||||
|
config['ui']['display']['type'] = 'waveshare1in54c'
|
||||||
|
|
||||||
|
elif config['ui']['display']['type'] in ('ws_154inchbv2', 'waveshare1in54bv2', 'waveshare1in54b_v2', 'ws154inchbv2', 'waveshare_154bv2', 'waveshare154bv2'):
|
||||||
|
config['ui']['display']['type'] = 'waveshare1in54b_v2'
|
||||||
|
|
||||||
|
elif config['ui']['display']['type'] in ('ws_154inchv2', 'waveshare1in54v2', 'ws154inchv2', 'waveshare_154inchv2', 'waveshare154v2', "waveshare1in54_v2"):
|
||||||
|
config['ui']['display']['type'] = 'waveshare1in54_v2'
|
||||||
|
|
||||||
|
elif config['ui']['display']['type'] in ('waveshare1in64g', 'ws1in64g', 'ws164g', 'waveshare_164g', 'waveshare_1in64g'):
|
||||||
|
config['ui']['display']['type'] = 'waveshare1in64g'
|
||||||
|
|
||||||
elif config['ui']['display']['type'] in ('ws_1', 'ws1', 'waveshare_1', 'waveshare1'):
|
elif config['ui']['display']['type'] in ('ws_1', 'ws1', 'waveshare_1', 'waveshare1'):
|
||||||
config['ui']['display']['type'] = 'waveshare_1'
|
config['ui']['display']['type'] = 'waveshare_1'
|
||||||
|
|
||||||
@ -258,50 +316,14 @@ def load_config(args):
|
|||||||
elif config['ui']['display']['type'] in ('ws_4', 'ws4', 'waveshare_4', 'waveshare4'):
|
elif config['ui']['display']['type'] in ('ws_4', 'ws4', 'waveshare_4', 'waveshare4'):
|
||||||
config['ui']['display']['type'] = 'waveshare_4'
|
config['ui']['display']['type'] = 'waveshare_4'
|
||||||
|
|
||||||
elif config['ui']['display']['type'] in ('ws_27inch', 'ws27inch', 'waveshare2in7', 'waveshare_27inch', 'waveshare27inch'):
|
elif config['ui']['display']['type'] in ('waveshare2in13b_v3', 'waveshare2in13b_v3', 'ws213bv3', 'waveshare_213bv3', 'waveshare213inb_v3'):
|
||||||
config['ui']['display']['type'] = 'waveshare2in7'
|
config['ui']['display']['type'] = 'waveshare2in13b_v3'
|
||||||
|
|
||||||
elif config['ui']['display']['type'] in ('ws_27inchv2', 'waveshare2in7_v2', 'ws27inchv2', 'waveshare_27inchv2', 'waveshare27inchv2'):
|
elif config['ui']['display']['type'] in ('ws_213bv4', 'waveshare2in13b_v4', 'ws213bv4', 'waveshare_213bv4', 'waveshare213inb_v4'):
|
||||||
config['ui']['display']['type'] = 'waveshare2in7_v2'
|
config['ui']['display']['type'] = 'waveshare2in13b_v4'
|
||||||
|
|
||||||
elif config['ui']['display']['type'] in ('ws_27inchbv2', 'waveshare2in7b_v2', 'ws27inchbv2', 'waveshare_27inchbv2', 'waveshare27inchbv2'):
|
elif config['ui']['display']['type'] in ('ws_213bc', 'ws213bc', 'waveshare2in13bc', 'waveshare_213bc', 'waveshare213bc'):
|
||||||
config['ui']['display']['type'] = 'waveshare2in7b_v2'
|
config['ui']['display']['type'] = 'waveshare2in13bc'
|
||||||
|
|
||||||
elif config['ui']['display']['type'] in ('ws_29inch', 'waveshare2in9', 'ws29inch', 'waveshare_29inch', 'waveshare29inch'):
|
|
||||||
config['ui']['display']['type'] = 'waveshare2in9'
|
|
||||||
|
|
||||||
elif config['ui']['display']['type'] in ('ws_29inchv2', 'waveshare2in9_v2', 'ws29inchv2', 'waveshare_29inchv2', 'waveshare29inchv2'):
|
|
||||||
config['ui']['display']['type'] = 'waveshare2in9_v2'
|
|
||||||
|
|
||||||
elif config['ui']['display']['type'] in ('ws_29inchbv3', 'waveshare2in9b_v3', 'ws29inchbv3', 'waveshare_29inchbv3', 'waveshare29inchbv3'):
|
|
||||||
config['ui']['display']['type'] = 'waveshare2in9b_v3'
|
|
||||||
|
|
||||||
elif config['ui']['display']['type'] in ('ws_29inchbv4', 'waveshare2in9b_v4', 'ws29inchbv4', 'waveshare_29inchbv4', 'waveshare29inchbv4'):
|
|
||||||
config['ui']['display']['type'] = 'waveshare2in9b_v4'
|
|
||||||
|
|
||||||
elif config['ui']['display']['type'] in 'lcdhat':
|
|
||||||
config['ui']['display']['type'] = 'lcdhat'
|
|
||||||
|
|
||||||
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', 'waveshare1in54', 'ws154inch', 'waveshare_154inch', 'waveshare154inch'):
|
|
||||||
config['ui']['display']['type'] = 'waveshare1in54'
|
|
||||||
|
|
||||||
elif config['ui']['display']['type'] in ('ws_154inchb', 'waveshare1in54b', 'ws154inchb', 'waveshare_154inchb', 'waveshare154inchb'):
|
|
||||||
config['ui']['display']['type'] = 'waveshare1in54b'
|
|
||||||
|
|
||||||
elif config['ui']['display']['type'] in ('ws_154inchbv2', 'waveshare1in54bv2', 'ws154inchbv2', 'waveshare_154inchbv2', 'waveshare154inchbv2'):
|
|
||||||
config['ui']['display']['type'] = 'waveshare1in54b_v2'
|
|
||||||
|
|
||||||
elif config['ui']['display']['type'] in ('ws_154inchv2', 'waveshare1in54v2', 'ws154inchv2', 'waveshare_154inchv2', 'waveshare154inchv2', "waveshare1in54_v2"):
|
|
||||||
config['ui']['display']['type'] = 'waveshare1in54_v2'
|
|
||||||
|
|
||||||
elif config['ui']['display']['type'] in ('waveshare144lcd', 'ws_144inch', 'ws144inch', 'waveshare_144inch', 'waveshare144inch'):
|
|
||||||
config['ui']['display']['type'] = 'waveshare144lcd'
|
|
||||||
|
|
||||||
elif config['ui']['display']['type'] in ('ws_213d', 'ws213d', 'waveshare2in13d', 'waveshare_213d', 'waveshare213d'):
|
elif config['ui']['display']['type'] in ('ws_213d', 'ws213d', 'waveshare2in13d', 'waveshare_213d', 'waveshare213d'):
|
||||||
config['ui']['display']['type'] = 'waveshare2in13d'
|
config['ui']['display']['type'] = 'waveshare2in13d'
|
||||||
@ -309,123 +331,127 @@ def load_config(args):
|
|||||||
elif config['ui']['display']['type'] in ('ws_213g', 'waveshare2in13g', 'waveshare213g', 'ws213g', 'waveshare_213g'):
|
elif config['ui']['display']['type'] in ('ws_213g', 'waveshare2in13g', 'waveshare213g', 'ws213g', 'waveshare_213g'):
|
||||||
config['ui']['display']['type'] = 'waveshare2in13g'
|
config['ui']['display']['type'] = 'waveshare2in13g'
|
||||||
|
|
||||||
elif config['ui']['display']['type'] in ('ws_213bc', 'ws213bc', 'waveshare2in13bc', 'waveshare_213bc', 'waveshare213bc'):
|
elif config['ui']['display']['type'] in ('ws_2in36g', 'waveshare2in36g', 'waveshare236g', 'ws236g', 'waveshare_236g'):
|
||||||
config['ui']['display']['type'] = 'waveshare2in13bc'
|
|
||||||
|
|
||||||
elif config['ui']['display']['type'] in ('ws_213bv4', 'waveshare2in13b_v4', 'ws213bv4', 'waveshare_213bv4', 'waveshare213inb_v4'):
|
|
||||||
config['ui']['display']['type'] = 'waveshare2in13b_v4'
|
|
||||||
|
|
||||||
elif config['ui']['display']['type'] in 'spotpear24inch':
|
|
||||||
config['ui']['display']['type'] = 'spotpear24inch'
|
|
||||||
|
|
||||||
elif config['ui']['display']['type'] in 'displayhatmini':
|
|
||||||
config['ui']['display']['type'] = 'displayhatmini'
|
|
||||||
|
|
||||||
elif config['ui']['display']['type'] in 'waveshare35lcd':
|
|
||||||
config['ui']['display']['type'] = 'waveshare35lcd'
|
|
||||||
|
|
||||||
elif config['ui']['display']['type'] in 'waveshare1in54c':
|
|
||||||
config['ui']['display']['type'] = 'waveshare1in54c'
|
|
||||||
|
|
||||||
elif config['ui']['display']['type'] in 'waveshare1in64g':
|
|
||||||
config['ui']['display']['type'] = 'waveshare1in64g'
|
|
||||||
|
|
||||||
elif config['ui']['display']['type'] in 'waveshare1in02':
|
|
||||||
config['ui']['display']['type'] = 'waveshare1in02'
|
|
||||||
|
|
||||||
elif config['ui']['display']['type'] in 'waveshare2in9bc':
|
|
||||||
config['ui']['display']['type'] = 'waveshare2in9bc'
|
|
||||||
|
|
||||||
elif config['ui']['display']['type'] in 'waveshare2in9d':
|
|
||||||
config['ui']['display']['type'] = 'waveshare2in9d'
|
|
||||||
|
|
||||||
elif config['ui']['display']['type'] in 'waveshare2in13b_v3':
|
|
||||||
config['ui']['display']['type'] = 'waveshare2in13b_v3'
|
|
||||||
|
|
||||||
elif config['ui']['display']['type'] in 'waveshare2in36g':
|
|
||||||
config['ui']['display']['type'] = 'waveshare2in36g'
|
config['ui']['display']['type'] = 'waveshare2in36g'
|
||||||
|
|
||||||
elif config['ui']['display']['type'] in 'waveshare2in66':
|
elif config['ui']['display']['type'] in ('ws_2in66', 'waveshare2in66', 'waveshare266', 'ws266', 'waveshare_266'):
|
||||||
config['ui']['display']['type'] = 'waveshare2in66'
|
config['ui']['display']['type'] = 'waveshare2in66'
|
||||||
|
|
||||||
elif config['ui']['display']['type'] in 'waveshare2in66b':
|
elif config['ui']['display']['type'] in ('ws_2in66b', 'waveshare2in66b', 'waveshare266b', 'ws266b', 'waveshare_266b'):
|
||||||
config['ui']['display']['type'] = 'waveshare2in66b'
|
config['ui']['display']['type'] = 'waveshare2in66b'
|
||||||
|
|
||||||
elif config['ui']['display']['type'] in 'waveshare2in66g':
|
elif config['ui']['display']['type'] in ('ws_2in66g', 'waveshare2in66g', 'waveshare266g', 'ws266g', 'waveshare_266g'):
|
||||||
config['ui']['display']['type'] = 'waveshare2in66g'
|
config['ui']['display']['type'] = 'waveshare2in66g'
|
||||||
|
|
||||||
elif config['ui']['display']['type'] in 'waveshare3in0g':
|
elif config['ui']['display']['type'] in ('ws_27inch', 'ws27inch', 'waveshare2in7', 'waveshare_27inch', 'waveshare27'):
|
||||||
|
config['ui']['display']['type'] = 'waveshare2in7'
|
||||||
|
|
||||||
|
elif config['ui']['display']['type'] in ('ws_2in7v2', 'waveshare2in7_v2', 'waveshare2in7v2', 'ws27inchv2', 'waveshare_27v2', 'waveshare27v2'):
|
||||||
|
config['ui']['display']['type'] = 'waveshare2in7_v2'
|
||||||
|
|
||||||
|
elif config['ui']['display']['type'] in ('ws_2in7bv2', 'waveshare2in7b_v2', 'waveshare2in7bv2', 'ws27inchbv2', 'waveshare_27bv2', 'waveshare27bv2'):
|
||||||
|
config['ui']['display']['type'] = 'waveshare2in7b_v2'
|
||||||
|
|
||||||
|
elif config['ui']['display']['type'] in ('ws_2in9', 'waveshare2in9', 'ws29inch', 'waveshare_29inch', 'waveshare29inch'):
|
||||||
|
config['ui']['display']['type'] = 'waveshare2in9'
|
||||||
|
|
||||||
|
elif config['ui']['display']['type'] in ('ws_2in9bc', 'waveshare2in9bc', 'ws2in9bc', 'ws29bc', 'waveshare_29bc', 'waveshare_2in9bc'):
|
||||||
|
config['ui']['display']['type'] = 'waveshare2in9bc'
|
||||||
|
|
||||||
|
elif config['ui']['display']['type'] in ('ws_2in9d', 'waveshare2in9d', 'ws2in9d', 'ws29d', 'waveshare_29d', 'waveshare_2in9d'):
|
||||||
|
config['ui']['display']['type'] = 'waveshare2in9d'
|
||||||
|
|
||||||
|
elif config['ui']['display']['type'] in ('ws_2in9v2', 'waveshare2in9_v2', 'waveshare2in9v2', 'ws2in9v2', 'waveshare_29v2', 'waveshare29v2'):
|
||||||
|
config['ui']['display']['type'] = 'waveshare2in9_v2'
|
||||||
|
|
||||||
|
elif config['ui']['display']['type'] in ('ws_2in9bv3', 'waveshare2in9b_v3', 'waveshare2in9bv3', 'ws2in9bv3', 'waveshare_29bv3', 'waveshare29bv3'):
|
||||||
|
config['ui']['display']['type'] = 'waveshare2in9b_v3'
|
||||||
|
|
||||||
|
elif config['ui']['display']['type'] in ('ws_2in9bv4', 'waveshare2in9b_v4', 'waveshare2in9bv4', 'ws2in9bv4', 'waveshare_29bv4', 'waveshare29bv4'):
|
||||||
|
config['ui']['display']['type'] = 'waveshare2in9b_v4'
|
||||||
|
|
||||||
|
elif config['ui']['display']['type'] in ('ws_3in0g', 'waveshare3in0g', 'ws3in0g', 'waveshare_30g', 'waveshare30g'):
|
||||||
config['ui']['display']['type'] = 'waveshare3in0g'
|
config['ui']['display']['type'] = 'waveshare3in0g'
|
||||||
|
|
||||||
elif config['ui']['display']['type'] in 'waveshare3in7':
|
elif config['ui']['display']['type'] in ('ws_3in7', 'waveshare3in7', 'ws3in7', 'waveshare_37', 'waveshare37'):
|
||||||
config['ui']['display']['type'] = 'waveshare3in7'
|
config['ui']['display']['type'] = 'waveshare3in7'
|
||||||
|
|
||||||
elif config['ui']['display']['type'] in 'waveshare3in52':
|
elif config['ui']['display']['type'] in ('ws_3in52', 'waveshare3in52', 'ws3in52', 'waveshare_352', 'waveshare352'):
|
||||||
config['ui']['display']['type'] = 'waveshare3in52'
|
config['ui']['display']['type'] = 'waveshare3in52'
|
||||||
|
|
||||||
elif config['ui']['display']['type'] in 'waveshare4in01f':
|
elif config['ui']['display']['type'] in ('ws_4in01f', 'waveshare4in01f', 'ws4in01f', 'waveshare_401f', 'waveshare401f'):
|
||||||
config['ui']['display']['type'] = 'waveshare4in01f'
|
config['ui']['display']['type'] = 'waveshare4in01f'
|
||||||
|
|
||||||
elif config['ui']['display']['type'] in 'waveshare4in2':
|
elif config['ui']['display']['type'] in ('ws_4in2', 'waveshare4in2', 'ws4in2', 'waveshare_42', 'waveshare42'):
|
||||||
config['ui']['display']['type'] = 'waveshare4in2'
|
config['ui']['display']['type'] = 'waveshare4in2'
|
||||||
|
|
||||||
elif config['ui']['display']['type'] in 'waveshare4in2_v2':
|
elif config['ui']['display']['type'] in ('ws_4in2v2', 'waveshare4in2v2', 'ws4in2v2', 'waveshare_42v2', 'waveshare42v2'):
|
||||||
config['ui']['display']['type'] = 'waveshare4in2_v2'
|
config['ui']['display']['type'] = 'waveshare4in2_v2'
|
||||||
|
|
||||||
elif config['ui']['display']['type'] in 'waveshare4in2b_v2':
|
elif config['ui']['display']['type'] in ('ws_4in2bv2', 'waveshare4in2bv2', 'ws4in2bv2', 'waveshare_42bv2', 'waveshare42bv2'):
|
||||||
config['ui']['display']['type'] = 'waveshare4in2b_v2'
|
config['ui']['display']['type'] = 'waveshare4in2b_v2'
|
||||||
|
|
||||||
elif config['ui']['display']['type'] in 'waveshare4in2bc':
|
elif config['ui']['display']['type'] in ('ws_4in2bc', 'waveshare4in2bc', 'ws4in2bc', 'waveshare_42bc', 'waveshare42bc'):
|
||||||
config['ui']['display']['type'] = 'waveshare4in2bc'
|
config['ui']['display']['type'] = 'waveshare4in2bc'
|
||||||
|
|
||||||
elif config['ui']['display']['type'] in 'waveshare4in26':
|
elif config['ui']['display']['type'] in ('ws_4in26', 'waveshare4in26', 'ws4in26', 'waveshare_426', 'waveshare426'):
|
||||||
config['ui']['display']['type'] = 'waveshare4in26'
|
config['ui']['display']['type'] = 'waveshare4in26'
|
||||||
|
|
||||||
elif config['ui']['display']['type'] in 'waveshare4in37g':
|
elif config['ui']['display']['type'] in ('ws_4in37g', 'waveshare4in37g', 'ws4in37g', 'waveshare_37g', 'waveshare437g'):
|
||||||
config['ui']['display']['type'] = 'waveshare4in37g'
|
config['ui']['display']['type'] = 'waveshare4in37g'
|
||||||
|
|
||||||
elif config['ui']['display']['type'] in 'waveshare5in65f':
|
elif config['ui']['display']['type'] in ('ws_5in65f', 'waveshare5in65f', 'ws5in65f', 'waveshare_565f', 'waveshare565f'):
|
||||||
config['ui']['display']['type'] = 'waveshare5in65f'
|
config['ui']['display']['type'] = 'waveshare5in65f'
|
||||||
|
|
||||||
elif config['ui']['display']['type'] in 'waveshare5in83':
|
elif config['ui']['display']['type'] in ('ws_5in79', 'waveshare5in79', 'ws5in79', 'waveshare_579', 'waveshare579'):
|
||||||
|
config['ui']['display']['type'] = 'waveshare5in79'
|
||||||
|
|
||||||
|
elif config['ui']['display']['type'] in ('ws_5in79b', 'waveshare5in79b', 'ws5in79b', 'waveshare_579b', 'waveshare579b'):
|
||||||
|
config['ui']['display']['type'] = 'waveshare5in79b'
|
||||||
|
|
||||||
|
elif config['ui']['display']['type'] in ('ws_5in83', 'waveshare5in83', 'ws5in83', 'waveshare_583', 'waveshare583'):
|
||||||
config['ui']['display']['type'] = 'waveshare5in83'
|
config['ui']['display']['type'] = 'waveshare5in83'
|
||||||
|
|
||||||
elif config['ui']['display']['type'] in 'waveshare5in83_v2':
|
elif config['ui']['display']['type'] in ('ws_5in83v2', 'waveshare5in83v2', 'ws5in83v2', 'waveshare_583v2', 'waveshare583v2'):
|
||||||
config['ui']['display']['type'] = 'waveshare5in83_v2'
|
config['ui']['display']['type'] = 'waveshare5in83_v2'
|
||||||
|
|
||||||
elif config['ui']['display']['type'] in 'waveshare5in83b_v2':
|
elif config['ui']['display']['type'] in ('ws_5in83bv2', 'waveshare5in83bv2', 'ws5in83bv2', 'waveshare_583bv2', 'waveshare583bv2'):
|
||||||
config['ui']['display']['type'] = 'waveshare5in83b_v2'
|
config['ui']['display']['type'] = 'waveshare5in83b_v2'
|
||||||
|
|
||||||
elif config['ui']['display']['type'] in 'waveshare5in83bc':
|
elif config['ui']['display']['type'] in ('ws_5in83bc', 'waveshare5in83bc', 'ws5in83bc', 'waveshare_583bc', 'waveshare583bc'):
|
||||||
config['ui']['display']['type'] = 'waveshare5in83bc'
|
config['ui']['display']['type'] = 'waveshare5in83bc'
|
||||||
|
|
||||||
elif config['ui']['display']['type'] in 'waveshare7in3f':
|
elif config['ui']['display']['type'] in ('ws_7in3f', 'waveshare7in3f', 'ws7in3f', 'waveshare_73f', 'waveshare73f'):
|
||||||
config['ui']['display']['type'] = 'waveshare7in3f'
|
config['ui']['display']['type'] = 'waveshare7in3f'
|
||||||
|
|
||||||
elif config['ui']['display']['type'] in 'waveshare7in3g':
|
elif config['ui']['display']['type'] in ('ws_7in3g', 'waveshare7in3g', 'ws7in3g', 'waveshare_73g', 'waveshare73g'):
|
||||||
config['ui']['display']['type'] = 'waveshare7in3g'
|
config['ui']['display']['type'] = 'waveshare7in3g'
|
||||||
|
|
||||||
elif config['ui']['display']['type'] in 'waveshare7in5':
|
elif config['ui']['display']['type'] in ('ws_7in5', 'waveshare7in5', 'ws7in5', 'waveshare_75', 'waveshare75'):
|
||||||
config['ui']['display']['type'] = 'waveshare7in5'
|
config['ui']['display']['type'] = 'waveshare7in5'
|
||||||
|
|
||||||
elif config['ui']['display']['type'] in 'waveshare7in5_HD':
|
elif config['ui']['display']['type'] in ('ws_7in5hd', 'waveshare7in5hd', 'ws7in5hd', 'waveshare_75hd', 'waveshare75hd'):
|
||||||
config['ui']['display']['type'] = 'waveshare7in5_HD'
|
config['ui']['display']['type'] = 'waveshare7in5_HD'
|
||||||
|
|
||||||
elif config['ui']['display']['type'] in 'waveshare7in5_v2':
|
elif config['ui']['display']['type'] in ('ws_7in5v2', 'waveshare7in5v2', 'ws7in5v2', 'waveshare_75v2', 'waveshare75v2'):
|
||||||
config['ui']['display']['type'] = 'waveshare7in5_v2'
|
config['ui']['display']['type'] = 'waveshare7in5_v2'
|
||||||
|
|
||||||
elif config['ui']['display']['type'] in 'waveshare7in5b_HD':
|
elif config['ui']['display']['type'] in ('ws_7in5bhd', 'waveshare7in5bhd', 'ws7in5bhd', 'waveshare_75bhd', 'waveshare75bhd'):
|
||||||
config['ui']['display']['type'] = 'waveshare7in5b_HD'
|
config['ui']['display']['type'] = 'waveshare7in5b_HD'
|
||||||
|
|
||||||
elif config['ui']['display']['type'] in 'waveshare7in5b_v2':
|
elif config['ui']['display']['type'] in ('ws_7in5bv2', 'waveshare7in5bv2', 'ws7in5bv2', 'waveshare_75bv2', 'waveshare75bv2'):
|
||||||
config['ui']['display']['type'] = 'waveshare7in5b_v2'
|
config['ui']['display']['type'] = 'waveshare7in5b_v2'
|
||||||
|
|
||||||
elif config['ui']['display']['type'] in 'waveshare7in5bc':
|
elif config['ui']['display']['type'] in ('ws_7in5bc', 'waveshare7in5bc', 'ws7in5bc', 'waveshare_75bc', 'waveshare75bc'):
|
||||||
config['ui']['display']['type'] = 'waveshare7in5bc'
|
config['ui']['display']['type'] = 'waveshare7in5bc'
|
||||||
|
|
||||||
elif config['ui']['display']['type'] in 'waveshare13in3k':
|
elif config['ui']['display']['type'] in ('ws_13in3k', 'waveshare13in3k', 'ws13in3k', 'waveshare_133k', 'waveshare133k'):
|
||||||
config['ui']['display']['type'] = 'waveshare13in3k'
|
config['ui']['display']['type'] = 'waveshare13in3k'
|
||||||
|
|
||||||
|
# WeAct e-ink
|
||||||
|
elif config['ui']['display']['type'] in ('weact2in9', 'weact29in'):
|
||||||
|
config['ui']['display']['type'] = 'weact2in9'
|
||||||
|
|
||||||
else:
|
else:
|
||||||
print("unsupported display type %s" % config['ui']['display']['type'])
|
print("unsupported display type %s" % config['ui']['display']['type'])
|
||||||
sys.exit(1)
|
sys.exit(1)
|
||||||
|
60
pyproject.toml
Normal file
60
pyproject.toml
Normal file
@ -0,0 +1,60 @@
|
|||||||
|
[build-system]
|
||||||
|
requires = ["setuptools"]
|
||||||
|
build-backend = "setuptools.build_meta"
|
||||||
|
|
||||||
|
[project]
|
||||||
|
name = "pwnagotchi"
|
||||||
|
dynamic = ["version"]
|
||||||
|
dependencies = [
|
||||||
|
"Pillow",
|
||||||
|
"PyYAML",
|
||||||
|
"RPi.GPIO",
|
||||||
|
"file-read-backwards",
|
||||||
|
"flask",
|
||||||
|
"flask-cors",
|
||||||
|
"flask-wtf",
|
||||||
|
"gast",
|
||||||
|
"gym",
|
||||||
|
"inky",
|
||||||
|
"pycryptodome",
|
||||||
|
"pydrive2",
|
||||||
|
"python-dateutil",
|
||||||
|
"requests",
|
||||||
|
"rpi_hardware_pwm",
|
||||||
|
"scapy",
|
||||||
|
"shimmy",
|
||||||
|
"smbus2",
|
||||||
|
"spidev",
|
||||||
|
"stable_baselines3",
|
||||||
|
"toml",
|
||||||
|
"torch",
|
||||||
|
"torchvision",
|
||||||
|
"tweepy",
|
||||||
|
"websockets"
|
||||||
|
]
|
||||||
|
requires-python = ">=3.9"
|
||||||
|
authors = [
|
||||||
|
{name = "Evilsocket", email = "evilsocket@gmail.com"},
|
||||||
|
{name = "Jayofelony", email = "oudshoorn.jeroen@gmail.com"}
|
||||||
|
]
|
||||||
|
maintainers = [
|
||||||
|
{name = "Jayofelony", email = "oudshoorn.jeroen@gmail.com"}
|
||||||
|
]
|
||||||
|
description = "(⌐■_■) - Deep Reinforcement Learning instrumenting bettercap for WiFI pwning."
|
||||||
|
readme = "README.md"
|
||||||
|
license = {file = "LICENSE.md"}
|
||||||
|
classifiers = [
|
||||||
|
'Programming Language :: Python :: 3',
|
||||||
|
'Development Status :: 5 - Production/Stable',
|
||||||
|
'License :: OSI Approved :: GNU General Public License (GPL)',
|
||||||
|
'Environment :: Console',
|
||||||
|
]
|
||||||
|
|
||||||
|
[project.urls]
|
||||||
|
Homepage = "https://pwnagotchi.org/"
|
||||||
|
Documentation = "https://pwnagotchi.org/"
|
||||||
|
Repository = "https://github.com/jayofelony/pwnagotchi.git"
|
||||||
|
Issues = "https://github.com/jayofelony/pwnagotchi/issues"
|
||||||
|
|
||||||
|
[project.scripts]
|
||||||
|
pwnagotchi_cli = "bin.pwnagotchi:pwnagotchi_cli"
|
@ -1,5 +1,5 @@
|
|||||||
gym
|
gym
|
||||||
shimmy; platform_machine!="armv6l"
|
shimmy
|
||||||
pycryptodome
|
pycryptodome
|
||||||
requests
|
requests
|
||||||
PyYAML
|
PyYAML
|
||||||
@ -18,17 +18,9 @@ dbus-python
|
|||||||
toml
|
toml
|
||||||
python-dateutil
|
python-dateutil
|
||||||
websockets
|
websockets
|
||||||
torch; platform_machine=="aarch64"
|
torch
|
||||||
torch>=2.0.1; platform_machine!="aarch64"
|
torchvision
|
||||||
|
stable_baselines3
|
||||||
torchvision; platform_machine=="aarch64"
|
RPi.GPIO
|
||||||
torchvision>=0.15.2; platform_machine!="aarch64"
|
rpi_hardware_pwm
|
||||||
|
|
||||||
stable_baselines3==1.8.0; platform_machine=="armv6l"
|
|
||||||
stable_baselines3; platform_machine!="armv6l"
|
|
||||||
|
|
||||||
RPi.GPIO; platform_release!="6.1.31-sun50iw9"
|
|
||||||
OPi.GPIO; platform_release=="6.1.31-sun50iw9"
|
|
||||||
|
|
||||||
rpi_hardware_pwm; platform_release!="6.1.31-sun50iw9"
|
|
||||||
pydrive2
|
pydrive2
|
2
setup.py
2
setup.py
@ -98,7 +98,7 @@ setup(name='pwnagotchi',
|
|||||||
"install": CustomInstall,
|
"install": CustomInstall,
|
||||||
},
|
},
|
||||||
scripts=['bin/pwnagotchi'],
|
scripts=['bin/pwnagotchi'],
|
||||||
package_data={'pwnagotchi': ['defaults.yml', 'pwnagotchi/defaults.yml', 'locale/*/LC_MESSAGES/*.mo']},
|
package_data={'pwnagotchi': ['defaults.toml', 'pwnagotchi/defaults.toml', 'locale/*/LC_MESSAGES/*.mo']},
|
||||||
include_package_data=True,
|
include_package_data=True,
|
||||||
packages=find_packages(),
|
packages=find_packages(),
|
||||||
classifiers=[
|
classifiers=[
|
||||||
|
Reference in New Issue
Block a user