mirror of
https://github.com/jayofelony/pwnagotchi.git
synced 2025-07-01 18:37:27 -04:00
Compare commits
135 Commits
wpa-2-patc
...
v2.8.9
Author | SHA1 | Date | |
---|---|---|---|
ac345c2ee7 | |||
468cfd9f4f | |||
d6bc5c0e66 | |||
4905eb6b26 | |||
697a7778b1 | |||
9287283ee7 | |||
8013109ef7 | |||
f140fe1a2d | |||
8fe503c67d | |||
d81013412e | |||
3c701822bd | |||
2f5ddb492a | |||
9e3324221d | |||
b069b82984 | |||
cb7d965271 | |||
7abf9ff8da | |||
18fb956251 | |||
afb1d11cd8 | |||
627be80e6c | |||
c4c4d6c417 | |||
8dcae13ce9 | |||
1352e99774 | |||
2182d7c29d | |||
703c05a93b | |||
697cc5d88b | |||
4b04f9b7a5 | |||
c7b94a0707 | |||
5116bac2a7 | |||
51625e61f9 | |||
bf9a0a96c1 | |||
bd03f07aa8 | |||
3f13df8f20 | |||
ce0275f2ae | |||
b971f18f75 | |||
ea9d11d018 | |||
8a572f1b70 | |||
0c4f2a5093 | |||
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 |
1
.github/FUNDING.yml
vendored
1
.github/FUNDING.yml
vendored
@ -1,3 +1,4 @@
|
||||
# These are supported funding model platforms
|
||||
|
||||
github: jayofelony
|
||||
custom: https://tikkie.me/pay/dubcto94hnskg539kar0
|
22
.github/workflows/publish.yml
vendored
22
.github/workflows/publish.yml
vendored
@ -20,21 +20,6 @@ jobs:
|
||||
VERSION=$(cut -d "'" -f2 < pwnagotchi/_version.py)
|
||||
echo "VERSION=$VERSION" >> $GITHUB_ENV
|
||||
|
||||
- name: Get latest tag
|
||||
uses: actions-ecosystem/action-get-latest-tag@v1
|
||||
id: get-latest-tag
|
||||
|
||||
- name: Set LAST_VERSION as an environment variable
|
||||
run: echo "LAST_VERSION=${{ steps.get-latest-tag.outputs.tag }}" >> $GITHUB_ENV
|
||||
|
||||
- name: Generate release notes
|
||||
id: generate_release_notes
|
||||
run: |
|
||||
COMMITS=$(git log --merges --pretty=format:"* %s" $LAST_VERSION--$VERSION | sed 's/$/\\n/g')
|
||||
CONTRIBUTORS=$(git shortlog -sn $LAST_VERSION--$VERSION | awk '{print "* @" $2}' | sed 's/$/\\n/g')
|
||||
RELEASE_BODY="**Full Changelog**: https://github.com/jayofelony/pwnagotchi/compare/$LAST_VERSION...$VERSION"
|
||||
echo "RELEASE_BODY=$RELEASE_BODY" >> $GITHUB_ENV
|
||||
|
||||
- name: Install qemu dependencies
|
||||
run: sudo apt update && sudo apt install qemu-user-static qemu-utils xz-utils -y
|
||||
|
||||
@ -61,12 +46,13 @@ jobs:
|
||||
run: mv "pwnagotchi-64bit.img.xz" "pwnagotchi-$VERSION-64bit.img.xz"
|
||||
|
||||
- name: Release
|
||||
uses: softprops/action-gh-release@v1
|
||||
uses: softprops/action-gh-release@v2
|
||||
with:
|
||||
prerelease: true
|
||||
prerelease: false
|
||||
make_latest: true
|
||||
tag_name: v${{ env.VERSION }}
|
||||
name: Pwnagotchi v${{ env.VERSION }}
|
||||
files: |
|
||||
pwnagotchi-${{ env.VERSION }}-32bit.img.xz
|
||||
pwnagotchi-${{ env.VERSION }}-64bit.img.xz
|
||||
body: ${{ env.RELEASE_BODY }}
|
||||
generate_release_notes: true
|
9
Makefile
9
Makefile
@ -46,15 +46,16 @@ packer: clean
|
||||
sudo mv /tmp/packer /usr/bin/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
|
||||
|
||||
bullseye: clean packer
|
||||
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
|
||||
export LC_ALL=en_GB.UTF-8
|
||||
cd builder && sudo /usr/bin/packer init raspberrypi32.json.pkr.hcl && sudo $(UNSHARE) /usr/bin/packer build -var "pwn_hostname=$(PWN_HOSTNAME)" -var "pwn_version=$(PWN_VERSION)" raspberrypi32.json.pkr.hcl
|
||||
|
||||
bookworm: clean packer
|
||||
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
|
||||
export LC_ALL=en_GB.UTF-8
|
||||
cd builder && sudo /usr/bin/packer init raspberrypi64.json.pkr.hcl && sudo $(UNSHARE) /usr/bin/packer build -var "pwn_hostname=$(PWN_HOSTNAME)" -var "pwn_version=$(PWN_VERSION)" raspberrypi64.json.pkr.hcl
|
||||
|
||||
clean:
|
||||
- rm -rf /tmp/packer*
|
||||
|
256
bin/pwnagotchi
256
bin/pwnagotchi
@ -7,6 +7,7 @@ import sys
|
||||
import toml
|
||||
import requests
|
||||
import os
|
||||
import re
|
||||
|
||||
import pwnagotchi
|
||||
from pwnagotchi import utils
|
||||
@ -18,85 +19,83 @@ from pwnagotchi import fs
|
||||
from pwnagotchi.utils import DottedTomlEncoder, parse_version as version_to_tuple
|
||||
|
||||
|
||||
def do_clear(display):
|
||||
logging.info("clearing the display ...")
|
||||
display.clear()
|
||||
sys.exit(0)
|
||||
def pwnagotchi_cli():
|
||||
def do_clear(display):
|
||||
logging.info("clearing the display ...")
|
||||
display.clear()
|
||||
sys.exit(0)
|
||||
|
||||
def do_manual_mode(agent):
|
||||
logging.info("entering manual mode ...")
|
||||
|
||||
def do_manual_mode(agent):
|
||||
logging.info("entering manual mode ...")
|
||||
|
||||
agent.mode = 'manual'
|
||||
agent.last_session.parse(agent.view(), args.skip_session)
|
||||
if not args.skip_session:
|
||||
logging.info(
|
||||
"the last session lasted %s (%d completed epochs, trained for %d), average reward:%s (min:%s max:%s)" % (
|
||||
agent.last_session.duration_human,
|
||||
agent.last_session.epochs,
|
||||
agent.last_session.train_epochs,
|
||||
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()
|
||||
agent.mode = 'manual'
|
||||
agent.last_session.parse(agent.view(), args.skip_session)
|
||||
if not args.skip_session:
|
||||
logging.info(
|
||||
"the last session lasted %s (%d completed epochs, trained for %d), average reward:%s (min:%s max:%s)" % (
|
||||
agent.last_session.duration_human,
|
||||
agent.last_session.epochs,
|
||||
agent.last_session.train_epochs,
|
||||
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)
|
||||
|
||||
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)
|
||||
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:
|
||||
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()
|
||||
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):
|
||||
"""
|
||||
Adds the plugins and google subcommands
|
||||
@ -133,6 +132,8 @@ if __name__ == '__main__':
|
||||
help="Print the configuration.")
|
||||
|
||||
# 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,
|
||||
help="Check for updates on Pwnagotchi. And tells current version.")
|
||||
parser.add_argument('--donate', dest="donate", action="store_true", default=False,
|
||||
@ -157,9 +158,113 @@ if __name__ == '__main__':
|
||||
print(pwnagotchi.__version__)
|
||||
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:
|
||||
print("Donations can made @ \n "
|
||||
"https://www.patreon.com/pwnagotchi_torch \n "
|
||||
print("Donations can be made @ \n "
|
||||
"https://github.com/sponsors/jayofelony \n\n"
|
||||
"But only if you really want to!")
|
||||
sys.exit(0)
|
||||
@ -172,8 +277,7 @@ if __name__ == '__main__':
|
||||
local = version_to_tuple(pwnagotchi.__version__)
|
||||
remote = version_to_tuple(latest_ver)
|
||||
if remote > local:
|
||||
user_input = input("There is a new version available! Update from v%s to v%s?\n[y(es)/n(o)]"
|
||||
% (pwnagotchi.__version__, latest_ver))
|
||||
user_input = input("There is a new version available! Update from v%s to v%s?\n[Y/N] " % (pwnagotchi.__version__, latest_ver))
|
||||
# input validation
|
||||
if user_input.lower() in ('y', 'yes'):
|
||||
if os.path.exists('/root/.auto-update'):
|
||||
@ -227,3 +331,7 @@ if __name__ == '__main__':
|
||||
do_manual_mode(agent)
|
||||
else:
|
||||
do_auto_mode(agent)
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
pwnagotchi_cli()
|
||||
|
@ -20,8 +20,8 @@ variable "pwn_version" {
|
||||
}
|
||||
|
||||
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_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_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.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_target_extension = "xz"
|
||||
file_unarchive_cmd = ["unxz", "$ARCHIVE_PATH"]
|
||||
@ -50,8 +50,8 @@ source "arm" "rpi64-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_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_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-2024-03-12/2024-03-12-raspios-bullseye-armhf-lite.img.xz"]
|
||||
file_checksum_type = "sha256"
|
||||
file_target_extension = "xz"
|
||||
file_unarchive_cmd = ["unxz", "$ARCHIVE_PATH"]
|
||||
@ -123,7 +123,7 @@ build {
|
||||
provisioner "ansible-local" {
|
||||
command = "ANSIBLE_FORCE_COLOR=1 PYTHONUNBUFFERED=1 PWN_VERSION=${var.pwn_version} PWN_HOSTNAME=${var.pwn_hostname} ansible-playbook"
|
||||
extra_arguments = ["--extra-vars \"ansible_python_interpreter=/usr/bin/python3\""]
|
||||
playbook_file = "data/64bit/raspberrypi64.yml"
|
||||
playbook_file = "raspberrypi64.yml"
|
||||
}
|
||||
}
|
||||
|
||||
@ -167,7 +167,7 @@ build {
|
||||
provisioner "ansible-local" {
|
||||
command = "ANSIBLE_FORCE_COLOR=1 PYTHONUNBUFFERED=1 PWN_VERSION=${var.pwn_version} PWN_HOSTNAME=${var.pwn_hostname} ansible-playbook"
|
||||
extra_arguments = ["--extra-vars \"ansible_python_interpreter=/usr/bin/python3\""]
|
||||
playbook_dir = "data/32bit/extras/"
|
||||
playbook_file = "data/32bit/raspberrypi32.yml"
|
||||
playbook_dir = "extras/"
|
||||
playbook_file = "raspberrypi32.yml"
|
||||
}
|
||||
}
|
67
builder/data/32bit/boot/config.txt
Normal file
67
builder/data/32bit/boot/config.txt
Normal file
@ -0,0 +1,67 @@
|
||||
# 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
|
||||
#dtoverlay=disable-wifi
|
||||
|
||||
[pi0]
|
||||
dtoverlay=spi1-3cs
|
||||
#dtoverlay=disable-wifi
|
||||
|
||||
[pi3]
|
||||
dtoverlay=spi1-3cs
|
||||
#dtoverlay=disable-wifi
|
||||
|
||||
[pi4]
|
||||
dtoverlay=spi1-3cs
|
||||
#dtoverlay=disable-wifi
|
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
|
40
builder/data/32bit/etc/profile
Normal file
40
builder/data/32bit/etc/profile
Normal file
@ -0,0 +1,40 @@
|
||||
# /etc/profile: system-wide .profile file for the Bourne shell (sh(1))
|
||||
# and Bourne compatible shells (bash(1), ksh(1), ash(1), ...).
|
||||
|
||||
if [ "$(id -u)" -eq 0 ]; then
|
||||
PATH="/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin"
|
||||
else
|
||||
PATH="/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin:/usr/local/games:/usr/games"
|
||||
fi
|
||||
export PATH
|
||||
|
||||
if [ "${PS1-}" ]; then
|
||||
if [ "${BASH-}" ] && [ "$BASH" != "/bin/sh" ]; then
|
||||
# The file bash.bashrc already sets the default PS1.
|
||||
# PS1='\h:\w\$ '
|
||||
if [ -f /etc/bash.bashrc ]; then
|
||||
. /etc/bash.bashrc
|
||||
fi
|
||||
else
|
||||
if [ "$(id -u)" -eq 0 ]; then
|
||||
PS1='# '
|
||||
else
|
||||
PS1='$ '
|
||||
fi
|
||||
fi
|
||||
fi
|
||||
|
||||
if [ -d /etc/profile.d ]; then
|
||||
for i in /etc/profile.d/*.sh; do
|
||||
if [ -r $i ]; then
|
||||
. $i
|
||||
fi
|
||||
done
|
||||
unset i
|
||||
fi
|
||||
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'
|
||||
|
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,6 +20,7 @@ echo " I'm managed by systemd. Here are some basic commands."
|
||||
echo
|
||||
echo " If you want to know what I'm doing, you can check my logs with the command"
|
||||
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 --donate, to see how you can donate to this project"
|
||||
echo " - sudo pwnagotchi --check-update, to see if there is a new version available"
|
||||
@ -30,4 +31,4 @@ echo
|
||||
echo " You can restart me using"
|
||||
echo " pwnkill"
|
||||
echo
|
||||
echo " You learn more about me at https://pwnagotchi.ai/"
|
||||
echo " You can learn more about me at https://pwnagotchi.org/"
|
@ -11,6 +11,7 @@ fi
|
||||
|
||||
if is_auto_mode; then
|
||||
/usr/local/bin/pwnagotchi
|
||||
systemctl restart bettercap
|
||||
else
|
||||
/usr/local/bin/pwnagotchi --manual
|
||||
fi
|
||||
|
70
builder/data/64bit/boot/firmware/config.txt
Normal file
70
builder/data/64bit/boot/firmware/config.txt
Normal file
@ -0,0 +1,70 @@
|
||||
# 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
|
||||
#dtoverlay=disable-wifi
|
||||
|
||||
[pi0]
|
||||
dtoverlay=spi0-0cs
|
||||
#dtoverlay=disable-wifi
|
||||
|
||||
[pi3]
|
||||
dtoverlay=spi0-0cs
|
||||
#dtoverlay=disable-wifi
|
||||
|
||||
[pi4]
|
||||
dtoverlay=spi0-0cs
|
||||
#dtoverlay=disable-wifi
|
||||
|
||||
[pi5]
|
||||
dtoverlay=spi0-0cs
|
||||
#dtoverlay=disable-wifi
|
@ -2,7 +2,7 @@ _show_complete()
|
||||
{
|
||||
local cur opts node_names all_options opt_line
|
||||
all_options="
|
||||
pwnagotchi -h --help -C --config -U --user-config --manual --skip-session --clear --debug --version --print-config --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 list -i --installed -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
|
40
builder/data/64bit/etc/profile
Normal file
40
builder/data/64bit/etc/profile
Normal file
@ -0,0 +1,40 @@
|
||||
# /etc/profile: system-wide .profile file for the Bourne shell (sh(1))
|
||||
# and Bourne compatible shells (bash(1), ksh(1), ash(1), ...).
|
||||
|
||||
if [ "$(id -u)" -eq 0 ]; then
|
||||
PATH="/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin"
|
||||
else
|
||||
PATH="/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin:/usr/local/games:/usr/games"
|
||||
fi
|
||||
export PATH
|
||||
|
||||
if [ "${PS1-}" ]; then
|
||||
if [ "${BASH-}" ] && [ "$BASH" != "/bin/sh" ]; then
|
||||
# The file bash.bashrc already sets the default PS1.
|
||||
# PS1='\h:\w\$ '
|
||||
if [ -f /etc/bash.bashrc ]; then
|
||||
. /etc/bash.bashrc
|
||||
fi
|
||||
else
|
||||
if [ "$(id -u)" -eq 0 ]; then
|
||||
PS1='# '
|
||||
else
|
||||
PS1='$ '
|
||||
fi
|
||||
fi
|
||||
fi
|
||||
|
||||
if [ -d /etc/profile.d ]; then
|
||||
for i in /etc/profile.d/*.sh; do
|
||||
if [ -r $i ]; then
|
||||
. $i
|
||||
fi
|
||||
done
|
||||
unset i
|
||||
fi
|
||||
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'
|
||||
|
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
|
@ -2,7 +2,7 @@
|
||||
_hostname=$(hostname)
|
||||
_version=$(cut -d"'" -f2 < /usr/local/lib/python3.11/dist-packages/pwnagotchi/_version.py)
|
||||
echo
|
||||
echo "(☉_☉ ) $_hostname"
|
||||
echo "(◕‿‿◕) $_hostname"
|
||||
echo
|
||||
echo " Hi! I'm a pwnagotchi $_version, please take good care of me!"
|
||||
echo " Here are some basic things you need to know to raise me properly!"
|
||||
@ -20,6 +20,7 @@ echo " I'm managed by systemd. Here are some basic commands."
|
||||
echo
|
||||
echo " If you want to know what I'm doing, you can check my logs with the command"
|
||||
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 --donate, to see how you can donate to this project"
|
||||
echo " - sudo pwnagotchi --check-update, to see if there is a new version available"
|
||||
|
@ -11,6 +11,7 @@ fi
|
||||
|
||||
if is_auto_mode; then
|
||||
/usr/local/bin/pwnagotchi
|
||||
systemctl restart bettercap
|
||||
else
|
||||
/usr/local/bin/pwnagotchi --manual
|
||||
fi
|
||||
|
@ -1,18 +1,5 @@
|
||||
#!/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_brcm() {
|
||||
if ! modprobe -r brcmfmac; then
|
||||
|
@ -20,8 +20,8 @@ variable "pwn_version" {
|
||||
}
|
||||
|
||||
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_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_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-2024-03-12/2024-03-12-raspios-bullseye-armhf-lite.img.xz"]
|
||||
file_checksum_type = "sha256"
|
||||
file_target_extension = "xz"
|
||||
file_unarchive_cmd = ["unxz", "$ARCHIVE_PATH"]
|
||||
@ -88,7 +88,7 @@ build {
|
||||
provisioner "ansible-local" {
|
||||
command = "ANSIBLE_FORCE_COLOR=1 PYTHONUNBUFFERED=1 PWN_VERSION=${var.pwn_version} PWN_HOSTNAME=${var.pwn_hostname} ansible-playbook"
|
||||
extra_arguments = ["--extra-vars \"ansible_python_interpreter=/usr/bin/python3\""]
|
||||
playbook_dir = "data/32bit/extras/"
|
||||
playbook_file = "data/32bit/raspberrypi32.yml"
|
||||
playbook_dir = "extras/"
|
||||
playbook_file = "raspberrypi32.yml"
|
||||
}
|
||||
}
|
@ -39,26 +39,6 @@
|
||||
hostname: "{{ lookup('env', 'PWN_HOSTNAME') | default('pwnagotchi', true) }}"
|
||||
version: "{{ lookup('env', 'PWN_VERSION') | default('pwnagotchi-torch', true) }}"
|
||||
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:
|
||||
enable:
|
||||
- bettercap.service
|
||||
@ -117,6 +97,7 @@
|
||||
- bc
|
||||
- bison
|
||||
- bluez
|
||||
- bluez-tools
|
||||
- build-essential
|
||||
- curl
|
||||
- dkms
|
||||
@ -177,6 +158,8 @@
|
||||
- libusb-1.0-0-dev
|
||||
- lsof
|
||||
- make
|
||||
- ntp
|
||||
- python3-dbus
|
||||
- python3-flask
|
||||
- python3-flask-cors
|
||||
- python3-flaskext.wtf
|
||||
@ -228,6 +211,11 @@
|
||||
path: '{{ pwnagotchi.custom_plugin_dir }}'
|
||||
state: directory
|
||||
|
||||
- name: remove current rc.local
|
||||
file:
|
||||
path: /etc/rc.local
|
||||
state: absent
|
||||
|
||||
- name: update apt package cache
|
||||
apt:
|
||||
update_cache: yes
|
||||
@ -451,7 +439,7 @@
|
||||
# Add your configuration overrides on this file any configuration changes done to default.toml will be lost!
|
||||
# Example:
|
||||
# ui.display.enabled = true
|
||||
# ui.display.type = "waveshare_2"
|
||||
# ui.display.type = "waveshare_4"
|
||||
when: not user_config.stat.exists
|
||||
|
||||
- name: Delete motd 10-uname
|
||||
@ -464,20 +452,6 @@
|
||||
path: /boot/ssh
|
||||
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
|
||||
replace:
|
||||
dest: /boot/cmdline.txt
|
||||
@ -494,24 +468,6 @@
|
||||
regexp: '(.*)$'
|
||||
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
|
||||
dpkg_selections:
|
||||
name: "{{ item }}"
|
@ -20,8 +20,8 @@ variable "pwn_version" {
|
||||
}
|
||||
|
||||
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_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_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.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_target_extension = "xz"
|
||||
file_unarchive_cmd = ["unxz", "$ARCHIVE_PATH"]
|
||||
@ -95,6 +95,6 @@ build {
|
||||
provisioner "ansible-local" {
|
||||
command = "ANSIBLE_FORCE_COLOR=1 PYTHONUNBUFFERED=1 PWN_VERSION=${var.pwn_version} PWN_HOSTNAME=${var.pwn_hostname} ansible-playbook"
|
||||
extra_arguments = ["--extra-vars \"ansible_python_interpreter=/usr/bin/python3\""]
|
||||
playbook_file = "data/64bit/raspberrypi64.yml"
|
||||
playbook_file = "raspberrypi64.yml"
|
||||
}
|
||||
}
|
@ -5,22 +5,12 @@
|
||||
become: true
|
||||
vars:
|
||||
kernel:
|
||||
min: "6.1"
|
||||
full: "6.1.0-rpi8-rpi-v8"
|
||||
full_pi5: "6.1.0-rpi8-rpi-2712"
|
||||
min: "6.6"
|
||||
full: "6.6.20+rpt-rpi-v8"
|
||||
full_pi5: "6.6.20+rpt-rpi-2712"
|
||||
pwnagotchi:
|
||||
hostname: "{{ lookup('env', 'PWN_HOSTNAME') | 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:
|
||||
enable:
|
||||
- bettercap.service
|
||||
@ -74,6 +64,7 @@
|
||||
- bc
|
||||
- bison
|
||||
- bluez
|
||||
- bluez-tools
|
||||
- build-essential
|
||||
- curl
|
||||
- dkms
|
||||
@ -137,6 +128,7 @@
|
||||
- libusb-1.0-0-dev
|
||||
- lsof
|
||||
- make
|
||||
- ntp
|
||||
- python3-dbus
|
||||
- python3-flask
|
||||
- python3-flask-cors
|
||||
@ -180,6 +172,12 @@
|
||||
update_cache: yes
|
||||
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
|
||||
- name: Create pi user
|
||||
copy:
|
||||
@ -192,12 +190,10 @@
|
||||
path: /boot/firmware/ssh
|
||||
state: touch
|
||||
|
||||
- name: adjust /boot/firmware/config.txt
|
||||
lineinfile:
|
||||
dest: /boot/firmware/config.txt
|
||||
insertafter: EOF
|
||||
line: '{{ item }}'
|
||||
with_items: "{{ system.boot_options }}"
|
||||
- name: remove current rc.local
|
||||
file:
|
||||
path: /etc/rc.local
|
||||
state: absent
|
||||
|
||||
- name: change root partition
|
||||
replace:
|
||||
@ -486,7 +482,7 @@
|
||||
|
||||
- name: Update .bashrc for go-1.21
|
||||
blockinfile:
|
||||
dest: /home/pi/.bashrc
|
||||
dest: /etc/profile
|
||||
state: present
|
||||
block: |
|
||||
export GOPATH=$HOME/go
|
||||
@ -579,7 +575,7 @@
|
||||
# Add your configuration overrides on this file any configuration changes done to default.toml will be lost!
|
||||
# Example:
|
||||
# ui.display.enabled = true
|
||||
# ui.display.type = "waveshare_2"
|
||||
# ui.display.type = "waveshare_4"
|
||||
when: not user_config.stat.exists
|
||||
|
||||
- name: Delete motd
|
||||
@ -592,24 +588,6 @@
|
||||
state: absent
|
||||
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
|
||||
dpkg_selections:
|
||||
name: "{{ item }}"
|
@ -1 +1 @@
|
||||
__version__ = '2.8.5'
|
||||
__version__ = '2.8.9'
|
||||
|
@ -254,7 +254,7 @@ class Agent(Client, Automata, AsyncAdvertiser, AsyncTrainer):
|
||||
txt = '%d (%d)' % (len(self._handshakes), tot)
|
||||
|
||||
if self._last_pwnd is not None:
|
||||
txt += ' [%s]' % self._last_pwnd[:11] # So it doesn't overlap with fix_brcmfmac_plugin
|
||||
txt += ' [%s]' % self._last_pwnd
|
||||
|
||||
self._view.set('shakes', txt)
|
||||
|
||||
|
@ -100,7 +100,6 @@ class Client(object):
|
||||
await asyncio.sleep(sleep_time)
|
||||
continue
|
||||
except OSError:
|
||||
sleep_time = min_sleep + max_sleep * random.random()
|
||||
logging.warning('connection to the bettercap endpoint failed...')
|
||||
pwnagotchi.restart("AUTO")
|
||||
|
||||
|
@ -32,7 +32,7 @@ main.plugins.bt-tether.devices.android-phone.netmask = 24
|
||||
main.plugins.bt-tether.devices.android-phone.interval = 1
|
||||
main.plugins.bt-tether.devices.android-phone.scantime = 10
|
||||
main.plugins.bt-tether.devices.android-phone.max_tries = 10
|
||||
main.plugins.bt-tether.devices.android-phone.share_internet = false
|
||||
main.plugins.bt-tether.devices.android-phone.share_internet = true
|
||||
main.plugins.bt-tether.devices.android-phone.priority = 1
|
||||
|
||||
main.plugins.bt-tether.devices.ios-phone.enabled = false
|
||||
@ -43,7 +43,7 @@ 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 = false
|
||||
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
|
||||
@ -112,8 +112,6 @@ main.mon_stop_cmd = "/usr/bin/monstop"
|
||||
main.mon_max_blind_epochs = 50
|
||||
main.no_restart = false
|
||||
|
||||
main.filter = ""
|
||||
|
||||
main.log.path = "/etc/pwnagotchi/log/pwnagotchi.log"
|
||||
main.log.rotation.enabled = true
|
||||
main.log.rotation.size = "10M"
|
||||
@ -154,6 +152,8 @@ personality.throttle_d = 0.9
|
||||
|
||||
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.font.name = "DejaVuSansMono" # for japanese: fonts-japanese-gothic
|
||||
ui.font.size_offset = 0 # will be added to the font size
|
||||
|
Binary file not shown.
@ -248,7 +248,7 @@ msgid "minutes"
|
||||
msgstr "minuten"
|
||||
|
||||
msgid "seconds"
|
||||
msgstr "seconds"
|
||||
msgstr "sekondes"
|
||||
|
||||
msgid "hour"
|
||||
msgstr "oere"
|
||||
|
@ -199,6 +199,9 @@ def list_plugins(args, config, pattern='*'):
|
||||
available_not_installed = set(available.keys()) - set(installed.keys())
|
||||
|
||||
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))
|
||||
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
|
||||
@ -239,7 +242,7 @@ def list_plugins(args, config, pattern='*'):
|
||||
print('-' * line_length)
|
||||
|
||||
if not found:
|
||||
logging.info('Maybe try: pwnagotchi plugins update')
|
||||
print('Maybe try: sudo pwnagotchi plugins update')
|
||||
return 1
|
||||
return 0
|
||||
|
||||
|
@ -29,6 +29,7 @@ def check(version, repo, native=True):
|
||||
latest = resp.json()
|
||||
info['available'] = latest_ver = latest['tag_name'].replace('v', '')
|
||||
is_armhf = info['arch'].startswith('arm')
|
||||
is_aarch = info['arch'].startswith('aarch')
|
||||
|
||||
local = version_to_tuple(info['current'])
|
||||
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['url'] = download_url
|
||||
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
|
||||
|
||||
@ -221,7 +230,7 @@ class AutoUpdate(plugins.Plugin):
|
||||
if num_installed > 0:
|
||||
display.update(force=True, new_data={'status': 'Rebooting ...'})
|
||||
time.sleep(3)
|
||||
os.system("service pwnagotchi restart")
|
||||
pwnagotchi.reboot()
|
||||
|
||||
except Exception as e:
|
||||
logging.error("[update] %s" % e)
|
||||
|
@ -4,6 +4,7 @@ import subprocess
|
||||
import time
|
||||
import random
|
||||
from io import TextIOWrapper
|
||||
import os
|
||||
|
||||
import pwnagotchi
|
||||
from pwnagotchi import plugins
|
||||
@ -32,17 +33,16 @@ class FixServices(plugins.Plugin):
|
||||
self.pattern2 = re.compile(r'wifi error while hopping to channel')
|
||||
self.pattern3 = re.compile(r'Firmware has halted or crashed')
|
||||
self.pattern4 = re.compile(r'error 400: could not find interface wlan0mon')
|
||||
self.pattern5 = re.compile(r'fatal error: concurrent map iteration and map write')
|
||||
self.pattern6 = re.compile(r'panic: runtime error')
|
||||
self.isReloadingMon = False
|
||||
self.connection = None
|
||||
self.LASTTRY = 0
|
||||
self._status = "--"
|
||||
self._count = 0
|
||||
|
||||
def on_loaded(self):
|
||||
"""
|
||||
Gets called when the plugin gets loaded
|
||||
"""
|
||||
self._status = "ld"
|
||||
logging.info("[Fix_Services] plugin loaded.")
|
||||
|
||||
def on_ready(self, agent):
|
||||
@ -50,31 +50,27 @@ class FixServices(plugins.Plugin):
|
||||
stdout=subprocess.PIPE).stdout))[-10:])
|
||||
try:
|
||||
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):
|
||||
logging.info("wlan0mon is up.")
|
||||
self._status = "up"
|
||||
logging.debug("wlan0mon is up.")
|
||||
|
||||
if len(self.pattern.findall(last_lines)) >= 3:
|
||||
self._status = "XX"
|
||||
if hasattr(agent, 'view'):
|
||||
display = agent.view()
|
||||
display.set('status', 'Blind-Bug detected. Restarting.')
|
||||
display.update(force=True)
|
||||
logging.info('[Fix_Services] Blind-Bug detected. Restarting.')
|
||||
logging.debug('[Fix_Services] Blind-Bug detected. Restarting.')
|
||||
try:
|
||||
self._tryTurningItOffAndOnAgain(agent)
|
||||
except Exception as err:
|
||||
logging.warning("[Fix_Services turnOffAndOn] %s" % repr(err))
|
||||
|
||||
else:
|
||||
logging.info("[Fix_Services] Logs look good!")
|
||||
self._status = ""
|
||||
logging.debug("[Fix_Services] Logs look good!")
|
||||
|
||||
except Exception as err:
|
||||
logging.error("[Fix_Services ip link show wlan0mon]: %s" % repr(err))
|
||||
try:
|
||||
self._status = "xx"
|
||||
self._tryTurningItOffAndOnAgain(agent)
|
||||
except Exception as err:
|
||||
logging.error("[Fix_Services OffNOn]: %s" % repr(err))
|
||||
@ -84,12 +80,12 @@ class FixServices(plugins.Plugin):
|
||||
# apparently this only gets messages from bettercap going to syslog, not from syslog
|
||||
def on_bcap_sys_log(self, agent, event):
|
||||
if re.search('wifi error while hopping to channel', event['data']['Message']):
|
||||
logging.info("[Fix_Services]SYSLOG MATCH: %s" % event['data']['Message'])
|
||||
logging.info("[Fix_Services]**** restarting wifi.recon")
|
||||
logging.debug("[Fix_Services]SYSLOG MATCH: %s" % event['data']['Message'])
|
||||
logging.debug("[Fix_Services]**** restarting wifi.recon")
|
||||
try:
|
||||
result = agent.run("wifi.recon off; wifi.recon on")
|
||||
if result["success"]:
|
||||
logging.info("[Fix_Services] wifi.recon flip: success!")
|
||||
logging.debug("[Fix_Services] wifi.recon flip: success!")
|
||||
if hasattr(agent, 'view'):
|
||||
display = agent.view()
|
||||
if display:
|
||||
@ -121,12 +117,12 @@ class FixServices(plugins.Plugin):
|
||||
|
||||
# Look for pattern 1
|
||||
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'):
|
||||
display = agent.view()
|
||||
display.set('status', 'Blind-Bug detected. Restarting.')
|
||||
display.update(force=True)
|
||||
logging.info('[Fix_Services] Blind-Bug detected. Restarting.')
|
||||
logging.debug('[Fix_Services] Blind-Bug detected. Restarting.')
|
||||
try:
|
||||
self._tryTurningItOffAndOnAgain(agent)
|
||||
except Exception as err:
|
||||
@ -134,20 +130,19 @@ class FixServices(plugins.Plugin):
|
||||
|
||||
# Look for pattern 2
|
||||
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'):
|
||||
display = agent.view()
|
||||
display.set('status', 'Wifi channel stuck. Restarting recon.')
|
||||
display.update(force=True)
|
||||
logging.info('[Fix_Services] Wifi channel stuck. Restarting recon.')
|
||||
logging.debug('[Fix_Services] Wifi channel stuck. Restarting recon.')
|
||||
|
||||
try:
|
||||
result = agent.run("wifi.recon off; wifi.recon on")
|
||||
if result["success"]:
|
||||
logging.info("[Fix_Services] wifi.recon flip: success!")
|
||||
logging.debug("[Fix_Services] wifi.recon flip: success!")
|
||||
if display:
|
||||
display.update(force=True, new_data={"status": "Wifi recon flipped!",
|
||||
"brcmfmac_status": self._status,
|
||||
"face": faces.COOL})
|
||||
else:
|
||||
print("Wifi recon flipped\nthat was easy!")
|
||||
@ -159,7 +154,7 @@ class FixServices(plugins.Plugin):
|
||||
|
||||
# Look for pattern 3
|
||||
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'):
|
||||
display = agent.view()
|
||||
display.set('status', 'Firmware has halted or crashed. Restarting wlan0mon.')
|
||||
@ -167,13 +162,13 @@ class FixServices(plugins.Plugin):
|
||||
try:
|
||||
# Run the monstart command to restart wlan0mon
|
||||
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:
|
||||
logging.error("[Fix_Services monstart]: %s" % repr(err))
|
||||
|
||||
# Look for pattern 4
|
||||
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'):
|
||||
display = agent.view()
|
||||
display.set('status', 'Restarting wlan0 now!')
|
||||
@ -181,10 +176,29 @@ class FixServices(plugins.Plugin):
|
||||
try:
|
||||
# Run the monstart command to restart wlan0mon
|
||||
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:
|
||||
logging.error("[Fix_Services monstart]: %s" % repr(err))
|
||||
|
||||
# Look for pattern 5
|
||||
elif len(self.pattern5.findall(other_other_last_lines)) >= 1:
|
||||
logging.debug("[Fix_Services] Bettercap has crashed!")
|
||||
if hasattr(agent, 'view'):
|
||||
display = agent.view()
|
||||
display.set('status', 'Restarting pwnagotchi!')
|
||||
display.update(force=True)
|
||||
os.system("systemctl restart bettercap")
|
||||
pwnagotchi.restart("AUTO")
|
||||
|
||||
# Look for pattern 6
|
||||
elif len(self.pattern6.findall(other_other_last_lines)) >= 1:
|
||||
logging.debug("[Fix_Services] Bettercap has crashed!")
|
||||
if hasattr(agent, 'view'):
|
||||
display = agent.view()
|
||||
display.set('status', 'Restarting pwnagotchi!')
|
||||
display.update(force=True)
|
||||
os.system("systemctl restart bettercap")
|
||||
pwnagotchi.restart("AUTO")
|
||||
else:
|
||||
print("logs look good")
|
||||
|
||||
@ -197,7 +211,7 @@ class FixServices(plugins.Plugin):
|
||||
elif level == "debug":
|
||||
logging.debug(message)
|
||||
else:
|
||||
logging.info(message)
|
||||
logging.debug(message)
|
||||
|
||||
if ui:
|
||||
ui.update(force=force, new_data=displayData)
|
||||
@ -212,17 +226,16 @@ class FixServices(plugins.Plugin):
|
||||
# avoid overlapping restarts, but allow it if it's been a while
|
||||
# (in case the last attempt failed before resetting "isReloadingMon")
|
||||
if self.isReloadingMon and (time.time() - self.LASTTRY) < 180:
|
||||
logging.info("[Fix_Services] Duplicate attempt ignored")
|
||||
logging.debug("[Fix_Services] Duplicate attempt ignored")
|
||||
else:
|
||||
self.isReloadingMon = True
|
||||
self.LASTTRY = time.time()
|
||||
|
||||
self._status = "BL"
|
||||
if hasattr(connection, 'view'):
|
||||
display = connection.view()
|
||||
if display:
|
||||
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:
|
||||
display = None
|
||||
|
||||
@ -238,9 +251,9 @@ class FixServices(plugins.Plugin):
|
||||
# is it up?
|
||||
try:
|
||||
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):
|
||||
logging.info("wlan0mon is up. Skip reset?")
|
||||
logging.debug("wlan0mon is up. Skip reset?")
|
||||
# not reliable, so don't skip just yet
|
||||
# print("wlan0mon is up. Skipping reset.")
|
||||
# self.isReloadingMon = False
|
||||
@ -261,11 +274,10 @@ class FixServices(plugins.Plugin):
|
||||
except Exception as 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:
|
||||
cmd_output = subprocess.check_output("monstop", shell=True)
|
||||
self._status = "dn"
|
||||
self.logPrintView("info", "[Fix_Services] wlan0mon down and deleted: %s" % cmd_output,
|
||||
display, {"status": "wlan0mon d-d-d-down!", "face": faces.BORED})
|
||||
except Exception as nope:
|
||||
@ -285,7 +297,6 @@ class FixServices(plugins.Plugin):
|
||||
cmd_output = subprocess.check_output("sudo modprobe -r brcmfmac", shell=True)
|
||||
self.logPrintView("info", "[Fix_Services] unloaded brcmfmac", display,
|
||||
{"status": "Turning it off #%s" % tries, "face": faces.SMART})
|
||||
self._status = "ul"
|
||||
|
||||
# reload the module
|
||||
try:
|
||||
@ -293,28 +304,24 @@ class FixServices(plugins.Plugin):
|
||||
cmd_output = subprocess.check_output("sudo modprobe brcmfmac", shell=True)
|
||||
|
||||
self.logPrintView("info", "[Fix_Services] reloaded brcmfmac")
|
||||
self._status = "rl"
|
||||
|
||||
# success! now make the mon0
|
||||
try:
|
||||
cmd_output = subprocess.check_output("monstart", shell=True)
|
||||
self.logPrintView("info", "[Fix_Services interface add wlan0mon worked #%s: %s"
|
||||
% (tries, cmd_output))
|
||||
self._status = "up"
|
||||
try:
|
||||
# try accessing mon0 in bettercap
|
||||
result = connection.run("set wifi.interface wlan0mon")
|
||||
if "success" in result:
|
||||
logging.info("[Fix_Services set wifi.interface wlan0mon worked!")
|
||||
self._status = ""
|
||||
self._count = self._count + 1
|
||||
logging.debug("[Fix_Services set wifi.interface wlan0mon worked!")
|
||||
# stop looping and get back to recon
|
||||
break
|
||||
else:
|
||||
logging.info(
|
||||
logging.debug(
|
||||
"[Fix_Services set wifi.interfaceface wlan0mon] failed? %s" % repr(result))
|
||||
except Exception as err:
|
||||
logging.info(
|
||||
logging.debug(
|
||||
"[Fix_Services set wifi.interface wlan0mon] except: %s" % repr(err))
|
||||
except Exception as cerr: #
|
||||
if not display:
|
||||
@ -333,41 +340,38 @@ class FixServices(plugins.Plugin):
|
||||
|
||||
tries = tries + 1
|
||||
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:
|
||||
print(" wlan0mon didn't make it. trying again")
|
||||
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()
|
||||
|
||||
# exited the loop, so hopefully it loaded
|
||||
if tries < 3:
|
||||
if display:
|
||||
display.update(force=True, new_data={"status": "And back on again...",
|
||||
"brcmfmac_status": self._status,
|
||||
"face": faces.INTENSE})
|
||||
else:
|
||||
print("And back on again...")
|
||||
logging.info("[Fix_Services] wlan0mon back up")
|
||||
logging.debug("[Fix_Services] wlan0mon back up")
|
||||
else:
|
||||
self.LASTTRY = time.time()
|
||||
|
||||
time.sleep(8 + tries * 2) # give it a bit before restarting recon in bettercap
|
||||
self.isReloadingMon = False
|
||||
|
||||
logging.info("[Fix_Services] re-enable recon")
|
||||
logging.debug("[Fix_Services] re-enable recon")
|
||||
try:
|
||||
result = connection.run("wifi.clear; wifi.recon on")
|
||||
|
||||
if "success" in result: # and result["success"] is True:
|
||||
self._status = ""
|
||||
if display:
|
||||
display.update(force=True, new_data={"status": "I can see again! (probably)",
|
||||
"brcmfmac_status": self._status,
|
||||
"face": faces.HAPPY})
|
||||
else:
|
||||
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.
|
||||
else:
|
||||
logging.error("[Fix_Services] wifi.recon did not start up")
|
||||
@ -388,25 +392,14 @@ class FixServices(plugins.Plugin):
|
||||
else:
|
||||
pos = (ui.width() / 2 + 35, ui.height() - 11)
|
||||
|
||||
logging.info("Got here")
|
||||
ui.add_element('brcmfmac_status', Text(color=BLACK, value='--', position=pos, font=fonts.Small))
|
||||
logging.debug("Got here")
|
||||
|
||||
# called when the ui is updated
|
||||
def on_ui_update(self, ui):
|
||||
# update those elements
|
||||
if self._status:
|
||||
ui.set('brcmfmac_status', "wlan0mon %s" % self._status)
|
||||
else:
|
||||
ui.set('brcmfmac_status', "rst#%s" % self._count)
|
||||
return
|
||||
|
||||
def on_unload(self, ui):
|
||||
with ui._lock:
|
||||
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
|
||||
return
|
||||
|
||||
|
||||
# run from command line to brute force a reload
|
||||
|
@ -98,7 +98,7 @@ class GPS(plugins.Plugin):
|
||||
lat_pos = (127, 74)
|
||||
lon_pos = (122, 84)
|
||||
alt_pos = (127, 94)
|
||||
elif ui.is_waveshare27inch():
|
||||
elif ui.is_waveshare2in7():
|
||||
lat_pos = (6, 120)
|
||||
lon_pos = (1, 135)
|
||||
alt_pos = (6, 150)
|
||||
|
@ -5,7 +5,6 @@ import glob
|
||||
import re
|
||||
|
||||
import pwnagotchi.grid as grid
|
||||
import pwnagotchi.plugins
|
||||
import pwnagotchi.plugins as plugins
|
||||
from pwnagotchi.utils import StatusFile, WifiInfo, extract_from_pcap
|
||||
from threading import Lock
|
||||
@ -87,7 +86,7 @@ class Grid(plugins.Plugin):
|
||||
agent.view().on_unread_messages(self.unread_messages, self.total_messages)
|
||||
|
||||
def check_handshakes(self, agent):
|
||||
logging.debug("checking pcaps")
|
||||
logging.debug("checking pcap's")
|
||||
config = agent.config()
|
||||
|
||||
pcap_files = glob.glob(os.path.join(agent.config()['bettercap']['handshakes'], "*.pcap"))
|
||||
|
@ -253,7 +253,6 @@ class Logtail(plugins.Plugin):
|
||||
"""
|
||||
logging.info("Logtail plugin loaded.")
|
||||
|
||||
|
||||
def on_webhook(self, path, request):
|
||||
if not self.ready:
|
||||
return "Plugin not ready"
|
||||
|
@ -142,6 +142,6 @@ class OnlineHashCrack(plugins.Plugin):
|
||||
for row in csv.DictReader(cracked_list):
|
||||
if row['password']:
|
||||
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:
|
||||
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:
|
||||
- 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
|
||||
- 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
|
||||
@ -87,7 +87,8 @@ class Webgpsmap(plugins.Plugin):
|
||||
# returns all positions
|
||||
try:
|
||||
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_mimetype = "application/json"
|
||||
response_header_contenttype = 'application/json'
|
||||
@ -100,7 +101,8 @@ class Webgpsmap(plugins.Plugin):
|
||||
self.ALREADY_SENT = list()
|
||||
json_data = json.dumps(self.load_gps_from_dir(self.config['bettercap']['handshakes']))
|
||||
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_status = 200
|
||||
response_mimetype = "application/xhtml+xml"
|
||||
@ -163,7 +165,8 @@ class Webgpsmap(plugins.Plugin):
|
||||
|
||||
all_files = os.listdir(handshake_dir)
|
||||
# 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 = []
|
||||
for filename_pcap in all_pcap_files:
|
||||
filename_base = filename_pcap[:-5] # remove ".pcap"
|
||||
@ -180,22 +183,18 @@ class Webgpsmap(plugins.Plugin):
|
||||
if check_for in all_files:
|
||||
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}")
|
||||
|
||||
if filename_position is not None:
|
||||
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:
|
||||
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:
|
||||
try:
|
||||
@ -213,9 +212,7 @@ class Webgpsmap(plugins.Plugin):
|
||||
pos_type = 'gps'
|
||||
elif pos.type() == PositionFile.GEO:
|
||||
pos_type = 'geo'
|
||||
elif pos.type() == PositionFile.PAWGPS:
|
||||
pos_type = 'paw'
|
||||
gps_data[ssid+"_"+mac] = {
|
||||
gps_data[ssid + "_" + mac] = {
|
||||
'ssid': ssid,
|
||||
'mac': mac,
|
||||
'type': pos_type,
|
||||
@ -224,7 +221,7 @@ class Webgpsmap(plugins.Plugin):
|
||||
'acc': pos.accuracy(),
|
||||
'ts_first': pos.timestamp_first(),
|
||||
'ts_last': pos.timestamp_last(),
|
||||
}
|
||||
}
|
||||
|
||||
# get ap password if exist
|
||||
check_for = os.path.basename(pos_file).split(".")[0] + ".pcap.cracked"
|
||||
@ -265,7 +262,6 @@ class PositionFile:
|
||||
"""
|
||||
GPS = 1
|
||||
GEO = 2
|
||||
PAWGPS = 3
|
||||
|
||||
def __init__(self, path):
|
||||
self._file = path
|
||||
@ -282,7 +278,7 @@ class PositionFile:
|
||||
"""
|
||||
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:
|
||||
mac = parsed_mac.groups()[0]
|
||||
return mac
|
||||
@ -292,7 +288,7 @@ class PositionFile:
|
||||
"""
|
||||
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:
|
||||
return parsed_ssid.groups()[0]
|
||||
return None
|
||||
@ -354,8 +350,6 @@ class PositionFile:
|
||||
return PositionFile.GPS
|
||||
if self._file.endswith('.geo.json'):
|
||||
return PositionFile.GEO
|
||||
if self._file.endswith('.paw-gps.json'):
|
||||
return PositionFile.PAWGPS
|
||||
return None
|
||||
|
||||
def lat(self):
|
||||
@ -402,9 +396,7 @@ class PositionFile:
|
||||
|
||||
def accuracy(self):
|
||||
if self.type() == PositionFile.GPS:
|
||||
return 50.0 # a default
|
||||
if self.type() == PositionFile.PAWGPS:
|
||||
return 50.0 # a default
|
||||
return 50.0 # a default
|
||||
if self.type() == PositionFile.GEO:
|
||||
try:
|
||||
return self._json['accuracy']
|
||||
|
@ -28,6 +28,39 @@ class Display(View):
|
||||
def is_lcdhat(self):
|
||||
return self._implementation.name == 'lcdhat'
|
||||
|
||||
def is_wavesharelcd0in96(self):
|
||||
return self._implementation.name == 'wavesharelcd0in96'
|
||||
|
||||
def is_wavesharelcd1in3(self):
|
||||
return self._implementation.name == 'wavesharelcd1in3'
|
||||
|
||||
def is_wavesharelcd1in8(self):
|
||||
return self._implementation.name == 'wavesharelcd1in8'
|
||||
|
||||
def is_wavesharelcd1in9(self):
|
||||
return self._implementation.name == 'wavesharelcd1in9'
|
||||
|
||||
def is_wavesharelcd1in14(self):
|
||||
return self._implementation.name == 'wavesharelcd1in14'
|
||||
|
||||
def is_wavesharelcd1in28(self):
|
||||
return self._implementation.name == 'wavesharelcd1in28'
|
||||
|
||||
def is_wavesharelcd1in47(self):
|
||||
return self._implementation.name == 'wavesharelcd1in47'
|
||||
|
||||
def is_wavesharelcd1in54(self):
|
||||
return self._implementation.name == 'wavesharelcd1in54'
|
||||
|
||||
def is_wavesharelcd1in69(self):
|
||||
return self._implementation.name == 'wavesharelcd1in69'
|
||||
|
||||
def is_wavesharelcd2in0(self):
|
||||
return self._implementation.name == 'wavesharelcd2in0'
|
||||
|
||||
def is_wavesharelcd2in4(self):
|
||||
return self._implementation.name == 'wavesharelcd2in4'
|
||||
|
||||
def is_waveshare144lcd(self):
|
||||
return self._implementation.name == 'waveshare144lcd'
|
||||
|
||||
@ -118,6 +151,9 @@ class Display(View):
|
||||
def is_waveshare2in66g(self):
|
||||
return self._implementation.name == 'waveshare2in66g'
|
||||
|
||||
def is_weact2in9(self):
|
||||
return self._implementation.name == 'weact2in9'
|
||||
|
||||
def is_waveshare3in0g(self):
|
||||
return self._implementation.name == 'waveshare3in0g'
|
||||
|
||||
@ -151,6 +187,12 @@ class Display(View):
|
||||
def is_waveshare5in65f(self):
|
||||
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):
|
||||
return self._implementation.name == 'waveshare5in83'
|
||||
|
||||
@ -193,6 +235,9 @@ class Display(View):
|
||||
def is_inky(self):
|
||||
return self._implementation.name == 'inky'
|
||||
|
||||
def is_dummy_display(self):
|
||||
return self._implementation.name == 'dummydisplay'
|
||||
|
||||
def is_papirus(self):
|
||||
return self._implementation.name == 'papirus'
|
||||
|
||||
@ -208,9 +253,24 @@ class Display(View):
|
||||
def is_displayhatmini(self):
|
||||
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):
|
||||
return self._implementation.name == 'waveshare35lcd'
|
||||
|
||||
def is_adfruit213v3(self):
|
||||
return self._implementation.name == 'adafruit2in13_v3'
|
||||
|
||||
def is_waveshare_any(self):
|
||||
return self.is_waveshare_v1() or self.is_waveshare_v2()
|
||||
|
||||
|
@ -27,6 +27,7 @@ PNG = False
|
||||
POSITION_X = 0
|
||||
POSITION_Y = 40
|
||||
|
||||
|
||||
def load_from_config(config):
|
||||
for face_name, face_value in config.items():
|
||||
globals()[face_name.upper()] = face_value
|
||||
|
@ -1,259 +1,333 @@
|
||||
from pwnagotchi.ui.hw.inky import Inky
|
||||
from pwnagotchi.ui.hw.papirus import Papirus
|
||||
from pwnagotchi.ui.hw.oledhat import OledHat
|
||||
from pwnagotchi.ui.hw.lcdhat import LcdHat
|
||||
from pwnagotchi.ui.hw.dfrobot import DFRobotV1
|
||||
from pwnagotchi.ui.hw.dfrobot_v2 import DFRobotV2
|
||||
from pwnagotchi.ui.hw.waveshare2in13 import WaveshareV1
|
||||
from pwnagotchi.ui.hw.waveshare2in13_V2 import WaveshareV2
|
||||
from pwnagotchi.ui.hw.waveshare2in13_V3 import WaveshareV3
|
||||
from pwnagotchi.ui.hw.waveshare2in13_V4 import WaveshareV4
|
||||
from pwnagotchi.ui.hw.waveshare2in7 import Waveshare27inch
|
||||
from pwnagotchi.ui.hw.waveshare2in7_V2 import Waveshare27inchV2
|
||||
from pwnagotchi.ui.hw.waveshare2in9 import Waveshare29inch
|
||||
from pwnagotchi.ui.hw.waveshare2in9_V2 import Waveshare29inchV2
|
||||
from pwnagotchi.ui.hw.waveshare1in44lcd import Waveshare144lcd
|
||||
from pwnagotchi.ui.hw.waveshare1in54b import Waveshare154inchb
|
||||
from pwnagotchi.ui.hw.waveshare2in13bc import Waveshare213bc
|
||||
from pwnagotchi.ui.hw.waveshare2in13d import Waveshare213d
|
||||
from pwnagotchi.ui.hw.waveshare2in13g import Waveshare2in13g
|
||||
from pwnagotchi.ui.hw.waveshare2in13b_V4 import Waveshare213bV4
|
||||
from pwnagotchi.ui.hw.waveshare3in5lcd import Waveshare35lcd
|
||||
from pwnagotchi.ui.hw.spotpear24in import Spotpear24inch
|
||||
from pwnagotchi.ui.hw.displayhatmini import DisplayHatMini
|
||||
from pwnagotchi.ui.hw.waveshare1in02 import Waveshare1in02
|
||||
from pwnagotchi.ui.hw.waveshare1in54 import Waveshare154
|
||||
from pwnagotchi.ui.hw.waveshare1in54_V2 import Waveshare154V2
|
||||
from pwnagotchi.ui.hw.waveshare1in54b_V2 import Waveshare154bV2
|
||||
from pwnagotchi.ui.hw.waveshare1in54c import Waveshare1in54c
|
||||
from pwnagotchi.ui.hw.waveshare1in64g import Waveshare1in64g
|
||||
from pwnagotchi.ui.hw.waveshare2in7b import Waveshare27b
|
||||
from pwnagotchi.ui.hw.waveshare2in7b_V2 import Waveshare27bV2
|
||||
from pwnagotchi.ui.hw.waveshare2in9b_V3 import Waveshare29bV3
|
||||
from pwnagotchi.ui.hw.waveshare2in9b_V4 import Waveshare29bV4
|
||||
from pwnagotchi.ui.hw.waveshare2in9bc import Waveshare2in9bc
|
||||
from pwnagotchi.ui.hw.waveshare2in9d import Waveshare2in9d
|
||||
from pwnagotchi.ui.hw.waveshare2in13b_V3 import Waveshare2in13bV3
|
||||
from pwnagotchi.ui.hw.waveshare2in36g import Waveshare2in36g
|
||||
from pwnagotchi.ui.hw.waveshare2in66 import Waveshare2in66
|
||||
from pwnagotchi.ui.hw.waveshare2in66b import Waveshare2in66b
|
||||
from pwnagotchi.ui.hw.waveshare2in66g import Waveshare2in66g
|
||||
from pwnagotchi.ui.hw.waveshare3in0g import Waveshare3in0g
|
||||
from pwnagotchi.ui.hw.waveshare3in7 import Waveshare3in7
|
||||
from pwnagotchi.ui.hw.waveshare3in52 import Waveshare3in52
|
||||
from pwnagotchi.ui.hw.waveshare4in01f import Waveshare4in01f
|
||||
from pwnagotchi.ui.hw.waveshare4in2 import Waveshare4in2
|
||||
from pwnagotchi.ui.hw.waveshare4in2_V2 import Waveshare4in2V2
|
||||
from pwnagotchi.ui.hw.waveshare4in2b_V2 import Waveshare4in2bV2
|
||||
from pwnagotchi.ui.hw.waveshare4in2bc import Waveshare4in2bc
|
||||
from pwnagotchi.ui.hw.waveshare4in26 import Waveshare4in26
|
||||
from pwnagotchi.ui.hw.waveshare4in37g import Waveshare4in37g
|
||||
from pwnagotchi.ui.hw.waveshare5in65f import Waveshare5in65f
|
||||
from pwnagotchi.ui.hw.waveshare5in83 import Waveshare5in83
|
||||
from pwnagotchi.ui.hw.waveshare5in83_V2 import Waveshare5in83V2
|
||||
from pwnagotchi.ui.hw.waveshare5in83b_V2 import Waveshare5in83bV2
|
||||
from pwnagotchi.ui.hw.waveshare5in83bc import Waveshare5in83bc
|
||||
from pwnagotchi.ui.hw.waveshare7in3f import Waveshare7in3f
|
||||
from pwnagotchi.ui.hw.waveshare7in3g import Waveshare7in3g
|
||||
from pwnagotchi.ui.hw.waveshare7in5 import Waveshare7in5
|
||||
from pwnagotchi.ui.hw.waveshare7in5_HD import Waveshare7in5HD
|
||||
from pwnagotchi.ui.hw.waveshare7in5_V2 import Waveshare7in5V2
|
||||
from pwnagotchi.ui.hw.waveshare7in5b_HD import Waveshare7in5bHD
|
||||
from pwnagotchi.ui.hw.waveshare7in5b_V2 import Waveshare7in5bV2
|
||||
from pwnagotchi.ui.hw.waveshare7in5bc import Waveshare7in5bc
|
||||
from pwnagotchi.ui.hw.waveshare13in3k import Waveshare13in3k
|
||||
|
||||
|
||||
def display_for(config):
|
||||
# config has been normalized already in utils.load_config
|
||||
if config['ui']['display']['type'] == 'inky':
|
||||
from pwnagotchi.ui.hw.inky import Inky
|
||||
return Inky(config)
|
||||
|
||||
elif config['ui']['display']['type'] == 'wavesharelcd0in96':
|
||||
from pwnagotchi.ui.hw.wavesharelcd0in96 import Wavesharelcd0in96
|
||||
return Wavesharelcd0in96(config)
|
||||
|
||||
elif config['ui']['display']['type'] == 'wavesharelcd1in3':
|
||||
from pwnagotchi.ui.hw.wavesharelcd1in3 import Wavesharelcd1in3
|
||||
return Wavesharelcd1in3(config)
|
||||
|
||||
elif config['ui']['display']['type'] == 'wavesharelcd1in8':
|
||||
from pwnagotchi.ui.hw.wavesharelcd1in8 import Wavesharelcd1in8
|
||||
return Wavesharelcd1in8(config)
|
||||
|
||||
elif config['ui']['display']['type'] == 'wavesharelcd1in9':
|
||||
from pwnagotchi.ui.hw.wavesharelcd1in9 import Wavesharelcd1in9
|
||||
return Wavesharelcd1in9(config)
|
||||
|
||||
elif config['ui']['display']['type'] == 'wavesharelcd1in14':
|
||||
from pwnagotchi.ui.hw.wavesharelcd1in14 import Wavesharelcd1in14
|
||||
return Wavesharelcd1in14(config)
|
||||
|
||||
elif config['ui']['display']['type'] == 'wavesharelcd1in28':
|
||||
from pwnagotchi.ui.hw.wavesharelcd1in28 import Wavesharelcd1in28
|
||||
return Wavesharelcd1in28(config)
|
||||
|
||||
elif config['ui']['display']['type'] == 'wavesharelcd1in47':
|
||||
from pwnagotchi.ui.hw.wavesharelcd1in47 import Wavesharelcd1in47
|
||||
return Wavesharelcd1in47(config)
|
||||
|
||||
elif config['ui']['display']['type'] == 'wavesharelcd1in54':
|
||||
from pwnagotchi.ui.hw.wavesharelcd1in54 import Wavesharelcd1in54
|
||||
return Wavesharelcd1in54(config)
|
||||
|
||||
elif config['ui']['display']['type'] == 'wavesharelcd1in69':
|
||||
from pwnagotchi.ui.hw.wavesharelcd1in69 import Wavesharelcd1in69
|
||||
return Wavesharelcd1in69(config)
|
||||
|
||||
elif config['ui']['display']['type'] == 'wavesharelcd2in0':
|
||||
from pwnagotchi.ui.hw.wavesharelcd2in0 import Wavesharelcd2in0
|
||||
return Wavesharelcd2in0(config)
|
||||
|
||||
elif config['ui']['display']['type'] == 'wavesharelcd2in4':
|
||||
from pwnagotchi.ui.hw.wavesharelcd2in4 import Wavesharelcd2in4
|
||||
return Wavesharelcd2in4(config)
|
||||
|
||||
elif config['ui']['display']['type'] == 'dummydisplay':
|
||||
from pwnagotchi.ui.hw.dummydisplay import DummyDisplay
|
||||
return DummyDisplay(config)
|
||||
|
||||
elif config['ui']['display']['type'] == 'papirus':
|
||||
from pwnagotchi.ui.hw.papirus import Papirus
|
||||
return Papirus(config)
|
||||
|
||||
elif config['ui']['display']['type'] == 'oledhat':
|
||||
from pwnagotchi.ui.hw.oledhat import OledHat
|
||||
return OledHat(config)
|
||||
|
||||
elif config['ui']['display']['type'] == 'lcdhat':
|
||||
from pwnagotchi.ui.hw.lcdhat import LcdHat
|
||||
return LcdHat(config)
|
||||
|
||||
elif config['ui']['display']['type'] == 'dfrobot_1':
|
||||
from pwnagotchi.ui.hw.dfrobot import DFRobotV1
|
||||
return DFRobotV1(config)
|
||||
|
||||
elif config['ui']['display']['type'] == 'dfrobot_2':
|
||||
from pwnagotchi.ui.hw.dfrobot_v2 import DFRobotV2
|
||||
return DFRobotV2(config)
|
||||
|
||||
elif config['ui']['display']['type'] == 'waveshare144lcd':
|
||||
from pwnagotchi.ui.hw.waveshare1in44lcd import Waveshare144lcd
|
||||
return Waveshare144lcd(config)
|
||||
|
||||
elif config['ui']['display']['type'] == 'waveshare35lcd':
|
||||
from pwnagotchi.ui.hw.waveshare3in5lcd import Waveshare35lcd
|
||||
return Waveshare35lcd(config)
|
||||
|
||||
elif config['ui']['display']['type'] == 'spotpear24inch':
|
||||
from pwnagotchi.ui.hw.spotpear24in import Spotpear24inch
|
||||
return Spotpear24inch(config)
|
||||
|
||||
elif config['ui']['display']['type'] == 'displayhatmini':
|
||||
from pwnagotchi.ui.hw.displayhatmini import DisplayHatMini
|
||||
return DisplayHatMini(config)
|
||||
|
||||
elif config['ui']['display']['type'] == 'pirateaudio':
|
||||
from pwnagotchi.ui.hw.pirateaudio import PirateAudio
|
||||
return PirateAudio(config)
|
||||
|
||||
elif config['ui']['display']['type'] == 'pitft':
|
||||
from pwnagotchi.ui.hw.pitft import Pitft
|
||||
return Pitft(config)
|
||||
|
||||
elif config['ui']['display']['type'] == 'tftbonnet':
|
||||
from pwnagotchi.ui.hw.tftbonnet import TftBonnet
|
||||
return TftBonnet(config)
|
||||
|
||||
elif config['ui']['display']['type'] == 'waveshareoledlcd':
|
||||
from pwnagotchi.ui.hw.waveshareoledlcd import Waveshareoledlcd
|
||||
return Waveshareoledlcd(config)
|
||||
|
||||
elif config['ui']['display']['type'] == 'waveshare1in02':
|
||||
from pwnagotchi.ui.hw.waveshare1in02 import Waveshare1in02
|
||||
return Waveshare1in02(config)
|
||||
|
||||
elif config['ui']['display']['type'] == 'waveshare1in54':
|
||||
from pwnagotchi.ui.hw.waveshare1in54 import Waveshare154
|
||||
return Waveshare154(config)
|
||||
|
||||
elif config['ui']['display']['type'] == 'waveshare1in54_v2':
|
||||
from pwnagotchi.ui.hw.waveshare1in54_V2 import Waveshare154V2
|
||||
return Waveshare154V2(config)
|
||||
|
||||
elif config['ui']['display']['type'] == 'waveshare1in54b':
|
||||
from pwnagotchi.ui.hw.waveshare1in54b import Waveshare154inchb
|
||||
return Waveshare154inchb(config)
|
||||
|
||||
elif config['ui']['display']['type'] == 'waveshare1in54b_v2':
|
||||
from pwnagotchi.ui.hw.waveshare1in54b_V2 import Waveshare154bV2
|
||||
return Waveshare154bV2(config)
|
||||
|
||||
elif config['ui']['display']['type'] == 'waveshare1in54c':
|
||||
from pwnagotchi.ui.hw.waveshare1in54c import Waveshare1in54c
|
||||
return Waveshare1in54c(config)
|
||||
|
||||
elif config['ui']['display']['type'] == 'waveshare1in64g':
|
||||
from pwnagotchi.ui.hw.waveshare1in64g import Waveshare1in64g
|
||||
return Waveshare1in64g(config)
|
||||
|
||||
elif config['ui']['display']['type'] == 'waveshare2in7':
|
||||
from pwnagotchi.ui.hw.waveshare2in7 import Waveshare27inch
|
||||
return Waveshare27inch(config)
|
||||
|
||||
elif config['ui']['display']['type'] == 'waveshare2in7_v2':
|
||||
from pwnagotchi.ui.hw.waveshare2in7_V2 import Waveshare27inchV2
|
||||
return Waveshare27inchV2(config)
|
||||
|
||||
elif config['ui']['display']['type'] == 'waveshare2in7b':
|
||||
from pwnagotchi.ui.hw.waveshare2in7b import Waveshare27b
|
||||
return Waveshare27b(config)
|
||||
|
||||
elif config['ui']['display']['type'] == 'waveshare2in7b_v2':
|
||||
from pwnagotchi.ui.hw.waveshare2in7b_V2 import Waveshare27bV2
|
||||
return Waveshare27bV2(config)
|
||||
|
||||
elif config['ui']['display']['type'] == 'waveshare2in9':
|
||||
from pwnagotchi.ui.hw.waveshare2in9 import Waveshare29inch
|
||||
return Waveshare29inch(config)
|
||||
|
||||
elif config['ui']['display']['type'] == 'waveshare2in9bc':
|
||||
from pwnagotchi.ui.hw.waveshare2in9bc import Waveshare2in9bc
|
||||
return Waveshare2in9bc(config)
|
||||
|
||||
elif config['ui']['display']['type'] == 'waveshare2in9d':
|
||||
from pwnagotchi.ui.hw.waveshare2in9d import Waveshare2in9d
|
||||
return Waveshare2in9d(config)
|
||||
|
||||
elif config['ui']['display']['type'] == 'waveshare2in9_v2':
|
||||
from pwnagotchi.ui.hw.waveshare2in9_V2 import Waveshare29inchV2
|
||||
return Waveshare29inchV2(config)
|
||||
|
||||
elif config['ui']['display']['type'] == 'waveshare2in9b_v3':
|
||||
from pwnagotchi.ui.hw.waveshare2in9b_V3 import Waveshare29bV3
|
||||
return Waveshare29bV3(config)
|
||||
|
||||
elif config['ui']['display']['type'] == 'waveshare2in9b_v4':
|
||||
from pwnagotchi.ui.hw.waveshare2in9b_V4 import Waveshare29bV4
|
||||
return Waveshare29bV4(config)
|
||||
|
||||
elif config['ui']['display']['type'] == 'waveshare_1':
|
||||
from pwnagotchi.ui.hw.waveshare2in13 import WaveshareV1
|
||||
return WaveshareV1(config)
|
||||
|
||||
elif config['ui']['display']['type'] == 'waveshare_2':
|
||||
from pwnagotchi.ui.hw.waveshare2in13_V2 import WaveshareV2
|
||||
return WaveshareV2(config)
|
||||
|
||||
elif config['ui']['display']['type'] == 'waveshare_3':
|
||||
from pwnagotchi.ui.hw.waveshare2in13_V3 import WaveshareV3
|
||||
return WaveshareV3(config)
|
||||
|
||||
elif config['ui']['display']['type'] == 'waveshare_4':
|
||||
from pwnagotchi.ui.hw.waveshare2in13_V4 import WaveshareV4
|
||||
return WaveshareV4(config)
|
||||
|
||||
elif config['ui']['display']['type'] == 'adafruit2in13_v3':
|
||||
from pwnagotchi.ui.hw.adafruit2in13 import Adafruit2in13V3
|
||||
return Adafruit2in13V3(config)
|
||||
|
||||
elif config['ui']['display']['type'] == 'waveshare2in13bc':
|
||||
from pwnagotchi.ui.hw.waveshare2in13bc import Waveshare213bc
|
||||
return Waveshare213bc(config)
|
||||
|
||||
elif config['ui']['display']['type'] == 'waveshare2in13d':
|
||||
from pwnagotchi.ui.hw.waveshare2in13d import Waveshare213d
|
||||
return Waveshare213d(config)
|
||||
|
||||
elif config['ui']['display']['type'] == 'waveshare2in13b_v3':
|
||||
from pwnagotchi.ui.hw.waveshare2in13b_V3 import Waveshare2in13bV3
|
||||
return Waveshare2in13bV3(config)
|
||||
|
||||
elif config['ui']['display']['type'] == 'waveshare2in13b_v4':
|
||||
from pwnagotchi.ui.hw.waveshare2in13b_V4 import Waveshare213bV4
|
||||
return Waveshare213bV4(config)
|
||||
|
||||
elif config['ui']['display']['type'] == 'waveshare2in13g':
|
||||
from pwnagotchi.ui.hw.waveshare2in13g import Waveshare2in13g
|
||||
return Waveshare2in13g(config)
|
||||
|
||||
elif config['ui']['display']['type'] == 'waveshare2in36g':
|
||||
from pwnagotchi.ui.hw.waveshare2in36g import Waveshare2in36g
|
||||
return Waveshare2in36g(config)
|
||||
|
||||
elif config['ui']['display']['type'] == 'waveshare2in66':
|
||||
from pwnagotchi.ui.hw.waveshare2in66 import Waveshare2in66
|
||||
return Waveshare2in66(config)
|
||||
|
||||
elif config['ui']['display']['type'] == 'waveshare2in66b':
|
||||
from pwnagotchi.ui.hw.waveshare2in66b import Waveshare2in66b
|
||||
return Waveshare2in66b(config)
|
||||
|
||||
elif config['ui']['display']['type'] == 'waveshare2in66g':
|
||||
from pwnagotchi.ui.hw.waveshare2in66g import Waveshare2in66g
|
||||
return Waveshare2in66g(config)
|
||||
|
||||
elif config['ui']['display']['type'] == 'waveshare3in0g':
|
||||
from pwnagotchi.ui.hw.waveshare3in0g import Waveshare3in0g
|
||||
return Waveshare3in0g(config)
|
||||
|
||||
elif config['ui']['display']['type'] == 'waveshare3in7':
|
||||
from pwnagotchi.ui.hw.waveshare3in7 import Waveshare3in7
|
||||
return Waveshare3in7(config)
|
||||
|
||||
elif config['ui']['display']['type'] == 'waveshare3in52':
|
||||
from pwnagotchi.ui.hw.waveshare3in52 import Waveshare3in52
|
||||
return Waveshare3in52(config)
|
||||
|
||||
elif config['ui']['display']['type'] == 'waveshare4in01f':
|
||||
from pwnagotchi.ui.hw.waveshare4in01f import Waveshare4in01f
|
||||
return Waveshare4in01f(config)
|
||||
|
||||
elif config['ui']['display']['type'] == 'waveshare4in2':
|
||||
from pwnagotchi.ui.hw.waveshare4in2 import Waveshare4in2
|
||||
return Waveshare4in2(config)
|
||||
|
||||
elif config['ui']['display']['type'] == 'waveshare4in2_v2':
|
||||
from pwnagotchi.ui.hw.waveshare4in2_V2 import Waveshare4in2V2
|
||||
return Waveshare4in2V2(config)
|
||||
|
||||
elif config['ui']['display']['type'] == 'waveshare4in2b_v2':
|
||||
from pwnagotchi.ui.hw.waveshare4in2b_V2 import Waveshare4in2bV2
|
||||
return Waveshare4in2bV2(config)
|
||||
|
||||
elif config['ui']['display']['type'] == 'waveshare4in2bc':
|
||||
from pwnagotchi.ui.hw.waveshare4in2bc import Waveshare4in2bc
|
||||
return Waveshare4in2bc(config)
|
||||
|
||||
elif config['ui']['display']['type'] == 'waveshare4in26':
|
||||
from pwnagotchi.ui.hw.waveshare4in26 import Waveshare4in26
|
||||
return Waveshare4in26(config)
|
||||
|
||||
elif config['ui']['display']['type'] == 'waveshare4in37g':
|
||||
from pwnagotchi.ui.hw.waveshare4in37g import Waveshare4in37g
|
||||
return Waveshare4in37g(config)
|
||||
|
||||
elif config['ui']['display']['type'] == 'waveshare5in65f':
|
||||
from pwnagotchi.ui.hw.waveshare5in65f import Waveshare5in65f
|
||||
return Waveshare5in65f(config)
|
||||
|
||||
elif config['ui']['display']['type'] == 'waveshare5in79':
|
||||
from pwnagotchi.ui.hw.waveshare5in79 import Waveshare5in79
|
||||
return Waveshare5in79(config)
|
||||
|
||||
elif config['ui']['display']['type'] == 'waveshare5in79b':
|
||||
from pwnagotchi.ui.hw.waveshare5in79b import Waveshare5in79b
|
||||
return Waveshare5in79b(config)
|
||||
|
||||
elif config['ui']['display']['type'] == 'waveshare5in83':
|
||||
from pwnagotchi.ui.hw.waveshare5in83 import Waveshare5in83
|
||||
return Waveshare5in83(config)
|
||||
|
||||
elif config['ui']['display']['type'] == 'waveshare5in83_v2':
|
||||
from pwnagotchi.ui.hw.waveshare5in83_V2 import Waveshare5in83V2
|
||||
return Waveshare5in83V2(config)
|
||||
|
||||
elif config['ui']['display']['type'] == 'waveshare5in83b_v2':
|
||||
from pwnagotchi.ui.hw.waveshare5in83b_V2 import Waveshare5in83bV2
|
||||
return Waveshare5in83bV2(config)
|
||||
|
||||
elif config['ui']['display']['type'] == 'waveshare5in83bc':
|
||||
from pwnagotchi.ui.hw.waveshare5in83bc import Waveshare5in83bc
|
||||
return Waveshare5in83bc(config)
|
||||
|
||||
elif config['ui']['display']['type'] == 'waveshare7in3f':
|
||||
from pwnagotchi.ui.hw.waveshare7in3f import Waveshare7in3f
|
||||
return Waveshare7in3f(config)
|
||||
|
||||
elif config['ui']['display']['type'] == 'waveshare7in3g':
|
||||
from pwnagotchi.ui.hw.waveshare7in3g import Waveshare7in3g
|
||||
return Waveshare7in3g(config)
|
||||
|
||||
elif config['ui']['display']['type'] == 'waveshare7in5':
|
||||
from pwnagotchi.ui.hw.waveshare7in5 import Waveshare7in5
|
||||
return Waveshare7in5(config)
|
||||
|
||||
elif config['ui']['display']['type'] == 'waveshare7in5_HD':
|
||||
from pwnagotchi.ui.hw.waveshare7in5_HD import Waveshare7in5HD
|
||||
return Waveshare7in5HD(config)
|
||||
|
||||
elif config['ui']['display']['type'] == 'waveshare7in5_v2':
|
||||
from pwnagotchi.ui.hw.waveshare7in5_V2 import Waveshare7in5V2
|
||||
return Waveshare7in5V2(config)
|
||||
|
||||
elif config['ui']['display']['type'] == 'waveshare7in5b_HD':
|
||||
from pwnagotchi.ui.hw.waveshare7in5b_HD import Waveshare7in5bHD
|
||||
return Waveshare7in5bHD(config)
|
||||
|
||||
elif config['ui']['display']['type'] == 'waveshare7in5b_v2':
|
||||
from pwnagotchi.ui.hw.waveshare7in5b_V2 import Waveshare7in5bV2
|
||||
return Waveshare7in5bV2(config)
|
||||
|
||||
elif config['ui']['display']['type'] == 'waveshare7in5bc':
|
||||
from pwnagotchi.ui.hw.waveshare7in5bc import Waveshare7in5bc
|
||||
return Waveshare7in5bc(config)
|
||||
|
||||
elif config['ui']['display']['type'] == 'waveshare13in3k':
|
||||
from pwnagotchi.ui.hw.waveshare13in3k import Waveshare13in3k
|
||||
return Waveshare13in3k(config)
|
||||
|
45
pwnagotchi/ui/hw/adafruit2in13.py
Normal file
45
pwnagotchi/ui/hw/adafruit2in13.py
Normal file
@ -0,0 +1,45 @@
|
||||
import logging
|
||||
|
||||
import pwnagotchi.ui.fonts as fonts
|
||||
from pwnagotchi.ui.hw.base import DisplayImpl
|
||||
|
||||
|
||||
class Adafruit2in13V3(DisplayImpl):
|
||||
def __init__(self, config):
|
||||
super(Adafruit2in13V3, self).__init__(config, 'adafruit2in13_v3')
|
||||
|
||||
def layout(self):
|
||||
fonts.setup(10, 9, 10, 35, 25, 9)
|
||||
self._layout['width'] = 250
|
||||
self._layout['height'] = 122
|
||||
self._layout['face'] = (0, 40)
|
||||
self._layout['name'] = (5, 20)
|
||||
self._layout['channel'] = (0, 0)
|
||||
self._layout['aps'] = (28, 0)
|
||||
self._layout['uptime'] = (185, 0)
|
||||
self._layout['line1'] = [0, 14, 250, 14]
|
||||
self._layout['line2'] = [0, 108, 250, 108]
|
||||
self._layout['friend_face'] = (0, 92)
|
||||
self._layout['friend_name'] = (40, 94)
|
||||
self._layout['shakes'] = (0, 109)
|
||||
self._layout['mode'] = (225, 109)
|
||||
self._layout['status'] = {
|
||||
'pos': (125, 20),
|
||||
'font': fonts.status_font(fonts.Medium),
|
||||
'max': 20
|
||||
}
|
||||
return self._layout
|
||||
|
||||
def initialize(self):
|
||||
logging.info("initializing adafruit 2in13 V3 display")
|
||||
from pwnagotchi.ui.hw.libs.adafruit.v2in13_v3.epd2in13_v3 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):
|
||||
self._display.Clear(0xFF)
|
@ -34,10 +34,10 @@ class DisplayHatMini(DisplayImpl):
|
||||
def initialize(self):
|
||||
logging.info("initializing Display Hat Mini")
|
||||
from pwnagotchi.ui.hw.libs.pimoroni.displayhatmini.ST7789 import ST7789
|
||||
self._display = ST7789(0,1,9,13)
|
||||
self._display = ST7789(0, 1, 9, 13)
|
||||
|
||||
def render(self, canvas):
|
||||
self._display.display(canvas)
|
||||
|
||||
def clear(self):
|
||||
self._display.clear()
|
||||
self._display.clear()
|
||||
|
43
pwnagotchi/ui/hw/dummydisplay.py
Normal file
43
pwnagotchi/ui/hw/dummydisplay.py
Normal file
@ -0,0 +1,43 @@
|
||||
import logging
|
||||
|
||||
import pwnagotchi.ui.fonts as fonts
|
||||
from pwnagotchi.ui.hw.base import DisplayImpl
|
||||
|
||||
|
||||
class DummyDisplay(DisplayImpl):
|
||||
def __init__(self, config):
|
||||
super(DummyDisplay, self).__init__(config, 'DummyDisplay')
|
||||
|
||||
def layout(self):
|
||||
width = 480 if 'width' not in self.config else self.config['width']
|
||||
height = 720 if 'height' not in self.config else self.config['height']
|
||||
fonts.setup(int(height/30), int(height/40), int(height/30), int(height/6), int(height/30), int(height/35))
|
||||
self._layout['width'] = width
|
||||
self._layout['height'] = height
|
||||
self._layout['face'] = (0, int(width/12))
|
||||
self._layout['name'] = (5, int(width/25))
|
||||
self._layout['channel'] = (0, 0)
|
||||
self._layout['aps'] = (int(width/8), 0)
|
||||
self._layout['uptime'] = (width-int(width/12), 0)
|
||||
self._layout['line1'] = [0, int(height/32), width, int(height/32)]
|
||||
self._layout['line2'] = [0, height-int(height/25)-1, width, height-int(height/25)-1]
|
||||
self._layout['friend_face'] = (0, int(height/10))
|
||||
self._layout['friend_name'] = (int(width/12), int(height/10))
|
||||
self._layout['shakes'] = (0, height-int(height/25))
|
||||
self._layout['mode'] = (width-int(width/8), height - int (height/25))
|
||||
lw, lh = fonts.Small.getsize("W")
|
||||
self._layout['status'] = {
|
||||
'pos': (int(width/48), int(height/3)),
|
||||
'font': fonts.status_font(fonts.Small),
|
||||
'max': int(width / lw)
|
||||
}
|
||||
return self._layout
|
||||
|
||||
def initialize(self):
|
||||
return
|
||||
|
||||
def render(self, canvas):
|
||||
return
|
||||
|
||||
def clear(self):
|
||||
return
|
@ -33,7 +33,7 @@ class LcdHat(DisplayImpl):
|
||||
|
||||
def initialize(self):
|
||||
logging.info("initializing lcdhat display")
|
||||
from pwnagotchi.ui.hw.libs.waveshare.lcdhat.epd import EPD
|
||||
from pwnagotchi.ui.hw.libs.waveshare.lcd.lcdhat.epd import EPD
|
||||
self._display = EPD()
|
||||
self._display.init()
|
||||
self._display.clear()
|
||||
|
132
pwnagotchi/ui/hw/libs/adafruit/epdconfig.py
Normal file
132
pwnagotchi/ui/hw/libs/adafruit/epdconfig.py
Normal file
@ -0,0 +1,132 @@
|
||||
# /*****************************************************************************
|
||||
# * | 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
|
||||
import subprocess
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class RaspberryPi:
|
||||
# Pin definition
|
||||
RST_PIN = 27
|
||||
DC_PIN = 22
|
||||
CS_PIN = 8
|
||||
BUSY_PIN = 17
|
||||
PWR_PIN = 18
|
||||
|
||||
def __init__(self):
|
||||
import spidev
|
||||
import gpiozero
|
||||
|
||||
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):
|
||||
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):
|
||||
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):
|
||||
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_PWR_PIN.on()
|
||||
|
||||
# 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, cleanup=False):
|
||||
logger.debug("spi end")
|
||||
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 ...")
|
||||
|
||||
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()
|
||||
|
||||
|
||||
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 ###
|
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()
|
@ -1,144 +1,164 @@
|
||||
FBIOGET_VSCREENINFO=0x4600
|
||||
FBIOPUT_VSCREENINFO=0x4601
|
||||
FBIOGET_FSCREENINFO=0x4602
|
||||
FBIOGETCMAP=0x4604
|
||||
FBIOPUTCMAP=0x4605
|
||||
FBIOPAN_DISPLAY=0x4606
|
||||
FBIOGET_VSCREENINFO = 0x4600
|
||||
FBIOPUT_VSCREENINFO = 0x4601
|
||||
FBIOGET_FSCREENINFO = 0x4602
|
||||
FBIOGETCMAP = 0x4604
|
||||
FBIOPUTCMAP = 0x4605
|
||||
FBIOPAN_DISPLAY = 0x4606
|
||||
|
||||
FBIOGET_CON2FBMAP=0x460F
|
||||
FBIOPUT_CON2FBMAP=0x4610
|
||||
FBIOBLANK=0x4611
|
||||
FBIO_ALLOC=0x4613
|
||||
FBIO_FREE=0x4614
|
||||
FBIOGET_GLYPH=0x4615
|
||||
FBIOGET_HWCINFO=0x4616
|
||||
FBIOPUT_MODEINFO=0x4617
|
||||
FBIOGET_DISPINFO=0x4618
|
||||
FBIOGET_CON2FBMAP = 0x460F
|
||||
FBIOPUT_CON2FBMAP = 0x4610
|
||||
FBIOBLANK = 0x4611
|
||||
FBIO_ALLOC = 0x4613
|
||||
FBIO_FREE = 0x4614
|
||||
FBIOGET_GLYPH = 0x4615
|
||||
FBIOGET_HWCINFO = 0x4616
|
||||
FBIOPUT_MODEINFO = 0x4617
|
||||
FBIOGET_DISPINFO = 0x4618
|
||||
|
||||
from mmap import mmap
|
||||
from fcntl import ioctl
|
||||
import struct
|
||||
|
||||
mm = None
|
||||
bpp, w, h = 0, 0, 0 # framebuffer bpp and size
|
||||
bpp, w, h = 0, 0, 0 # framebuffer bpp and size
|
||||
bytepp = 0
|
||||
vx, vy, vw, vh = 0, 0, 0, 0 #virtual window offset and size
|
||||
vx, vy, vw, vh = 0, 0, 0, 0 # virtual window offset and size
|
||||
vi, fi = None, None
|
||||
_fb_cmap = 'IIPPPP' # start, len, r, g, b, a
|
||||
_fb_cmap = 'IIPPPP' # start, len, r, g, b, a
|
||||
RGB = False
|
||||
_verbose = False
|
||||
msize_kb = 0
|
||||
|
||||
|
||||
def report_fb(i=0, layer=0):
|
||||
with open('/dev/fb'+str(i), 'r+b')as f:
|
||||
vi = ioctl(f, FBIOGET_VSCREENINFO, bytes(160))
|
||||
vi = list(struct.unpack('I'*40, vi))
|
||||
ffm = 'c'*16+'L'+'I'*4+'H'*3+'ILIIHHH'
|
||||
fic = struct.calcsize(ffm)
|
||||
fi = struct.unpack(ffm, ioctl(f, FBIOGET_FSCREENINFO, bytes(fic)))
|
||||
with open('/dev/fb' + str(i), 'r+b') as f:
|
||||
vi = ioctl(f, FBIOGET_VSCREENINFO, bytes(160))
|
||||
vi = list(struct.unpack('I' * 40, vi))
|
||||
ffm = 'c' * 16 + 'L' + 'I' * 4 + 'H' * 3 + 'ILIIHHH'
|
||||
fic = struct.calcsize(ffm)
|
||||
fi = struct.unpack(ffm, ioctl(f, FBIOGET_FSCREENINFO, bytes(fic)))
|
||||
|
||||
|
||||
def ready_fb(_bpp=None, i=0, layer=0, _win=None):
|
||||
global mm, bpp, w, h, vi, fi, RGB, msize_kb, vx, vy, vw, vh, bytepp
|
||||
if mm and bpp == _bpp: return mm, w, h, bpp
|
||||
with open('/dev/fb'+str(i), 'r+b')as f:
|
||||
vi = ioctl(f, FBIOGET_VSCREENINFO, bytes(160))
|
||||
vi = list(struct.unpack('I'*40, vi))
|
||||
bpp = vi[6]
|
||||
bytepp = bpp//8
|
||||
if _bpp:
|
||||
vi[6] = _bpp # 24 bit = BGR 888 mode
|
||||
try:
|
||||
vi = ioctl(f, FBIOPUT_VSCREENINFO, struct.pack('I'*40, *vi)) # fb_var_screeninfo
|
||||
vi = struct.unpack('I'*40,vi)
|
||||
global mm, bpp, w, h, vi, fi, RGB, msize_kb, vx, vy, vw, vh, bytepp
|
||||
if mm and bpp == _bpp:
|
||||
return mm, w, h, bpp
|
||||
with open('/dev/fb' + str(i), 'r+b') as f:
|
||||
vi = ioctl(f, FBIOGET_VSCREENINFO, bytes(160))
|
||||
vi = list(struct.unpack('I' * 40, vi))
|
||||
bpp = vi[6]
|
||||
bytepp = bpp//8
|
||||
except:
|
||||
pass
|
||||
|
||||
if vi[8] == 0 : RGB = True
|
||||
|
||||
ffm = 'c'*16+'L'+'I'*4+'H'*3+'ILIIHHH'
|
||||
fic = struct.calcsize(ffm)
|
||||
fi = struct.unpack(ffm, ioctl(f, FBIOGET_FSCREENINFO, bytes(fic)))
|
||||
msize = fi[17] # = w*h*bpp//8
|
||||
ll, start = fi[-7:-5]
|
||||
w, h = ll//bytepp, vi[1] # when screen is vertical, width becomes wrong. ll//3 is more accurate at such time.
|
||||
if _win and len(_win)==4: # virtual window settings
|
||||
vx, vy, vw, vh = _win
|
||||
if vw == 'w': vw = w
|
||||
if vh == 'h': vh = h
|
||||
vx, vy, vw, vh = map(int, (vx, vy, vw, vh))
|
||||
if vx>=w: vx = 0
|
||||
if vy>=h: vy = 0
|
||||
if vx>w: vw = w - vx
|
||||
else: vw -= vx
|
||||
if vy>h: vh = h - vy
|
||||
else: vh -= vy
|
||||
else:
|
||||
vx, vy, vw, vh = 0,0,w,h
|
||||
msize_kb = vw*vh*bytepp//1024 # more accurate FB memory size in kb
|
||||
bytepp = bpp // 8
|
||||
if _bpp:
|
||||
vi[6] = _bpp # 24 bit = BGR 888 mode
|
||||
try:
|
||||
vi = ioctl(f, FBIOPUT_VSCREENINFO, struct.pack('I' * 40, *vi)) # fb_var_screeninfo
|
||||
vi = struct.unpack('I' * 40, vi)
|
||||
bpp = vi[6]
|
||||
bytepp = bpp // 8
|
||||
except:
|
||||
pass
|
||||
|
||||
mm = mmap(f.fileno(), msize, offset=start)
|
||||
return mm, w, h, bpp#ll//(bpp//8), h
|
||||
if vi[8] == 0:
|
||||
RGB = True
|
||||
|
||||
ffm = 'c' * 16 + 'L' + 'I' * 4 + 'H' * 3 + 'ILIIHHH'
|
||||
fic = struct.calcsize(ffm)
|
||||
fi = struct.unpack(ffm, ioctl(f, FBIOGET_FSCREENINFO, bytes(fic)))
|
||||
msize = fi[17] # = w*h*bpp//8
|
||||
ll, start = fi[-7:-5]
|
||||
w, h = ll // bytepp, vi[1] # when screen is vertical, width becomes wrong. ll//3 is more accurate at such time.
|
||||
if _win and len(_win) == 4: # virtual window settings
|
||||
vx, vy, vw, vh = _win
|
||||
if vw == 'w':
|
||||
vw = w
|
||||
if vh == 'h':
|
||||
vh = h
|
||||
vx, vy, vw, vh = map(int, (vx, vy, vw, vh))
|
||||
if vx >= w:
|
||||
vx = 0
|
||||
if vy >= h:
|
||||
vy = 0
|
||||
if vx > w:
|
||||
vw = w - vx
|
||||
else:
|
||||
vw -= vx
|
||||
if vy > h:
|
||||
vh = h - vy
|
||||
else:
|
||||
vh -= vy
|
||||
else:
|
||||
vx, vy, vw, vh = 0, 0, w, h
|
||||
msize_kb = vw * vh * bytepp // 1024 # more accurate FB memory size in kb
|
||||
|
||||
mm = mmap(f.fileno(), msize, offset=start)
|
||||
return mm, w, h, bpp # ll//(bpp//8), h
|
||||
|
||||
|
||||
def fill_scr(r, g, b):
|
||||
if bpp == 32:
|
||||
seed = struct.pack('BBBB', b, g, r, 255)
|
||||
elif bpp == 24:
|
||||
seed = struct.pack('BBB', b, g, r)
|
||||
elif bpp == 16:
|
||||
seed = struct.pack('H', r >> 3 << 11 | g >> 2 << 5 | b >> 3)
|
||||
mm.seek(0)
|
||||
show_img(seed * vw * vh)
|
||||
|
||||
def fill_scr(r,g,b):
|
||||
if bpp == 32:
|
||||
seed = struct.pack('BBBB', b, g, r, 255)
|
||||
elif bpp == 24:
|
||||
seed = struct.pack('BBB', b, g, r)
|
||||
elif bpp == 16:
|
||||
seed = struct.pack('H', r>>3<<11 | g>>2<<5 | b>>3)
|
||||
mm.seek(0)
|
||||
show_img(seed * vw * vh)
|
||||
|
||||
def black_scr():
|
||||
fill_scr(0,0,0)
|
||||
fill_scr(0, 0, 0)
|
||||
|
||||
|
||||
def white_scr():
|
||||
fill_scr(255,255,255)
|
||||
fill_scr(255, 255, 255)
|
||||
|
||||
|
||||
def mmseekto(x, y):
|
||||
mm.seek((x + y * w) * bytepp)
|
||||
|
||||
def mmseekto(x,y):
|
||||
mm.seek((x + y*w) * bytepp)
|
||||
|
||||
def dot(x, y, r, g, b):
|
||||
mmseekto(x,y)
|
||||
mm.write(struct.pack('BBB',*((r,g,b) if RGB else (b,g,r))))
|
||||
mmseekto(x, y)
|
||||
mm.write(struct.pack('BBB', *((r, g, b) if RGB else (b, g, r))))
|
||||
|
||||
|
||||
def get_pixel(x, y):
|
||||
mmseekto(x, y)
|
||||
return mm.read(bytepp)
|
||||
|
||||
def get_pixel(x,y):
|
||||
mmseekto(x,y)
|
||||
return mm.read(bytepp)
|
||||
|
||||
def _888_to_565(bt):
|
||||
b = b''
|
||||
for i in range(0, len(bt),3):
|
||||
b += int.to_bytes(bt[i]>>3<<11|bt[i+1]>>2<<5|bt[i+2]>>3, 2, 'little')
|
||||
return b
|
||||
b = b''
|
||||
for i in range(0, len(bt), 3):
|
||||
b += int.to_bytes(bt[i] >> 3 << 11 | bt[i + 1] >> 2 << 5 | bt[i + 2] >> 3, 2, 'little')
|
||||
return b
|
||||
|
||||
|
||||
def numpy_888_565(bt):
|
||||
import numpy as np
|
||||
arr = np.fromstring(bt, dtype=np.uint32)
|
||||
return (((0xF80000 & arr)>>8)|((0xFC00 & arr)>>5)|((0xF8 & arr)>>3)).astype(np.uint16).tostring()
|
||||
import numpy as np
|
||||
arr = np.fromstring(bt, dtype=np.uint32)
|
||||
return (((0xF80000 & arr) >> 8) | ((0xFC00 & arr) >> 5) | ((0xF8 & arr) >> 3)).astype(np.uint16).tostring()
|
||||
|
||||
|
||||
def show_img(img):
|
||||
if not type(img) is bytes:
|
||||
if not RGB:
|
||||
if bpp == 24: # for RPI
|
||||
img = img.tobytes('raw', 'BGR')
|
||||
else:
|
||||
img = img.convert('RGBA').tobytes('raw', 'BGRA')
|
||||
if bpp == 16:
|
||||
img = numpy_888_565(img)
|
||||
else:
|
||||
if bpp == 24:
|
||||
img = img.tobytes()
|
||||
else:
|
||||
img = img.convert('RGBA').tobytes()
|
||||
if bpp == 16:
|
||||
img = numpy_888_565(img)
|
||||
from io import BytesIO
|
||||
b = BytesIO(img)
|
||||
s = vw*bytepp
|
||||
for y in range(vh): # virtual window drawing
|
||||
mmseekto(vx,vy+y)
|
||||
mm.write(b.read(s))
|
||||
|
||||
if not type(img) is bytes:
|
||||
if not RGB:
|
||||
if bpp == 24: # for RPI
|
||||
img = img.tobytes('raw', 'BGR')
|
||||
else:
|
||||
img = img.convert('RGBA').tobytes('raw', 'BGRA')
|
||||
if bpp == 16:
|
||||
img = numpy_888_565(img)
|
||||
else:
|
||||
if bpp == 24:
|
||||
img = img.tobytes()
|
||||
else:
|
||||
img = img.convert('RGBA').tobytes()
|
||||
if bpp == 16:
|
||||
img = numpy_888_565(img)
|
||||
from io import BytesIO
|
||||
b = BytesIO(img)
|
||||
s = vw * bytepp
|
||||
for y in range(vh): # virtual window drawing
|
||||
mmseekto(vx, vy + y)
|
||||
mm.write(b.read(s))
|
||||
|
@ -90,8 +90,8 @@ 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=0, invert=True, spi_speed_hz=60 * 1000 * 1000,
|
||||
def __init__(self, port, cs, dc, backlight=None, rst=None, width=240,
|
||||
height=240, rotation=90, invert=True, spi_speed_hz=4000000,
|
||||
offset_left=0,
|
||||
offset_top=0):
|
||||
"""Create an instance of the display using SPI communication.
|
||||
|
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()
|
0
pwnagotchi/ui/hw/libs/waveshare/epaper/__init__.py
Normal file
0
pwnagotchi/ui/hw/libs/waveshare/epaper/__init__.py
Normal file
132
pwnagotchi/ui/hw/libs/waveshare/epaper/epdconfig.py
Normal file
132
pwnagotchi/ui/hw/libs/waveshare/epaper/epdconfig.py
Normal file
@ -0,0 +1,132 @@
|
||||
# /*****************************************************************************
|
||||
# * | 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
|
||||
import subprocess
|
||||
|
||||
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 gpiozero
|
||||
|
||||
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):
|
||||
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):
|
||||
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):
|
||||
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_PWR_PIN.on()
|
||||
|
||||
# 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, cleanup=False):
|
||||
logger.debug("spi end")
|
||||
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 ...")
|
||||
|
||||
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()
|
||||
|
||||
|
||||
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 ###
|
@ -29,7 +29,7 @@
|
||||
|
||||
|
||||
import logging
|
||||
from .. import epdconfig
|
||||
from pwnagotchi.ui.hw.libs.waveshare.epaper import epdconfig
|
||||
|
||||
# Display resolution
|
||||
EPD_WIDTH = 960
|
@ -29,7 +29,7 @@
|
||||
#
|
||||
|
||||
import logging
|
||||
from .. import epdconfig
|
||||
from pwnagotchi.ui.hw.libs.waveshare.epaper import epdconfig
|
||||
|
||||
# Display resolution
|
||||
EPD_WIDTH = 80
|
@ -80,7 +80,7 @@
|
||||
#
|
||||
|
||||
import logging
|
||||
from .. import epdconfig
|
||||
from pwnagotchi.ui.hw.libs.waveshare.epaper import epdconfig
|
||||
|
||||
# Display resolution
|
||||
EPD_WIDTH = 200
|
@ -28,7 +28,7 @@
|
||||
#
|
||||
|
||||
import logging
|
||||
from .. import epdconfig
|
||||
from pwnagotchi.ui.hw.libs.waveshare.epaper import epdconfig
|
||||
|
||||
# Display resolution
|
||||
EPD_WIDTH = 200
|
@ -28,7 +28,7 @@
|
||||
#
|
||||
|
||||
import logging
|
||||
from .. import epdconfig
|
||||
from pwnagotchi.ui.hw.libs.waveshare.epaper import epdconfig
|
||||
|
||||
# Display resolution
|
||||
EPD_WIDTH = 200
|
@ -28,7 +28,7 @@
|
||||
#
|
||||
|
||||
import logging
|
||||
from .. import epdconfig
|
||||
from pwnagotchi.ui.hw.libs.waveshare.epaper import epdconfig
|
||||
|
||||
# Display resolution
|
||||
EPD_WIDTH = 200
|
@ -27,7 +27,7 @@
|
||||
# THE SOFTWARE.
|
||||
#
|
||||
import logging
|
||||
from .. import epdconfig
|
||||
from pwnagotchi.ui.hw.libs.waveshare.epaper import epdconfig
|
||||
|
||||
# Display resolution
|
||||
EPD_WIDTH = 152
|
@ -29,11 +29,9 @@
|
||||
#
|
||||
|
||||
import logging
|
||||
from .. import epdconfig
|
||||
from pwnagotchi.ui.hw.libs.waveshare.epaper import epdconfig
|
||||
|
||||
import PIL
|
||||
from PIL import Image
|
||||
import io
|
||||
|
||||
# Display resolution
|
||||
EPD_WIDTH = 168
|
@ -29,7 +29,7 @@
|
||||
|
||||
|
||||
import logging
|
||||
from .. import epdconfig
|
||||
from pwnagotchi.ui.hw.libs.waveshare.epaper import epdconfig
|
||||
|
||||
# Display resolution
|
||||
EPD_WIDTH = 122
|
@ -27,7 +27,7 @@
|
||||
# THE SOFTWARE.
|
||||
#
|
||||
|
||||
from .. import epdconfig
|
||||
from pwnagotchi.ui.hw.libs.waveshare.epaper import epdconfig
|
||||
import RPi.GPIO as GPIO
|
||||
# import numpy as np
|
||||
|
@ -40,9 +40,8 @@
|
||||
|
||||
|
||||
import logging
|
||||
from . import epdconfig
|
||||
from pwnagotchi.ui.hw.libs.waveshare.epaper import epdconfig
|
||||
from PIL import Image
|
||||
import RPi.GPIO as GPIO
|
||||
|
||||
# Display resolution
|
||||
EPD_WIDTH = 104
|
@ -29,7 +29,7 @@
|
||||
|
||||
|
||||
import logging
|
||||
from .. import epdconfig
|
||||
from pwnagotchi.ui.hw.libs.waveshare.epaper import epdconfig
|
||||
|
||||
# Display resolution
|
||||
EPD_WIDTH = 122
|
@ -4,8 +4,8 @@
|
||||
# * | Function : Electronic paper driver
|
||||
# * | Info :
|
||||
# *----------------
|
||||
# * | This version: V1.1
|
||||
# * | Date : 2021-10-30
|
||||
# * | This version: V1.2
|
||||
# * | Date : 2022-08-9
|
||||
# # | Info : python demo
|
||||
# -----------------------------------------------------------------------------
|
||||
# Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
@ -29,15 +29,15 @@
|
||||
|
||||
|
||||
import logging
|
||||
from . import epdconfig
|
||||
import numpy as np
|
||||
from pwnagotchi.ui.hw.libs.waveshare.epaper import epdconfig
|
||||
|
||||
# Display resolution
|
||||
EPD_WIDTH = 122
|
||||
EPD_HEIGHT = 250
|
||||
EPD_WIDTH = 122
|
||||
EPD_HEIGHT = 250
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class EPD:
|
||||
def __init__(self):
|
||||
self.reset_pin = epdconfig.RST_PIN
|
||||
@ -46,68 +46,70 @@ class EPD:
|
||||
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_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,
|
||||
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.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)
|
||||
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)
|
||||
@ -119,71 +121,84 @@ class EPD:
|
||||
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)
|
||||
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_command(0x22) # Display Update Control
|
||||
self.send_data(0xC7)
|
||||
self.send_command(0x20) # Activate Display Update Sequence
|
||||
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.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_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_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:
|
||||
@ -192,13 +207,14 @@ class EPD:
|
||||
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
|
||||
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((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)
|
||||
@ -210,52 +226,54 @@ class EPD:
|
||||
x : X-axis starting position
|
||||
y : Y-axis starting position
|
||||
'''
|
||||
|
||||
def SetCursor(self, x, y):
|
||||
self.send_command(0x4E) # SET_RAM_X_ADDRESS_COUNTER
|
||||
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_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.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_command(0x11) # data entry mode
|
||||
self.send_data(0x03)
|
||||
|
||||
self.SetWindow(0, 0, self.width-1, self.height-1)
|
||||
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_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
|
||||
|
||||
@ -264,54 +282,52 @@ class EPD:
|
||||
parameter:
|
||||
image : Image data
|
||||
'''
|
||||
|
||||
def getbuffer(self, image):
|
||||
img = image
|
||||
imwidth, imheight = img.size
|
||||
if(imwidth == self.width and imheight == self.height):
|
||||
if (imwidth == self.width and imheight == self.height):
|
||||
img = img.convert('1')
|
||||
elif(imwidth == self.height and imheight == self.width):
|
||||
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)
|
||||
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)
|
||||
if self.width % 8 == 0:
|
||||
linewidth = int(self.width / 8)
|
||||
else:
|
||||
linewidth = int(self.width/8) + 1
|
||||
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.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):
|
||||
if self.width%8 == 0:
|
||||
linewidth = int(self.width/8)
|
||||
else:
|
||||
linewidth = int(self.width/8) + 1
|
||||
|
||||
def displayPartial(self, image):
|
||||
epdconfig.digital_write(self.reset_pin, 0)
|
||||
epdconfig.delay_ms(1)
|
||||
epdconfig.digital_write(self.reset_pin, 1)
|
||||
|
||||
epdconfig.digital_write(self.reset_pin, 1)
|
||||
|
||||
self.SetLut(self.lut_partial_update)
|
||||
self.send_command(0x37)
|
||||
self.send_data(0x00)
|
||||
@ -322,24 +338,25 @@ class EPD:
|
||||
self.send_data(0x40)
|
||||
self.send_data(0x00)
|
||||
self.send_data(0x00)
|
||||
self.send_data(0x00)
|
||||
self.send_data(0x00)
|
||||
self.send_data(0x00)
|
||||
|
||||
self.send_command(0x3C) #BorderWavefrom
|
||||
self.send_command(0x3C) # BorderWavefrom
|
||||
self.send_data(0x80)
|
||||
|
||||
self.send_command(0x22)
|
||||
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_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()
|
||||
|
||||
'''
|
||||
@ -347,51 +364,41 @@ class EPD:
|
||||
parameter:
|
||||
image : Image data
|
||||
'''
|
||||
def displayPartBaseImage(self, image):
|
||||
if self.width%8 == 0:
|
||||
linewidth = int(self.width/8)
|
||||
else:
|
||||
linewidth = int(self.width/8) + 1
|
||||
|
||||
def displayPartBaseImage(self, image):
|
||||
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.send_data2(image)
|
||||
|
||||
self.send_command(0x26)
|
||||
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.TurnOnDisplay()
|
||||
|
||||
|
||||
'''
|
||||
function : Clear screen
|
||||
parameter:
|
||||
'''
|
||||
def Clear(self, color):
|
||||
if self.width%8 == 0:
|
||||
linewidth = int(self.width/8)
|
||||
|
||||
def Clear(self, color=0xFF):
|
||||
if self.width % 8 == 0:
|
||||
linewidth = int(self.width / 8)
|
||||
else:
|
||||
linewidth = int(self.width/8) + 1
|
||||
linewidth = int(self.width / 8) + 1
|
||||
# logger.debug(linewidth)
|
||||
|
||||
|
||||
self.send_command(0x24)
|
||||
for j in range(0, self.height):
|
||||
for i in range(0, linewidth):
|
||||
self.send_data(color)
|
||||
|
||||
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_command(0x10) # enter deep sleep
|
||||
self.send_data(0x01)
|
||||
|
||||
|
||||
epdconfig.delay_ms(2000)
|
||||
epdconfig.module_exit()
|
||||
|
||||
### END OF FILE ###
|
||||
|
@ -29,7 +29,7 @@
|
||||
|
||||
|
||||
import logging
|
||||
from . import epdconfig
|
||||
from pwnagotchi.ui.hw.libs.waveshare.epaper import epdconfig
|
||||
|
||||
# Display resolution
|
||||
EPD_WIDTH = 122
|
@ -28,7 +28,7 @@
|
||||
#
|
||||
|
||||
import logging
|
||||
from .. import epdconfig
|
||||
from pwnagotchi.ui.hw.libs.waveshare.epaper import epdconfig
|
||||
|
||||
# Display resolution
|
||||
EPD_WIDTH = 104
|
@ -28,7 +28,7 @@
|
||||
#
|
||||
|
||||
import logging
|
||||
from .. import epdconfig
|
||||
from pwnagotchi.ui.hw.libs.waveshare.epaper import epdconfig
|
||||
|
||||
# Display resolution
|
||||
EPD_WIDTH = 122
|
@ -28,7 +28,7 @@
|
||||
#
|
||||
|
||||
import logging
|
||||
from .. import epdconfig
|
||||
from pwnagotchi.ui.hw.libs.waveshare.epaper import epdconfig
|
||||
from PIL import Image
|
||||
|
||||
# Display resolution
|
@ -29,9 +29,8 @@
|
||||
|
||||
|
||||
import logging
|
||||
from .. import epdconfig
|
||||
from pwnagotchi.ui.hw.libs.waveshare.epaper import epdconfig
|
||||
from PIL import Image
|
||||
import RPi.GPIO as GPIO
|
||||
|
||||
# Display resolution
|
||||
EPD_WIDTH = 104
|
@ -29,11 +29,9 @@
|
||||
#
|
||||
|
||||
import logging
|
||||
from .. import epdconfig
|
||||
from pwnagotchi.ui.hw.libs.waveshare.epaper import epdconfig
|
||||
|
||||
import PIL
|
||||
from PIL import Image
|
||||
import io
|
||||
|
||||
# Display resolution
|
||||
EPD_WIDTH = 122
|
@ -29,11 +29,9 @@
|
||||
#
|
||||
|
||||
import logging
|
||||
from .. import epdconfig
|
||||
from pwnagotchi.ui.hw.libs.waveshare.epaper import epdconfig
|
||||
|
||||
import PIL
|
||||
from PIL import Image
|
||||
import io
|
||||
|
||||
# Display resolution
|
||||
EPD_WIDTH = 122
|
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user