mirror of
https://github.com/jayofelony/pwnagotchi.git
synced 2025-07-01 18:37:27 -04:00
Compare commits
40 Commits
Author | SHA1 | Date | |
---|---|---|---|
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 | |||
0585fe75fe | |||
a7634a2b4a | |||
ca4feb895e |
4
.github/workflows/publish.yml
vendored
4
.github/workflows/publish.yml
vendored
@ -23,6 +23,8 @@ jobs:
|
|||||||
- name: Get latest tag
|
- name: Get latest tag
|
||||||
uses: actions-ecosystem/action-get-latest-tag@v1
|
uses: actions-ecosystem/action-get-latest-tag@v1
|
||||||
id: get-latest-tag
|
id: get-latest-tag
|
||||||
|
with:
|
||||||
|
token: ${{ secrets.GITHUB_TOKEN }}
|
||||||
|
|
||||||
- name: Set LAST_VERSION as an environment variable
|
- name: Set LAST_VERSION as an environment variable
|
||||||
run: echo "LAST_VERSION=${{ steps.get-latest-tag.outputs.tag }}" >> $GITHUB_ENV
|
run: echo "LAST_VERSION=${{ steps.get-latest-tag.outputs.tag }}" >> $GITHUB_ENV
|
||||||
@ -61,7 +63,7 @@ jobs:
|
|||||||
run: mv "pwnagotchi-64bit.img.xz" "pwnagotchi-$VERSION-64bit.img.xz"
|
run: mv "pwnagotchi-64bit.img.xz" "pwnagotchi-$VERSION-64bit.img.xz"
|
||||||
|
|
||||||
- name: Release
|
- name: Release
|
||||||
uses: softprops/action-gh-release@v1
|
uses: softprops/action-gh-release@v2
|
||||||
with:
|
with:
|
||||||
prerelease: true
|
prerelease: true
|
||||||
tag_name: v${{ env.VERSION }}
|
tag_name: v${{ env.VERSION }}
|
||||||
|
3
Makefile
3
Makefile
@ -46,13 +46,16 @@ packer: clean
|
|||||||
sudo mv /tmp/packer /usr/bin/packer
|
sudo mv /tmp/packer /usr/bin/packer
|
||||||
|
|
||||||
image: clean packer
|
image: clean packer
|
||||||
|
export LC_ALL=en_GB.UTF-8
|
||||||
cd builder && sudo /usr/bin/packer init combined.json.pkr.hcl && sudo $(UNSHARE) /usr/bin/packer build -var "pwn_hostname=$(PWN_HOSTNAME)" -var "pwn_version=$(PWN_VERSION)" combined.json.pkr.hcl
|
cd builder && sudo /usr/bin/packer init combined.json.pkr.hcl && sudo $(UNSHARE) /usr/bin/packer build -var "pwn_hostname=$(PWN_HOSTNAME)" -var "pwn_version=$(PWN_VERSION)" combined.json.pkr.hcl
|
||||||
|
|
||||||
bullseye: clean packer
|
bullseye: clean packer
|
||||||
|
export LC_ALL=en_GB.UTF-8
|
||||||
cd builder && sudo /usr/bin/packer init data/32bit/raspberrypi32.json.pkr.hcl && sudo $(UNSHARE) /usr/bin/packer build -var "pwn_hostname=$(PWN_HOSTNAME)" -var "pwn_version=$(PWN_VERSION)" data/32bit/raspberrypi32.json.pkr.hcl
|
cd builder && sudo /usr/bin/packer init data/32bit/raspberrypi32.json.pkr.hcl && sudo $(UNSHARE) /usr/bin/packer build -var "pwn_hostname=$(PWN_HOSTNAME)" -var "pwn_version=$(PWN_VERSION)" data/32bit/raspberrypi32.json.pkr.hcl
|
||||||
sudo pishrink -vaZ pwnagotchi-32bit.img
|
sudo pishrink -vaZ pwnagotchi-32bit.img
|
||||||
|
|
||||||
bookworm: clean packer
|
bookworm: clean packer
|
||||||
|
export LC_ALL=en_GB.UTF-8
|
||||||
cd builder && sudo /usr/bin/packer init data/64bit/raspberrypi64.json.pkr.hcl && sudo $(UNSHARE) /usr/bin/packer build -var "pwn_hostname=$(PWN_HOSTNAME)" -var "pwn_version=$(PWN_VERSION)" data/64bit/raspberrypi64.json.pkr.hcl
|
cd builder && sudo /usr/bin/packer init data/64bit/raspberrypi64.json.pkr.hcl && sudo $(UNSHARE) /usr/bin/packer build -var "pwn_hostname=$(PWN_HOSTNAME)" -var "pwn_version=$(PWN_VERSION)" data/64bit/raspberrypi64.json.pkr.hcl
|
||||||
sudo pishrink -vaZ pwnagotchi-64bit.img
|
sudo pishrink -vaZ pwnagotchi-64bit.img
|
||||||
|
|
||||||
|
@ -19,12 +19,12 @@ from pwnagotchi import fs
|
|||||||
from pwnagotchi.utils import DottedTomlEncoder, parse_version as version_to_tuple
|
from pwnagotchi.utils import DottedTomlEncoder, parse_version as version_to_tuple
|
||||||
|
|
||||||
|
|
||||||
|
def pwnagotchi_cli():
|
||||||
def do_clear(display):
|
def do_clear(display):
|
||||||
logging.info("clearing the display ...")
|
logging.info("clearing the display ...")
|
||||||
display.clear()
|
display.clear()
|
||||||
sys.exit(0)
|
sys.exit(0)
|
||||||
|
|
||||||
|
|
||||||
def do_manual_mode(agent):
|
def do_manual_mode(agent):
|
||||||
logging.info("entering manual mode ...")
|
logging.info("entering manual mode ...")
|
||||||
|
|
||||||
@ -46,7 +46,6 @@ def do_manual_mode(agent):
|
|||||||
if grid.is_connected():
|
if grid.is_connected():
|
||||||
plugins.on('internet_available', agent)
|
plugins.on('internet_available', agent)
|
||||||
|
|
||||||
|
|
||||||
def do_auto_mode(agent):
|
def do_auto_mode(agent):
|
||||||
logging.info("entering auto mode ...")
|
logging.info("entering auto mode ...")
|
||||||
|
|
||||||
@ -61,6 +60,7 @@ def do_auto_mode(agent):
|
|||||||
channels = agent.get_access_points_by_channel()
|
channels = agent.get_access_points_by_channel()
|
||||||
# for each channel
|
# for each channel
|
||||||
for ch, aps in channels:
|
for ch, aps in channels:
|
||||||
|
time.sleep(0.2)
|
||||||
agent.set_channel(ch)
|
agent.set_channel(ch)
|
||||||
|
|
||||||
if not agent.is_stale() and agent.any_activity():
|
if not agent.is_stale() and agent.any_activity():
|
||||||
@ -96,8 +96,6 @@ def do_auto_mode(agent):
|
|||||||
else:
|
else:
|
||||||
logging.exception("main loop exception (%s)", e)
|
logging.exception("main loop exception (%s)", e)
|
||||||
|
|
||||||
|
|
||||||
if __name__ == '__main__':
|
|
||||||
def add_parsers(parser):
|
def add_parsers(parser):
|
||||||
"""
|
"""
|
||||||
Adds the plugins and google subcommands
|
Adds the plugins and google subcommands
|
||||||
@ -169,21 +167,23 @@ if __name__ == '__main__':
|
|||||||
allowed = re.compile("(?!-)[A-Z\d-]{1,63}(?<!-)$", re.IGNORECASE)
|
allowed = re.compile("(?!-)[A-Z\d-]{1,63}(?<!-)$", re.IGNORECASE)
|
||||||
return all(allowed.match(x) for x in hostname.split("."))
|
return all(allowed.match(x) for x in hostname.split("."))
|
||||||
|
|
||||||
pwn_restore = input("Do you want to restore the previous configuration? [Y/N]\n")
|
pwn_restore = input("Do you want to restore the previous configuration?\n\n"
|
||||||
|
"[Y/N]: ")
|
||||||
if pwn_restore in ('y', 'yes'):
|
if pwn_restore in ('y', 'yes'):
|
||||||
os.system("cp -f /etc/pwnagotchi/config.toml.bak /etc/pwnagotchi/config.toml")
|
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.")
|
print("Your previous configuration is restored, and I will restart in 5 seconds.")
|
||||||
time.sleep(5)
|
time.sleep(5)
|
||||||
os.system("service pwnagotchi restart")
|
os.system("service pwnagotchi restart")
|
||||||
else:
|
else:
|
||||||
pwn_check = input("This will create a new configuration file and overwrite your current backup, are you sure? [Y/N]\n")
|
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'):
|
if pwn_check.lower() in ('y', 'yes'):
|
||||||
os.system("mv -f /etc/pwnagotchi/config.toml /etc/pwnagotchi/config.toml.bak")
|
os.system("mv -f /etc/pwnagotchi/config.toml /etc/pwnagotchi/config.toml.bak")
|
||||||
with open("/etc/pwnagotchi/config.toml", "a+") as f:
|
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")
|
f.write("# Do not edit this file if you do not know what you are doing!!!\n\n")
|
||||||
# Set pwnagotchi name
|
# Set pwnagotchi name
|
||||||
print("Welcome to the interactive installation of your personal Pwnagotchi configuration!\n"
|
print("Welcome to the interactive installation of your personal Pwnagotchi configuration!\n"
|
||||||
"My name is Jayofelony, how may I call you?\n")
|
"My name is Jayofelony, how may I call you?\n\n")
|
||||||
pwn_name = input("Pwnagotchi name (no spaces): ")
|
pwn_name = input("Pwnagotchi name (no spaces): ")
|
||||||
if pwn_name == "":
|
if pwn_name == "":
|
||||||
pwn_name = "Pwnagotchi"
|
pwn_name = "Pwnagotchi"
|
||||||
@ -212,32 +212,39 @@ if __name__ == '__main__':
|
|||||||
f.write(f"\t\"{bssid}\",\n")
|
f.write(f"\t\"{bssid}\",\n")
|
||||||
f.write("]\n")
|
f.write("]\n")
|
||||||
# set bluetooth tether
|
# set bluetooth tether
|
||||||
pwn_bluetooth = input("Do you want to enable BT-Tether? [Y/N] ")
|
pwn_bluetooth = input("Do you want to enable BT-Tether?\n\n"
|
||||||
|
"[Y/N] ")
|
||||||
if pwn_bluetooth.lower() in ('y', 'yes'):
|
if pwn_bluetooth.lower() in ('y', 'yes'):
|
||||||
f.write("main.plugins.bt-tether.enabled = true\n\n")
|
f.write("main.plugins.bt-tether.enabled = true\n\n")
|
||||||
pwn_bluetooth_device = input("What device do you use? Android or iOS? ")
|
pwn_bluetooth_device = input("What device do you use? Android or iOS?\n\n"
|
||||||
|
"Device: ")
|
||||||
if pwn_bluetooth_device.lower() == "android":
|
if pwn_bluetooth_device.lower() == "android":
|
||||||
f.write("main.plugins.bt-tether.devices.android-phone.enabled = true\n")
|
f.write("main.plugins.bt-tether.devices.android-phone.enabled = true\n")
|
||||||
pwn_bluetooth_mac = input("What is the bluetooth MAC of your device? ")
|
pwn_bluetooth_mac = input("What is the bluetooth MAC of your device?\n\n"
|
||||||
|
"MAC: ")
|
||||||
if pwn_bluetooth_mac != "":
|
if pwn_bluetooth_mac != "":
|
||||||
f.write(f"main.plugins.bt-tether.devices.android-phone.mac = \"{pwn_bluetooth_mac}\"\n")
|
f.write(f"main.plugins.bt-tether.devices.android-phone.mac = \"{pwn_bluetooth_mac}\"\n")
|
||||||
elif pwn_bluetooth_device.lower() == "ios":
|
elif pwn_bluetooth_device.lower() == "ios":
|
||||||
f.write("main.plugins.bt-tether.devices.ios-phone.enabled = true\n")
|
f.write("main.plugins.bt-tether.devices.ios-phone.enabled = true\n")
|
||||||
pwn_bluetooth_mac = input("What is the bluetooth MAC of your device? ")
|
pwn_bluetooth_mac = input("What is the bluetooth MAC of your device?\n\n"
|
||||||
|
"MAC: ")
|
||||||
if pwn_bluetooth_mac != "":
|
if pwn_bluetooth_mac != "":
|
||||||
f.write(f"main.plugins.bt-tether.devices.ios-phone.mac = \"{pwn_bluetooth_mac}\"\n")
|
f.write(f"main.plugins.bt-tether.devices.ios-phone.mac = \"{pwn_bluetooth_mac}\"\n")
|
||||||
# set up display settings
|
# set up display settings
|
||||||
pwn_display_enabled = input("Do you use a display? [Y/N] ")
|
pwn_display_enabled = input("Do you want to enable a display?\n\n"
|
||||||
|
"[Y/N]: ")
|
||||||
if pwn_display_enabled.lower() in ('y', 'yes'):
|
if pwn_display_enabled.lower() in ('y', 'yes'):
|
||||||
f.write("ui.display.enabled = true\n")
|
f.write("ui.display.enabled = true\n")
|
||||||
pwn_display_type = input("What display do you use?\n\n"
|
pwn_display_type = input("What display do you use?\n\n"
|
||||||
"Be sure to check for the correct display type @ \n"
|
"Be sure to check for the correct display type @ \n"
|
||||||
"https://github.com/jayofelony/pwnagotchi/blob/master/pwnagotchi/utils.py#L240-L431\n")
|
"https://github.com/jayofelony/pwnagotchi/blob/master/pwnagotchi/utils.py#L240-L431\n\n"
|
||||||
|
"Display type: ")
|
||||||
if pwn_display_type != "":
|
if pwn_display_type != "":
|
||||||
f.write(f"ui.display.type = \"{pwn_display_type}\"\n")
|
f.write(f"ui.display.type = \"{pwn_display_type}\"\n")
|
||||||
pwn_display_invert = input("Do you want to invert the display colors? [Y/N]\n\n"
|
pwn_display_invert = input("Do you want to invert the display colors?\n"
|
||||||
"N = Black background\n"
|
"N = Black background\n"
|
||||||
"Y = White background\n")
|
"Y = White background\n\n"
|
||||||
|
"[Y/N]: ")
|
||||||
if pwn_display_invert.lower() in ('y', 'yes'):
|
if pwn_display_invert.lower() in ('y', 'yes'):
|
||||||
f.write("ui.invert = true\n")
|
f.write("ui.invert = true\n")
|
||||||
f.close()
|
f.close()
|
||||||
@ -326,3 +333,6 @@ if __name__ == '__main__':
|
|||||||
do_manual_mode(agent)
|
do_manual_mode(agent)
|
||||||
else:
|
else:
|
||||||
do_auto_mode(agent)
|
do_auto_mode(agent)
|
||||||
|
|
||||||
|
if __name__ == '__main__':
|
||||||
|
pwnagotchi_cli()
|
@ -20,8 +20,8 @@ variable "pwn_version" {
|
|||||||
}
|
}
|
||||||
|
|
||||||
source "arm" "rpi64-pwnagotchi" {
|
source "arm" "rpi64-pwnagotchi" {
|
||||||
file_checksum_url = "https://downloads.raspberrypi.org/raspios_lite_arm64/images/raspios_lite_arm64-2023-12-11/2023-12-11-raspios-bookworm-arm64-lite.img.xz.sha256"
|
file_checksum_url = "https://downloads.raspberrypi.com/raspios_lite_arm64/images/raspios_lite_arm64-2024-03-15/2024-03-15-raspios-bookworm-arm64-lite.img.xz.sha256"
|
||||||
file_urls = ["https://downloads.raspberrypi.org/raspios_lite_arm64/images/raspios_lite_arm64-2023-12-11/2023-12-11-raspios-bookworm-arm64-lite.img.xz"]
|
file_urls = ["https://downloads.raspberrypi.com/raspios_lite_arm64/images/raspios_lite_arm64-2024-03-15/2024-03-15-raspios-bookworm-arm64-lite.img.xz"]
|
||||||
file_checksum_type = "sha256"
|
file_checksum_type = "sha256"
|
||||||
file_target_extension = "xz"
|
file_target_extension = "xz"
|
||||||
file_unarchive_cmd = ["unxz", "$ARCHIVE_PATH"]
|
file_unarchive_cmd = ["unxz", "$ARCHIVE_PATH"]
|
||||||
@ -50,8 +50,8 @@ source "arm" "rpi64-pwnagotchi" {
|
|||||||
}
|
}
|
||||||
|
|
||||||
source "arm" "rpi32-pwnagotchi" {
|
source "arm" "rpi32-pwnagotchi" {
|
||||||
file_checksum_url = "https://downloads.raspberrypi.com/raspios_oldstable_lite_armhf/images/raspios_oldstable_lite_armhf-2023-12-06/2023-12-05-raspios-bullseye-armhf-lite.img.xz.sha256"
|
file_checksum_url = "https://downloads.raspberrypi.com/raspios_oldstable_lite_armhf/images/raspios_oldstable_lite_armhf-2024-03-12/2024-03-12-raspios-bullseye-armhf-lite.img.xz.sha256"
|
||||||
file_urls = ["https://downloads.raspberrypi.com/raspios_oldstable_lite_armhf/images/raspios_oldstable_lite_armhf-2023-12-06/2023-12-05-raspios-bullseye-armhf-lite.img.xz"]
|
file_urls = ["https://downloads.raspberrypi.com/raspios_oldstable_lite_armhf/images/raspios_oldstable_lite_armhf-2024-03-12/2024-03-12-raspios-bullseye-armhf-lite.img.xz"]
|
||||||
file_checksum_type = "sha256"
|
file_checksum_type = "sha256"
|
||||||
file_target_extension = "xz"
|
file_target_extension = "xz"
|
||||||
file_unarchive_cmd = ["unxz", "$ARCHIVE_PATH"]
|
file_unarchive_cmd = ["unxz", "$ARCHIVE_PATH"]
|
||||||
|
16
builder/data/32bit/etc/rc.local
Normal file
16
builder/data/32bit/etc/rc.local
Normal file
@ -0,0 +1,16 @@
|
|||||||
|
#!/bin/sh -e
|
||||||
|
#
|
||||||
|
# rc.local
|
||||||
|
#
|
||||||
|
# This script is executed at the end of each multiuser runlevel.
|
||||||
|
# Make sure that the script will "exit 0" on success or any other
|
||||||
|
# value on error.
|
||||||
|
#
|
||||||
|
# In order to enable or disable this script just change the execution
|
||||||
|
# bits.
|
||||||
|
#
|
||||||
|
# By default this script does nothing.
|
||||||
|
|
||||||
|
timedatectl set-ntp true
|
||||||
|
|
||||||
|
exit 0
|
@ -20,8 +20,8 @@ variable "pwn_version" {
|
|||||||
}
|
}
|
||||||
|
|
||||||
source "arm" "rpi32-pwnagotchi" {
|
source "arm" "rpi32-pwnagotchi" {
|
||||||
file_checksum_url = "https://downloads.raspberrypi.com/raspios_oldstable_lite_armhf/images/raspios_oldstable_lite_armhf-2023-12-06/2023-12-05-raspios-bullseye-armhf-lite.img.xz.sha256"
|
file_checksum_url = "https://downloads.raspberrypi.com/raspios_oldstable_lite_armhf/images/raspios_oldstable_lite_armhf-2024-03-12/2024-03-12-raspios-bullseye-armhf-lite.img.xz.sha256"
|
||||||
file_urls = ["https://downloads.raspberrypi.com/raspios_oldstable_lite_armhf/images/raspios_oldstable_lite_armhf-2023-12-06/2023-12-05-raspios-bullseye-armhf-lite.img.xz"]
|
file_urls = ["https://downloads.raspberrypi.com/raspios_oldstable_lite_armhf/images/raspios_oldstable_lite_armhf-2024-03-12/2024-03-12-raspios-bullseye-armhf-lite.img.xz"]
|
||||||
file_checksum_type = "sha256"
|
file_checksum_type = "sha256"
|
||||||
file_target_extension = "xz"
|
file_target_extension = "xz"
|
||||||
file_unarchive_cmd = ["unxz", "$ARCHIVE_PATH"]
|
file_unarchive_cmd = ["unxz", "$ARCHIVE_PATH"]
|
||||||
|
@ -46,7 +46,7 @@
|
|||||||
- "arm_64bit=0"
|
- "arm_64bit=0"
|
||||||
- "# dwc2 for RNDIS. comment out, and remove dwc2 and g_ether from cmdline.txt for X306 usb battery hat"
|
- "# dwc2 for RNDIS. comment out, and remove dwc2 and g_ether from cmdline.txt for X306 usb battery hat"
|
||||||
- "dtoverlay=dwc2"
|
- "dtoverlay=dwc2"
|
||||||
- "dtoverlay=spi1-3cs"
|
- "dtoverlay=spi0-0cs"
|
||||||
- "dtparam=i2c1=on"
|
- "dtparam=i2c1=on"
|
||||||
- "dtparam=i2c_arm=on"
|
- "dtparam=i2c_arm=on"
|
||||||
- "dtparam=spi=on"
|
- "dtparam=spi=on"
|
||||||
@ -177,6 +177,7 @@
|
|||||||
- libusb-1.0-0-dev
|
- libusb-1.0-0-dev
|
||||||
- lsof
|
- lsof
|
||||||
- make
|
- make
|
||||||
|
- ntp
|
||||||
- python3-flask
|
- python3-flask
|
||||||
- python3-flask-cors
|
- python3-flask-cors
|
||||||
- python3-flaskext.wtf
|
- python3-flaskext.wtf
|
||||||
@ -228,6 +229,11 @@
|
|||||||
path: '{{ pwnagotchi.custom_plugin_dir }}'
|
path: '{{ pwnagotchi.custom_plugin_dir }}'
|
||||||
state: directory
|
state: directory
|
||||||
|
|
||||||
|
- name: remove current rc.local
|
||||||
|
file:
|
||||||
|
path: /etc/rc.local
|
||||||
|
state: absent
|
||||||
|
|
||||||
- name: update apt package cache
|
- name: update apt package cache
|
||||||
apt:
|
apt:
|
||||||
update_cache: yes
|
update_cache: yes
|
||||||
|
@ -11,6 +11,7 @@ fi
|
|||||||
|
|
||||||
if is_auto_mode; then
|
if is_auto_mode; then
|
||||||
/usr/local/bin/pwnagotchi
|
/usr/local/bin/pwnagotchi
|
||||||
|
systemctl restart bettercap
|
||||||
else
|
else
|
||||||
/usr/local/bin/pwnagotchi --manual
|
/usr/local/bin/pwnagotchi --manual
|
||||||
fi
|
fi
|
||||||
|
16
builder/data/64bit/etc/rc.local
Normal file
16
builder/data/64bit/etc/rc.local
Normal file
@ -0,0 +1,16 @@
|
|||||||
|
#!/bin/sh -e
|
||||||
|
#
|
||||||
|
# rc.local
|
||||||
|
#
|
||||||
|
# This script is executed at the end of each multiuser runlevel.
|
||||||
|
# Make sure that the script will "exit 0" on success or any other
|
||||||
|
# value on error.
|
||||||
|
#
|
||||||
|
# In order to enable or disable this script just change the execution
|
||||||
|
# bits.
|
||||||
|
#
|
||||||
|
# By default this script does nothing.
|
||||||
|
|
||||||
|
timedatectl set-ntp true
|
||||||
|
|
||||||
|
exit 0
|
@ -20,8 +20,8 @@ variable "pwn_version" {
|
|||||||
}
|
}
|
||||||
|
|
||||||
source "arm" "rpi64-pwnagotchi" {
|
source "arm" "rpi64-pwnagotchi" {
|
||||||
file_checksum_url = "https://downloads.raspberrypi.org/raspios_lite_arm64/images/raspios_lite_arm64-2023-12-11/2023-12-11-raspios-bookworm-arm64-lite.img.xz.sha256"
|
file_checksum_url = "https://downloads.raspberrypi.com/raspios_lite_arm64/images/raspios_lite_arm64-2024-03-15/2024-03-15-raspios-bookworm-arm64-lite.img.xz.sha256"
|
||||||
file_urls = ["https://downloads.raspberrypi.org/raspios_lite_arm64/images/raspios_lite_arm64-2023-12-11/2023-12-11-raspios-bookworm-arm64-lite.img.xz"]
|
file_urls = ["https://downloads.raspberrypi.com/raspios_lite_arm64/images/raspios_lite_arm64-2024-03-15/2024-03-15-raspios-bookworm-arm64-lite.img.xz"]
|
||||||
file_checksum_type = "sha256"
|
file_checksum_type = "sha256"
|
||||||
file_target_extension = "xz"
|
file_target_extension = "xz"
|
||||||
file_unarchive_cmd = ["unxz", "$ARCHIVE_PATH"]
|
file_unarchive_cmd = ["unxz", "$ARCHIVE_PATH"]
|
||||||
|
@ -5,20 +5,27 @@
|
|||||||
become: true
|
become: true
|
||||||
vars:
|
vars:
|
||||||
kernel:
|
kernel:
|
||||||
min: "6.1"
|
min: "6.6"
|
||||||
full: "6.1.0-rpi8-rpi-v8"
|
full: "6.6.20+rpt-rpi-v8"
|
||||||
full_pi5: "6.1.0-rpi8-rpi-2712"
|
full_pi5: "6.6.20+rpt-rpi-2712"
|
||||||
pwnagotchi:
|
pwnagotchi:
|
||||||
hostname: "{{ lookup('env', 'PWN_HOSTNAME') | default('pwnagotchi', true) }}"
|
hostname: "{{ lookup('env', 'PWN_HOSTNAME') | default('pwnagotchi', true) }}"
|
||||||
version: "{{ lookup('env', 'PWN_VERSION') | default('pwnagotchi', true) }}"
|
version: "{{ lookup('env', 'PWN_VERSION') | default('pwnagotchi', true) }}"
|
||||||
system:
|
system:
|
||||||
boot_options:
|
boot_options:
|
||||||
- "dtoverlay=dwc2"
|
- "dtoverlay=dwc2"
|
||||||
- "dtoverlay=spi1-3cs"
|
|
||||||
- "dtparam=i2c1=on"
|
- "dtparam=i2c1=on"
|
||||||
- "dtparam=i2c_arm=on"
|
- "dtparam=i2c_arm=on"
|
||||||
- "dtparam=spi=on"
|
- "dtparam=spi=on"
|
||||||
- "gpu_mem=16"
|
- "gpu_mem=16"
|
||||||
|
- "[pi0]"
|
||||||
|
- "dtoverlay=spi0-0cs"
|
||||||
|
- "[pi3]"
|
||||||
|
- "dtoverlay=spi0-0cs"
|
||||||
|
- "[pi4]"
|
||||||
|
- "dtoverlay=spi0-0cs"
|
||||||
|
- "[pi5]"
|
||||||
|
- "dtoverlay=spi0-0cs"
|
||||||
modules:
|
modules:
|
||||||
- "i2c-dev"
|
- "i2c-dev"
|
||||||
services:
|
services:
|
||||||
@ -137,6 +144,7 @@
|
|||||||
- libusb-1.0-0-dev
|
- libusb-1.0-0-dev
|
||||||
- lsof
|
- lsof
|
||||||
- make
|
- make
|
||||||
|
- ntp
|
||||||
- python3-dbus
|
- python3-dbus
|
||||||
- python3-flask
|
- python3-flask
|
||||||
- python3-flask-cors
|
- python3-flask-cors
|
||||||
@ -198,12 +206,29 @@
|
|||||||
path: /boot/firmware/ssh
|
path: /boot/firmware/ssh
|
||||||
state: touch
|
state: touch
|
||||||
|
|
||||||
|
- name: remove current rc.local
|
||||||
|
file:
|
||||||
|
path: /etc/rc.local
|
||||||
|
state: absent
|
||||||
|
|
||||||
- name: adjust /boot/firmware/config.txt
|
- name: adjust /boot/firmware/config.txt
|
||||||
lineinfile:
|
blockinfile:
|
||||||
dest: /boot/firmware/config.txt
|
dest: /boot/firmware/config.txt
|
||||||
insertafter: EOF
|
insertafter: EOF
|
||||||
line: '{{ item }}'
|
block: |
|
||||||
with_items: "{{ system.boot_options }}"
|
dtoverlay=dwc2
|
||||||
|
dtparam=i2c1=on
|
||||||
|
dtparam=i2c_arm=on"
|
||||||
|
dtparam=spi=on
|
||||||
|
gpu_mem=16
|
||||||
|
[pi0]
|
||||||
|
dtoverlay=spi0-0cs
|
||||||
|
[pi3]
|
||||||
|
dtoverlay=spi0-0cs
|
||||||
|
[pi4]
|
||||||
|
dtoverlay=spi0-0cs
|
||||||
|
[pi5]
|
||||||
|
dtoverlay=spi0-0cs
|
||||||
|
|
||||||
- name: change root partition
|
- name: change root partition
|
||||||
replace:
|
replace:
|
||||||
|
@ -11,6 +11,7 @@ fi
|
|||||||
|
|
||||||
if is_auto_mode; then
|
if is_auto_mode; then
|
||||||
/usr/local/bin/pwnagotchi
|
/usr/local/bin/pwnagotchi
|
||||||
|
systemctl restart bettercap
|
||||||
else
|
else
|
||||||
/usr/local/bin/pwnagotchi --manual
|
/usr/local/bin/pwnagotchi --manual
|
||||||
fi
|
fi
|
||||||
|
@ -1 +1 @@
|
|||||||
__version__ = '2.8.6'
|
__version__ = '2.8.7.1'
|
||||||
|
@ -98,7 +98,7 @@ class GPS(plugins.Plugin):
|
|||||||
lat_pos = (127, 74)
|
lat_pos = (127, 74)
|
||||||
lon_pos = (122, 84)
|
lon_pos = (122, 84)
|
||||||
alt_pos = (127, 94)
|
alt_pos = (127, 94)
|
||||||
elif ui.is_waveshare27inch():
|
elif ui.is_waveshare2in7():
|
||||||
lat_pos = (6, 120)
|
lat_pos = (6, 120)
|
||||||
lon_pos = (1, 135)
|
lon_pos = (1, 135)
|
||||||
alt_pos = (6, 150)
|
alt_pos = (6, 150)
|
||||||
|
@ -5,7 +5,6 @@ import glob
|
|||||||
import re
|
import re
|
||||||
|
|
||||||
import pwnagotchi.grid as grid
|
import pwnagotchi.grid as grid
|
||||||
import pwnagotchi.plugins
|
|
||||||
import pwnagotchi.plugins as plugins
|
import pwnagotchi.plugins as plugins
|
||||||
from pwnagotchi.utils import StatusFile, WifiInfo, extract_from_pcap
|
from pwnagotchi.utils import StatusFile, WifiInfo, extract_from_pcap
|
||||||
from threading import Lock
|
from threading import Lock
|
||||||
@ -87,7 +86,7 @@ class Grid(plugins.Plugin):
|
|||||||
agent.view().on_unread_messages(self.unread_messages, self.total_messages)
|
agent.view().on_unread_messages(self.unread_messages, self.total_messages)
|
||||||
|
|
||||||
def check_handshakes(self, agent):
|
def check_handshakes(self, agent):
|
||||||
logging.debug("checking pcaps")
|
logging.debug("checking pcap's")
|
||||||
config = agent.config()
|
config = agent.config()
|
||||||
|
|
||||||
pcap_files = glob.glob(os.path.join(agent.config()['bettercap']['handshakes'], "*.pcap"))
|
pcap_files = glob.glob(os.path.join(agent.config()['bettercap']['handshakes'], "*.pcap"))
|
||||||
|
@ -1,42 +0,0 @@
|
|||||||
import logging
|
|
||||||
import requests
|
|
||||||
import pwnagotchi.plugins as plugins
|
|
||||||
|
|
||||||
'''
|
|
||||||
You need an bluetooth connection to your android phone which is running PAW server with the GPS "hack" from Systemik and edited by shaynemk
|
|
||||||
GUIDE HERE: https://community.pwnagotchi.ai/t/setting-up-paw-gps-on-android
|
|
||||||
'''
|
|
||||||
|
|
||||||
|
|
||||||
class PawGPS(plugins.Plugin):
|
|
||||||
__author__ = 'leont'
|
|
||||||
__version__ = '1.0.1'
|
|
||||||
__name__ = 'pawgps'
|
|
||||||
__license__ = 'GPL3'
|
|
||||||
__description__ = 'Saves GPS coordinates whenever an handshake is captured. The GPS data is get from PAW on android.'
|
|
||||||
|
|
||||||
def __init__(self):
|
|
||||||
self.options = dict()
|
|
||||||
|
|
||||||
def on_loaded(self):
|
|
||||||
logging.info("[paw-gps] plugin loaded")
|
|
||||||
if 'ip' not in self.options or ('ip' in self.options and self.options['ip'] is None) or (len('ip' in self.options and self.options['ip']) is 0):
|
|
||||||
logging.info("[paw-gps] no IP Address defined in the config file, will uses paw server default (192.168.44.1:8080)")
|
|
||||||
|
|
||||||
def on_handshake(self, agent, filename, access_point, client_station):
|
|
||||||
if 'ip' not in self.options or ('ip' in self.options and self.options['ip'] is None or (len('ip' in self.options and self.options['ip']) is 0)):
|
|
||||||
ip = "192.168.44.1:8080"
|
|
||||||
else:
|
|
||||||
ip = self.options['ip']
|
|
||||||
|
|
||||||
try:
|
|
||||||
gps = requests.get('http://' + ip + '/gps.xhtml')
|
|
||||||
try:
|
|
||||||
gps_filename = filename.replace('.pcap', '.paw-gps.json')
|
|
||||||
logging.info("[paw-gps] saving GPS data to %s" % (gps_filename))
|
|
||||||
with open(gps_filename, 'w+t') as f:
|
|
||||||
f.write(gps.text)
|
|
||||||
except Exception as error:
|
|
||||||
logging.error(f"[paw-gps] encountered error while saving gps data: {error}")
|
|
||||||
except Exception as error:
|
|
||||||
logging.error(f"[paw-gps] encountered error while getting gps data: {error}")
|
|
@ -14,7 +14,7 @@ from dateutil.parser import parse
|
|||||||
|
|
||||||
the plugin does the following:
|
the plugin does the following:
|
||||||
- search for *.pcap files in your /handshakes/ dir
|
- search for *.pcap files in your /handshakes/ dir
|
||||||
- for every found .pcap file it looks for a .geo.json or .gps.json or .paw-gps.json file with
|
- for every found .pcap file it looks for a .geo.json or .gps.json or file with
|
||||||
latitude+longitude data inside and shows this position on the map
|
latitude+longitude data inside and shows this position on the map
|
||||||
- if also an .cracked file with a plaintext password inside exist, it reads the content and shows the
|
- if also an .cracked file with a plaintext password inside exist, it reads the content and shows the
|
||||||
position as green instead of red and the password inside the infopox of the position
|
position as green instead of red and the password inside the infopox of the position
|
||||||
@ -87,7 +87,8 @@ class Webgpsmap(plugins.Plugin):
|
|||||||
# returns all positions
|
# returns all positions
|
||||||
try:
|
try:
|
||||||
self.ALREADY_SENT = list()
|
self.ALREADY_SENT = list()
|
||||||
response_data = bytes(json.dumps(self.load_gps_from_dir(self.config['bettercap']['handshakes'])), "utf-8")
|
response_data = bytes(
|
||||||
|
json.dumps(self.load_gps_from_dir(self.config['bettercap']['handshakes'])), "utf-8")
|
||||||
response_status = 200
|
response_status = 200
|
||||||
response_mimetype = "application/json"
|
response_mimetype = "application/json"
|
||||||
response_header_contenttype = 'application/json'
|
response_header_contenttype = 'application/json'
|
||||||
@ -100,7 +101,8 @@ class Webgpsmap(plugins.Plugin):
|
|||||||
self.ALREADY_SENT = list()
|
self.ALREADY_SENT = list()
|
||||||
json_data = json.dumps(self.load_gps_from_dir(self.config['bettercap']['handshakes']))
|
json_data = json.dumps(self.load_gps_from_dir(self.config['bettercap']['handshakes']))
|
||||||
html_data = self.get_html()
|
html_data = self.get_html()
|
||||||
html_data = html_data.replace('var positions = [];', 'var positions = ' + json_data + ';positionsLoaded=true;drawPositions();')
|
html_data = html_data.replace('var positions = [];',
|
||||||
|
'var positions = ' + json_data + ';positionsLoaded=true;drawPositions();')
|
||||||
response_data = bytes(html_data, "utf-8")
|
response_data = bytes(html_data, "utf-8")
|
||||||
response_status = 200
|
response_status = 200
|
||||||
response_mimetype = "application/xhtml+xml"
|
response_mimetype = "application/xhtml+xml"
|
||||||
@ -163,7 +165,8 @@ class Webgpsmap(plugins.Plugin):
|
|||||||
|
|
||||||
all_files = os.listdir(handshake_dir)
|
all_files = os.listdir(handshake_dir)
|
||||||
# print(all_files)
|
# print(all_files)
|
||||||
all_pcap_files = [os.path.join(handshake_dir, filename) for filename in all_files if filename.endswith('.pcap')]
|
all_pcap_files = [os.path.join(handshake_dir, filename) for filename in all_files if
|
||||||
|
filename.endswith('.pcap')]
|
||||||
all_geo_or_gps_files = []
|
all_geo_or_gps_files = []
|
||||||
for filename_pcap in all_pcap_files:
|
for filename_pcap in all_pcap_files:
|
||||||
filename_base = filename_pcap[:-5] # remove ".pcap"
|
filename_base = filename_pcap[:-5] # remove ".pcap"
|
||||||
@ -180,11 +183,6 @@ class Webgpsmap(plugins.Plugin):
|
|||||||
if check_for in all_files:
|
if check_for in all_files:
|
||||||
filename_position = str(os.path.join(handshake_dir, check_for))
|
filename_position = str(os.path.join(handshake_dir, check_for))
|
||||||
|
|
||||||
logging.debug("[webgpsmap] search for .paw-gps.json")
|
|
||||||
check_for = os.path.basename(filename_base) + ".paw-gps.json"
|
|
||||||
if check_for in all_files:
|
|
||||||
filename_position = str(os.path.join(handshake_dir, check_for))
|
|
||||||
|
|
||||||
logging.debug(f"[webgpsmap] end search for position data files and use {filename_position}")
|
logging.debug(f"[webgpsmap] end search for position data files and use {filename_position}")
|
||||||
|
|
||||||
if filename_position is not None:
|
if filename_position is not None:
|
||||||
@ -195,7 +193,8 @@ class Webgpsmap(plugins.Plugin):
|
|||||||
if newest_only:
|
if newest_only:
|
||||||
all_geo_or_gps_files = set(all_geo_or_gps_files) - set(self.ALREADY_SENT)
|
all_geo_or_gps_files = set(all_geo_or_gps_files) - set(self.ALREADY_SENT)
|
||||||
|
|
||||||
logging.info(f"[webgpsmap] Found {len(all_geo_or_gps_files)} position-data files from {len(all_pcap_files)} handshakes. Fetching positions ...")
|
logging.info(
|
||||||
|
f"[webgpsmap] Found {len(all_geo_or_gps_files)} position-data files from {len(all_pcap_files)} handshakes. Fetching positions ...")
|
||||||
|
|
||||||
for pos_file in all_geo_or_gps_files:
|
for pos_file in all_geo_or_gps_files:
|
||||||
try:
|
try:
|
||||||
@ -213,8 +212,6 @@ class Webgpsmap(plugins.Plugin):
|
|||||||
pos_type = 'gps'
|
pos_type = 'gps'
|
||||||
elif pos.type() == PositionFile.GEO:
|
elif pos.type() == PositionFile.GEO:
|
||||||
pos_type = 'geo'
|
pos_type = 'geo'
|
||||||
elif pos.type() == PositionFile.PAWGPS:
|
|
||||||
pos_type = 'paw'
|
|
||||||
gps_data[ssid + "_" + mac] = {
|
gps_data[ssid + "_" + mac] = {
|
||||||
'ssid': ssid,
|
'ssid': ssid,
|
||||||
'mac': mac,
|
'mac': mac,
|
||||||
@ -265,7 +262,6 @@ class PositionFile:
|
|||||||
"""
|
"""
|
||||||
GPS = 1
|
GPS = 1
|
||||||
GEO = 2
|
GEO = 2
|
||||||
PAWGPS = 3
|
|
||||||
|
|
||||||
def __init__(self, path):
|
def __init__(self, path):
|
||||||
self._file = path
|
self._file = path
|
||||||
@ -282,7 +278,7 @@ class PositionFile:
|
|||||||
"""
|
"""
|
||||||
Returns the mac from filename
|
Returns the mac from filename
|
||||||
"""
|
"""
|
||||||
parsed_mac = re.search(r'.*_?([a-zA-Z0-9]{12})\.(?:gps|geo|paw-gps)\.json', self._filename)
|
parsed_mac = re.search(r'.*_?([a-zA-Z0-9]{12})\.(?:gps|geo)\.json', self._filename)
|
||||||
if parsed_mac:
|
if parsed_mac:
|
||||||
mac = parsed_mac.groups()[0]
|
mac = parsed_mac.groups()[0]
|
||||||
return mac
|
return mac
|
||||||
@ -292,7 +288,7 @@ class PositionFile:
|
|||||||
"""
|
"""
|
||||||
Returns the ssid from filename
|
Returns the ssid from filename
|
||||||
"""
|
"""
|
||||||
parsed_ssid = re.search(r'(.+)_[a-zA-Z0-9]{12}\.(?:gps|geo|paw-gps)\.json', self._filename)
|
parsed_ssid = re.search(r'(.+)_[a-zA-Z0-9]{12}\.(?:gps|geo)\.json', self._filename)
|
||||||
if parsed_ssid:
|
if parsed_ssid:
|
||||||
return parsed_ssid.groups()[0]
|
return parsed_ssid.groups()[0]
|
||||||
return None
|
return None
|
||||||
@ -354,8 +350,6 @@ class PositionFile:
|
|||||||
return PositionFile.GPS
|
return PositionFile.GPS
|
||||||
if self._file.endswith('.geo.json'):
|
if self._file.endswith('.geo.json'):
|
||||||
return PositionFile.GEO
|
return PositionFile.GEO
|
||||||
if self._file.endswith('.paw-gps.json'):
|
|
||||||
return PositionFile.PAWGPS
|
|
||||||
return None
|
return None
|
||||||
|
|
||||||
def lat(self):
|
def lat(self):
|
||||||
@ -403,8 +397,6 @@ class PositionFile:
|
|||||||
def accuracy(self):
|
def accuracy(self):
|
||||||
if self.type() == PositionFile.GPS:
|
if self.type() == PositionFile.GPS:
|
||||||
return 50.0 # a default
|
return 50.0 # a default
|
||||||
if self.type() == PositionFile.PAWGPS:
|
|
||||||
return 50.0 # a default
|
|
||||||
if self.type() == PositionFile.GEO:
|
if self.type() == PositionFile.GEO:
|
||||||
try:
|
try:
|
||||||
return self._json['accuracy']
|
return self._json['accuracy']
|
||||||
|
@ -118,6 +118,9 @@ class Display(View):
|
|||||||
def is_waveshare2in66g(self):
|
def is_waveshare2in66g(self):
|
||||||
return self._implementation.name == 'waveshare2in66g'
|
return self._implementation.name == 'waveshare2in66g'
|
||||||
|
|
||||||
|
def is_weact2in9(self):
|
||||||
|
return self._implementation.name == 'weact2in9'
|
||||||
|
|
||||||
def is_waveshare3in0g(self):
|
def is_waveshare3in0g(self):
|
||||||
return self._implementation.name == 'waveshare3in0g'
|
return self._implementation.name == 'waveshare3in0g'
|
||||||
|
|
||||||
@ -151,6 +154,12 @@ class Display(View):
|
|||||||
def is_waveshare5in65f(self):
|
def is_waveshare5in65f(self):
|
||||||
return self._implementation.name == 'waveshare5in65f'
|
return self._implementation.name == 'waveshare5in65f'
|
||||||
|
|
||||||
|
def is_waveshare5in79(self):
|
||||||
|
return self._implementation.name == 'waveshare5in79'
|
||||||
|
|
||||||
|
def is_waveshare5in79b(self):
|
||||||
|
return self._implementation.name == 'waveshare5in79b'
|
||||||
|
|
||||||
def is_waveshare5in83(self):
|
def is_waveshare5in83(self):
|
||||||
return self._implementation.name == 'waveshare5in83'
|
return self._implementation.name == 'waveshare5in83'
|
||||||
|
|
||||||
@ -208,6 +217,18 @@ class Display(View):
|
|||||||
def is_displayhatmini(self):
|
def is_displayhatmini(self):
|
||||||
return self._implementation.name == 'displayhatmini'
|
return self._implementation.name == 'displayhatmini'
|
||||||
|
|
||||||
|
def is_pirateaudio(self):
|
||||||
|
return self._implementation.name == 'pirateaudio'
|
||||||
|
|
||||||
|
def is_pitft(self):
|
||||||
|
return self._implementation.name == 'pitft'
|
||||||
|
|
||||||
|
def is_tftbonnet(self):
|
||||||
|
return self._implementation.name == 'tftbonnet'
|
||||||
|
|
||||||
|
def is_waveshareoledlcd(self):
|
||||||
|
return self._implementation.name == 'waveshareoledlcd'
|
||||||
|
|
||||||
def is_waveshare35lcd(self):
|
def is_waveshare35lcd(self):
|
||||||
return self._implementation.name == 'waveshare35lcd'
|
return self._implementation.name == 'waveshare35lcd'
|
||||||
|
|
||||||
|
@ -21,6 +21,10 @@ from pwnagotchi.ui.hw.waveshare2in13b_V4 import Waveshare213bV4
|
|||||||
from pwnagotchi.ui.hw.waveshare3in5lcd import Waveshare35lcd
|
from pwnagotchi.ui.hw.waveshare3in5lcd import Waveshare35lcd
|
||||||
from pwnagotchi.ui.hw.spotpear24in import Spotpear24inch
|
from pwnagotchi.ui.hw.spotpear24in import Spotpear24inch
|
||||||
from pwnagotchi.ui.hw.displayhatmini import DisplayHatMini
|
from pwnagotchi.ui.hw.displayhatmini import DisplayHatMini
|
||||||
|
from pwnagotchi.ui.hw.pirateaudio import PirateAudio
|
||||||
|
from pwnagotchi.ui.hw.pitft import Pitft
|
||||||
|
from pwnagotchi.ui.hw.tftbonnet import TftBonnet
|
||||||
|
from pwnagotchi.ui.hw.waveshareoledlcd import Waveshareoledlcd
|
||||||
from pwnagotchi.ui.hw.waveshare1in02 import Waveshare1in02
|
from pwnagotchi.ui.hw.waveshare1in02 import Waveshare1in02
|
||||||
from pwnagotchi.ui.hw.waveshare1in54 import Waveshare154
|
from pwnagotchi.ui.hw.waveshare1in54 import Waveshare154
|
||||||
from pwnagotchi.ui.hw.waveshare1in54_V2 import Waveshare154V2
|
from pwnagotchi.ui.hw.waveshare1in54_V2 import Waveshare154V2
|
||||||
@ -49,6 +53,8 @@ from pwnagotchi.ui.hw.waveshare4in2bc import Waveshare4in2bc
|
|||||||
from pwnagotchi.ui.hw.waveshare4in26 import Waveshare4in26
|
from pwnagotchi.ui.hw.waveshare4in26 import Waveshare4in26
|
||||||
from pwnagotchi.ui.hw.waveshare4in37g import Waveshare4in37g
|
from pwnagotchi.ui.hw.waveshare4in37g import Waveshare4in37g
|
||||||
from pwnagotchi.ui.hw.waveshare5in65f import Waveshare5in65f
|
from pwnagotchi.ui.hw.waveshare5in65f import Waveshare5in65f
|
||||||
|
from pwnagotchi.ui.hw.waveshare5in79 import Waveshare5in79
|
||||||
|
from pwnagotchi.ui.hw.waveshare5in79b import Waveshare5in79b
|
||||||
from pwnagotchi.ui.hw.waveshare5in83 import Waveshare5in83
|
from pwnagotchi.ui.hw.waveshare5in83 import Waveshare5in83
|
||||||
from pwnagotchi.ui.hw.waveshare5in83_V2 import Waveshare5in83V2
|
from pwnagotchi.ui.hw.waveshare5in83_V2 import Waveshare5in83V2
|
||||||
from pwnagotchi.ui.hw.waveshare5in83b_V2 import Waveshare5in83bV2
|
from pwnagotchi.ui.hw.waveshare5in83b_V2 import Waveshare5in83bV2
|
||||||
@ -96,6 +102,18 @@ def display_for(config):
|
|||||||
elif config['ui']['display']['type'] == 'displayhatmini':
|
elif config['ui']['display']['type'] == 'displayhatmini':
|
||||||
return DisplayHatMini(config)
|
return DisplayHatMini(config)
|
||||||
|
|
||||||
|
elif config['ui']['display']['type'] == 'pirateaudio':
|
||||||
|
return PirateAudio(config)
|
||||||
|
|
||||||
|
elif config['ui']['display']['type'] == 'pitft':
|
||||||
|
return Pitft(config)
|
||||||
|
|
||||||
|
elif config['ui']['display']['type'] == 'tftbonnet':
|
||||||
|
return TftBonnet(config)
|
||||||
|
|
||||||
|
elif config['ui']['display']['type'] == 'waveshareoledlcd':
|
||||||
|
return Waveshareoledlcd(config)
|
||||||
|
|
||||||
elif config['ui']['display']['type'] == 'waveshare1in02':
|
elif config['ui']['display']['type'] == 'waveshare1in02':
|
||||||
return Waveshare1in02(config)
|
return Waveshare1in02(config)
|
||||||
|
|
||||||
@ -219,6 +237,12 @@ def display_for(config):
|
|||||||
elif config['ui']['display']['type'] == 'waveshare5in65f':
|
elif config['ui']['display']['type'] == 'waveshare5in65f':
|
||||||
return Waveshare5in65f(config)
|
return Waveshare5in65f(config)
|
||||||
|
|
||||||
|
elif config['ui']['display']['type'] == 'waveshare5in79':
|
||||||
|
return Waveshare5in79(config)
|
||||||
|
|
||||||
|
elif config['ui']['display']['type'] == 'waveshare5in79b':
|
||||||
|
return Waveshare5in79b(config)
|
||||||
|
|
||||||
elif config['ui']['display']['type'] == 'waveshare5in83':
|
elif config['ui']['display']['type'] == 'waveshare5in83':
|
||||||
return Waveshare5in83(config)
|
return Waveshare5in83(config)
|
||||||
|
|
||||||
|
362
pwnagotchi/ui/hw/libs/adafruit/pitft/ILI9341.py
Normal file
362
pwnagotchi/ui/hw/libs/adafruit/pitft/ILI9341.py
Normal file
@ -0,0 +1,362 @@
|
|||||||
|
# Copyright (c) 2014 Adafruit Industries
|
||||||
|
# Author: Tony DiCola
|
||||||
|
#
|
||||||
|
# Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
|
# of this software and associated documentation files (the "Software"), to deal
|
||||||
|
# in the Software without restriction, including without limitation the rights
|
||||||
|
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
|
# copies of the Software, and to permit persons to whom the Software is
|
||||||
|
# furnished to do so, subject to the following conditions:
|
||||||
|
#
|
||||||
|
# The above copyright notice and this permission notice shall be included in
|
||||||
|
# all copies or substantial portions of the Software.
|
||||||
|
#
|
||||||
|
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
|
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||||
|
# THE SOFTWARE.
|
||||||
|
|
||||||
|
# Modified for Pwnagotchi by RasTacsko
|
||||||
|
# Based on ST7899 driver for pimoroni displayhatmini by Do-Ki
|
||||||
|
|
||||||
|
import numbers
|
||||||
|
import time
|
||||||
|
import numpy as np
|
||||||
|
|
||||||
|
from PIL import Image
|
||||||
|
from PIL import ImageDraw
|
||||||
|
|
||||||
|
import spidev
|
||||||
|
import RPi.GPIO as GPIO
|
||||||
|
|
||||||
|
__version__ = '0.0.1'
|
||||||
|
|
||||||
|
# Constants for interacting with display registers.
|
||||||
|
ILI9341_TFTWIDTH = 320
|
||||||
|
ILI9341_TFTHEIGHT = 240
|
||||||
|
|
||||||
|
ILI9341_NOP = 0x00
|
||||||
|
ILI9341_SWRESET = 0x01
|
||||||
|
ILI9341_RDDID = 0x04
|
||||||
|
ILI9341_RDDST = 0x09
|
||||||
|
|
||||||
|
ILI9341_SLPIN = 0x10
|
||||||
|
ILI9341_SLPOUT = 0x11
|
||||||
|
ILI9341_PTLON = 0x12
|
||||||
|
ILI9341_NORON = 0x13
|
||||||
|
|
||||||
|
ILI9341_RDMODE = 0x0A
|
||||||
|
ILI9341_RDMADCTL = 0x0B
|
||||||
|
ILI9341_RDPIXFMT = 0x0C
|
||||||
|
ILI9341_RDIMGFMT = 0x0A
|
||||||
|
ILI9341_RDSELFDIAG = 0x0F
|
||||||
|
|
||||||
|
ILI9341_INVOFF = 0x20
|
||||||
|
ILI9341_INVON = 0x21
|
||||||
|
ILI9341_GAMMASET = 0x26
|
||||||
|
ILI9341_DISPOFF = 0x28
|
||||||
|
ILI9341_DISPON = 0x29
|
||||||
|
|
||||||
|
ILI9341_CASET = 0x2A
|
||||||
|
ILI9341_PASET = 0x2B
|
||||||
|
ILI9341_RAMWR = 0x2C
|
||||||
|
ILI9341_RAMRD = 0x2E
|
||||||
|
|
||||||
|
ILI9341_PTLAR = 0x30
|
||||||
|
ILI9341_MADCTL = 0x36
|
||||||
|
ILI9341_PIXFMT = 0x3A
|
||||||
|
|
||||||
|
ILI9341_FRMCTR1 = 0xB1
|
||||||
|
ILI9341_FRMCTR2 = 0xB2
|
||||||
|
ILI9341_FRMCTR3 = 0xB3
|
||||||
|
ILI9341_INVCTR = 0xB4
|
||||||
|
ILI9341_DFUNCTR = 0xB6
|
||||||
|
|
||||||
|
ILI9341_PWCTR1 = 0xC0
|
||||||
|
ILI9341_PWCTR2 = 0xC1
|
||||||
|
ILI9341_PWCTR3 = 0xC2
|
||||||
|
ILI9341_PWCTR4 = 0xC3
|
||||||
|
ILI9341_PWCTR5 = 0xC4
|
||||||
|
ILI9341_VMCTR1 = 0xC5
|
||||||
|
ILI9341_VMCTR2 = 0xC7
|
||||||
|
|
||||||
|
ILI9341_RDID1 = 0xDA
|
||||||
|
ILI9341_RDID2 = 0xDB
|
||||||
|
ILI9341_RDID3 = 0xDC
|
||||||
|
ILI9341_RDID4 = 0xDD
|
||||||
|
|
||||||
|
ILI9341_GMCTRP1 = 0xE0
|
||||||
|
ILI9341_GMCTRN1 = 0xE1
|
||||||
|
|
||||||
|
ILI9341_PWCTR6 = 0xFC
|
||||||
|
|
||||||
|
class ILI9341(object):
|
||||||
|
"""Representation of an ILI9341 TFT LCD."""
|
||||||
|
|
||||||
|
def __init__(self, port, cs, dc, backlight, rst=None,
|
||||||
|
width=ILI9341_TFTWIDTH, height=ILI9341_TFTHEIGHT,
|
||||||
|
rotation=270, invert=False, spi_speed_hz=64000000,
|
||||||
|
offset_left=0, offset_top=0):
|
||||||
|
"""Create an instance of the display using SPI communication.
|
||||||
|
Must provide the GPIO pin number for the D/C pin and the SPI driver.
|
||||||
|
Can optionally provide the GPIO pin number for the reset pin as the rst parameter.
|
||||||
|
:param port: SPI port number -> 0
|
||||||
|
:param cs: SPI chip-select number (0 or 1 for BCM) -> 1
|
||||||
|
:param backlight: Pin for controlling backlight -> 18
|
||||||
|
:param rst: Reset pin for ILI9341 -> 24?
|
||||||
|
:param width: Width of display connected to ILI9341 -> 240
|
||||||
|
:param height: Height of display connected to ILI9341 -> 320
|
||||||
|
:param rotation: Rotation of display connected to ILI9341
|
||||||
|
:param invert: Invert display
|
||||||
|
:param spi_speed_hz: SPI speed (in Hz)
|
||||||
|
"""
|
||||||
|
|
||||||
|
if rotation not in [0, 90, 180, 270]:
|
||||||
|
raise ValueError("Invalid rotation {}".format(rotation))
|
||||||
|
|
||||||
|
GPIO.setwarnings(False)
|
||||||
|
GPIO.setmode(GPIO.BCM)
|
||||||
|
|
||||||
|
self._spi = spidev.SpiDev(port, cs)
|
||||||
|
self._spi.mode = 0
|
||||||
|
self._spi.lsbfirst = False
|
||||||
|
self._spi.max_speed_hz = spi_speed_hz
|
||||||
|
|
||||||
|
self._dc = dc
|
||||||
|
self._rst = rst
|
||||||
|
self._width = width
|
||||||
|
self._height = height
|
||||||
|
self._rotation = rotation
|
||||||
|
self._invert = invert
|
||||||
|
|
||||||
|
self._offset_left = offset_left
|
||||||
|
self._offset_top = offset_top
|
||||||
|
|
||||||
|
# Set DC as output.
|
||||||
|
GPIO.setup(dc, GPIO.OUT)
|
||||||
|
|
||||||
|
# Setup backlight as output (if provided).
|
||||||
|
self._backlight = backlight
|
||||||
|
if backlight is not None:
|
||||||
|
GPIO.setup(backlight, GPIO.OUT)
|
||||||
|
GPIO.output(backlight, GPIO.LOW)
|
||||||
|
time.sleep(0.05)
|
||||||
|
GPIO.output(backlight, GPIO.HIGH)
|
||||||
|
|
||||||
|
# Setup reset as output (if provided).
|
||||||
|
if rst is not None:
|
||||||
|
GPIO.setup(self._rst, GPIO.OUT)
|
||||||
|
self.reset()
|
||||||
|
|
||||||
|
# Create an image buffer.
|
||||||
|
self.buffer = Image.new('RGB', (width, height))
|
||||||
|
|
||||||
|
self._init()
|
||||||
|
|
||||||
|
def send(self, data, is_data=True, chunk_size=4096):
|
||||||
|
"""Write a byte or array of bytes to the display. Is_data parameter
|
||||||
|
controls if byte should be interpreted as display data (True) or command
|
||||||
|
data (False). Chunk_size is an optional size of bytes to write in a
|
||||||
|
single SPI transaction, with a default of 4096.
|
||||||
|
"""
|
||||||
|
# Set DC low for command, high for data.
|
||||||
|
GPIO.output(self._dc, is_data)
|
||||||
|
# Convert scalar argument to list so either can be passed as parameter.
|
||||||
|
if isinstance(data, numbers.Number):
|
||||||
|
data = [data & 0xFF]
|
||||||
|
# Write data a chunk at a time.
|
||||||
|
for start in range(0, len(data), chunk_size):
|
||||||
|
end = min(start+chunk_size, len(data))
|
||||||
|
self._spi.xfer(data[start:end])
|
||||||
|
|
||||||
|
def set_backlight(self, value):
|
||||||
|
"""Set the backlight on/off."""
|
||||||
|
if self._backlight is not None:
|
||||||
|
GPIO.output(self._backlight, value)
|
||||||
|
|
||||||
|
@property
|
||||||
|
def width(self):
|
||||||
|
return self._width if self._rotation == 0 or self._rotation == 180 else self._height
|
||||||
|
|
||||||
|
@property
|
||||||
|
def height(self):
|
||||||
|
return self._height if self._rotation == 0 or self._rotation == 180 else self._width
|
||||||
|
|
||||||
|
def command(self, data):
|
||||||
|
"""Write a byte or array of bytes to the display as command data."""
|
||||||
|
self.send(data, False)
|
||||||
|
|
||||||
|
def data(self, data):
|
||||||
|
"""Write a byte or array of bytes to the display as display data."""
|
||||||
|
self.send(data, True)
|
||||||
|
|
||||||
|
def reset(self):
|
||||||
|
"""Reset the display, if reset pin is connected."""
|
||||||
|
if self._rst is not None:
|
||||||
|
GPIO.output(self._rst, 1)
|
||||||
|
time.sleep(0.005)
|
||||||
|
GPIO.output(self._rst, 0)
|
||||||
|
time.sleep(0.02)
|
||||||
|
GPIO.output(self._rst, 1)
|
||||||
|
time.sleep(0.150)
|
||||||
|
|
||||||
|
def _init(self):
|
||||||
|
# Initialize the display. Broken out as a separate function so it can
|
||||||
|
# be overridden by other displays in the future.
|
||||||
|
self.command(0xEF)
|
||||||
|
self.data(0x03)
|
||||||
|
self.data(0x80)
|
||||||
|
self.data(0x02)
|
||||||
|
self.command(0xCF)
|
||||||
|
self.data(0x00)
|
||||||
|
self.data(0XC1)
|
||||||
|
self.data(0X30)
|
||||||
|
self.command(0xED)
|
||||||
|
self.data(0x64)
|
||||||
|
self.data(0x03)
|
||||||
|
self.data(0X12)
|
||||||
|
self.data(0X81)
|
||||||
|
self.command(0xE8)
|
||||||
|
self.data(0x85)
|
||||||
|
self.data(0x00)
|
||||||
|
self.data(0x78)
|
||||||
|
self.command(0xCB)
|
||||||
|
self.data(0x39)
|
||||||
|
self.data(0x2C)
|
||||||
|
self.data(0x00)
|
||||||
|
self.data(0x34)
|
||||||
|
self.data(0x02)
|
||||||
|
self.command(0xF7)
|
||||||
|
self.data(0x20)
|
||||||
|
self.command(0xEA)
|
||||||
|
self.data(0x00)
|
||||||
|
self.data(0x00)
|
||||||
|
self.command(ILI9341_PWCTR1) # Power control
|
||||||
|
self.data(0x23) # VRH[5:0]
|
||||||
|
self.command(ILI9341_PWCTR2) # Power control
|
||||||
|
self.data(0x10) # SAP[2:0];BT[3:0]
|
||||||
|
self.command(ILI9341_VMCTR1) # VCM control
|
||||||
|
self.data(0x3e)
|
||||||
|
self.data(0x28)
|
||||||
|
self.command(ILI9341_VMCTR2) # VCM control2
|
||||||
|
self.data(0x86) # --
|
||||||
|
self.command(ILI9341_MADCTL) # Memory Access Control
|
||||||
|
self.data(0x48)
|
||||||
|
self.command(ILI9341_PIXFMT)
|
||||||
|
self.data(0x55)
|
||||||
|
self.command(ILI9341_FRMCTR1)
|
||||||
|
self.data(0x00)
|
||||||
|
self.data(0x18)
|
||||||
|
self.command(ILI9341_DFUNCTR) # Display Function Control
|
||||||
|
self.data(0x08)
|
||||||
|
self.data(0x82)
|
||||||
|
self.data(0x27)
|
||||||
|
self.command(0xF2) # 3Gamma Function Disable
|
||||||
|
self.data(0x00)
|
||||||
|
self.command(ILI9341_GAMMASET) # Gamma curve selected
|
||||||
|
self.data(0x01)
|
||||||
|
self.command(ILI9341_GMCTRP1) # Set Gamma
|
||||||
|
self.data(0x0F)
|
||||||
|
self.data(0x31)
|
||||||
|
self.data(0x2B)
|
||||||
|
self.data(0x0C)
|
||||||
|
self.data(0x0E)
|
||||||
|
self.data(0x08)
|
||||||
|
self.data(0x4E)
|
||||||
|
self.data(0xF1)
|
||||||
|
self.data(0x37)
|
||||||
|
self.data(0x07)
|
||||||
|
self.data(0x10)
|
||||||
|
self.data(0x03)
|
||||||
|
self.data(0x0E)
|
||||||
|
self.data(0x09)
|
||||||
|
self.data(0x00)
|
||||||
|
self.command(ILI9341_GMCTRN1) # Set Gamma
|
||||||
|
self.data(0x00)
|
||||||
|
self.data(0x0E)
|
||||||
|
self.data(0x14)
|
||||||
|
self.data(0x03)
|
||||||
|
self.data(0x11)
|
||||||
|
self.data(0x07)
|
||||||
|
self.data(0x31)
|
||||||
|
self.data(0xC1)
|
||||||
|
self.data(0x48)
|
||||||
|
self.data(0x08)
|
||||||
|
self.data(0x0F)
|
||||||
|
self.data(0x0C)
|
||||||
|
self.data(0x31)
|
||||||
|
self.data(0x36)
|
||||||
|
self.data(0x0F)
|
||||||
|
if self._invert:
|
||||||
|
self.command(ILI9341_INVON) # Invert display
|
||||||
|
else:
|
||||||
|
self.command(ILI9341_INVOFF) # Don't invert display
|
||||||
|
self.command(ILI9341_SLPOUT) # Exit Sleep
|
||||||
|
time.sleep(0.120)
|
||||||
|
self.command(ILI9341_DISPON) # Display on
|
||||||
|
|
||||||
|
def begin(self):
|
||||||
|
"""Set up the display deprecated.
|
||||||
|
Included in __init__. """
|
||||||
|
pass
|
||||||
|
|
||||||
|
def set_window(self, x0=0, y0=0, x1=None, y1=None):
|
||||||
|
"""Set the pixel address window for proceeding drawing commands. x0 and
|
||||||
|
x1 should define the minimum and maximum x pixel bounds. y0 and y1
|
||||||
|
should define the minimum and maximum y pixel bound. If no parameters
|
||||||
|
are specified the default will be to update the entire display from 0,0
|
||||||
|
to 239,319.
|
||||||
|
"""
|
||||||
|
if x1 is None:
|
||||||
|
x1 = self.width-1
|
||||||
|
if y1 is None:
|
||||||
|
y1 = self.height-1
|
||||||
|
|
||||||
|
self.command(ILI9341_CASET) # Column addr set
|
||||||
|
self.data(x0 >> 8)
|
||||||
|
self.data(x0 & 0xFF) # XSTART
|
||||||
|
self.data(x1 >> 8)
|
||||||
|
self.data(x1 & 0xFF) # XEND
|
||||||
|
self.command(ILI9341_PASET) # Row addr set
|
||||||
|
self.data(y0 >> 8)
|
||||||
|
self.data(y0 & 0xFF) # YSTART
|
||||||
|
self.data(y1 >> 8)
|
||||||
|
self.data(y1 & 0xFF) # YEND
|
||||||
|
self.command(ILI9341_RAMWR) # write to RAM
|
||||||
|
|
||||||
|
def display(self, image):
|
||||||
|
"""Write the provided image to the hardware.
|
||||||
|
:param image: Should be RGB format and the same dimensions as the display hardware.
|
||||||
|
"""
|
||||||
|
# Set address bounds to entire display.
|
||||||
|
self.set_window()
|
||||||
|
|
||||||
|
# Convert image to 16bit RGB565 format and
|
||||||
|
# flatten into bytes.
|
||||||
|
pixelbytes = self.image_to_data(image, self._rotation)
|
||||||
|
|
||||||
|
# Write data to hardware.
|
||||||
|
for i in range(0, len(pixelbytes), 4096):
|
||||||
|
self.data(pixelbytes[i:i + 4096])
|
||||||
|
|
||||||
|
def image_to_data(self, image, rotation=0):
|
||||||
|
if not isinstance(image, np.ndarray):
|
||||||
|
image = np.array(image.convert('RGB'))
|
||||||
|
|
||||||
|
# Rotate the image
|
||||||
|
pb = np.rot90(image, rotation // 90).astype('uint16')
|
||||||
|
|
||||||
|
|
||||||
|
# Mask and shift the 888 RGB into 565 RGB
|
||||||
|
red = (pb[..., [0]] & 0xf8) << 8
|
||||||
|
green = (pb[..., [1]] & 0xfc) << 3
|
||||||
|
blue = (pb[..., [2]] & 0xf8) >> 3
|
||||||
|
|
||||||
|
# Stick 'em together
|
||||||
|
result = red | green | blue
|
||||||
|
|
||||||
|
# Output the raw bytes
|
||||||
|
return result.byteswap().tobytes()
|
360
pwnagotchi/ui/hw/libs/adafruit/tftbonnet/ST7789.py
Normal file
360
pwnagotchi/ui/hw/libs/adafruit/tftbonnet/ST7789.py
Normal file
@ -0,0 +1,360 @@
|
|||||||
|
# Copyright (c) 2014 Adafruit Industries
|
||||||
|
# Author: Tony DiCola
|
||||||
|
#
|
||||||
|
# Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
|
# of this software and associated documentation files (the "Software"), to deal
|
||||||
|
# in the Software without restriction, including without limitation the rights
|
||||||
|
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
|
# copies of the Software, and to permit persons to whom the Software is
|
||||||
|
# furnished to do so, subject to the following conditions:
|
||||||
|
#
|
||||||
|
# The above copyright notice and this permission notice shall be included in
|
||||||
|
# all copies or substantial portions of the Software.
|
||||||
|
#
|
||||||
|
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
|
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||||
|
# THE SOFTWARE.
|
||||||
|
import numbers
|
||||||
|
import time
|
||||||
|
import numpy as np
|
||||||
|
|
||||||
|
import spidev
|
||||||
|
import RPi.GPIO as GPIO
|
||||||
|
|
||||||
|
|
||||||
|
__version__ = '0.0.4'
|
||||||
|
|
||||||
|
BG_SPI_CS_BACK = 0
|
||||||
|
BG_SPI_CS_FRONT = 1
|
||||||
|
|
||||||
|
SPI_CLOCK_HZ = 16000000
|
||||||
|
|
||||||
|
ST7789_NOP = 0x00
|
||||||
|
ST7789_SWRESET = 0x01
|
||||||
|
ST7789_RDDID = 0x04
|
||||||
|
ST7789_RDDST = 0x09
|
||||||
|
|
||||||
|
ST7789_SLPIN = 0x10
|
||||||
|
ST7789_SLPOUT = 0x11
|
||||||
|
ST7789_PTLON = 0x12
|
||||||
|
ST7789_NORON = 0x13
|
||||||
|
|
||||||
|
ST7789_INVOFF = 0x20
|
||||||
|
ST7789_INVON = 0x21
|
||||||
|
ST7789_DISPOFF = 0x28
|
||||||
|
ST7789_DISPON = 0x29
|
||||||
|
|
||||||
|
ST7789_CASET = 0x2A
|
||||||
|
ST7789_RASET = 0x2B
|
||||||
|
ST7789_RAMWR = 0x2C
|
||||||
|
ST7789_RAMRD = 0x2E
|
||||||
|
|
||||||
|
ST7789_PTLAR = 0x30
|
||||||
|
ST7789_MADCTL = 0x36
|
||||||
|
ST7789_COLMOD = 0x3A
|
||||||
|
|
||||||
|
ST7789_FRMCTR1 = 0xB1
|
||||||
|
ST7789_FRMCTR2 = 0xB2
|
||||||
|
ST7789_FRMCTR3 = 0xB3
|
||||||
|
ST7789_INVCTR = 0xB4
|
||||||
|
ST7789_DISSET5 = 0xB6
|
||||||
|
|
||||||
|
ST7789_GCTRL = 0xB7
|
||||||
|
ST7789_GTADJ = 0xB8
|
||||||
|
ST7789_VCOMS = 0xBB
|
||||||
|
|
||||||
|
ST7789_LCMCTRL = 0xC0
|
||||||
|
ST7789_IDSET = 0xC1
|
||||||
|
ST7789_VDVVRHEN = 0xC2
|
||||||
|
ST7789_VRHS = 0xC3
|
||||||
|
ST7789_VDVS = 0xC4
|
||||||
|
ST7789_VMCTR1 = 0xC5
|
||||||
|
ST7789_FRCTRL2 = 0xC6
|
||||||
|
ST7789_CABCCTRL = 0xC7
|
||||||
|
|
||||||
|
ST7789_RDID1 = 0xDA
|
||||||
|
ST7789_RDID2 = 0xDB
|
||||||
|
ST7789_RDID3 = 0xDC
|
||||||
|
ST7789_RDID4 = 0xDD
|
||||||
|
|
||||||
|
ST7789_GMCTRP1 = 0xE0
|
||||||
|
ST7789_GMCTRN1 = 0xE1
|
||||||
|
|
||||||
|
ST7789_PWCTR6 = 0xFC
|
||||||
|
|
||||||
|
|
||||||
|
class ST7789(object):
|
||||||
|
"""Representation of an ST7789 TFT LCD."""
|
||||||
|
|
||||||
|
def __init__(self, port, cs, dc, backlight, rst=None, width=240,
|
||||||
|
height=240, rotation=90, invert=True, spi_speed_hz=60 * 1000 * 1000,
|
||||||
|
offset_left=0,
|
||||||
|
offset_top=0):
|
||||||
|
"""Create an instance of the display using SPI communication.
|
||||||
|
|
||||||
|
Must provide the GPIO pin number for the D/C pin and the SPI driver.
|
||||||
|
|
||||||
|
Can optionally provide the GPIO pin number for the reset pin as the rst parameter.
|
||||||
|
|
||||||
|
:param port: SPI port number
|
||||||
|
:param cs: SPI chip-select number (0 or 1 for BCM
|
||||||
|
:param backlight: Pin for controlling backlight
|
||||||
|
:param rst: Reset pin for ST7789
|
||||||
|
:param width: Width of display connected to ST7789
|
||||||
|
:param height: Height of display connected to ST7789
|
||||||
|
:param rotation: Rotation of display connected to ST7789
|
||||||
|
:param invert: Invert display
|
||||||
|
:param spi_speed_hz: SPI speed (in Hz)
|
||||||
|
|
||||||
|
"""
|
||||||
|
if rotation not in [0, 90, 180, 270]:
|
||||||
|
raise ValueError("Invalid rotation {}".format(rotation))
|
||||||
|
|
||||||
|
if width != height and rotation in [90, 270]:
|
||||||
|
raise ValueError("Invalid rotation {} for {}x{} resolution".format(rotation, width, height))
|
||||||
|
|
||||||
|
GPIO.setwarnings(False)
|
||||||
|
GPIO.setmode(GPIO.BCM)
|
||||||
|
|
||||||
|
self._spi = spidev.SpiDev(port, cs)
|
||||||
|
self._spi.mode = 0
|
||||||
|
self._spi.lsbfirst = False
|
||||||
|
self._spi.max_speed_hz = spi_speed_hz
|
||||||
|
|
||||||
|
self._dc = dc
|
||||||
|
self._rst = rst
|
||||||
|
self._width = width
|
||||||
|
self._height = height
|
||||||
|
self._rotation = rotation
|
||||||
|
self._invert = invert
|
||||||
|
|
||||||
|
self._offset_left = offset_left
|
||||||
|
self._offset_top = offset_top
|
||||||
|
|
||||||
|
# Set DC as output.
|
||||||
|
GPIO.setup(dc, GPIO.OUT)
|
||||||
|
|
||||||
|
# Setup backlight as output (if provided).
|
||||||
|
self._backlight = backlight
|
||||||
|
if backlight is not None:
|
||||||
|
GPIO.setup(backlight, GPIO.OUT)
|
||||||
|
GPIO.output(backlight, GPIO.LOW)
|
||||||
|
time.sleep(0.1)
|
||||||
|
GPIO.output(backlight, GPIO.HIGH)
|
||||||
|
|
||||||
|
# Setup reset as output (if provided).
|
||||||
|
if rst is not None:
|
||||||
|
GPIO.setup(self._rst, GPIO.OUT)
|
||||||
|
self.reset()
|
||||||
|
self._init()
|
||||||
|
|
||||||
|
def send(self, data, is_data=True, chunk_size=4096):
|
||||||
|
"""Write a byte or array of bytes to the display. Is_data parameter
|
||||||
|
controls if byte should be interpreted as display data (True) or command
|
||||||
|
data (False). Chunk_size is an optional size of bytes to write in a
|
||||||
|
single SPI transaction, with a default of 4096.
|
||||||
|
"""
|
||||||
|
# Set DC low for command, high for data.
|
||||||
|
GPIO.output(self._dc, is_data)
|
||||||
|
# Convert scalar argument to list so either can be passed as parameter.
|
||||||
|
if isinstance(data, numbers.Number):
|
||||||
|
data = [data & 0xFF]
|
||||||
|
# Write data a chunk at a time.
|
||||||
|
for start in range(0, len(data), chunk_size):
|
||||||
|
end = min(start + chunk_size, len(data))
|
||||||
|
self._spi.xfer(data[start:end])
|
||||||
|
|
||||||
|
def set_backlight(self, value):
|
||||||
|
"""Set the backlight on/off."""
|
||||||
|
if self._backlight is not None:
|
||||||
|
GPIO.output(self._backlight, value)
|
||||||
|
|
||||||
|
@property
|
||||||
|
def width(self):
|
||||||
|
return self._width if self._rotation == 0 or self._rotation == 180 else self._height
|
||||||
|
|
||||||
|
@property
|
||||||
|
def height(self):
|
||||||
|
return self._height if self._rotation == 0 or self._rotation == 180 else self._width
|
||||||
|
|
||||||
|
def command(self, data):
|
||||||
|
"""Write a byte or array of bytes to the display as command data."""
|
||||||
|
self.send(data, False)
|
||||||
|
|
||||||
|
def data(self, data):
|
||||||
|
"""Write a byte or array of bytes to the display as display data."""
|
||||||
|
self.send(data, True)
|
||||||
|
|
||||||
|
def reset(self):
|
||||||
|
"""Reset the display, if reset pin is connected."""
|
||||||
|
if self._rst is not None:
|
||||||
|
GPIO.output(self._rst, 1)
|
||||||
|
time.sleep(0.500)
|
||||||
|
GPIO.output(self._rst, 0)
|
||||||
|
time.sleep(0.500)
|
||||||
|
GPIO.output(self._rst, 1)
|
||||||
|
time.sleep(0.500)
|
||||||
|
|
||||||
|
def _init(self):
|
||||||
|
# Initialize the display.
|
||||||
|
|
||||||
|
self.command(ST7789_SWRESET) # Software reset
|
||||||
|
time.sleep(0.150) # delay 150 ms
|
||||||
|
|
||||||
|
self.command(ST7789_MADCTL)
|
||||||
|
self.data(0x70)
|
||||||
|
|
||||||
|
self.command(ST7789_FRMCTR2) # Frame rate ctrl - idle mode
|
||||||
|
self.data(0x0C)
|
||||||
|
self.data(0x0C)
|
||||||
|
self.data(0x00)
|
||||||
|
self.data(0x33)
|
||||||
|
self.data(0x33)
|
||||||
|
|
||||||
|
self.command(ST7789_COLMOD)
|
||||||
|
self.data(0x05)
|
||||||
|
|
||||||
|
self.command(ST7789_GCTRL)
|
||||||
|
self.data(0x14)
|
||||||
|
|
||||||
|
self.command(ST7789_VCOMS)
|
||||||
|
self.data(0x37)
|
||||||
|
|
||||||
|
self.command(ST7789_LCMCTRL) # Power control
|
||||||
|
self.data(0x2C)
|
||||||
|
|
||||||
|
self.command(ST7789_VDVVRHEN) # Power control
|
||||||
|
self.data(0x01)
|
||||||
|
|
||||||
|
self.command(ST7789_VRHS) # Power control
|
||||||
|
self.data(0x12)
|
||||||
|
|
||||||
|
self.command(ST7789_VDVS) # Power control
|
||||||
|
self.data(0x20)
|
||||||
|
|
||||||
|
self.command(0xD0)
|
||||||
|
self.data(0xA4)
|
||||||
|
self.data(0xA1)
|
||||||
|
|
||||||
|
self.command(ST7789_FRCTRL2)
|
||||||
|
self.data(0x0F)
|
||||||
|
|
||||||
|
self.command(ST7789_GMCTRP1) # Set Gamma
|
||||||
|
self.data(0xD0)
|
||||||
|
self.data(0x04)
|
||||||
|
self.data(0x0D)
|
||||||
|
self.data(0x11)
|
||||||
|
self.data(0x13)
|
||||||
|
self.data(0x2B)
|
||||||
|
self.data(0x3F)
|
||||||
|
self.data(0x54)
|
||||||
|
self.data(0x4C)
|
||||||
|
self.data(0x18)
|
||||||
|
self.data(0x0D)
|
||||||
|
self.data(0x0B)
|
||||||
|
self.data(0x1F)
|
||||||
|
self.data(0x23)
|
||||||
|
|
||||||
|
self.command(ST7789_GMCTRN1) # Set Gamma
|
||||||
|
self.data(0xD0)
|
||||||
|
self.data(0x04)
|
||||||
|
self.data(0x0C)
|
||||||
|
self.data(0x11)
|
||||||
|
self.data(0x13)
|
||||||
|
self.data(0x2C)
|
||||||
|
self.data(0x3F)
|
||||||
|
self.data(0x44)
|
||||||
|
self.data(0x51)
|
||||||
|
self.data(0x2F)
|
||||||
|
self.data(0x1F)
|
||||||
|
self.data(0x1F)
|
||||||
|
self.data(0x20)
|
||||||
|
self.data(0x23)
|
||||||
|
|
||||||
|
if self._invert:
|
||||||
|
self.command(ST7789_INVON) # Invert display
|
||||||
|
else:
|
||||||
|
self.command(ST7789_INVOFF) # Don't invert display
|
||||||
|
|
||||||
|
self.command(ST7789_SLPOUT)
|
||||||
|
|
||||||
|
self.command(ST7789_DISPON) # Display on
|
||||||
|
time.sleep(0.100) # 100 ms
|
||||||
|
|
||||||
|
def begin(self):
|
||||||
|
"""Set up the display
|
||||||
|
|
||||||
|
Deprecated. Included in __init__.
|
||||||
|
|
||||||
|
"""
|
||||||
|
pass
|
||||||
|
|
||||||
|
def set_window(self, x0=0, y0=0, x1=None, y1=None):
|
||||||
|
"""Set the pixel address window for proceeding drawing commands. x0 and
|
||||||
|
x1 should define the minimum and maximum x pixel bounds. y0 and y1
|
||||||
|
should define the minimum and maximum y pixel bound. If no parameters
|
||||||
|
are specified the default will be to update the entire display from 0,0
|
||||||
|
to width-1,height-1.
|
||||||
|
"""
|
||||||
|
if x1 is None:
|
||||||
|
x1 = self._width - 1
|
||||||
|
|
||||||
|
if y1 is None:
|
||||||
|
y1 = self._height - 1
|
||||||
|
|
||||||
|
y0 += self._offset_top
|
||||||
|
y1 += self._offset_top
|
||||||
|
|
||||||
|
x0 += self._offset_left
|
||||||
|
x1 += self._offset_left
|
||||||
|
|
||||||
|
self.command(ST7789_CASET) # Column addr set
|
||||||
|
self.data(x0 >> 8)
|
||||||
|
self.data(x0 & 0xFF) # XSTART
|
||||||
|
self.data(x1 >> 8)
|
||||||
|
self.data(x1 & 0xFF) # XEND
|
||||||
|
self.command(ST7789_RASET) # Row addr set
|
||||||
|
self.data(y0 >> 8)
|
||||||
|
self.data(y0 & 0xFF) # YSTART
|
||||||
|
self.data(y1 >> 8)
|
||||||
|
self.data(y1 & 0xFF) # YEND
|
||||||
|
self.command(ST7789_RAMWR) # write to RAM
|
||||||
|
|
||||||
|
def display(self, image):
|
||||||
|
"""Write the provided image to the hardware.
|
||||||
|
|
||||||
|
:param image: Should be RGB format and the same dimensions as the display hardware.
|
||||||
|
|
||||||
|
"""
|
||||||
|
# Set address bounds to entire display.
|
||||||
|
self.set_window()
|
||||||
|
|
||||||
|
# Convert image to 16bit RGB565 format and
|
||||||
|
# flatten into bytes.
|
||||||
|
pixelbytes = self.image_to_data(image, self._rotation)
|
||||||
|
|
||||||
|
# Write data to hardware.
|
||||||
|
for i in range(0, len(pixelbytes), 4096):
|
||||||
|
self.data(pixelbytes[i:i + 4096])
|
||||||
|
|
||||||
|
def image_to_data(self, image, rotation=0):
|
||||||
|
if not isinstance(image, np.ndarray):
|
||||||
|
image = np.array(image.convert('RGB'))
|
||||||
|
|
||||||
|
# Rotate the image
|
||||||
|
pb = np.rot90(image, rotation // 90).astype('uint16')
|
||||||
|
|
||||||
|
# Mask and shift the 888 RGB into 565 RGB
|
||||||
|
red = (pb[..., [0]] & 0xf8) << 8
|
||||||
|
green = (pb[..., [1]] & 0xfc) << 3
|
||||||
|
blue = (pb[..., [2]] & 0xf8) >> 3
|
||||||
|
|
||||||
|
# Stick 'em together
|
||||||
|
result = red | green | blue
|
||||||
|
|
||||||
|
# Output the raw bytes
|
||||||
|
return result.byteswap().tobytes()
|
360
pwnagotchi/ui/hw/libs/pimoroni/pirateaudio/ST7789.py
Normal file
360
pwnagotchi/ui/hw/libs/pimoroni/pirateaudio/ST7789.py
Normal file
@ -0,0 +1,360 @@
|
|||||||
|
# Copyright (c) 2014 Adafruit Industries
|
||||||
|
# Author: Tony DiCola
|
||||||
|
#
|
||||||
|
# Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
|
# of this software and associated documentation files (the "Software"), to deal
|
||||||
|
# in the Software without restriction, including without limitation the rights
|
||||||
|
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
|
# copies of the Software, and to permit persons to whom the Software is
|
||||||
|
# furnished to do so, subject to the following conditions:
|
||||||
|
#
|
||||||
|
# The above copyright notice and this permission notice shall be included in
|
||||||
|
# all copies or substantial portions of the Software.
|
||||||
|
#
|
||||||
|
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
|
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||||
|
# THE SOFTWARE.
|
||||||
|
import numbers
|
||||||
|
import time
|
||||||
|
import numpy as np
|
||||||
|
|
||||||
|
import spidev
|
||||||
|
import RPi.GPIO as GPIO
|
||||||
|
|
||||||
|
|
||||||
|
__version__ = '0.0.4'
|
||||||
|
|
||||||
|
BG_SPI_CS_BACK = 0
|
||||||
|
BG_SPI_CS_FRONT = 1
|
||||||
|
|
||||||
|
SPI_CLOCK_HZ = 16000000
|
||||||
|
|
||||||
|
ST7789_NOP = 0x00
|
||||||
|
ST7789_SWRESET = 0x01
|
||||||
|
ST7789_RDDID = 0x04
|
||||||
|
ST7789_RDDST = 0x09
|
||||||
|
|
||||||
|
ST7789_SLPIN = 0x10
|
||||||
|
ST7789_SLPOUT = 0x11
|
||||||
|
ST7789_PTLON = 0x12
|
||||||
|
ST7789_NORON = 0x13
|
||||||
|
|
||||||
|
ST7789_INVOFF = 0x20
|
||||||
|
ST7789_INVON = 0x21
|
||||||
|
ST7789_DISPOFF = 0x28
|
||||||
|
ST7789_DISPON = 0x29
|
||||||
|
|
||||||
|
ST7789_CASET = 0x2A
|
||||||
|
ST7789_RASET = 0x2B
|
||||||
|
ST7789_RAMWR = 0x2C
|
||||||
|
ST7789_RAMRD = 0x2E
|
||||||
|
|
||||||
|
ST7789_PTLAR = 0x30
|
||||||
|
ST7789_MADCTL = 0x36
|
||||||
|
ST7789_COLMOD = 0x3A
|
||||||
|
|
||||||
|
ST7789_FRMCTR1 = 0xB1
|
||||||
|
ST7789_FRMCTR2 = 0xB2
|
||||||
|
ST7789_FRMCTR3 = 0xB3
|
||||||
|
ST7789_INVCTR = 0xB4
|
||||||
|
ST7789_DISSET5 = 0xB6
|
||||||
|
|
||||||
|
ST7789_GCTRL = 0xB7
|
||||||
|
ST7789_GTADJ = 0xB8
|
||||||
|
ST7789_VCOMS = 0xBB
|
||||||
|
|
||||||
|
ST7789_LCMCTRL = 0xC0
|
||||||
|
ST7789_IDSET = 0xC1
|
||||||
|
ST7789_VDVVRHEN = 0xC2
|
||||||
|
ST7789_VRHS = 0xC3
|
||||||
|
ST7789_VDVS = 0xC4
|
||||||
|
ST7789_VMCTR1 = 0xC5
|
||||||
|
ST7789_FRCTRL2 = 0xC6
|
||||||
|
ST7789_CABCCTRL = 0xC7
|
||||||
|
|
||||||
|
ST7789_RDID1 = 0xDA
|
||||||
|
ST7789_RDID2 = 0xDB
|
||||||
|
ST7789_RDID3 = 0xDC
|
||||||
|
ST7789_RDID4 = 0xDD
|
||||||
|
|
||||||
|
ST7789_GMCTRP1 = 0xE0
|
||||||
|
ST7789_GMCTRN1 = 0xE1
|
||||||
|
|
||||||
|
ST7789_PWCTR6 = 0xFC
|
||||||
|
|
||||||
|
|
||||||
|
class ST7789(object):
|
||||||
|
"""Representation of an ST7789 TFT LCD."""
|
||||||
|
|
||||||
|
def __init__(self, port, cs, dc, backlight, rst=None, width=240,
|
||||||
|
height=240, rotation=0, invert=True, spi_speed_hz=60 * 1000 * 1000,
|
||||||
|
offset_left=0,
|
||||||
|
offset_top=0):
|
||||||
|
"""Create an instance of the display using SPI communication.
|
||||||
|
|
||||||
|
Must provide the GPIO pin number for the D/C pin and the SPI driver.
|
||||||
|
|
||||||
|
Can optionally provide the GPIO pin number for the reset pin as the rst parameter.
|
||||||
|
|
||||||
|
:param port: SPI port number
|
||||||
|
:param cs: SPI chip-select number (0 or 1 for BCM
|
||||||
|
:param backlight: Pin for controlling backlight
|
||||||
|
:param rst: Reset pin for ST7789
|
||||||
|
:param width: Width of display connected to ST7789
|
||||||
|
:param height: Height of display connected to ST7789
|
||||||
|
:param rotation: Rotation of display connected to ST7789
|
||||||
|
:param invert: Invert display
|
||||||
|
:param spi_speed_hz: SPI speed (in Hz)
|
||||||
|
|
||||||
|
"""
|
||||||
|
if rotation not in [0, 90, 180, 270]:
|
||||||
|
raise ValueError("Invalid rotation {}".format(rotation))
|
||||||
|
|
||||||
|
if width != height and rotation in [90, 270]:
|
||||||
|
raise ValueError("Invalid rotation {} for {}x{} resolution".format(rotation, width, height))
|
||||||
|
|
||||||
|
GPIO.setwarnings(False)
|
||||||
|
GPIO.setmode(GPIO.BCM)
|
||||||
|
|
||||||
|
self._spi = spidev.SpiDev(port, cs)
|
||||||
|
self._spi.mode = 0
|
||||||
|
self._spi.lsbfirst = False
|
||||||
|
self._spi.max_speed_hz = spi_speed_hz
|
||||||
|
|
||||||
|
self._dc = dc
|
||||||
|
self._rst = rst
|
||||||
|
self._width = width
|
||||||
|
self._height = height
|
||||||
|
self._rotation = rotation
|
||||||
|
self._invert = invert
|
||||||
|
|
||||||
|
self._offset_left = offset_left
|
||||||
|
self._offset_top = offset_top
|
||||||
|
|
||||||
|
# Set DC as output.
|
||||||
|
GPIO.setup(dc, GPIO.OUT)
|
||||||
|
|
||||||
|
# Setup backlight as output (if provided).
|
||||||
|
self._backlight = backlight
|
||||||
|
if backlight is not None:
|
||||||
|
GPIO.setup(backlight, GPIO.OUT)
|
||||||
|
GPIO.output(backlight, GPIO.LOW)
|
||||||
|
time.sleep(0.1)
|
||||||
|
GPIO.output(backlight, GPIO.HIGH)
|
||||||
|
|
||||||
|
# Setup reset as output (if provided).
|
||||||
|
if rst is not None:
|
||||||
|
GPIO.setup(self._rst, GPIO.OUT)
|
||||||
|
self.reset()
|
||||||
|
self._init()
|
||||||
|
|
||||||
|
def send(self, data, is_data=True, chunk_size=4096):
|
||||||
|
"""Write a byte or array of bytes to the display. Is_data parameter
|
||||||
|
controls if byte should be interpreted as display data (True) or command
|
||||||
|
data (False). Chunk_size is an optional size of bytes to write in a
|
||||||
|
single SPI transaction, with a default of 4096.
|
||||||
|
"""
|
||||||
|
# Set DC low for command, high for data.
|
||||||
|
GPIO.output(self._dc, is_data)
|
||||||
|
# Convert scalar argument to list so either can be passed as parameter.
|
||||||
|
if isinstance(data, numbers.Number):
|
||||||
|
data = [data & 0xFF]
|
||||||
|
# Write data a chunk at a time.
|
||||||
|
for start in range(0, len(data), chunk_size):
|
||||||
|
end = min(start + chunk_size, len(data))
|
||||||
|
self._spi.xfer(data[start:end])
|
||||||
|
|
||||||
|
def set_backlight(self, value):
|
||||||
|
"""Set the backlight on/off."""
|
||||||
|
if self._backlight is not None:
|
||||||
|
GPIO.output(self._backlight, value)
|
||||||
|
|
||||||
|
@property
|
||||||
|
def width(self):
|
||||||
|
return self._width if self._rotation == 0 or self._rotation == 180 else self._height
|
||||||
|
|
||||||
|
@property
|
||||||
|
def height(self):
|
||||||
|
return self._height if self._rotation == 0 or self._rotation == 180 else self._width
|
||||||
|
|
||||||
|
def command(self, data):
|
||||||
|
"""Write a byte or array of bytes to the display as command data."""
|
||||||
|
self.send(data, False)
|
||||||
|
|
||||||
|
def data(self, data):
|
||||||
|
"""Write a byte or array of bytes to the display as display data."""
|
||||||
|
self.send(data, True)
|
||||||
|
|
||||||
|
def reset(self):
|
||||||
|
"""Reset the display, if reset pin is connected."""
|
||||||
|
if self._rst is not None:
|
||||||
|
GPIO.output(self._rst, 1)
|
||||||
|
time.sleep(0.500)
|
||||||
|
GPIO.output(self._rst, 0)
|
||||||
|
time.sleep(0.500)
|
||||||
|
GPIO.output(self._rst, 1)
|
||||||
|
time.sleep(0.500)
|
||||||
|
|
||||||
|
def _init(self):
|
||||||
|
# Initialize the display.
|
||||||
|
|
||||||
|
self.command(ST7789_SWRESET) # Software reset
|
||||||
|
time.sleep(0.150) # delay 150 ms
|
||||||
|
|
||||||
|
self.command(ST7789_MADCTL)
|
||||||
|
self.data(0x70)
|
||||||
|
|
||||||
|
self.command(ST7789_FRMCTR2) # Frame rate ctrl - idle mode
|
||||||
|
self.data(0x0C)
|
||||||
|
self.data(0x0C)
|
||||||
|
self.data(0x00)
|
||||||
|
self.data(0x33)
|
||||||
|
self.data(0x33)
|
||||||
|
|
||||||
|
self.command(ST7789_COLMOD)
|
||||||
|
self.data(0x05)
|
||||||
|
|
||||||
|
self.command(ST7789_GCTRL)
|
||||||
|
self.data(0x14)
|
||||||
|
|
||||||
|
self.command(ST7789_VCOMS)
|
||||||
|
self.data(0x37)
|
||||||
|
|
||||||
|
self.command(ST7789_LCMCTRL) # Power control
|
||||||
|
self.data(0x2C)
|
||||||
|
|
||||||
|
self.command(ST7789_VDVVRHEN) # Power control
|
||||||
|
self.data(0x01)
|
||||||
|
|
||||||
|
self.command(ST7789_VRHS) # Power control
|
||||||
|
self.data(0x12)
|
||||||
|
|
||||||
|
self.command(ST7789_VDVS) # Power control
|
||||||
|
self.data(0x20)
|
||||||
|
|
||||||
|
self.command(0xD0)
|
||||||
|
self.data(0xA4)
|
||||||
|
self.data(0xA1)
|
||||||
|
|
||||||
|
self.command(ST7789_FRCTRL2)
|
||||||
|
self.data(0x0F)
|
||||||
|
|
||||||
|
self.command(ST7789_GMCTRP1) # Set Gamma
|
||||||
|
self.data(0xD0)
|
||||||
|
self.data(0x04)
|
||||||
|
self.data(0x0D)
|
||||||
|
self.data(0x11)
|
||||||
|
self.data(0x13)
|
||||||
|
self.data(0x2B)
|
||||||
|
self.data(0x3F)
|
||||||
|
self.data(0x54)
|
||||||
|
self.data(0x4C)
|
||||||
|
self.data(0x18)
|
||||||
|
self.data(0x0D)
|
||||||
|
self.data(0x0B)
|
||||||
|
self.data(0x1F)
|
||||||
|
self.data(0x23)
|
||||||
|
|
||||||
|
self.command(ST7789_GMCTRN1) # Set Gamma
|
||||||
|
self.data(0xD0)
|
||||||
|
self.data(0x04)
|
||||||
|
self.data(0x0C)
|
||||||
|
self.data(0x11)
|
||||||
|
self.data(0x13)
|
||||||
|
self.data(0x2C)
|
||||||
|
self.data(0x3F)
|
||||||
|
self.data(0x44)
|
||||||
|
self.data(0x51)
|
||||||
|
self.data(0x2F)
|
||||||
|
self.data(0x1F)
|
||||||
|
self.data(0x1F)
|
||||||
|
self.data(0x20)
|
||||||
|
self.data(0x23)
|
||||||
|
|
||||||
|
if self._invert:
|
||||||
|
self.command(ST7789_INVON) # Invert display
|
||||||
|
else:
|
||||||
|
self.command(ST7789_INVOFF) # Don't invert display
|
||||||
|
|
||||||
|
self.command(ST7789_SLPOUT)
|
||||||
|
|
||||||
|
self.command(ST7789_DISPON) # Display on
|
||||||
|
time.sleep(0.100) # 100 ms
|
||||||
|
|
||||||
|
def begin(self):
|
||||||
|
"""Set up the display
|
||||||
|
|
||||||
|
Deprecated. Included in __init__.
|
||||||
|
|
||||||
|
"""
|
||||||
|
pass
|
||||||
|
|
||||||
|
def set_window(self, x0=0, y0=0, x1=None, y1=None):
|
||||||
|
"""Set the pixel address window for proceeding drawing commands. x0 and
|
||||||
|
x1 should define the minimum and maximum x pixel bounds. y0 and y1
|
||||||
|
should define the minimum and maximum y pixel bound. If no parameters
|
||||||
|
are specified the default will be to update the entire display from 0,0
|
||||||
|
to width-1,height-1.
|
||||||
|
"""
|
||||||
|
if x1 is None:
|
||||||
|
x1 = self._width - 1
|
||||||
|
|
||||||
|
if y1 is None:
|
||||||
|
y1 = self._height - 1
|
||||||
|
|
||||||
|
y0 += self._offset_top
|
||||||
|
y1 += self._offset_top
|
||||||
|
|
||||||
|
x0 += self._offset_left
|
||||||
|
x1 += self._offset_left
|
||||||
|
|
||||||
|
self.command(ST7789_CASET) # Column addr set
|
||||||
|
self.data(x0 >> 8)
|
||||||
|
self.data(x0 & 0xFF) # XSTART
|
||||||
|
self.data(x1 >> 8)
|
||||||
|
self.data(x1 & 0xFF) # XEND
|
||||||
|
self.command(ST7789_RASET) # Row addr set
|
||||||
|
self.data(y0 >> 8)
|
||||||
|
self.data(y0 & 0xFF) # YSTART
|
||||||
|
self.data(y1 >> 8)
|
||||||
|
self.data(y1 & 0xFF) # YEND
|
||||||
|
self.command(ST7789_RAMWR) # write to RAM
|
||||||
|
|
||||||
|
def display(self, image):
|
||||||
|
"""Write the provided image to the hardware.
|
||||||
|
|
||||||
|
:param image: Should be RGB format and the same dimensions as the display hardware.
|
||||||
|
|
||||||
|
"""
|
||||||
|
# Set address bounds to entire display.
|
||||||
|
self.set_window()
|
||||||
|
|
||||||
|
# Convert image to 16bit RGB565 format and
|
||||||
|
# flatten into bytes.
|
||||||
|
pixelbytes = self.image_to_data(image, self._rotation)
|
||||||
|
|
||||||
|
# Write data to hardware.
|
||||||
|
for i in range(0, len(pixelbytes), 4096):
|
||||||
|
self.data(pixelbytes[i:i + 4096])
|
||||||
|
|
||||||
|
def image_to_data(self, image, rotation=0):
|
||||||
|
if not isinstance(image, np.ndarray):
|
||||||
|
image = np.array(image.convert('RGB'))
|
||||||
|
|
||||||
|
# Rotate the image
|
||||||
|
pb = np.rot90(image, rotation // 90).astype('uint16')
|
||||||
|
|
||||||
|
# Mask and shift the 888 RGB into 565 RGB
|
||||||
|
red = (pb[..., [0]] & 0xf8) << 8
|
||||||
|
green = (pb[..., [1]] & 0xfc) << 3
|
||||||
|
blue = (pb[..., [2]] & 0xf8) >> 3
|
||||||
|
|
||||||
|
# Stick 'em together
|
||||||
|
result = red | green | blue
|
||||||
|
|
||||||
|
# Output the raw bytes
|
||||||
|
return result.byteswap().tobytes()
|
@ -31,6 +31,7 @@ import os
|
|||||||
import logging
|
import logging
|
||||||
import sys
|
import sys
|
||||||
import time
|
import time
|
||||||
|
import subprocess
|
||||||
|
|
||||||
logger = logging.getLogger(__name__)
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
@ -45,16 +46,48 @@ class RaspberryPi:
|
|||||||
|
|
||||||
def __init__(self):
|
def __init__(self):
|
||||||
import spidev
|
import spidev
|
||||||
import RPi.GPIO
|
import gpiozero
|
||||||
|
|
||||||
self.GPIO = RPi.GPIO
|
|
||||||
self.SPI = spidev.SpiDev()
|
self.SPI = spidev.SpiDev()
|
||||||
|
self.GPIO_RST_PIN = gpiozero.LED(self.RST_PIN)
|
||||||
|
self.GPIO_DC_PIN = gpiozero.LED(self.DC_PIN)
|
||||||
|
self.GPIO_CS_PIN = gpiozero.LED(self.CS_PIN)
|
||||||
|
self.GPIO_PWR_PIN = gpiozero.LED(self.PWR_PIN)
|
||||||
|
self.GPIO_BUSY_PIN = gpiozero.Button(self.BUSY_PIN, pull_up=False)
|
||||||
|
|
||||||
def digital_write(self, pin, value):
|
def digital_write(self, pin, value):
|
||||||
self.GPIO.output(pin, value)
|
if pin == self.RST_PIN:
|
||||||
|
if value:
|
||||||
|
self.GPIO_RST_PIN.on()
|
||||||
|
else:
|
||||||
|
self.GPIO_RST_PIN.off()
|
||||||
|
elif pin == self.DC_PIN:
|
||||||
|
if value:
|
||||||
|
self.GPIO_DC_PIN.on()
|
||||||
|
else:
|
||||||
|
self.GPIO_DC_PIN.off()
|
||||||
|
elif pin == self.CS_PIN:
|
||||||
|
if value:
|
||||||
|
self.GPIO_CS_PIN.on()
|
||||||
|
else:
|
||||||
|
self.GPIO_CS_PIN.off()
|
||||||
|
elif pin == self.PWR_PIN:
|
||||||
|
if value:
|
||||||
|
self.GPIO_PWR_PIN.on()
|
||||||
|
else:
|
||||||
|
self.GPIO_PWR_PIN.off()
|
||||||
|
|
||||||
def digital_read(self, pin):
|
def digital_read(self, pin):
|
||||||
return self.GPIO.input(pin)
|
if pin == self.BUSY_PIN:
|
||||||
|
return self.GPIO_BUSY_PIN.value
|
||||||
|
elif pin == self.RST_PIN:
|
||||||
|
return self.RST_PIN.value
|
||||||
|
elif pin == self.DC_PIN:
|
||||||
|
return self.DC_PIN.value
|
||||||
|
elif pin == self.CS_PIN:
|
||||||
|
return self.CS_PIN.value
|
||||||
|
elif pin == self.PWR_PIN:
|
||||||
|
return self.PWR_PIN.value
|
||||||
|
|
||||||
def delay_ms(self, delaytime):
|
def delay_ms(self, delaytime):
|
||||||
time.sleep(delaytime / 1000.0)
|
time.sleep(delaytime / 1000.0)
|
||||||
@ -66,15 +99,7 @@ class RaspberryPi:
|
|||||||
self.SPI.writebytes2(data)
|
self.SPI.writebytes2(data)
|
||||||
|
|
||||||
def module_init(self):
|
def module_init(self):
|
||||||
self.GPIO.setmode(self.GPIO.BCM)
|
self.GPIO_PWR_PIN.on()
|
||||||
self.GPIO.setwarnings(False)
|
|
||||||
self.GPIO.setup(self.RST_PIN, self.GPIO.OUT)
|
|
||||||
self.GPIO.setup(self.DC_PIN, self.GPIO.OUT)
|
|
||||||
self.GPIO.setup(self.CS_PIN, self.GPIO.OUT)
|
|
||||||
self.GPIO.setup(self.PWR_PIN, self.GPIO.OUT)
|
|
||||||
self.GPIO.setup(self.BUSY_PIN, self.GPIO.IN)
|
|
||||||
|
|
||||||
self.GPIO.output(self.PWR_PIN, 1)
|
|
||||||
|
|
||||||
# SPI device, bus = 0, device = 0
|
# SPI device, bus = 0, device = 0
|
||||||
self.SPI.open(0, 0)
|
self.SPI.open(0, 0)
|
||||||
@ -82,159 +107,23 @@ class RaspberryPi:
|
|||||||
self.SPI.mode = 0b00
|
self.SPI.mode = 0b00
|
||||||
return 0
|
return 0
|
||||||
|
|
||||||
def module_exit(self):
|
def module_exit(self, cleanup=False):
|
||||||
logger.debug("spi end")
|
logger.debug("spi end")
|
||||||
self.SPI.close()
|
self.SPI.close()
|
||||||
|
|
||||||
|
self.GPIO_RST_PIN.off()
|
||||||
|
self.GPIO_DC_PIN.off()
|
||||||
|
self.GPIO_PWR_PIN.off()
|
||||||
logger.debug("close 5V, Module enters 0 power consumption ...")
|
logger.debug("close 5V, Module enters 0 power consumption ...")
|
||||||
self.GPIO.output(self.RST_PIN, 0)
|
|
||||||
self.GPIO.output(self.DC_PIN, 0)
|
|
||||||
self.GPIO.output(self.PWR_PIN, 0)
|
|
||||||
|
|
||||||
self.GPIO.cleanup([self.RST_PIN, self.DC_PIN, self.CS_PIN, self.BUSY_PIN, self.PWR_PIN])
|
if cleanup:
|
||||||
|
self.GPIO_RST_PIN.close()
|
||||||
|
self.GPIO_DC_PIN.close()
|
||||||
|
self.GPIO_CS_PIN.close()
|
||||||
|
self.GPIO_PWR_PIN.close()
|
||||||
|
self.GPIO_BUSY_PIN.close()
|
||||||
|
|
||||||
|
|
||||||
class JetsonNano:
|
|
||||||
# Pin definition
|
|
||||||
RST_PIN = 17
|
|
||||||
DC_PIN = 25
|
|
||||||
CS_PIN = 8
|
|
||||||
BUSY_PIN = 24
|
|
||||||
PWR_PIN = 18
|
|
||||||
|
|
||||||
def __init__(self):
|
|
||||||
import ctypes
|
|
||||||
find_dirs = [
|
|
||||||
os.path.dirname(os.path.realpath(__file__)),
|
|
||||||
'/usr/local/lib',
|
|
||||||
'/usr/lib',
|
|
||||||
]
|
|
||||||
self.SPI = None
|
|
||||||
for find_dir in find_dirs:
|
|
||||||
so_filename = os.path.join(find_dir, 'sysfs_software_spi.so')
|
|
||||||
if os.path.exists(so_filename):
|
|
||||||
self.SPI = ctypes.cdll.LoadLibrary(so_filename)
|
|
||||||
break
|
|
||||||
if self.SPI is None:
|
|
||||||
raise RuntimeError('Cannot find sysfs_software_spi.so')
|
|
||||||
|
|
||||||
import Jetson.GPIO
|
|
||||||
self.GPIO = Jetson.GPIO
|
|
||||||
|
|
||||||
def digital_write(self, pin, value):
|
|
||||||
self.GPIO.output(pin, value)
|
|
||||||
|
|
||||||
def digital_read(self, pin):
|
|
||||||
return self.GPIO.input(self.BUSY_PIN)
|
|
||||||
|
|
||||||
def delay_ms(self, delaytime):
|
|
||||||
time.sleep(delaytime / 1000.0)
|
|
||||||
|
|
||||||
def spi_writebyte(self, data):
|
|
||||||
self.SPI.SYSFS_software_spi_transfer(data[0])
|
|
||||||
|
|
||||||
def spi_writebyte2(self, data):
|
|
||||||
for i in range(len(data)):
|
|
||||||
self.SPI.SYSFS_software_spi_transfer(data[i])
|
|
||||||
|
|
||||||
def module_init(self):
|
|
||||||
self.GPIO.setmode(self.GPIO.BCM)
|
|
||||||
self.GPIO.setwarnings(False)
|
|
||||||
self.GPIO.setup(self.RST_PIN, self.GPIO.OUT)
|
|
||||||
self.GPIO.setup(self.DC_PIN, self.GPIO.OUT)
|
|
||||||
self.GPIO.setup(self.CS_PIN, self.GPIO.OUT)
|
|
||||||
self.GPIO.setup(self.PWR_PIN, self.GPIO.OUT)
|
|
||||||
self.GPIO.setup(self.BUSY_PIN, self.GPIO.IN)
|
|
||||||
|
|
||||||
self.GPIO.output(self.PWR_PIN, 1)
|
|
||||||
|
|
||||||
self.SPI.SYSFS_software_spi_begin()
|
|
||||||
return 0
|
|
||||||
|
|
||||||
def module_exit(self):
|
|
||||||
logger.debug("spi end")
|
|
||||||
self.SPI.SYSFS_software_spi_end()
|
|
||||||
|
|
||||||
logger.debug("close 5V, Module enters 0 power consumption ...")
|
|
||||||
self.GPIO.output(self.RST_PIN, 0)
|
|
||||||
self.GPIO.output(self.DC_PIN, 0)
|
|
||||||
self.GPIO.output(self.PWR_PIN, 0)
|
|
||||||
|
|
||||||
self.GPIO.cleanup([self.RST_PIN, self.DC_PIN, self.CS_PIN, self.BUSY_PIN, self.PWR_PIN])
|
|
||||||
|
|
||||||
|
|
||||||
class SunriseX3:
|
|
||||||
# Pin definition
|
|
||||||
RST_PIN = 17
|
|
||||||
DC_PIN = 25
|
|
||||||
CS_PIN = 8
|
|
||||||
BUSY_PIN = 24
|
|
||||||
PWR_PIN = 18
|
|
||||||
Flag = 0
|
|
||||||
|
|
||||||
def __init__(self):
|
|
||||||
import spidev
|
|
||||||
import Hobot.GPIO
|
|
||||||
|
|
||||||
self.GPIO = Hobot.GPIO
|
|
||||||
self.SPI = spidev.SpiDev()
|
|
||||||
|
|
||||||
def digital_write(self, pin, value):
|
|
||||||
self.GPIO.output(pin, value)
|
|
||||||
|
|
||||||
def digital_read(self, pin):
|
|
||||||
return self.GPIO.input(pin)
|
|
||||||
|
|
||||||
def delay_ms(self, delaytime):
|
|
||||||
time.sleep(delaytime / 1000.0)
|
|
||||||
|
|
||||||
def spi_writebyte(self, data):
|
|
||||||
self.SPI.writebytes(data)
|
|
||||||
|
|
||||||
def spi_writebyte2(self, data):
|
|
||||||
# for i in range(len(data)):
|
|
||||||
# self.SPI.writebytes([data[i]])
|
|
||||||
self.SPI.xfer3(data)
|
|
||||||
|
|
||||||
def module_init(self):
|
|
||||||
if self.Flag == 0:
|
|
||||||
self.Flag = 1
|
|
||||||
self.GPIO.setmode(self.GPIO.BCM)
|
|
||||||
self.GPIO.setwarnings(False)
|
|
||||||
self.GPIO.setup(self.RST_PIN, self.GPIO.OUT)
|
|
||||||
self.GPIO.setup(self.DC_PIN, self.GPIO.OUT)
|
|
||||||
self.GPIO.setup(self.CS_PIN, self.GPIO.OUT)
|
|
||||||
self.GPIO.setup(self.PWR_PIN, self.GPIO.OUT)
|
|
||||||
self.GPIO.setup(self.BUSY_PIN, self.GPIO.IN)
|
|
||||||
|
|
||||||
self.GPIO.output(self.PWR_PIN, 1)
|
|
||||||
|
|
||||||
# SPI device, bus = 0, device = 0
|
|
||||||
self.SPI.open(2, 0)
|
|
||||||
self.SPI.max_speed_hz = 4000000
|
|
||||||
self.SPI.mode = 0b00
|
|
||||||
return 0
|
|
||||||
else:
|
|
||||||
return 0
|
|
||||||
|
|
||||||
def module_exit(self):
|
|
||||||
logger.debug("spi end")
|
|
||||||
self.SPI.close()
|
|
||||||
|
|
||||||
logger.debug("close 5V, Module enters 0 power consumption ...")
|
|
||||||
self.Flag = 0
|
|
||||||
self.GPIO.output(self.RST_PIN, 0)
|
|
||||||
self.GPIO.output(self.DC_PIN, 0)
|
|
||||||
self.GPIO.output(self.PWR_PIN, 0)
|
|
||||||
|
|
||||||
self.GPIO.cleanup([self.RST_PIN, self.DC_PIN, self.CS_PIN, self.BUSY_PIN], self.PWR_PIN)
|
|
||||||
|
|
||||||
|
|
||||||
if os.path.exists('/sys/bus/platform/drivers/gpiomem-bcm2835'):
|
|
||||||
implementation = RaspberryPi()
|
|
||||||
elif os.path.exists('/sys/bus/platform/drivers/gpio-x3'):
|
|
||||||
implementation = SunriseX3()
|
|
||||||
else:
|
|
||||||
implementation = RaspberryPi()
|
implementation = RaspberryPi()
|
||||||
|
|
||||||
for func in [x for x in dir(implementation) if not x.startswith('_')]:
|
for func in [x for x in dir(implementation) if not x.startswith('_')]:
|
||||||
|
360
pwnagotchi/ui/hw/libs/waveshare/oledlcd/ST7789.py
Normal file
360
pwnagotchi/ui/hw/libs/waveshare/oledlcd/ST7789.py
Normal file
@ -0,0 +1,360 @@
|
|||||||
|
# Copyright (c) 2014 Adafruit Industries
|
||||||
|
# Author: Tony DiCola
|
||||||
|
#
|
||||||
|
# Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
|
# of this software and associated documentation files (the "Software"), to deal
|
||||||
|
# in the Software without restriction, including without limitation the rights
|
||||||
|
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
|
# copies of the Software, and to permit persons to whom the Software is
|
||||||
|
# furnished to do so, subject to the following conditions:
|
||||||
|
#
|
||||||
|
# The above copyright notice and this permission notice shall be included in
|
||||||
|
# all copies or substantial portions of the Software.
|
||||||
|
#
|
||||||
|
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
|
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||||
|
# THE SOFTWARE.
|
||||||
|
import numbers
|
||||||
|
import time
|
||||||
|
import numpy as np
|
||||||
|
|
||||||
|
import spidev
|
||||||
|
import RPi.GPIO as GPIO
|
||||||
|
|
||||||
|
|
||||||
|
__version__ = '0.0.4'
|
||||||
|
|
||||||
|
BG_SPI_CS_BACK = 0
|
||||||
|
BG_SPI_CS_FRONT = 1
|
||||||
|
|
||||||
|
SPI_CLOCK_HZ = 16000000
|
||||||
|
|
||||||
|
ST7789_NOP = 0x00
|
||||||
|
ST7789_SWRESET = 0x01
|
||||||
|
ST7789_RDDID = 0x04
|
||||||
|
ST7789_RDDST = 0x09
|
||||||
|
|
||||||
|
ST7789_SLPIN = 0x10
|
||||||
|
ST7789_SLPOUT = 0x11
|
||||||
|
ST7789_PTLON = 0x12
|
||||||
|
ST7789_NORON = 0x13
|
||||||
|
|
||||||
|
ST7789_INVOFF = 0x20
|
||||||
|
ST7789_INVON = 0x21
|
||||||
|
ST7789_DISPOFF = 0x28
|
||||||
|
ST7789_DISPON = 0x29
|
||||||
|
|
||||||
|
ST7789_CASET = 0x2A
|
||||||
|
ST7789_RASET = 0x2B
|
||||||
|
ST7789_RAMWR = 0x2C
|
||||||
|
ST7789_RAMRD = 0x2E
|
||||||
|
|
||||||
|
ST7789_PTLAR = 0x30
|
||||||
|
ST7789_MADCTL = 0x36
|
||||||
|
ST7789_COLMOD = 0x3A
|
||||||
|
|
||||||
|
ST7789_FRMCTR1 = 0xB1
|
||||||
|
ST7789_FRMCTR2 = 0xB2
|
||||||
|
ST7789_FRMCTR3 = 0xB3
|
||||||
|
ST7789_INVCTR = 0xB4
|
||||||
|
ST7789_DISSET5 = 0xB6
|
||||||
|
|
||||||
|
ST7789_GCTRL = 0xB7
|
||||||
|
ST7789_GTADJ = 0xB8
|
||||||
|
ST7789_VCOMS = 0xBB
|
||||||
|
|
||||||
|
ST7789_LCMCTRL = 0xC0
|
||||||
|
ST7789_IDSET = 0xC1
|
||||||
|
ST7789_VDVVRHEN = 0xC2
|
||||||
|
ST7789_VRHS = 0xC3
|
||||||
|
ST7789_VDVS = 0xC4
|
||||||
|
ST7789_VMCTR1 = 0xC5
|
||||||
|
ST7789_FRCTRL2 = 0xC6
|
||||||
|
ST7789_CABCCTRL = 0xC7
|
||||||
|
|
||||||
|
ST7789_RDID1 = 0xDA
|
||||||
|
ST7789_RDID2 = 0xDB
|
||||||
|
ST7789_RDID3 = 0xDC
|
||||||
|
ST7789_RDID4 = 0xDD
|
||||||
|
|
||||||
|
ST7789_GMCTRP1 = 0xE0
|
||||||
|
ST7789_GMCTRN1 = 0xE1
|
||||||
|
|
||||||
|
ST7789_PWCTR6 = 0xFC
|
||||||
|
|
||||||
|
|
||||||
|
class ST7789(object):
|
||||||
|
"""Representation of an ST7789 TFT LCD."""
|
||||||
|
|
||||||
|
def __init__(self, port, cs, dc, backlight, rst=None, width=320,
|
||||||
|
height=240, rotation=180, invert=True, spi_speed_hz=60 * 1000 * 1000,
|
||||||
|
offset_left=0,
|
||||||
|
offset_top=0):
|
||||||
|
"""Create an instance of the display using SPI communication.
|
||||||
|
|
||||||
|
Must provide the GPIO pin number for the D/C pin and the SPI driver.
|
||||||
|
|
||||||
|
Can optionally provide the GPIO pin number for the reset pin as the rst parameter.
|
||||||
|
|
||||||
|
:param port: SPI port number
|
||||||
|
:param cs: SPI chip-select number (0 or 1 for BCM
|
||||||
|
:param backlight: Pin for controlling backlight
|
||||||
|
:param rst: Reset pin for ST7789
|
||||||
|
:param width: Width of display connected to ST7789
|
||||||
|
:param height: Height of display connected to ST7789
|
||||||
|
:param rotation: Rotation of display connected to ST7789
|
||||||
|
:param invert: Invert display
|
||||||
|
:param spi_speed_hz: SPI speed (in Hz)
|
||||||
|
|
||||||
|
"""
|
||||||
|
if rotation not in [0, 90, 180, 270]:
|
||||||
|
raise ValueError("Invalid rotation {}".format(rotation))
|
||||||
|
|
||||||
|
if width != height and rotation in [90, 270]:
|
||||||
|
raise ValueError("Invalid rotation {} for {}x{} resolution".format(rotation, width, height))
|
||||||
|
|
||||||
|
GPIO.setwarnings(False)
|
||||||
|
GPIO.setmode(GPIO.BCM)
|
||||||
|
|
||||||
|
self._spi = spidev.SpiDev(port, cs)
|
||||||
|
self._spi.mode = 0
|
||||||
|
self._spi.lsbfirst = False
|
||||||
|
self._spi.max_speed_hz = spi_speed_hz
|
||||||
|
|
||||||
|
self._dc = dc
|
||||||
|
self._rst = rst
|
||||||
|
self._width = width
|
||||||
|
self._height = height
|
||||||
|
self._rotation = rotation
|
||||||
|
self._invert = invert
|
||||||
|
|
||||||
|
self._offset_left = offset_left
|
||||||
|
self._offset_top = offset_top
|
||||||
|
|
||||||
|
# Set DC as output.
|
||||||
|
GPIO.setup(dc, GPIO.OUT)
|
||||||
|
|
||||||
|
# Setup backlight as output (if provided).
|
||||||
|
self._backlight = backlight
|
||||||
|
if backlight is not None:
|
||||||
|
GPIO.setup(backlight, GPIO.OUT)
|
||||||
|
GPIO.output(backlight, GPIO.LOW)
|
||||||
|
time.sleep(0.1)
|
||||||
|
GPIO.output(backlight, GPIO.HIGH)
|
||||||
|
|
||||||
|
# Setup reset as output (if provided).
|
||||||
|
if rst is not None:
|
||||||
|
GPIO.setup(self._rst, GPIO.OUT)
|
||||||
|
self.reset()
|
||||||
|
self._init()
|
||||||
|
|
||||||
|
def send(self, data, is_data=True, chunk_size=4096):
|
||||||
|
"""Write a byte or array of bytes to the display. Is_data parameter
|
||||||
|
controls if byte should be interpreted as display data (True) or command
|
||||||
|
data (False). Chunk_size is an optional size of bytes to write in a
|
||||||
|
single SPI transaction, with a default of 4096.
|
||||||
|
"""
|
||||||
|
# Set DC low for command, high for data.
|
||||||
|
GPIO.output(self._dc, is_data)
|
||||||
|
# Convert scalar argument to list so either can be passed as parameter.
|
||||||
|
if isinstance(data, numbers.Number):
|
||||||
|
data = [data & 0xFF]
|
||||||
|
# Write data a chunk at a time.
|
||||||
|
for start in range(0, len(data), chunk_size):
|
||||||
|
end = min(start + chunk_size, len(data))
|
||||||
|
self._spi.xfer(data[start:end])
|
||||||
|
|
||||||
|
def set_backlight(self, value):
|
||||||
|
"""Set the backlight on/off."""
|
||||||
|
if self._backlight is not None:
|
||||||
|
GPIO.output(self._backlight, value)
|
||||||
|
|
||||||
|
@property
|
||||||
|
def width(self):
|
||||||
|
return self._width if self._rotation == 0 or self._rotation == 180 else self._height
|
||||||
|
|
||||||
|
@property
|
||||||
|
def height(self):
|
||||||
|
return self._height if self._rotation == 0 or self._rotation == 180 else self._width
|
||||||
|
|
||||||
|
def command(self, data):
|
||||||
|
"""Write a byte or array of bytes to the display as command data."""
|
||||||
|
self.send(data, False)
|
||||||
|
|
||||||
|
def data(self, data):
|
||||||
|
"""Write a byte or array of bytes to the display as display data."""
|
||||||
|
self.send(data, True)
|
||||||
|
|
||||||
|
def reset(self):
|
||||||
|
"""Reset the display, if reset pin is connected."""
|
||||||
|
if self._rst is not None:
|
||||||
|
GPIO.output(self._rst, 1)
|
||||||
|
time.sleep(0.500)
|
||||||
|
GPIO.output(self._rst, 0)
|
||||||
|
time.sleep(0.500)
|
||||||
|
GPIO.output(self._rst, 1)
|
||||||
|
time.sleep(0.500)
|
||||||
|
|
||||||
|
def _init(self):
|
||||||
|
# Initialize the display.
|
||||||
|
|
||||||
|
self.command(ST7789_SWRESET) # Software reset
|
||||||
|
time.sleep(0.150) # delay 150 ms
|
||||||
|
|
||||||
|
self.command(ST7789_MADCTL)
|
||||||
|
self.data(0x70)
|
||||||
|
|
||||||
|
self.command(ST7789_FRMCTR2) # Frame rate ctrl - idle mode
|
||||||
|
self.data(0x0C)
|
||||||
|
self.data(0x0C)
|
||||||
|
self.data(0x00)
|
||||||
|
self.data(0x33)
|
||||||
|
self.data(0x33)
|
||||||
|
|
||||||
|
self.command(ST7789_COLMOD)
|
||||||
|
self.data(0x05)
|
||||||
|
|
||||||
|
self.command(ST7789_GCTRL)
|
||||||
|
self.data(0x14)
|
||||||
|
|
||||||
|
self.command(ST7789_VCOMS)
|
||||||
|
self.data(0x37)
|
||||||
|
|
||||||
|
self.command(ST7789_LCMCTRL) # Power control
|
||||||
|
self.data(0x2C)
|
||||||
|
|
||||||
|
self.command(ST7789_VDVVRHEN) # Power control
|
||||||
|
self.data(0x01)
|
||||||
|
|
||||||
|
self.command(ST7789_VRHS) # Power control
|
||||||
|
self.data(0x12)
|
||||||
|
|
||||||
|
self.command(ST7789_VDVS) # Power control
|
||||||
|
self.data(0x20)
|
||||||
|
|
||||||
|
self.command(0xD0)
|
||||||
|
self.data(0xA4)
|
||||||
|
self.data(0xA1)
|
||||||
|
|
||||||
|
self.command(ST7789_FRCTRL2)
|
||||||
|
self.data(0x0F)
|
||||||
|
|
||||||
|
self.command(ST7789_GMCTRP1) # Set Gamma
|
||||||
|
self.data(0xD0)
|
||||||
|
self.data(0x04)
|
||||||
|
self.data(0x0D)
|
||||||
|
self.data(0x11)
|
||||||
|
self.data(0x13)
|
||||||
|
self.data(0x2B)
|
||||||
|
self.data(0x3F)
|
||||||
|
self.data(0x54)
|
||||||
|
self.data(0x4C)
|
||||||
|
self.data(0x18)
|
||||||
|
self.data(0x0D)
|
||||||
|
self.data(0x0B)
|
||||||
|
self.data(0x1F)
|
||||||
|
self.data(0x23)
|
||||||
|
|
||||||
|
self.command(ST7789_GMCTRN1) # Set Gamma
|
||||||
|
self.data(0xD0)
|
||||||
|
self.data(0x04)
|
||||||
|
self.data(0x0C)
|
||||||
|
self.data(0x11)
|
||||||
|
self.data(0x13)
|
||||||
|
self.data(0x2C)
|
||||||
|
self.data(0x3F)
|
||||||
|
self.data(0x44)
|
||||||
|
self.data(0x51)
|
||||||
|
self.data(0x2F)
|
||||||
|
self.data(0x1F)
|
||||||
|
self.data(0x1F)
|
||||||
|
self.data(0x20)
|
||||||
|
self.data(0x23)
|
||||||
|
|
||||||
|
if self._invert:
|
||||||
|
self.command(ST7789_INVON) # Invert display
|
||||||
|
else:
|
||||||
|
self.command(ST7789_INVOFF) # Don't invert display
|
||||||
|
|
||||||
|
self.command(ST7789_SLPOUT)
|
||||||
|
|
||||||
|
self.command(ST7789_DISPON) # Display on
|
||||||
|
time.sleep(0.100) # 100 ms
|
||||||
|
|
||||||
|
def begin(self):
|
||||||
|
"""Set up the display
|
||||||
|
|
||||||
|
Deprecated. Included in __init__.
|
||||||
|
|
||||||
|
"""
|
||||||
|
pass
|
||||||
|
|
||||||
|
def set_window(self, x0=0, y0=0, x1=None, y1=None):
|
||||||
|
"""Set the pixel address window for proceeding drawing commands. x0 and
|
||||||
|
x1 should define the minimum and maximum x pixel bounds. y0 and y1
|
||||||
|
should define the minimum and maximum y pixel bound. If no parameters
|
||||||
|
are specified the default will be to update the entire display from 0,0
|
||||||
|
to width-1,height-1.
|
||||||
|
"""
|
||||||
|
if x1 is None:
|
||||||
|
x1 = self._width - 1
|
||||||
|
|
||||||
|
if y1 is None:
|
||||||
|
y1 = self._height - 1
|
||||||
|
|
||||||
|
y0 += self._offset_top
|
||||||
|
y1 += self._offset_top
|
||||||
|
|
||||||
|
x0 += self._offset_left
|
||||||
|
x1 += self._offset_left
|
||||||
|
|
||||||
|
self.command(ST7789_CASET) # Column addr set
|
||||||
|
self.data(x0 >> 8)
|
||||||
|
self.data(x0 & 0xFF) # XSTART
|
||||||
|
self.data(x1 >> 8)
|
||||||
|
self.data(x1 & 0xFF) # XEND
|
||||||
|
self.command(ST7789_RASET) # Row addr set
|
||||||
|
self.data(y0 >> 8)
|
||||||
|
self.data(y0 & 0xFF) # YSTART
|
||||||
|
self.data(y1 >> 8)
|
||||||
|
self.data(y1 & 0xFF) # YEND
|
||||||
|
self.command(ST7789_RAMWR) # write to RAM
|
||||||
|
|
||||||
|
def display(self, image):
|
||||||
|
"""Write the provided image to the hardware.
|
||||||
|
|
||||||
|
:param image: Should be RGB format and the same dimensions as the display hardware.
|
||||||
|
|
||||||
|
"""
|
||||||
|
# Set address bounds to entire display.
|
||||||
|
self.set_window()
|
||||||
|
|
||||||
|
# Convert image to 16bit RGB565 format and
|
||||||
|
# flatten into bytes.
|
||||||
|
pixelbytes = self.image_to_data(image, self._rotation)
|
||||||
|
|
||||||
|
# Write data to hardware.
|
||||||
|
for i in range(0, len(pixelbytes), 4096):
|
||||||
|
self.data(pixelbytes[i:i + 4096])
|
||||||
|
|
||||||
|
def image_to_data(self, image, rotation=0):
|
||||||
|
if not isinstance(image, np.ndarray):
|
||||||
|
image = np.array(image.convert('RGB'))
|
||||||
|
|
||||||
|
# Rotate the image
|
||||||
|
pb = np.rot90(image, rotation // 90).astype('uint16')
|
||||||
|
|
||||||
|
# Mask and shift the 888 RGB into 565 RGB
|
||||||
|
red = (pb[..., [0]] & 0xf8) << 8
|
||||||
|
green = (pb[..., [1]] & 0xfc) << 3
|
||||||
|
blue = (pb[..., [2]] & 0xf8) >> 3
|
||||||
|
|
||||||
|
# Stick 'em together
|
||||||
|
result = red | green | blue
|
||||||
|
|
||||||
|
# Output the raw bytes
|
||||||
|
return result.byteswap().tobytes()
|
675
pwnagotchi/ui/hw/libs/waveshare/v5in79/epd5in79.py
Normal file
675
pwnagotchi/ui/hw/libs/waveshare/v5in79/epd5in79.py
Normal file
@ -0,0 +1,675 @@
|
|||||||
|
# *****************************************************************************
|
||||||
|
# * | File : epd5in79.py
|
||||||
|
# * | Author : Waveshare team
|
||||||
|
# * | Function : Electronic paper driver
|
||||||
|
# * | Info :
|
||||||
|
# *----------------
|
||||||
|
# * | This version: V1.0
|
||||||
|
# * | Date : 2024-03-05
|
||||||
|
# # | Info : python demo
|
||||||
|
# -----------------------------------------------------------------------------
|
||||||
|
# Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
|
# of this software and associated documnetation files (the "Software"), to deal
|
||||||
|
# in the Software without restriction, including without limitation the rights
|
||||||
|
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
|
# copies of the Software, and to permit persons to whom the Software is
|
||||||
|
# furished to do so, subject to the following conditions:
|
||||||
|
#
|
||||||
|
# The above copyright notice and this permission notice shall be included in
|
||||||
|
# all copies or substantial portions of the Software.
|
||||||
|
#
|
||||||
|
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
# FITNESS OR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
# LIABILITY WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
|
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||||
|
# THE SOFTWARE.
|
||||||
|
#
|
||||||
|
|
||||||
|
|
||||||
|
import logging
|
||||||
|
from .. import epdconfig
|
||||||
|
|
||||||
|
# Display resolution
|
||||||
|
EPD_WIDTH = 792
|
||||||
|
EPD_HEIGHT = 272
|
||||||
|
|
||||||
|
GRAY1 = 0xff # white
|
||||||
|
GRAY2 = 0xC0
|
||||||
|
GRAY3 = 0x80 # gray
|
||||||
|
GRAY4 = 0x00 # Blackest
|
||||||
|
|
||||||
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
|
class EPD:
|
||||||
|
def __init__(self):
|
||||||
|
self.reset_pin = epdconfig.RST_PIN
|
||||||
|
self.dc_pin = epdconfig.DC_PIN
|
||||||
|
self.busy_pin = epdconfig.BUSY_PIN
|
||||||
|
self.cs_pin = epdconfig.CS_PIN
|
||||||
|
self.width = EPD_WIDTH
|
||||||
|
self.height = EPD_HEIGHT
|
||||||
|
self.GRAY1 = GRAY1 # white
|
||||||
|
self.GRAY2 = GRAY2
|
||||||
|
self.GRAY3 = GRAY3 # gray
|
||||||
|
self.GRAY4 = GRAY4 # Blackest
|
||||||
|
|
||||||
|
self.LUT_DATA_4Gray = [
|
||||||
|
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||||
|
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||||
|
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||||
|
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||||
|
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||||
|
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||||
|
|
||||||
|
0x01, 0x4A, 0x00, 0x00, 0x00, 0x01, 0x00,
|
||||||
|
0x01, 0x82, 0x42, 0x00, 0x00, 0x10, 0x00,
|
||||||
|
0x01, 0x8A, 0x00, 0x00, 0x00, 0x01, 0x00,
|
||||||
|
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||||
|
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||||
|
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||||
|
|
||||||
|
0x01, 0x41, 0x00, 0x00, 0x00, 0x01, 0x00,
|
||||||
|
0x01, 0x82, 0x42, 0x00, 0x00, 0x10, 0x00,
|
||||||
|
0x01, 0x81, 0x00, 0x00, 0x00, 0x01, 0x00,
|
||||||
|
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||||
|
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||||
|
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||||
|
|
||||||
|
0x01, 0x81, 0x00, 0x00, 0x00, 0x01, 0x00,
|
||||||
|
0x01, 0x82, 0x42, 0x00, 0x00, 0x10, 0x00,
|
||||||
|
0x01, 0x41, 0x00, 0x00, 0x00, 0x01, 0x00,
|
||||||
|
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||||
|
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||||
|
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||||
|
|
||||||
|
0x01, 0x8A, 0x00, 0x00, 0x00, 0x01, 0x00,
|
||||||
|
0x01, 0x82, 0x42, 0x00, 0x00, 0x10, 0x00,
|
||||||
|
0x01, 0x4A, 0x00, 0x00, 0x00, 0x01, 0x00,
|
||||||
|
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||||
|
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||||
|
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||||
|
|
||||||
|
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||||
|
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||||
|
|
||||||
|
0x02, 0x00, 0x00,
|
||||||
|
0x22, 0x17, 0x41, 0xA8, 0x32, 0x40, ]
|
||||||
|
|
||||||
|
# Hardware reset
|
||||||
|
def reset(self):
|
||||||
|
epdconfig.digital_write(self.reset_pin, 1)
|
||||||
|
epdconfig.delay_ms(200)
|
||||||
|
epdconfig.digital_write(self.reset_pin, 0)
|
||||||
|
epdconfig.delay_ms(1)
|
||||||
|
epdconfig.digital_write(self.reset_pin, 1)
|
||||||
|
epdconfig.delay_ms(200)
|
||||||
|
|
||||||
|
def send_command(self, command):
|
||||||
|
epdconfig.digital_write(self.dc_pin, 0)
|
||||||
|
epdconfig.digital_write(self.cs_pin, 0)
|
||||||
|
epdconfig.spi_writebyte([command])
|
||||||
|
epdconfig.digital_write(self.cs_pin, 1)
|
||||||
|
|
||||||
|
def send_data(self, data):
|
||||||
|
epdconfig.digital_write(self.dc_pin, 1)
|
||||||
|
epdconfig.digital_write(self.cs_pin, 0)
|
||||||
|
epdconfig.spi_writebyte([data])
|
||||||
|
epdconfig.digital_write(self.cs_pin, 1)
|
||||||
|
|
||||||
|
# send a lot of data
|
||||||
|
def send_data2(self, data):
|
||||||
|
epdconfig.digital_write(self.dc_pin, 1)
|
||||||
|
epdconfig.digital_write(self.cs_pin, 0)
|
||||||
|
epdconfig.spi_writebyte2(data)
|
||||||
|
epdconfig.digital_write(self.cs_pin, 1)
|
||||||
|
|
||||||
|
def ReadBusy(self):
|
||||||
|
logger.debug("e-Paper busy")
|
||||||
|
while (epdconfig.digital_read(self.busy_pin) == 1): # 0: idle, 1: busy
|
||||||
|
epdconfig.delay_ms(200)
|
||||||
|
logger.debug("e-Paper busy release")
|
||||||
|
|
||||||
|
def TurnOnDisplay(self):
|
||||||
|
self.send_command(0x22)
|
||||||
|
self.send_data(0xF7)
|
||||||
|
self.send_command(0x20) # DISPLAY REFRESH
|
||||||
|
epdconfig.delay_ms(100) # The delay here is necessary, 200uS at least!!!
|
||||||
|
self.ReadBusy() # waiting for the electronic paper IC to release the idle signal
|
||||||
|
|
||||||
|
def TurnOnDisplay_Fast(self):
|
||||||
|
self.send_command(0x22)
|
||||||
|
self.send_data(0xC7)
|
||||||
|
self.send_command(0x20) # DISPLAY REFRESH
|
||||||
|
epdconfig.delay_ms(100) # The delay here is necessary, 200uS at least!!!
|
||||||
|
self.ReadBusy() # waiting for the electronic paper IC to release the idle signal
|
||||||
|
|
||||||
|
def TurnOnDisplay_Partial(self):
|
||||||
|
self.send_command(0x22)
|
||||||
|
self.send_data(0xFF)
|
||||||
|
self.send_command(0x20) # DISPLAY REFRESH
|
||||||
|
epdconfig.delay_ms(100) # The delay here is necessary, 200uS at least!!!
|
||||||
|
self.ReadBusy() # waiting for the electronic paper IC to release the idle signal
|
||||||
|
|
||||||
|
def TurnOnDisplay_4GRAY(self):
|
||||||
|
self.send_command(0x22)
|
||||||
|
self.send_data(0xCF)
|
||||||
|
self.send_command(0x20) # DISPLAY REFRESH
|
||||||
|
epdconfig.delay_ms(100) # The delay here is necessary, 200uS at least!!!
|
||||||
|
self.ReadBusy() # waiting for the electronic paper IC to release the idle signal
|
||||||
|
|
||||||
|
def EPD_5in79_Lut(self):
|
||||||
|
self.send_command(0x32)
|
||||||
|
self.send_data2(self.LUT_DATA_4Gray[:227])
|
||||||
|
self.send_command(0x3f)
|
||||||
|
self.send_data(self.LUT_DATA_4Gray[227])
|
||||||
|
|
||||||
|
self.send_command(0x03)
|
||||||
|
self.send_data(self.LUT_DATA_4Gray[228])
|
||||||
|
|
||||||
|
self.send_command(0x04)
|
||||||
|
self.send_data(self.LUT_DATA_4Gray[229])
|
||||||
|
self.send_data(self.LUT_DATA_4Gray[230])
|
||||||
|
self.send_data(self.LUT_DATA_4Gray[231])
|
||||||
|
|
||||||
|
self.send_command(0x2C)
|
||||||
|
self.send_data(self.LUT_DATA_4Gray[232])
|
||||||
|
|
||||||
|
def init(self):
|
||||||
|
if (epdconfig.module_init() != 0):
|
||||||
|
return -1
|
||||||
|
|
||||||
|
self.reset()
|
||||||
|
self.ReadBusy() # waiting for the electronic paper IC to release the idle signal
|
||||||
|
self.send_command(0x12) # POWER ON
|
||||||
|
self.ReadBusy() # waiting for the electronic paper IC to release the idle signal
|
||||||
|
|
||||||
|
self.send_command(0x11)
|
||||||
|
self.send_data(0x01)
|
||||||
|
|
||||||
|
self.send_command(0x44) # Set Ram X- address Start / End position
|
||||||
|
self.send_data(0x00) # XStart, POR = 00h
|
||||||
|
self.send_data(0x31) # 400/8-1
|
||||||
|
self.send_command(0x45) # Set Ram Y- address Start / End position
|
||||||
|
self.send_data(0x0f)
|
||||||
|
self.send_data(0x01) # 300-1
|
||||||
|
self.send_data(0x00) # YEnd L
|
||||||
|
self.send_data(0x00) # YEnd H
|
||||||
|
|
||||||
|
self.send_command(0x4e)
|
||||||
|
self.send_data(0x00)
|
||||||
|
self.send_command(0x4f)
|
||||||
|
self.send_data(0x0f)
|
||||||
|
self.send_data(0x01)
|
||||||
|
|
||||||
|
self.ReadBusy()
|
||||||
|
|
||||||
|
self.send_command(0x91)
|
||||||
|
self.send_data(0x00)
|
||||||
|
|
||||||
|
self.send_command(0xC4) # Set Ram X- address Start / End position
|
||||||
|
self.send_data(0x31) # XStart, POR = 00h
|
||||||
|
self.send_data(0x00) # 400/8-1
|
||||||
|
self.send_command(0xC5) # Set Ram Y- address Start / End position
|
||||||
|
self.send_data(0x0f)
|
||||||
|
self.send_data(0x01) # 300-1
|
||||||
|
self.send_data(0x00) # YEnd L
|
||||||
|
self.send_data(0x00) # YEnd H
|
||||||
|
|
||||||
|
self.send_command(0xCE)
|
||||||
|
self.send_data(0x31)
|
||||||
|
self.send_command(0xCF)
|
||||||
|
self.send_data(0x0f)
|
||||||
|
self.send_data(0x01)
|
||||||
|
|
||||||
|
self.ReadBusy()
|
||||||
|
|
||||||
|
return 0
|
||||||
|
|
||||||
|
def init_Fast(self):
|
||||||
|
if (epdconfig.module_init() != 0):
|
||||||
|
return -1
|
||||||
|
|
||||||
|
self.reset()
|
||||||
|
self.ReadBusy()
|
||||||
|
self.send_command(0x12)
|
||||||
|
self.ReadBusy()
|
||||||
|
|
||||||
|
self.send_command(0x18)
|
||||||
|
self.send_data(0x80)
|
||||||
|
|
||||||
|
self.send_command(0x22)
|
||||||
|
self.send_data(0xB1)
|
||||||
|
self.send_command(0x20)
|
||||||
|
self.ReadBusy()
|
||||||
|
|
||||||
|
self.send_command(0x1A)
|
||||||
|
self.send_data(0x64)
|
||||||
|
self.send_data(0x00)
|
||||||
|
|
||||||
|
self.send_command(0x22)
|
||||||
|
self.send_data(0x91)
|
||||||
|
self.send_command(0x20)
|
||||||
|
self.ReadBusy()
|
||||||
|
|
||||||
|
self.send_command(0x11)
|
||||||
|
self.send_data(0x01)
|
||||||
|
|
||||||
|
self.send_command(0x44)
|
||||||
|
self.send_data(0x00)
|
||||||
|
self.send_data(0x31)
|
||||||
|
self.send_command(0x45)
|
||||||
|
self.send_data(0x0f)
|
||||||
|
self.send_data(0x01)
|
||||||
|
self.send_data(0x00)
|
||||||
|
self.send_data(0x00)
|
||||||
|
|
||||||
|
self.send_command(0x4e)
|
||||||
|
self.send_data(0x00)
|
||||||
|
self.send_command(0x4f)
|
||||||
|
self.send_data(0x0f)
|
||||||
|
self.send_data(0x01)
|
||||||
|
|
||||||
|
self.ReadBusy()
|
||||||
|
|
||||||
|
self.send_command(0x91)
|
||||||
|
self.send_data(0x00)
|
||||||
|
|
||||||
|
self.send_command(0xC4)
|
||||||
|
self.send_data(0x31)
|
||||||
|
self.send_data(0x00)
|
||||||
|
self.send_command(0xC5)
|
||||||
|
self.send_data(0x0f)
|
||||||
|
self.send_data(0x01)
|
||||||
|
self.send_data(0x00)
|
||||||
|
self.send_data(0x00)
|
||||||
|
|
||||||
|
self.send_command(0xCe)
|
||||||
|
self.send_data(0x31)
|
||||||
|
self.send_command(0xCf)
|
||||||
|
self.send_data(0x0f)
|
||||||
|
self.send_data(0x01)
|
||||||
|
|
||||||
|
self.ReadBusy()
|
||||||
|
|
||||||
|
return 0
|
||||||
|
|
||||||
|
def init_Partial(self):
|
||||||
|
if (epdconfig.module_init() != 0):
|
||||||
|
return -1
|
||||||
|
|
||||||
|
self.reset()
|
||||||
|
self.ReadBusy()
|
||||||
|
self.send_command(0x12)
|
||||||
|
self.ReadBusy()
|
||||||
|
|
||||||
|
self.send_command(0x3C)
|
||||||
|
self.send_data(0x80)
|
||||||
|
|
||||||
|
return 0
|
||||||
|
|
||||||
|
def init_4Gray(self):
|
||||||
|
if (epdconfig.module_init() != 0):
|
||||||
|
return -1
|
||||||
|
|
||||||
|
self.reset()
|
||||||
|
self.ReadBusy()
|
||||||
|
self.send_command(0x12)
|
||||||
|
self.ReadBusy()
|
||||||
|
|
||||||
|
self.send_command(0x0C)
|
||||||
|
self.send_data(0x8B)
|
||||||
|
self.send_data(0x9C)
|
||||||
|
self.send_data(0xA6)
|
||||||
|
self.send_data(0x0F)
|
||||||
|
|
||||||
|
self.send_command(0x3C)
|
||||||
|
self.send_data(0x81)
|
||||||
|
|
||||||
|
self.ReadBusy()
|
||||||
|
|
||||||
|
self.send_command(0x11)
|
||||||
|
self.send_data(0x01)
|
||||||
|
|
||||||
|
self.send_command(0x44)
|
||||||
|
self.send_data(0x00)
|
||||||
|
self.send_data(0x31)
|
||||||
|
self.send_command(0x45)
|
||||||
|
self.send_data(0x0f)
|
||||||
|
self.send_data(0x01)
|
||||||
|
self.send_data(0x00)
|
||||||
|
self.send_data(0x00)
|
||||||
|
|
||||||
|
self.send_command(0x4e)
|
||||||
|
self.send_data(0x00)
|
||||||
|
self.send_command(0x4f)
|
||||||
|
self.send_data(0x0f)
|
||||||
|
self.send_data(0x01)
|
||||||
|
|
||||||
|
self.ReadBusy()
|
||||||
|
|
||||||
|
self.send_command(0x91)
|
||||||
|
self.send_data(0x00)
|
||||||
|
|
||||||
|
self.send_command(0xC4)
|
||||||
|
self.send_data(0x31)
|
||||||
|
self.send_data(0x00)
|
||||||
|
self.send_command(0xC5)
|
||||||
|
self.send_data(0x0f)
|
||||||
|
self.send_data(0x01)
|
||||||
|
self.send_data(0x00)
|
||||||
|
self.send_data(0x00)
|
||||||
|
|
||||||
|
self.send_command(0xCe)
|
||||||
|
self.send_data(0x31)
|
||||||
|
self.send_command(0xCf)
|
||||||
|
self.send_data(0x0f)
|
||||||
|
self.send_data(0x01)
|
||||||
|
|
||||||
|
self.EPD_5in79_Lut()
|
||||||
|
return 0
|
||||||
|
|
||||||
|
def getbuffer(self, image):
|
||||||
|
# logger.debug("bufsiz = ",int(self.width/8) * self.height)
|
||||||
|
buf = [0xFF] * (int(self.width / 8) * self.height)
|
||||||
|
image_monocolor = image.convert('1')
|
||||||
|
imwidth, imheight = image_monocolor.size
|
||||||
|
pixels = image_monocolor.load()
|
||||||
|
# logger.debug("imwidth = %d, imheight = %d",imwidth,imheight)
|
||||||
|
if imwidth == self.width and imheight == self.height:
|
||||||
|
logger.debug("Horizontal")
|
||||||
|
for y in range(imheight):
|
||||||
|
for x in range(imwidth):
|
||||||
|
# Set the bits for the column of pixels at the current position.
|
||||||
|
if pixels[x, y] == 0:
|
||||||
|
buf[int((x + y * self.width) / 8)] &= ~(0x80 >> (x % 8))
|
||||||
|
elif imwidth == self.height and imheight == self.width:
|
||||||
|
logger.debug("Vertical")
|
||||||
|
for y in range(imheight):
|
||||||
|
for x in range(imwidth):
|
||||||
|
newx = y
|
||||||
|
newy = self.height - x - 1
|
||||||
|
if pixels[x, y] == 0:
|
||||||
|
buf[int((newx + newy * self.width) / 8)] &= ~(0x80 >> (y % 8))
|
||||||
|
return buf
|
||||||
|
|
||||||
|
def getbuffer_4Gray(self, image):
|
||||||
|
# logger.debug("bufsiz = ",int(self.width/8) * self.height)
|
||||||
|
buf = [0xFF] * (int(self.width / 4) * self.height)
|
||||||
|
image_monocolor = image.convert('L')
|
||||||
|
imwidth, imheight = image_monocolor.size
|
||||||
|
pixels = image_monocolor.load()
|
||||||
|
i = 0
|
||||||
|
# logger.debug("imwidth = %d, imheight = %d",imwidth,imheight)
|
||||||
|
if (imwidth == self.width and imheight == self.height):
|
||||||
|
logger.debug("Vertical")
|
||||||
|
for y in range(imheight):
|
||||||
|
for x in range(imwidth):
|
||||||
|
# Set the bits for the column of pixels at the current position.
|
||||||
|
if (pixels[x, y] == 0xC0):
|
||||||
|
pixels[x, y] = 0x80
|
||||||
|
elif (pixels[x, y] == 0x80):
|
||||||
|
pixels[x, y] = 0x40
|
||||||
|
i = i + 1
|
||||||
|
if (i % 4 == 0):
|
||||||
|
buf[int((x + (y * self.width)) / 4)] = (
|
||||||
|
(pixels[x - 3, y] & 0xc0) | (pixels[x - 2, y] & 0xc0) >> 2 | (
|
||||||
|
pixels[x - 1, y] & 0xc0) >> 4 | (pixels[x, y] & 0xc0) >> 6)
|
||||||
|
|
||||||
|
elif (imwidth == self.height and imheight == self.width):
|
||||||
|
logger.debug("Horizontal")
|
||||||
|
for x in range(imwidth):
|
||||||
|
for y in range(imheight):
|
||||||
|
newx = y
|
||||||
|
newy = self.height - x - 1
|
||||||
|
if (pixels[x, y] == 0xC0):
|
||||||
|
pixels[x, y] = 0x80
|
||||||
|
elif (pixels[x, y] == 0x80):
|
||||||
|
pixels[x, y] = 0x40
|
||||||
|
i = i + 1
|
||||||
|
if (i % 4 == 0):
|
||||||
|
buf[int((newx + (newy * self.width)) / 4)] = (
|
||||||
|
(pixels[x, y - 3] & 0xc0) | (pixels[x, y - 2] & 0xc0) >> 2 | (
|
||||||
|
pixels[x, y - 1] & 0xc0) >> 4 | (pixels[x, y] & 0xc0) >> 6)
|
||||||
|
return buf
|
||||||
|
|
||||||
|
def display(self, imageblack):
|
||||||
|
Width = int(self.width / 16) + 1
|
||||||
|
Width1 = int(self.width / 8)
|
||||||
|
|
||||||
|
self.send_command(0x24)
|
||||||
|
for i in range(self.height):
|
||||||
|
self.send_data2(imageblack[i * Width1: i * Width1 + Width])
|
||||||
|
self.send_command(0X26)
|
||||||
|
self.send_data2([0x00] * 13600)
|
||||||
|
|
||||||
|
self.send_command(0xA4)
|
||||||
|
for i in range(self.height):
|
||||||
|
self.send_data2(imageblack[i * Width1 + Width - 1: i * Width1 + Width * 2 - 1])
|
||||||
|
self.send_command(0xA6)
|
||||||
|
self.send_data2([0x00] * 13600)
|
||||||
|
|
||||||
|
self.TurnOnDisplay()
|
||||||
|
|
||||||
|
def display_Base(self, imageblack):
|
||||||
|
Width = int(self.width / 16) + 1
|
||||||
|
Width1 = int(self.width / 8)
|
||||||
|
|
||||||
|
self.send_command(0x24)
|
||||||
|
for i in range(self.height):
|
||||||
|
self.send_data2(imageblack[i * Width1: i * Width1 + Width])
|
||||||
|
self.send_command(0X26)
|
||||||
|
self.send_data2([0x00] * 13600)
|
||||||
|
|
||||||
|
self.send_command(0xA4)
|
||||||
|
for i in range(self.height):
|
||||||
|
self.send_data2(imageblack[i * Width1 + Width - 1: i * Width1 + Width * 2 - 1])
|
||||||
|
self.send_command(0xA6)
|
||||||
|
self.send_data2([0x00] * 13600)
|
||||||
|
|
||||||
|
self.TurnOnDisplay()
|
||||||
|
|
||||||
|
self.send_command(0x26)
|
||||||
|
for i in range(self.height):
|
||||||
|
self.send_data2(imageblack[i * Width1: i * Width1 + Width])
|
||||||
|
|
||||||
|
self.send_command(0xA6)
|
||||||
|
for i in range(self.height):
|
||||||
|
self.send_data2(imageblack[i * Width1 + Width - 1: i * Width1 + Width * 2 - 1])
|
||||||
|
|
||||||
|
def display_Base_color(self, color):
|
||||||
|
Width = int(self.width / 16) + 1
|
||||||
|
Width1 = int(self.width / 8)
|
||||||
|
|
||||||
|
self.send_command(0x24)
|
||||||
|
self.send_data2([color] * 13600)
|
||||||
|
self.send_command(0X26)
|
||||||
|
self.send_data2([0x00] * 13600)
|
||||||
|
|
||||||
|
self.send_command(0xA4)
|
||||||
|
self.send_data2([color] * 13600)
|
||||||
|
self.send_command(0xA6)
|
||||||
|
self.send_data2([0x00] * 13600)
|
||||||
|
|
||||||
|
self.TurnOnDisplay()
|
||||||
|
|
||||||
|
self.send_command(0x26)
|
||||||
|
self.send_data2([color] * 13600)
|
||||||
|
|
||||||
|
self.send_command(0xA6)
|
||||||
|
self.send_data2([color] * 13600)
|
||||||
|
|
||||||
|
def display_Fast(self, imageblack):
|
||||||
|
Width = int(self.width / 16) + 1
|
||||||
|
Width1 = int(self.width / 8)
|
||||||
|
|
||||||
|
self.send_command(0x24)
|
||||||
|
for i in range(self.height):
|
||||||
|
self.send_data2(imageblack[i * Width1: i * Width1 + Width])
|
||||||
|
self.send_command(0X26)
|
||||||
|
self.send_data2([0x00] * 13600)
|
||||||
|
|
||||||
|
self.send_command(0xA4)
|
||||||
|
for i in range(self.height):
|
||||||
|
self.send_data2(imageblack[i * Width1 + Width - 1: i * Width1 + Width * 2 - 1])
|
||||||
|
self.send_command(0xA6)
|
||||||
|
self.send_data2([0x00] * 13600)
|
||||||
|
|
||||||
|
self.TurnOnDisplay_Fast()
|
||||||
|
|
||||||
|
def display_Partial(self, Image):
|
||||||
|
Width = int(self.width / 16) + 1
|
||||||
|
Width1 = int(self.width / 8)
|
||||||
|
Height = self.height
|
||||||
|
self.send_command(0x44)
|
||||||
|
self.send_data(0x00)
|
||||||
|
self.send_data(0x31)
|
||||||
|
self.send_command(0x45)
|
||||||
|
self.send_data(0x0f)
|
||||||
|
self.send_data(0x01) # 300-1
|
||||||
|
self.send_data(0x00) # YEnd L
|
||||||
|
self.send_data(0x00) # YEnd H
|
||||||
|
|
||||||
|
self.send_command(0x4e)
|
||||||
|
self.send_data(0x00)
|
||||||
|
self.send_command(0x4f)
|
||||||
|
self.send_data(0x0f)
|
||||||
|
self.send_data(0x01)
|
||||||
|
|
||||||
|
self.send_command(0x24)
|
||||||
|
for i in range(Height):
|
||||||
|
self.send_data2(Image[i * Width1: i * Width1 + Width])
|
||||||
|
|
||||||
|
self.send_command(0xC4) # Set Ram X- address Start / End position
|
||||||
|
self.send_data(0x31) # XStart, POR = 00h
|
||||||
|
self.send_data(0x00) # 400/8-1
|
||||||
|
self.send_command(0xC5) # Set Ram Y- address Start / End position
|
||||||
|
self.send_data(0x0f)
|
||||||
|
self.send_data(0x01) # 300-1
|
||||||
|
self.send_data(0x00) # YEnd L
|
||||||
|
self.send_data(0x00) # YEnd H
|
||||||
|
|
||||||
|
self.send_command(0xCe)
|
||||||
|
self.send_data(0x31)
|
||||||
|
self.send_command(0xCf)
|
||||||
|
self.send_data(0x0f)
|
||||||
|
self.send_data(0x01)
|
||||||
|
|
||||||
|
self.send_command(0xA4)
|
||||||
|
for i in range(Height):
|
||||||
|
self.send_data2(Image[i * Width1 + Width - 1: i * Width1 + Width * 2 - 1])
|
||||||
|
|
||||||
|
self.TurnOnDisplay_Partial()
|
||||||
|
|
||||||
|
def display_4Gray(self, image):
|
||||||
|
Width = int(self.width / 16) + 1
|
||||||
|
Width1 = int(self.width / 8)
|
||||||
|
|
||||||
|
self.send_command(0x24)
|
||||||
|
for j in range(self.height):
|
||||||
|
for i in range(Width):
|
||||||
|
temp3 = 0
|
||||||
|
for o in range(0, 2):
|
||||||
|
temp1 = image[(j * Width1 + i) * 2 + o]
|
||||||
|
for k in range(0, 4):
|
||||||
|
temp2 = temp1 & 0xC0
|
||||||
|
if (temp2 == 0xC0):
|
||||||
|
temp3 |= 0x01
|
||||||
|
elif (temp2 == 0x00):
|
||||||
|
temp3 |= 0x00
|
||||||
|
elif (temp2 == 0x80):
|
||||||
|
temp3 |= 0x00
|
||||||
|
else: # 0x40
|
||||||
|
temp3 |= 0x01
|
||||||
|
if (o != 1 or k != 3):
|
||||||
|
temp3 <<= 1
|
||||||
|
temp1 <<= 2
|
||||||
|
self.send_data(temp3)
|
||||||
|
|
||||||
|
self.send_command(0x26)
|
||||||
|
for j in range(self.height):
|
||||||
|
for i in range(Width):
|
||||||
|
temp3 = 0
|
||||||
|
for o in range(0, 2):
|
||||||
|
temp1 = image[(j * Width1 + i) * 2 + o]
|
||||||
|
for k in range(0, 4):
|
||||||
|
temp2 = temp1 & 0xC0
|
||||||
|
if (temp2 == 0xC0):
|
||||||
|
temp3 |= 0x01
|
||||||
|
elif (temp2 == 0x00):
|
||||||
|
temp3 |= 0x00
|
||||||
|
elif (temp2 == 0x80):
|
||||||
|
temp3 |= 0x01
|
||||||
|
else: # 0x40
|
||||||
|
temp3 |= 0x00
|
||||||
|
if (o != 1 or k != 3):
|
||||||
|
temp3 <<= 1
|
||||||
|
temp1 <<= 2
|
||||||
|
self.send_data(temp3)
|
||||||
|
|
||||||
|
self.send_command(0xA4)
|
||||||
|
for j in range(self.height):
|
||||||
|
for i in range(Width):
|
||||||
|
temp3 = 0
|
||||||
|
for o in range(0, 2):
|
||||||
|
temp1 = image[(j * Width1 + i + Width - 1) * 2 + o]
|
||||||
|
for k in range(0, 4):
|
||||||
|
temp2 = temp1 & 0xC0
|
||||||
|
if (temp2 == 0xC0):
|
||||||
|
temp3 |= 0x01
|
||||||
|
elif (temp2 == 0x00):
|
||||||
|
temp3 |= 0x00
|
||||||
|
elif (temp2 == 0x80):
|
||||||
|
temp3 |= 0x00
|
||||||
|
else: # 0x40
|
||||||
|
temp3 |= 0x01
|
||||||
|
if (o != 1 or k != 3):
|
||||||
|
temp3 <<= 1
|
||||||
|
temp1 <<= 2
|
||||||
|
self.send_data(temp3)
|
||||||
|
|
||||||
|
self.send_command(0xA6)
|
||||||
|
for j in range(self.height):
|
||||||
|
for i in range(Width):
|
||||||
|
temp3 = 0
|
||||||
|
for o in range(0, 2):
|
||||||
|
temp1 = image[(j * Width1 + i + Width - 1) * 2 + o]
|
||||||
|
for k in range(0, 4):
|
||||||
|
temp2 = temp1 & 0xC0
|
||||||
|
if (temp2 == 0xC0):
|
||||||
|
temp3 |= 0x01
|
||||||
|
elif (temp2 == 0x00):
|
||||||
|
temp3 |= 0x00
|
||||||
|
elif (temp2 == 0x80):
|
||||||
|
temp3 |= 0x01
|
||||||
|
else: # 0x40
|
||||||
|
temp3 |= 0x00
|
||||||
|
if (o != 1 or k != 3):
|
||||||
|
temp3 <<= 1
|
||||||
|
temp1 <<= 2
|
||||||
|
self.send_data(temp3)
|
||||||
|
|
||||||
|
self.TurnOnDisplay_4GRAY()
|
||||||
|
|
||||||
|
def Clear(self):
|
||||||
|
self.send_command(0x24)
|
||||||
|
self.send_data2([0xFF] * 13600)
|
||||||
|
self.send_command(0X26)
|
||||||
|
self.send_data2([0x00] * 13600)
|
||||||
|
|
||||||
|
self.send_command(0xA4)
|
||||||
|
self.send_data2([0xFF] * 13600)
|
||||||
|
self.send_command(0xA6)
|
||||||
|
self.send_data2([0x00] * 13600)
|
||||||
|
|
||||||
|
self.TurnOnDisplay()
|
||||||
|
|
||||||
|
def sleep(self):
|
||||||
|
self.send_command(0X10) # deep sleep
|
||||||
|
self.send_data(0x03)
|
||||||
|
|
||||||
|
epdconfig.delay_ms(2000)
|
||||||
|
epdconfig.module_exit()
|
||||||
|
### END OF FILE ###
|
205
pwnagotchi/ui/hw/libs/waveshare/v5in79b/epd5in79b.py
Normal file
205
pwnagotchi/ui/hw/libs/waveshare/v5in79b/epd5in79b.py
Normal file
@ -0,0 +1,205 @@
|
|||||||
|
# *****************************************************************************
|
||||||
|
# * | File : epd5in79b.py
|
||||||
|
# * | Author : Waveshare team
|
||||||
|
# * | Function : Electronic paper driver
|
||||||
|
# * | Info :
|
||||||
|
# *----------------
|
||||||
|
# * | This version: V1.0
|
||||||
|
# * | Date : 2024-03-05
|
||||||
|
# # | Info : python demo
|
||||||
|
# -----------------------------------------------------------------------------
|
||||||
|
# Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
|
# of this software and associated documnetation files (the "Software"), to deal
|
||||||
|
# in the Software without restriction, including without limitation the rights
|
||||||
|
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
|
# copies of the Software, and to permit persons to whom the Software is
|
||||||
|
# furished to do so, subject to the following conditions:
|
||||||
|
#
|
||||||
|
# The above copyright notice and this permission notice shall be included in
|
||||||
|
# all copies or substantial portions of the Software.
|
||||||
|
#
|
||||||
|
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
# FITNESS OR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
# LIABILITY WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
|
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||||
|
# THE SOFTWARE.
|
||||||
|
#
|
||||||
|
|
||||||
|
|
||||||
|
import logging
|
||||||
|
from .. import epdconfig
|
||||||
|
|
||||||
|
# Display resolution
|
||||||
|
EPD_WIDTH = 792
|
||||||
|
EPD_HEIGHT = 272
|
||||||
|
|
||||||
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
|
class EPD:
|
||||||
|
def __init__(self):
|
||||||
|
self.reset_pin = epdconfig.RST_PIN
|
||||||
|
self.dc_pin = epdconfig.DC_PIN
|
||||||
|
self.busy_pin = epdconfig.BUSY_PIN
|
||||||
|
self.cs_pin = epdconfig.CS_PIN
|
||||||
|
self.width = EPD_WIDTH
|
||||||
|
self.height = EPD_HEIGHT
|
||||||
|
|
||||||
|
# Hardware reset
|
||||||
|
def reset(self):
|
||||||
|
epdconfig.digital_write(self.reset_pin, 1)
|
||||||
|
epdconfig.delay_ms(200)
|
||||||
|
epdconfig.digital_write(self.reset_pin, 0)
|
||||||
|
epdconfig.delay_ms(1)
|
||||||
|
epdconfig.digital_write(self.reset_pin, 1)
|
||||||
|
epdconfig.delay_ms(200)
|
||||||
|
|
||||||
|
def send_command(self, command):
|
||||||
|
epdconfig.digital_write(self.dc_pin, 0)
|
||||||
|
epdconfig.digital_write(self.cs_pin, 0)
|
||||||
|
epdconfig.spi_writebyte([command])
|
||||||
|
epdconfig.digital_write(self.cs_pin, 1)
|
||||||
|
|
||||||
|
def send_data(self, data):
|
||||||
|
epdconfig.digital_write(self.dc_pin, 1)
|
||||||
|
epdconfig.digital_write(self.cs_pin, 0)
|
||||||
|
epdconfig.spi_writebyte([data])
|
||||||
|
epdconfig.digital_write(self.cs_pin, 1)
|
||||||
|
|
||||||
|
# send a lot of data
|
||||||
|
def send_data2(self, data):
|
||||||
|
epdconfig.digital_write(self.dc_pin, 1)
|
||||||
|
epdconfig.digital_write(self.cs_pin, 0)
|
||||||
|
epdconfig.spi_writebyte2(data)
|
||||||
|
epdconfig.digital_write(self.cs_pin, 1)
|
||||||
|
|
||||||
|
def ReadBusy(self):
|
||||||
|
logger.debug("e-Paper busy")
|
||||||
|
while (epdconfig.digital_read(self.busy_pin) == 1): # 0: idle, 1: busy
|
||||||
|
epdconfig.delay_ms(200)
|
||||||
|
logger.debug("e-Paper busy release")
|
||||||
|
|
||||||
|
def TurnOnDisplay(self):
|
||||||
|
self.send_command(0x22)
|
||||||
|
self.send_data(0xF7) # 24s # 0xD7 16s Probability refresh bad, probability damage ink screen
|
||||||
|
self.send_command(0x20) # DISPLAY REFRESH
|
||||||
|
epdconfig.delay_ms(100) # The delay here is necessary, 200uS at least!!!
|
||||||
|
self.ReadBusy() # waiting for the electronic paper IC to release the idle signal
|
||||||
|
|
||||||
|
def init(self):
|
||||||
|
if (epdconfig.module_init() != 0):
|
||||||
|
return -1
|
||||||
|
|
||||||
|
self.reset()
|
||||||
|
|
||||||
|
self.ReadBusy() # waiting for the electronic paper IC to release the idle signal
|
||||||
|
self.send_command(0x12) # POWER ON
|
||||||
|
self.ReadBusy() # waiting for the electronic paper IC to release the idle signal
|
||||||
|
|
||||||
|
self.send_command(0x11)
|
||||||
|
self.send_data(0x01)
|
||||||
|
self.send_command(0x44) # Set Ram X- address Start / End position
|
||||||
|
self.send_data(0x00) # XStart, POR = 00h
|
||||||
|
self.send_data(0x31) # 400/8-1
|
||||||
|
self.send_command(0x45) # Set Ram Y- address Start / End position
|
||||||
|
self.send_data(0x0f)
|
||||||
|
self.send_data(0x01) # 300-1
|
||||||
|
self.send_data(0x00) # YEnd L
|
||||||
|
self.send_data(0x00) # YEnd H
|
||||||
|
|
||||||
|
self.send_command(0x4e)
|
||||||
|
self.send_data(0x00)
|
||||||
|
self.send_command(0x4f)
|
||||||
|
self.send_data(0x0f)
|
||||||
|
self.send_data(0x01)
|
||||||
|
|
||||||
|
self.send_command(0x91)
|
||||||
|
self.send_data(0x00)
|
||||||
|
|
||||||
|
self.send_command(0xC4) # Set Ram X- address Start / End position
|
||||||
|
self.send_data(0x31) # XStart, POR = 00h
|
||||||
|
self.send_data(0x00) # 400/8-1
|
||||||
|
self.send_command(0xC5) # Set Ram Y- address Start / End position
|
||||||
|
self.send_data(0x0f)
|
||||||
|
self.send_data(0x01) # 300-1
|
||||||
|
self.send_data(0x00) # YEnd L
|
||||||
|
self.send_data(0x00) # YEnd H
|
||||||
|
|
||||||
|
self.send_command(0xCe)
|
||||||
|
self.send_data(0x31)
|
||||||
|
self.send_command(0xCf)
|
||||||
|
self.send_data(0x0f)
|
||||||
|
self.send_data(0x01)
|
||||||
|
return 0
|
||||||
|
|
||||||
|
def getbuffer(self, image):
|
||||||
|
# logger.debug("bufsiz = ",int(self.width/8) * self.height)
|
||||||
|
buf = [0xFF] * (int(self.width / 8) * self.height)
|
||||||
|
image_monocolor = image.convert('1')
|
||||||
|
imwidth, imheight = image_monocolor.size
|
||||||
|
pixels = image_monocolor.load()
|
||||||
|
# logger.debug("imwidth = %d, imheight = %d",imwidth,imheight)
|
||||||
|
if (imwidth == self.width and imheight == self.height):
|
||||||
|
logger.debug("Vertical")
|
||||||
|
for y in range(imheight):
|
||||||
|
for x in range(imwidth):
|
||||||
|
# Set the bits for the column of pixels at the current position.
|
||||||
|
if pixels[x, y] == 0:
|
||||||
|
buf[int((x + y * self.width) / 8)] &= ~(0x80 >> (x % 8))
|
||||||
|
elif (imwidth == self.height and imheight == self.width):
|
||||||
|
logger.debug("Horizontal")
|
||||||
|
for y in range(imheight):
|
||||||
|
for x in range(imwidth):
|
||||||
|
newx = y
|
||||||
|
newy = self.height - x - 1
|
||||||
|
if pixels[x, y] == 0:
|
||||||
|
buf[int((newx + newy * self.width) / 8)] &= ~(0x80 >> (y % 8))
|
||||||
|
return buf
|
||||||
|
|
||||||
|
def display(self, imageblack, imagered):
|
||||||
|
buf = [0x00] * int(self.width * self.height / 8)
|
||||||
|
for i in range(0, int(self.width * self.height / 8)):
|
||||||
|
buf[i] = ~imagered[i]
|
||||||
|
|
||||||
|
Width = int(self.width / 16) + 1
|
||||||
|
Width1 = int(self.width / 8)
|
||||||
|
Height = self.height
|
||||||
|
|
||||||
|
self.send_command(0x24)
|
||||||
|
for i in range(Height):
|
||||||
|
self.send_data2(imageblack[i * Width1: i * Width1 + Width])
|
||||||
|
self.send_command(0X26)
|
||||||
|
for i in range(self.height):
|
||||||
|
self.send_data2(buf[i * Width1: i * Width1 + Width])
|
||||||
|
|
||||||
|
self.send_command(0xA4)
|
||||||
|
for i in range(self.height):
|
||||||
|
self.send_data2(imageblack[i * Width1 + Width - 1: i * Width1 + Width * 2 - 1])
|
||||||
|
self.send_command(0xA6)
|
||||||
|
for i in range(self.height):
|
||||||
|
self.send_data2(buf[i * Width1 + Width - 1: i * Width1 + Width * 2 - 1])
|
||||||
|
|
||||||
|
self.TurnOnDisplay()
|
||||||
|
|
||||||
|
def Clear(self):
|
||||||
|
self.send_command(0x24)
|
||||||
|
self.send_data2([0xFF] * 13600)
|
||||||
|
self.send_command(0X26)
|
||||||
|
self.send_data2([0x00] * 13600)
|
||||||
|
|
||||||
|
self.send_command(0xA4)
|
||||||
|
self.send_data2([0xFF] * 13600)
|
||||||
|
self.send_command(0xA6)
|
||||||
|
self.send_data2([0x00] * 13600)
|
||||||
|
|
||||||
|
self.TurnOnDisplay()
|
||||||
|
|
||||||
|
def sleep(self):
|
||||||
|
self.send_command(0X10) # deep sleep
|
||||||
|
self.send_data(0x03)
|
||||||
|
|
||||||
|
epdconfig.delay_ms(2000)
|
||||||
|
epdconfig.module_exit()
|
||||||
|
### END OF FILE ###
|
243
pwnagotchi/ui/hw/libs/weact/epdconfig.py
Normal file
243
pwnagotchi/ui/hw/libs/weact/epdconfig.py
Normal file
@ -0,0 +1,243 @@
|
|||||||
|
# /*****************************************************************************
|
||||||
|
# * | File : epdconfig.py
|
||||||
|
# * | Author : Waveshare team
|
||||||
|
# * | Function : Hardware underlying interface
|
||||||
|
# * | Info :
|
||||||
|
# *----------------
|
||||||
|
# * | This version: V1.2
|
||||||
|
# * | Date : 2022-10-29
|
||||||
|
# * | Info :
|
||||||
|
# ******************************************************************************
|
||||||
|
# Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
|
# of this software and associated documnetation files (the "Software"), to deal
|
||||||
|
# in the Software without restriction, including without limitation the rights
|
||||||
|
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
|
# copies of the Software, and to permit persons to whom the Software is
|
||||||
|
# furished to do so, subject to the following conditions:
|
||||||
|
#
|
||||||
|
# The above copyright notice and this permission notice shall be included in
|
||||||
|
# all copies or substantial portions of the Software.
|
||||||
|
#
|
||||||
|
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
# FITNESS OR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
# LIABILITY WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
|
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||||
|
# THE SOFTWARE.
|
||||||
|
#
|
||||||
|
|
||||||
|
import os
|
||||||
|
import logging
|
||||||
|
import sys
|
||||||
|
import time
|
||||||
|
|
||||||
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
|
class RaspberryPi:
|
||||||
|
# Pin definition
|
||||||
|
RST_PIN = 17
|
||||||
|
DC_PIN = 25
|
||||||
|
CS_PIN = 8
|
||||||
|
BUSY_PIN = 24
|
||||||
|
PWR_PIN = 18
|
||||||
|
|
||||||
|
def __init__(self):
|
||||||
|
import spidev
|
||||||
|
import RPi.GPIO
|
||||||
|
|
||||||
|
self.GPIO = RPi.GPIO
|
||||||
|
self.SPI = spidev.SpiDev()
|
||||||
|
|
||||||
|
def digital_write(self, pin, value):
|
||||||
|
self.GPIO.output(pin, value)
|
||||||
|
|
||||||
|
def digital_read(self, pin):
|
||||||
|
return self.GPIO.input(pin)
|
||||||
|
|
||||||
|
def delay_ms(self, delaytime):
|
||||||
|
time.sleep(delaytime / 1000.0)
|
||||||
|
|
||||||
|
def spi_writebyte(self, data):
|
||||||
|
self.SPI.writebytes(data)
|
||||||
|
|
||||||
|
def spi_writebyte2(self, data):
|
||||||
|
self.SPI.writebytes2(data)
|
||||||
|
|
||||||
|
def module_init(self):
|
||||||
|
self.GPIO.setmode(self.GPIO.BCM)
|
||||||
|
self.GPIO.setwarnings(False)
|
||||||
|
self.GPIO.setup(self.RST_PIN, self.GPIO.OUT)
|
||||||
|
self.GPIO.setup(self.DC_PIN, self.GPIO.OUT)
|
||||||
|
self.GPIO.setup(self.CS_PIN, self.GPIO.OUT)
|
||||||
|
self.GPIO.setup(self.PWR_PIN, self.GPIO.OUT)
|
||||||
|
self.GPIO.setup(self.BUSY_PIN, self.GPIO.IN)
|
||||||
|
|
||||||
|
self.GPIO.output(self.PWR_PIN, 1)
|
||||||
|
|
||||||
|
# SPI device, bus = 0, device = 0
|
||||||
|
self.SPI.open(0, 0)
|
||||||
|
self.SPI.max_speed_hz = 4000000
|
||||||
|
self.SPI.mode = 0b00
|
||||||
|
return 0
|
||||||
|
|
||||||
|
def module_exit(self):
|
||||||
|
logger.debug("spi end")
|
||||||
|
self.SPI.close()
|
||||||
|
|
||||||
|
logger.debug("close 5V, Module enters 0 power consumption ...")
|
||||||
|
self.GPIO.output(self.RST_PIN, 0)
|
||||||
|
self.GPIO.output(self.DC_PIN, 0)
|
||||||
|
self.GPIO.output(self.PWR_PIN, 0)
|
||||||
|
|
||||||
|
self.GPIO.cleanup([self.RST_PIN, self.DC_PIN, self.CS_PIN, self.BUSY_PIN, self.PWR_PIN])
|
||||||
|
|
||||||
|
|
||||||
|
class JetsonNano:
|
||||||
|
# Pin definition
|
||||||
|
RST_PIN = 17
|
||||||
|
DC_PIN = 25
|
||||||
|
CS_PIN = 8
|
||||||
|
BUSY_PIN = 24
|
||||||
|
PWR_PIN = 18
|
||||||
|
|
||||||
|
def __init__(self):
|
||||||
|
import ctypes
|
||||||
|
find_dirs = [
|
||||||
|
os.path.dirname(os.path.realpath(__file__)),
|
||||||
|
'/usr/local/lib',
|
||||||
|
'/usr/lib',
|
||||||
|
]
|
||||||
|
self.SPI = None
|
||||||
|
for find_dir in find_dirs:
|
||||||
|
so_filename = os.path.join(find_dir, 'sysfs_software_spi.so')
|
||||||
|
if os.path.exists(so_filename):
|
||||||
|
self.SPI = ctypes.cdll.LoadLibrary(so_filename)
|
||||||
|
break
|
||||||
|
if self.SPI is None:
|
||||||
|
raise RuntimeError('Cannot find sysfs_software_spi.so')
|
||||||
|
|
||||||
|
import Jetson.GPIO
|
||||||
|
self.GPIO = Jetson.GPIO
|
||||||
|
|
||||||
|
def digital_write(self, pin, value):
|
||||||
|
self.GPIO.output(pin, value)
|
||||||
|
|
||||||
|
def digital_read(self, pin):
|
||||||
|
return self.GPIO.input(self.BUSY_PIN)
|
||||||
|
|
||||||
|
def delay_ms(self, delaytime):
|
||||||
|
time.sleep(delaytime / 1000.0)
|
||||||
|
|
||||||
|
def spi_writebyte(self, data):
|
||||||
|
self.SPI.SYSFS_software_spi_transfer(data[0])
|
||||||
|
|
||||||
|
def spi_writebyte2(self, data):
|
||||||
|
for i in range(len(data)):
|
||||||
|
self.SPI.SYSFS_software_spi_transfer(data[i])
|
||||||
|
|
||||||
|
def module_init(self):
|
||||||
|
self.GPIO.setmode(self.GPIO.BCM)
|
||||||
|
self.GPIO.setwarnings(False)
|
||||||
|
self.GPIO.setup(self.RST_PIN, self.GPIO.OUT)
|
||||||
|
self.GPIO.setup(self.DC_PIN, self.GPIO.OUT)
|
||||||
|
self.GPIO.setup(self.CS_PIN, self.GPIO.OUT)
|
||||||
|
self.GPIO.setup(self.PWR_PIN, self.GPIO.OUT)
|
||||||
|
self.GPIO.setup(self.BUSY_PIN, self.GPIO.IN)
|
||||||
|
|
||||||
|
self.GPIO.output(self.PWR_PIN, 1)
|
||||||
|
|
||||||
|
self.SPI.SYSFS_software_spi_begin()
|
||||||
|
return 0
|
||||||
|
|
||||||
|
def module_exit(self):
|
||||||
|
logger.debug("spi end")
|
||||||
|
self.SPI.SYSFS_software_spi_end()
|
||||||
|
|
||||||
|
logger.debug("close 5V, Module enters 0 power consumption ...")
|
||||||
|
self.GPIO.output(self.RST_PIN, 0)
|
||||||
|
self.GPIO.output(self.DC_PIN, 0)
|
||||||
|
self.GPIO.output(self.PWR_PIN, 0)
|
||||||
|
|
||||||
|
self.GPIO.cleanup([self.RST_PIN, self.DC_PIN, self.CS_PIN, self.BUSY_PIN, self.PWR_PIN])
|
||||||
|
|
||||||
|
|
||||||
|
class SunriseX3:
|
||||||
|
# Pin definition
|
||||||
|
RST_PIN = 17
|
||||||
|
DC_PIN = 25
|
||||||
|
CS_PIN = 8
|
||||||
|
BUSY_PIN = 24
|
||||||
|
PWR_PIN = 18
|
||||||
|
Flag = 0
|
||||||
|
|
||||||
|
def __init__(self):
|
||||||
|
import spidev
|
||||||
|
import Hobot.GPIO
|
||||||
|
|
||||||
|
self.GPIO = Hobot.GPIO
|
||||||
|
self.SPI = spidev.SpiDev()
|
||||||
|
|
||||||
|
def digital_write(self, pin, value):
|
||||||
|
self.GPIO.output(pin, value)
|
||||||
|
|
||||||
|
def digital_read(self, pin):
|
||||||
|
return self.GPIO.input(pin)
|
||||||
|
|
||||||
|
def delay_ms(self, delaytime):
|
||||||
|
time.sleep(delaytime / 1000.0)
|
||||||
|
|
||||||
|
def spi_writebyte(self, data):
|
||||||
|
self.SPI.writebytes(data)
|
||||||
|
|
||||||
|
def spi_writebyte2(self, data):
|
||||||
|
# for i in range(len(data)):
|
||||||
|
# self.SPI.writebytes([data[i]])
|
||||||
|
self.SPI.xfer3(data)
|
||||||
|
|
||||||
|
def module_init(self):
|
||||||
|
if self.Flag == 0:
|
||||||
|
self.Flag = 1
|
||||||
|
self.GPIO.setmode(self.GPIO.BCM)
|
||||||
|
self.GPIO.setwarnings(False)
|
||||||
|
self.GPIO.setup(self.RST_PIN, self.GPIO.OUT)
|
||||||
|
self.GPIO.setup(self.DC_PIN, self.GPIO.OUT)
|
||||||
|
self.GPIO.setup(self.CS_PIN, self.GPIO.OUT)
|
||||||
|
self.GPIO.setup(self.PWR_PIN, self.GPIO.OUT)
|
||||||
|
self.GPIO.setup(self.BUSY_PIN, self.GPIO.IN)
|
||||||
|
|
||||||
|
self.GPIO.output(self.PWR_PIN, 1)
|
||||||
|
|
||||||
|
# SPI device, bus = 0, device = 0
|
||||||
|
self.SPI.open(2, 0)
|
||||||
|
self.SPI.max_speed_hz = 4000000
|
||||||
|
self.SPI.mode = 0b00
|
||||||
|
return 0
|
||||||
|
else:
|
||||||
|
return 0
|
||||||
|
|
||||||
|
def module_exit(self):
|
||||||
|
logger.debug("spi end")
|
||||||
|
self.SPI.close()
|
||||||
|
|
||||||
|
logger.debug("close 5V, Module enters 0 power consumption ...")
|
||||||
|
self.Flag = 0
|
||||||
|
self.GPIO.output(self.RST_PIN, 0)
|
||||||
|
self.GPIO.output(self.DC_PIN, 0)
|
||||||
|
self.GPIO.output(self.PWR_PIN, 0)
|
||||||
|
|
||||||
|
self.GPIO.cleanup([self.RST_PIN, self.DC_PIN, self.CS_PIN, self.BUSY_PIN], self.PWR_PIN)
|
||||||
|
|
||||||
|
|
||||||
|
if os.path.exists('/sys/bus/platform/drivers/gpiomem-bcm2835'):
|
||||||
|
implementation = RaspberryPi()
|
||||||
|
elif os.path.exists('/sys/bus/platform/drivers/gpio-x3'):
|
||||||
|
implementation = SunriseX3()
|
||||||
|
else:
|
||||||
|
implementation = RaspberryPi()
|
||||||
|
|
||||||
|
for func in [x for x in dir(implementation) if not x.startswith('_')]:
|
||||||
|
setattr(sys.modules[__name__], func, getattr(implementation, func))
|
||||||
|
|
||||||
|
### END OF FILE ###
|
374
pwnagotchi/ui/hw/libs/weact/v2in9/epd2in9.py
Normal file
374
pwnagotchi/ui/hw/libs/weact/v2in9/epd2in9.py
Normal file
@ -0,0 +1,374 @@
|
|||||||
|
import logging
|
||||||
|
from .. import epdconfig
|
||||||
|
|
||||||
|
# Display resolution
|
||||||
|
EPD_WIDTH = 296
|
||||||
|
EPD_HEIGHT = 128
|
||||||
|
|
||||||
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
|
class EPD:
|
||||||
|
def __init__(self):
|
||||||
|
self.reset_pin = epdconfig.RST_PIN
|
||||||
|
self.dc_pin = epdconfig.DC_PIN
|
||||||
|
self.busy_pin = epdconfig.BUSY_PIN
|
||||||
|
self.cs_pin = epdconfig.CS_PIN
|
||||||
|
self.width = EPD_WIDTH
|
||||||
|
self.height = EPD_HEIGHT
|
||||||
|
|
||||||
|
lut_partial_update = [
|
||||||
|
0x0, 0x40, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
|
||||||
|
0x80, 0x80, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
|
||||||
|
0x40, 0x40, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
|
||||||
|
0x0, 0x80, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
|
||||||
|
0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
|
||||||
|
0x14, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
|
||||||
|
0x1, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
|
||||||
|
0x1, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
|
||||||
|
0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
|
||||||
|
0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
|
||||||
|
0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
|
||||||
|
0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
|
||||||
|
0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
|
||||||
|
0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
|
||||||
|
0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
|
||||||
|
0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
|
||||||
|
0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
|
||||||
|
0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x0, 0x0, 0x0,
|
||||||
|
0x22, 0x17, 0x41, 0x00, 0x32, 0x36,
|
||||||
|
]
|
||||||
|
|
||||||
|
lut_full_update = [
|
||||||
|
0x80, 0x4A, 0x40, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
|
||||||
|
0x40, 0x4A, 0x80, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
|
||||||
|
0x80, 0x4A, 0x40, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
|
||||||
|
0x40, 0x4A, 0x80, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
|
||||||
|
0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
|
||||||
|
0xF, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
|
||||||
|
0xF, 0x0, 0x0, 0xF, 0x0, 0x0, 0x2,
|
||||||
|
0xF, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
|
||||||
|
0x1, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
|
||||||
|
0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
|
||||||
|
0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
|
||||||
|
0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
|
||||||
|
0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
|
||||||
|
0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
|
||||||
|
0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
|
||||||
|
0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
|
||||||
|
0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
|
||||||
|
0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x0, 0x0, 0x0,
|
||||||
|
0x22, 0x17, 0x41, 0x0, 0x32, 0x36,
|
||||||
|
]
|
||||||
|
|
||||||
|
'''
|
||||||
|
function :Hardware reset
|
||||||
|
parameter:
|
||||||
|
'''
|
||||||
|
|
||||||
|
def reset(self):
|
||||||
|
epdconfig.digital_write(self.reset_pin, 1)
|
||||||
|
epdconfig.delay_ms(20)
|
||||||
|
epdconfig.digital_write(self.reset_pin, 0)
|
||||||
|
epdconfig.delay_ms(2)
|
||||||
|
epdconfig.digital_write(self.reset_pin, 1)
|
||||||
|
epdconfig.delay_ms(20)
|
||||||
|
|
||||||
|
'''
|
||||||
|
function :send command
|
||||||
|
parameter:
|
||||||
|
command : Command register
|
||||||
|
'''
|
||||||
|
|
||||||
|
def send_command(self, command):
|
||||||
|
epdconfig.digital_write(self.dc_pin, 0)
|
||||||
|
epdconfig.digital_write(self.cs_pin, 0)
|
||||||
|
epdconfig.spi_writebyte([command])
|
||||||
|
epdconfig.digital_write(self.cs_pin, 1)
|
||||||
|
|
||||||
|
'''
|
||||||
|
function :send data
|
||||||
|
parameter:
|
||||||
|
data : Write data
|
||||||
|
'''
|
||||||
|
|
||||||
|
def send_data(self, data):
|
||||||
|
epdconfig.digital_write(self.dc_pin, 1)
|
||||||
|
epdconfig.digital_write(self.cs_pin, 0)
|
||||||
|
epdconfig.spi_writebyte([data])
|
||||||
|
epdconfig.digital_write(self.cs_pin, 1)
|
||||||
|
|
||||||
|
# send a lot of data
|
||||||
|
def send_data2(self, data):
|
||||||
|
epdconfig.digital_write(self.dc_pin, 1)
|
||||||
|
epdconfig.digital_write(self.cs_pin, 0)
|
||||||
|
epdconfig.spi_writebyte2(data)
|
||||||
|
epdconfig.digital_write(self.cs_pin, 1)
|
||||||
|
|
||||||
|
'''
|
||||||
|
function :Wait until the busy_pin goes LOW
|
||||||
|
parameter:
|
||||||
|
'''
|
||||||
|
|
||||||
|
def ReadBusy(self):
|
||||||
|
logger.debug("e-Paper busy")
|
||||||
|
while (epdconfig.digital_read(self.busy_pin) == 1): # 0: idle, 1: busy
|
||||||
|
epdconfig.delay_ms(10)
|
||||||
|
logger.debug("e-Paper busy release")
|
||||||
|
|
||||||
|
'''
|
||||||
|
function : Turn On Display
|
||||||
|
parameter:
|
||||||
|
'''
|
||||||
|
|
||||||
|
def TurnOnDisplay(self):
|
||||||
|
self.send_command(0x22) # Display Update Control
|
||||||
|
self.send_data(0xC7)
|
||||||
|
self.send_command(0x20) # Activate Display Update Sequence
|
||||||
|
self.ReadBusy()
|
||||||
|
|
||||||
|
'''
|
||||||
|
function : Turn On Display Part
|
||||||
|
parameter:
|
||||||
|
'''
|
||||||
|
|
||||||
|
def TurnOnDisplayPart(self):
|
||||||
|
self.send_command(0x22) # Display Update Control
|
||||||
|
self.send_data(0x0f) # fast:0x0c, quality:0x0f, 0xcf
|
||||||
|
self.send_command(0x20) # Activate Display Update Sequence
|
||||||
|
self.ReadBusy()
|
||||||
|
|
||||||
|
'''
|
||||||
|
function : Set lut
|
||||||
|
parameter:
|
||||||
|
lut : lut data
|
||||||
|
'''
|
||||||
|
|
||||||
|
def Lut(self, lut):
|
||||||
|
self.send_command(0x32)
|
||||||
|
for i in range(0, 153):
|
||||||
|
self.send_data(lut[i])
|
||||||
|
self.ReadBusy()
|
||||||
|
|
||||||
|
'''
|
||||||
|
function : Send lut data and configuration
|
||||||
|
parameter:
|
||||||
|
lut : lut data
|
||||||
|
'''
|
||||||
|
|
||||||
|
def SetLut(self, lut):
|
||||||
|
self.Lut(lut)
|
||||||
|
self.send_command(0x3f)
|
||||||
|
self.send_data(lut[153])
|
||||||
|
self.send_command(0x03) # gate voltage
|
||||||
|
self.send_data(lut[154])
|
||||||
|
self.send_command(0x04) # source voltage
|
||||||
|
self.send_data(lut[155]) # VSH
|
||||||
|
self.send_data(lut[156]) # VSH2
|
||||||
|
self.send_data(lut[157]) # VSL
|
||||||
|
self.send_command(0x2c) # VCOM
|
||||||
|
self.send_data(lut[158])
|
||||||
|
|
||||||
|
'''
|
||||||
|
function : Setting the display window
|
||||||
|
parameter:
|
||||||
|
xstart : X-axis starting position
|
||||||
|
ystart : Y-axis starting position
|
||||||
|
xend : End position of X-axis
|
||||||
|
yend : End position of Y-axis
|
||||||
|
'''
|
||||||
|
|
||||||
|
def SetWindow(self, x_start, y_start, x_end, y_end):
|
||||||
|
self.send_command(0x44) # SET_RAM_X_ADDRESS_START_END_POSITION
|
||||||
|
# x point must be the multiple of 8 or the last 3 bits will be ignored
|
||||||
|
self.send_data((x_start >> 3) & 0xFF)
|
||||||
|
self.send_data((x_end >> 3) & 0xFF)
|
||||||
|
|
||||||
|
self.send_command(0x45) # SET_RAM_Y_ADDRESS_START_END_POSITION
|
||||||
|
self.send_data(y_start & 0xFF)
|
||||||
|
self.send_data((y_start >> 8) & 0xFF)
|
||||||
|
self.send_data(y_end & 0xFF)
|
||||||
|
self.send_data((y_end >> 8) & 0xFF)
|
||||||
|
|
||||||
|
'''
|
||||||
|
function : Set Cursor
|
||||||
|
parameter:
|
||||||
|
x : X-axis starting position
|
||||||
|
y : Y-axis starting position
|
||||||
|
'''
|
||||||
|
|
||||||
|
def SetCursor(self, x, y):
|
||||||
|
self.send_command(0x4E) # SET_RAM_X_ADDRESS_COUNTER
|
||||||
|
# x point must be the multiple of 8 or the last 3 bits will be ignored
|
||||||
|
self.send_data(x & 0xFF)
|
||||||
|
|
||||||
|
self.send_command(0x4F) # SET_RAM_Y_ADDRESS_COUNTER
|
||||||
|
self.send_data(y & 0xFF)
|
||||||
|
self.send_data((y >> 8) & 0xFF)
|
||||||
|
|
||||||
|
'''
|
||||||
|
function : Initialize the e-Paper register
|
||||||
|
parameter:
|
||||||
|
'''
|
||||||
|
|
||||||
|
def init(self):
|
||||||
|
if (epdconfig.module_init() != 0):
|
||||||
|
return -1
|
||||||
|
# EPD hardware init start
|
||||||
|
self.reset()
|
||||||
|
|
||||||
|
self.ReadBusy()
|
||||||
|
self.send_command(0x12) # SWRESET
|
||||||
|
self.ReadBusy()
|
||||||
|
|
||||||
|
self.send_command(0x01) # Driver output control
|
||||||
|
self.send_data(0xf9)
|
||||||
|
self.send_data(0x00)
|
||||||
|
self.send_data(0x00)
|
||||||
|
|
||||||
|
self.send_command(0x11) # data entry mode
|
||||||
|
self.send_data(0x03)
|
||||||
|
|
||||||
|
self.SetWindow(0, 0, self.width - 1, self.height - 1)
|
||||||
|
self.SetCursor(0, 0)
|
||||||
|
|
||||||
|
self.send_command(0x3c)
|
||||||
|
self.send_data(0x05)
|
||||||
|
|
||||||
|
self.send_command(0x21) # Display update control
|
||||||
|
self.send_data(0x00)
|
||||||
|
self.send_data(0x80)
|
||||||
|
|
||||||
|
self.send_command(0x18)
|
||||||
|
self.send_data(0x80)
|
||||||
|
|
||||||
|
self.ReadBusy()
|
||||||
|
|
||||||
|
self.SetLut(self.lut_full_update)
|
||||||
|
return 0
|
||||||
|
|
||||||
|
'''
|
||||||
|
function : Display images
|
||||||
|
parameter:
|
||||||
|
image : Image data
|
||||||
|
'''
|
||||||
|
|
||||||
|
def getbuffer(self, image):
|
||||||
|
img = image
|
||||||
|
imwidth, imheight = img.size
|
||||||
|
if (imwidth == self.width and imheight == self.height):
|
||||||
|
img = img.convert('1')
|
||||||
|
elif (imwidth == self.height and imheight == self.width):
|
||||||
|
# image has correct dimensions, but needs to be rotated
|
||||||
|
img = img.rotate(90, expand=True).convert('1')
|
||||||
|
else:
|
||||||
|
logger.warning("Wrong image dimensions: must be " + str(self.width) + "x" + str(self.height))
|
||||||
|
# return a blank buffer
|
||||||
|
return [0x00] * (int(self.width / 8) * self.height)
|
||||||
|
|
||||||
|
buf = bytearray(img.tobytes('raw'))
|
||||||
|
return buf
|
||||||
|
|
||||||
|
'''
|
||||||
|
function : Sends the image buffer in RAM to e-Paper and displays
|
||||||
|
parameter:
|
||||||
|
image : Image data
|
||||||
|
'''
|
||||||
|
|
||||||
|
def display(self, image):
|
||||||
|
if self.width % 8 == 0:
|
||||||
|
linewidth = int(self.width / 8)
|
||||||
|
else:
|
||||||
|
linewidth = int(self.width / 8) + 1
|
||||||
|
|
||||||
|
self.send_command(0x24)
|
||||||
|
for j in range(0, self.height):
|
||||||
|
for i in range(0, linewidth):
|
||||||
|
self.send_data(image[i + j * linewidth])
|
||||||
|
self.TurnOnDisplay()
|
||||||
|
|
||||||
|
'''
|
||||||
|
function : Sends the image buffer in RAM to e-Paper and partial refresh
|
||||||
|
parameter:
|
||||||
|
image : Image data
|
||||||
|
'''
|
||||||
|
|
||||||
|
def displayPartial(self, image):
|
||||||
|
epdconfig.digital_write(self.reset_pin, 0)
|
||||||
|
epdconfig.delay_ms(1)
|
||||||
|
epdconfig.digital_write(self.reset_pin, 1)
|
||||||
|
|
||||||
|
self.SetLut(self.lut_partial_update)
|
||||||
|
self.send_command(0x37)
|
||||||
|
self.send_data(0x00)
|
||||||
|
self.send_data(0x00)
|
||||||
|
self.send_data(0x00)
|
||||||
|
self.send_data(0x00)
|
||||||
|
self.send_data(0x00)
|
||||||
|
self.send_data(0x40)
|
||||||
|
self.send_data(0x00)
|
||||||
|
self.send_data(0x00)
|
||||||
|
self.send_data(0x00)
|
||||||
|
self.send_data(0x00)
|
||||||
|
|
||||||
|
self.send_command(0x3C) # BorderWavefrom
|
||||||
|
self.send_data(0x80)
|
||||||
|
|
||||||
|
self.send_command(0x22)
|
||||||
|
self.send_data(0xC0)
|
||||||
|
self.send_command(0x20)
|
||||||
|
self.ReadBusy()
|
||||||
|
|
||||||
|
self.SetWindow(0, 0, self.width - 1, self.height - 1)
|
||||||
|
self.SetCursor(0, 0)
|
||||||
|
|
||||||
|
self.send_command(0x24) # WRITE_RAM
|
||||||
|
# for j in range(0, self.height):
|
||||||
|
# for i in range(0, linewidth):
|
||||||
|
# self.send_data(image[i + j * linewidth])
|
||||||
|
self.send_data2(image)
|
||||||
|
self.TurnOnDisplayPart()
|
||||||
|
|
||||||
|
'''
|
||||||
|
function : Refresh a base image
|
||||||
|
parameter:
|
||||||
|
image : Image data
|
||||||
|
'''
|
||||||
|
|
||||||
|
def displayPartBaseImage(self, image):
|
||||||
|
self.send_command(0x24)
|
||||||
|
self.send_data2(image)
|
||||||
|
|
||||||
|
self.send_command(0x26)
|
||||||
|
self.send_data2(image)
|
||||||
|
self.TurnOnDisplay()
|
||||||
|
|
||||||
|
'''
|
||||||
|
function : Clear screen
|
||||||
|
parameter:
|
||||||
|
'''
|
||||||
|
|
||||||
|
def Clear(self, color=0xFF):
|
||||||
|
if self.width % 8 == 0:
|
||||||
|
linewidth = int(self.width / 8)
|
||||||
|
else:
|
||||||
|
linewidth = int(self.width / 8) + 1
|
||||||
|
# logger.debug(linewidth)
|
||||||
|
|
||||||
|
self.send_command(0x24)
|
||||||
|
self.send_data2([color] * int(self.height * linewidth))
|
||||||
|
self.TurnOnDisplay()
|
||||||
|
|
||||||
|
'''
|
||||||
|
function : Enter sleep mode
|
||||||
|
parameter:
|
||||||
|
'''
|
||||||
|
|
||||||
|
def sleep(self):
|
||||||
|
self.send_command(0x10) # enter deep sleep
|
||||||
|
self.send_data(0x01)
|
||||||
|
|
||||||
|
epdconfig.delay_ms(2000)
|
||||||
|
epdconfig.module_exit()
|
||||||
|
|
||||||
|
### END OF FILE ###
|
55
pwnagotchi/ui/hw/pirateaudio.py
Normal file
55
pwnagotchi/ui/hw/pirateaudio.py
Normal file
@ -0,0 +1,55 @@
|
|||||||
|
# board GPIO:
|
||||||
|
# A: GPIO5
|
||||||
|
# B: GPIO6
|
||||||
|
# X: GPIO16
|
||||||
|
# Y: GPIO20 / GPIO24 (refer to the pimoroni site or pinout.xyz)
|
||||||
|
# I2S pins for the DAC: GPIO 18 GPIO19 GPIO21
|
||||||
|
#
|
||||||
|
# HW datasheet: https://shop.pimoroni.com/products/pirate-audio-line-out?variant=31189750546515
|
||||||
|
# pinout xyz: https://pinout.xyz/pinout/pirate_audio_line_out
|
||||||
|
|
||||||
|
import logging
|
||||||
|
|
||||||
|
import pwnagotchi.ui.fonts as fonts
|
||||||
|
from pwnagotchi.ui.hw.base import DisplayImpl
|
||||||
|
|
||||||
|
|
||||||
|
class PirateAudio(DisplayImpl):
|
||||||
|
def __init__(self, config):
|
||||||
|
super(PirateAudio, self).__init__(config, 'pirateaudio')
|
||||||
|
self._display = None
|
||||||
|
|
||||||
|
def layout(self):
|
||||||
|
fonts.setup(10, 9, 10, 35, 25, 9)
|
||||||
|
self._layout['width'] = 240
|
||||||
|
self._layout['height'] = 240
|
||||||
|
self._layout['face'] = (0, 40)
|
||||||
|
self._layout['name'] = (5, 20)
|
||||||
|
self._layout['channel'] = (0, 0)
|
||||||
|
self._layout['aps'] = (28, 0)
|
||||||
|
self._layout['uptime'] = (175, 0)
|
||||||
|
self._layout['line1'] = [0, 14, 240, 14]
|
||||||
|
self._layout['line2'] = [0, 108, 240, 108]
|
||||||
|
self._layout['friend_face'] = (0, 92)
|
||||||
|
self._layout['friend_name'] = (40, 94)
|
||||||
|
self._layout['shakes'] = (0, 109)
|
||||||
|
self._layout['mode'] = (215, 109)
|
||||||
|
self._layout['status'] = {
|
||||||
|
'pos': (125, 20),
|
||||||
|
'font': fonts.status_font(fonts.Medium),
|
||||||
|
'max': 20
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
return self._layout
|
||||||
|
|
||||||
|
def initialize(self):
|
||||||
|
logging.info("Initializing PirateAudio - display only")
|
||||||
|
from pwnagotchi.ui.hw.libs.pimoroni.pirateaudio.ST7789 import ST7789
|
||||||
|
self._display = ST7789(0,1,9,13)
|
||||||
|
|
||||||
|
def render(self, canvas):
|
||||||
|
self._display.display(canvas.rotate(90))
|
||||||
|
|
||||||
|
def clear(self):
|
||||||
|
self._display.clear()
|
62
pwnagotchi/ui/hw/pitft.py
Normal file
62
pwnagotchi/ui/hw/pitft.py
Normal file
@ -0,0 +1,62 @@
|
|||||||
|
# board GPIO:
|
||||||
|
# Key1 (for2,8"): GPIO17
|
||||||
|
# Key2 (for2,8"): GPIO22
|
||||||
|
# Key3 (for2,8"): GPIO23
|
||||||
|
# Key4 (for2,8"): GPIO27
|
||||||
|
# Key5 (for2,4"): GPIO27
|
||||||
|
#
|
||||||
|
# Key1 (for2,4"): GPIO16
|
||||||
|
# Key2 (for2,4"): GPIO13
|
||||||
|
# Key3 (for2,4"): GPIO12
|
||||||
|
# Key4 (for2,4"): GPIO6
|
||||||
|
# Key5 (for2,4"): GPIO5
|
||||||
|
#
|
||||||
|
# Touch chipset: STMPE610
|
||||||
|
# HW datasheet 2,8": https://learn.adafruit.com/adafruit-pitft-28-inch-resistive-touchscreen-display-raspberry-pi/overview
|
||||||
|
# HW datasheet 2,4": https://learn.adafruit.com/adafruit-2-4-pitft-hat-with-resistive-touchscreen-mini-kit/overview
|
||||||
|
|
||||||
|
import logging
|
||||||
|
|
||||||
|
import pwnagotchi.ui.fonts as fonts
|
||||||
|
from pwnagotchi.ui.hw.base import DisplayImpl
|
||||||
|
|
||||||
|
import os,time
|
||||||
|
|
||||||
|
class Pitft(DisplayImpl):
|
||||||
|
def __init__(self, config):
|
||||||
|
super(Pitft, self).__init__(config, 'pitft')
|
||||||
|
self._display = None
|
||||||
|
|
||||||
|
def layout(self):
|
||||||
|
fonts.setup(12, 10, 12, 70, 25, 9)
|
||||||
|
self._layout['width'] = 320
|
||||||
|
self._layout['height'] = 240
|
||||||
|
self._layout['face'] = (0, 36)
|
||||||
|
self._layout['name'] = (150, 36)
|
||||||
|
self._layout['channel'] = (0, 0)
|
||||||
|
self._layout['aps'] = (40, 0)
|
||||||
|
self._layout['uptime'] = (240, 0)
|
||||||
|
self._layout['line1'] = [0, 14, 320, 14]
|
||||||
|
self._layout['line2'] = [0, 220, 320, 220]
|
||||||
|
self._layout['friend_face'] = (0, 130)
|
||||||
|
self._layout['friend_name'] = (40, 135)
|
||||||
|
self._layout['shakes'] = (0, 220)
|
||||||
|
self._layout['mode'] = (280, 220)
|
||||||
|
self._layout['status'] = {
|
||||||
|
'pos': (150, 48),
|
||||||
|
'font': fonts.status_font(fonts.Medium),
|
||||||
|
'max': 20
|
||||||
|
}
|
||||||
|
|
||||||
|
return self._layout
|
||||||
|
|
||||||
|
def initialize(self):
|
||||||
|
logging.info("Initializing adafruit pitft 320x240 screen")
|
||||||
|
from pwnagotchi.ui.hw.libs.adafruit.pitft.ILI9341 import ILI9341
|
||||||
|
self._display = ILI9341(0,0,25,18)
|
||||||
|
|
||||||
|
def render(self, canvas):
|
||||||
|
self._display.display(canvas)
|
||||||
|
|
||||||
|
def clear(self):
|
||||||
|
self._display.clear()
|
54
pwnagotchi/ui/hw/tftbonnet.py
Normal file
54
pwnagotchi/ui/hw/tftbonnet.py
Normal file
@ -0,0 +1,54 @@
|
|||||||
|
# board GPIO:
|
||||||
|
# UP: GPIO17
|
||||||
|
# DOWN: GPIO22
|
||||||
|
# LEFT: GPIO27
|
||||||
|
# RIGHT: GPIO23
|
||||||
|
# CENTER: GPIO4
|
||||||
|
# A: GPIO5
|
||||||
|
# B: GPIO6
|
||||||
|
#
|
||||||
|
# HW datasheet: https://learn.adafruit.com/adafruit-1-3-color-tft-bonnet-for-raspberry-pi/overview
|
||||||
|
|
||||||
|
import logging
|
||||||
|
|
||||||
|
import pwnagotchi.ui.fonts as fonts
|
||||||
|
from pwnagotchi.ui.hw.base import DisplayImpl
|
||||||
|
|
||||||
|
|
||||||
|
class TftBonnet(DisplayImpl):
|
||||||
|
def __init__(self, config):
|
||||||
|
super(TftBonnet, self).__init__(config, 'tftbonnet')
|
||||||
|
|
||||||
|
def layout(self):
|
||||||
|
fonts.setup(10, 9, 10, 35, 25, 9)
|
||||||
|
self._layout['width'] = 240
|
||||||
|
self._layout['height'] = 240
|
||||||
|
self._layout['face'] = (0, 40)
|
||||||
|
self._layout['name'] = (5, 20)
|
||||||
|
self._layout['channel'] = (0, 0)
|
||||||
|
self._layout['aps'] = (28, 0)
|
||||||
|
self._layout['uptime'] = (175, 0)
|
||||||
|
self._layout['line1'] = [0, 14, 240, 14]
|
||||||
|
self._layout['line2'] = [0, 108, 240, 108]
|
||||||
|
self._layout['friend_face'] = (0, 92)
|
||||||
|
self._layout['friend_name'] = (40, 94)
|
||||||
|
self._layout['shakes'] = (0, 109)
|
||||||
|
self._layout['mode'] = (215, 109)
|
||||||
|
self._layout['status'] = {
|
||||||
|
'pos': (125, 20),
|
||||||
|
'font': fonts.status_font(fonts.Medium),
|
||||||
|
'max': 20
|
||||||
|
}
|
||||||
|
|
||||||
|
return self._layout
|
||||||
|
|
||||||
|
def initialize(self):
|
||||||
|
logging.info("initializing Adafruit Tft Bonnet")
|
||||||
|
from pwnagotchi.ui.hw.libs.adafruit.tftbonnet.ST7789 import ST7789
|
||||||
|
self._display = ST7789(0,0,25,26)
|
||||||
|
|
||||||
|
def render(self, canvas):
|
||||||
|
self._display.display(canvas)
|
||||||
|
|
||||||
|
def clear(self):
|
||||||
|
self._display.clear()
|
@ -39,7 +39,7 @@ class Waveshare27inchV2(DisplayImpl):
|
|||||||
|
|
||||||
def render(self, canvas):
|
def render(self, canvas):
|
||||||
buf = self._display.getbuffer(canvas)
|
buf = self._display.getbuffer(canvas)
|
||||||
self._display.display_Fast(buf)
|
self._display.display_Partial(buf, 0, 0, 176, 264)
|
||||||
|
|
||||||
def clear(self):
|
def clear(self):
|
||||||
# This line also removes the 0xFF
|
# This line also removes the 0xFF
|
||||||
|
@ -9,22 +9,22 @@ class Waveshare3in52(DisplayImpl):
|
|||||||
super(Waveshare3in52, self).__init__(config, 'waveshare3in52')
|
super(Waveshare3in52, self).__init__(config, 'waveshare3in52')
|
||||||
|
|
||||||
def layout(self):
|
def layout(self):
|
||||||
fonts.setup(16, 14, 16, 100, 31, 15)
|
fonts.setup(16, 14, 16, 50, 31, 15)
|
||||||
self._layout['width'] = 360
|
self._layout['width'] = 360
|
||||||
self._layout['height'] = 240
|
self._layout['height'] = 240
|
||||||
self._layout['face'] = (0, 40)
|
self._layout['face'] = (0, 85)
|
||||||
self._layout['name'] = (0, 0)
|
self._layout['name'] = (10, 30)
|
||||||
self._layout['channel'] = (300, 0)
|
self._layout['channel'] = (1, 4)
|
||||||
self._layout['aps'] = (0, 220)
|
self._layout['aps'] = (45, 4)
|
||||||
self._layout['uptime'] = (120, 0)
|
self._layout['uptime'] = (250, 4)
|
||||||
self._layout['line1'] = [0, 24, 360, 24]
|
self._layout['line1'] = [0, 24, 360, 24]
|
||||||
self._layout['line2'] = [0, 220, 360, 220]
|
self._layout['line2'] = [0, 220, 360, 220]
|
||||||
self._layout['friend_face'] = (0, 195)
|
self._layout['friend_face'] = (0, 180)
|
||||||
self._layout['friend_name'] = (0, 185)
|
self._layout['friend_name'] = (0, 170)
|
||||||
self._layout['shakes'] = (100, 220)
|
self._layout['shakes'] = (1, 223)
|
||||||
self._layout['mode'] = (0,200)
|
self._layout['mode'] = (320, 222)
|
||||||
self._layout['status'] = {
|
self._layout['status'] = {
|
||||||
'pos': (3, 170),
|
'pos': (185, 50),
|
||||||
'font': fonts.status_font(fonts.Small),
|
'font': fonts.status_font(fonts.Small),
|
||||||
'max': 100
|
'max': 100
|
||||||
}
|
}
|
||||||
|
45
pwnagotchi/ui/hw/waveshare5in79.py
Normal file
45
pwnagotchi/ui/hw/waveshare5in79.py
Normal file
@ -0,0 +1,45 @@
|
|||||||
|
import logging
|
||||||
|
|
||||||
|
import pwnagotchi.ui.fonts as fonts
|
||||||
|
from pwnagotchi.ui.hw.base import DisplayImpl
|
||||||
|
|
||||||
|
|
||||||
|
class Waveshare5in79(DisplayImpl):
|
||||||
|
def __init__(self, config):
|
||||||
|
super(Waveshare5in79, self).__init__(config, 'waveshare5in79')
|
||||||
|
|
||||||
|
def layout(self):
|
||||||
|
fonts.setup(10, 8, 10, 18, 25, 9)
|
||||||
|
self._layout['width'] = 792
|
||||||
|
self._layout['height'] = 272
|
||||||
|
self._layout['face'] = (0, 43)
|
||||||
|
self._layout['name'] = (0, 14)
|
||||||
|
self._layout['channel'] = (0, 0)
|
||||||
|
self._layout['aps'] = (0, 71)
|
||||||
|
self._layout['uptime'] = (0, 25)
|
||||||
|
self._layout['line1'] = [0, 12, 600, 12]
|
||||||
|
self._layout['line2'] = [0, 116, 600, 116]
|
||||||
|
self._layout['friend_face'] = (12, 88)
|
||||||
|
self._layout['friend_name'] = (1, 103)
|
||||||
|
self._layout['shakes'] = (26, 117)
|
||||||
|
self._layout['mode'] = (0, 117)
|
||||||
|
self._layout['status'] = {
|
||||||
|
'pos': (65, 26),
|
||||||
|
'font': fonts.status_font(fonts.Small),
|
||||||
|
'max': 12
|
||||||
|
}
|
||||||
|
return self._layout
|
||||||
|
|
||||||
|
def initialize(self):
|
||||||
|
logging.info("initializing waveshare 5.79 inch lcd display")
|
||||||
|
from pwnagotchi.ui.hw.libs.waveshare.v5in79.epd5in79 import EPD
|
||||||
|
self._display = EPD()
|
||||||
|
self._display.init()
|
||||||
|
self._display.Clear()
|
||||||
|
|
||||||
|
def render(self, canvas):
|
||||||
|
buf = self._display.getbuffer(canvas)
|
||||||
|
self._display.display_Partial(buf)
|
||||||
|
|
||||||
|
def clear(self):
|
||||||
|
self._display.Clear()
|
45
pwnagotchi/ui/hw/waveshare5in79b.py
Normal file
45
pwnagotchi/ui/hw/waveshare5in79b.py
Normal file
@ -0,0 +1,45 @@
|
|||||||
|
import logging
|
||||||
|
|
||||||
|
import pwnagotchi.ui.fonts as fonts
|
||||||
|
from pwnagotchi.ui.hw.base import DisplayImpl
|
||||||
|
|
||||||
|
|
||||||
|
class Waveshare5in79b(DisplayImpl):
|
||||||
|
def __init__(self, config):
|
||||||
|
super(Waveshare5in79b, self).__init__(config, 'waveshare5in79b')
|
||||||
|
|
||||||
|
def layout(self):
|
||||||
|
fonts.setup(10, 8, 10, 18, 25, 9)
|
||||||
|
self._layout['width'] = 792
|
||||||
|
self._layout['height'] = 272
|
||||||
|
self._layout['face'] = (0, 43)
|
||||||
|
self._layout['name'] = (0, 14)
|
||||||
|
self._layout['channel'] = (0, 0)
|
||||||
|
self._layout['aps'] = (0, 71)
|
||||||
|
self._layout['uptime'] = (0, 25)
|
||||||
|
self._layout['line1'] = [0, 12, 600, 12]
|
||||||
|
self._layout['line2'] = [0, 116, 600, 116]
|
||||||
|
self._layout['friend_face'] = (12, 88)
|
||||||
|
self._layout['friend_name'] = (1, 103)
|
||||||
|
self._layout['shakes'] = (26, 117)
|
||||||
|
self._layout['mode'] = (0, 117)
|
||||||
|
self._layout['status'] = {
|
||||||
|
'pos': (65, 26),
|
||||||
|
'font': fonts.status_font(fonts.Small),
|
||||||
|
'max': 12
|
||||||
|
}
|
||||||
|
return self._layout
|
||||||
|
|
||||||
|
def initialize(self):
|
||||||
|
logging.info("initializing waveshare 5.79b inch lcd display")
|
||||||
|
from pwnagotchi.ui.hw.libs.waveshare.v5in79b.epd5in79b import EPD
|
||||||
|
self._display = EPD()
|
||||||
|
self._display.init()
|
||||||
|
self._display.Clear()
|
||||||
|
|
||||||
|
def render(self, canvas):
|
||||||
|
buf = self._display.getbuffer(canvas)
|
||||||
|
self._display.display(buf, None)
|
||||||
|
|
||||||
|
def clear(self):
|
||||||
|
self._display.Clear()
|
59
pwnagotchi/ui/hw/waveshareoledlcd.py
Normal file
59
pwnagotchi/ui/hw/waveshareoledlcd.py
Normal file
@ -0,0 +1,59 @@
|
|||||||
|
# workinprogress based on the displayhatmini driver
|
||||||
|
# LCD support OK
|
||||||
|
# OLED support ongoing
|
||||||
|
# board GPIO:
|
||||||
|
# Key1: GPIO4 / pin7
|
||||||
|
# Key2: GPIO17 / pin11
|
||||||
|
# Key3: GPIO23 / pin16
|
||||||
|
# Key4: GPIO24 / pin18
|
||||||
|
# OLED SDA: GPIO2 / pin3
|
||||||
|
# OLED SCL: GPIO3 / pin5
|
||||||
|
# OLED info:
|
||||||
|
# driver: SSD1315 (I2C)
|
||||||
|
# resolution: 128x64
|
||||||
|
# I2C address: 0x3C 0x3D
|
||||||
|
# HW datasheet: https://www.waveshare.com/wiki/OLED/LCD_HAT_(A)
|
||||||
|
|
||||||
|
import logging
|
||||||
|
|
||||||
|
import pwnagotchi.ui.fonts as fonts
|
||||||
|
from pwnagotchi.ui.hw.base import DisplayImpl
|
||||||
|
|
||||||
|
|
||||||
|
class Waveshareoledlcd(DisplayImpl):
|
||||||
|
def __init__(self, config):
|
||||||
|
super(Waveshareoledlcd, self).__init__(config, 'waveshareoledlcd')
|
||||||
|
|
||||||
|
def layout(self):
|
||||||
|
fonts.setup(12, 10, 12, 70, 25, 9)
|
||||||
|
self._layout['width'] = 320
|
||||||
|
self._layout['height'] = 240
|
||||||
|
self._layout['face'] = (0, 36)
|
||||||
|
self._layout['name'] = (150, 36)
|
||||||
|
self._layout['channel'] = (0, 0)
|
||||||
|
self._layout['aps'] = (40, 0)
|
||||||
|
self._layout['uptime'] = (240, 0)
|
||||||
|
self._layout['line1'] = [0, 14, 320, 14]
|
||||||
|
self._layout['line2'] = [0, 220, 320, 220]
|
||||||
|
self._layout['friend_face'] = (0, 130)
|
||||||
|
self._layout['friend_name'] = (40, 135)
|
||||||
|
self._layout['shakes'] = (0, 220)
|
||||||
|
self._layout['mode'] = (280, 220)
|
||||||
|
self._layout['status'] = {
|
||||||
|
'pos': (150, 48),
|
||||||
|
'font': fonts.status_font(fonts.Medium),
|
||||||
|
'max': 20
|
||||||
|
}
|
||||||
|
|
||||||
|
return self._layout
|
||||||
|
|
||||||
|
def initialize(self):
|
||||||
|
logging.info("initializing Waveshare OLED/LCD hat")
|
||||||
|
from pwnagotchi.ui.hw.libs.waveshare.oledlcd.ST7789 import ST7789
|
||||||
|
self._display = ST7789(0,0,22,18)
|
||||||
|
|
||||||
|
def render(self, canvas):
|
||||||
|
self._display.display(canvas)
|
||||||
|
|
||||||
|
def clear(self):
|
||||||
|
self._display.clear()
|
47
pwnagotchi/ui/hw/weact_2in9.py
Normal file
47
pwnagotchi/ui/hw/weact_2in9.py
Normal file
@ -0,0 +1,47 @@
|
|||||||
|
import logging
|
||||||
|
|
||||||
|
import pwnagotchi.ui.fonts as fonts
|
||||||
|
from pwnagotchi.ui.hw.base import DisplayImpl
|
||||||
|
|
||||||
|
|
||||||
|
class WeAct2in9(DisplayImpl):
|
||||||
|
def __init__(self, config):
|
||||||
|
super(WeAct2in9, self).__init__(config, 'weact2in9')
|
||||||
|
|
||||||
|
def layout(self):
|
||||||
|
fonts.setup(10, 8, 10, 35, 25, 9)
|
||||||
|
self._layout['width'] = 296
|
||||||
|
self._layout['height'] = 128
|
||||||
|
self._layout['face'] = (0, 40)
|
||||||
|
self._layout['name'] = (5, 20)
|
||||||
|
self._layout['channel'] = (0, 0)
|
||||||
|
self._layout['aps'] = (28, 0)
|
||||||
|
self._layout['uptime'] = (185, 0)
|
||||||
|
self._layout['line1'] = [0, 14, 250, 14]
|
||||||
|
self._layout['line2'] = [0, 108, 250, 108]
|
||||||
|
self._layout['friend_face'] = (0, 92)
|
||||||
|
self._layout['friend_name'] = (40, 94)
|
||||||
|
self._layout['shakes'] = (0, 109)
|
||||||
|
self._layout['mode'] = (225, 109)
|
||||||
|
self._layout['status'] = {
|
||||||
|
'pos': (125, 20),
|
||||||
|
'font': fonts.status_font(fonts.Medium),
|
||||||
|
'max': 20
|
||||||
|
}
|
||||||
|
|
||||||
|
return self._layout
|
||||||
|
|
||||||
|
def initialize(self):
|
||||||
|
logging.info("initializing WeAct 2.9 inch display")
|
||||||
|
from pwnagotchi.ui.hw.libs.weact.v2in9.epd2in9 import EPD
|
||||||
|
self._display = EPD()
|
||||||
|
self._display.init()
|
||||||
|
self._display.Clear(0xFF)
|
||||||
|
|
||||||
|
def render(self, canvas):
|
||||||
|
buf = self._display.getbuffer(canvas)
|
||||||
|
self._display.displayPartial(buf)
|
||||||
|
|
||||||
|
def clear(self):
|
||||||
|
#pass
|
||||||
|
self._display.Clear(0xFF)
|
@ -71,7 +71,7 @@ class View(object):
|
|||||||
'face': Text(value=faces.SLEEP, position=(config['ui']['faces']['position_x'], config['ui']['faces']['position_y']), color=BLACK, font=fonts.Huge, png=config['ui']['faces']['png']),
|
'face': Text(value=faces.SLEEP, position=(config['ui']['faces']['position_x'], config['ui']['faces']['position_y']), color=BLACK, font=fonts.Huge, png=config['ui']['faces']['png']),
|
||||||
|
|
||||||
# 'friend_face': Text(value=None, position=self._layout['friend_face'], font=fonts.Bold, color=BLACK),
|
# 'friend_face': Text(value=None, position=self._layout['friend_face'], font=fonts.Bold, color=BLACK),
|
||||||
'friend_name': Text(value=None, position=self._layout['friend_name'], font=fonts.BoldSmall, color=BLACK),
|
'friend_name': Text(value=None, position=self._layout['friend_face'], font=fonts.BoldSmall, color=BLACK),
|
||||||
|
|
||||||
'name': Text(value='%s>' % 'pwnagotchi', position=self._layout['name'], color=BLACK, font=fonts.Bold),
|
'name': Text(value='%s>' % 'pwnagotchi', position=self._layout['name'], color=BLACK, font=fonts.Bold),
|
||||||
|
|
||||||
|
@ -266,6 +266,18 @@ def load_config(args):
|
|||||||
elif config['ui']['display']['type'] in ('displayhatmini'):
|
elif config['ui']['display']['type'] in ('displayhatmini'):
|
||||||
config['ui']['display']['type'] = 'displayhatmini'
|
config['ui']['display']['type'] = 'displayhatmini'
|
||||||
|
|
||||||
|
elif config['ui']['display']['type'] in ('pirateaudio'):
|
||||||
|
config['ui']['display']['type'] = 'pirateaudio'
|
||||||
|
|
||||||
|
elif config['ui']['display']['type'] in ('pitft'):
|
||||||
|
config['ui']['display']['type'] = 'pitft'
|
||||||
|
|
||||||
|
elif config['ui']['display']['type'] in ('tftbonnet'):
|
||||||
|
config['ui']['display']['type'] = 'tftbonnet'
|
||||||
|
|
||||||
|
elif config['ui']['display']['type'] in ('waveshareoledlcd'):
|
||||||
|
config['ui']['display']['type'] = 'waveshareoledlcd'
|
||||||
|
|
||||||
elif config['ui']['display']['type'] in ('waveshare35lcd'):
|
elif config['ui']['display']['type'] in ('waveshare35lcd'):
|
||||||
config['ui']['display']['type'] = 'waveshare35lcd'
|
config['ui']['display']['type'] = 'waveshare35lcd'
|
||||||
|
|
||||||
@ -391,6 +403,12 @@ def load_config(args):
|
|||||||
elif config['ui']['display']['type'] in ('ws_5in65f', 'waveshare5in65f', 'ws5in65f', 'waveshare_565f', 'waveshare565f'):
|
elif config['ui']['display']['type'] in ('ws_5in65f', 'waveshare5in65f', 'ws5in65f', 'waveshare_565f', 'waveshare565f'):
|
||||||
config['ui']['display']['type'] = 'waveshare5in65f'
|
config['ui']['display']['type'] = 'waveshare5in65f'
|
||||||
|
|
||||||
|
elif config['ui']['display']['type'] in ('ws_5in79', 'waveshare5in79', 'ws5in79', 'waveshare_579', 'waveshare579'):
|
||||||
|
config['ui']['display']['type'] = 'waveshare5in79'
|
||||||
|
|
||||||
|
elif config['ui']['display']['type'] in ('ws_5in79b', 'waveshare5in79b', 'ws5in79b', 'waveshare_579b', 'waveshare579b'):
|
||||||
|
config['ui']['display']['type'] = 'waveshare5in79b'
|
||||||
|
|
||||||
elif config['ui']['display']['type'] in ('ws_5in83', 'waveshare5in83', 'ws5in83', 'waveshare_583', 'waveshare583'):
|
elif config['ui']['display']['type'] in ('ws_5in83', 'waveshare5in83', 'ws5in83', 'waveshare_583', 'waveshare583'):
|
||||||
config['ui']['display']['type'] = 'waveshare5in83'
|
config['ui']['display']['type'] = 'waveshare5in83'
|
||||||
|
|
||||||
@ -430,6 +448,10 @@ def load_config(args):
|
|||||||
elif config['ui']['display']['type'] in ('ws_13in3k', 'waveshare13in3k', 'ws13in3k', 'waveshare_133k', 'waveshare133k'):
|
elif config['ui']['display']['type'] in ('ws_13in3k', 'waveshare13in3k', 'ws13in3k', 'waveshare_133k', 'waveshare133k'):
|
||||||
config['ui']['display']['type'] = 'waveshare13in3k'
|
config['ui']['display']['type'] = 'waveshare13in3k'
|
||||||
|
|
||||||
|
# WeAct e-ink
|
||||||
|
elif config['ui']['display']['type'] in ('weact2in9', 'weact29in'):
|
||||||
|
config['ui']['display']['type'] = 'weact2in9'
|
||||||
|
|
||||||
else:
|
else:
|
||||||
print("unsupported display type %s" % config['ui']['display']['type'])
|
print("unsupported display type %s" % config['ui']['display']['type'])
|
||||||
sys.exit(1)
|
sys.exit(1)
|
||||||
|
60
pyproject.toml
Normal file
60
pyproject.toml
Normal file
@ -0,0 +1,60 @@
|
|||||||
|
[build-system]
|
||||||
|
requires = ["setuptools"]
|
||||||
|
build-backend = "setuptools.build_meta"
|
||||||
|
|
||||||
|
[project]
|
||||||
|
name = "pwnagotchi"
|
||||||
|
dynamic = ["version"]
|
||||||
|
dependencies = [
|
||||||
|
"Pillow",
|
||||||
|
"PyYAML",
|
||||||
|
"RPi.GPIO",
|
||||||
|
"file-read-backwards",
|
||||||
|
"flask",
|
||||||
|
"flask-cors",
|
||||||
|
"flask-wtf",
|
||||||
|
"gast",
|
||||||
|
"gym",
|
||||||
|
"inky",
|
||||||
|
"pycryptodome",
|
||||||
|
"pydrive2",
|
||||||
|
"python-dateutil",
|
||||||
|
"requests",
|
||||||
|
"rpi_hardware_pwm",
|
||||||
|
"scapy",
|
||||||
|
"shimmy",
|
||||||
|
"smbus2",
|
||||||
|
"spidev",
|
||||||
|
"stable_baselines3",
|
||||||
|
"toml",
|
||||||
|
"torch",
|
||||||
|
"torchvision",
|
||||||
|
"tweepy",
|
||||||
|
"websockets"
|
||||||
|
]
|
||||||
|
requires-python = ">=3.9"
|
||||||
|
authors = [
|
||||||
|
{name = "Evilsocket", email = "evilsocket@gmail.com"},
|
||||||
|
{name = "Jayofelony", email = "oudshoorn.jeroen@gmail.com"}
|
||||||
|
]
|
||||||
|
maintainers = [
|
||||||
|
{name = "Jayofelony", email = "oudshoorn.jeroen@gmail.com"}
|
||||||
|
]
|
||||||
|
description = "(⌐■_■) - Deep Reinforcement Learning instrumenting bettercap for WiFI pwning."
|
||||||
|
readme = "README.md"
|
||||||
|
license = {file = "LICENSE.md"}
|
||||||
|
classifiers = [
|
||||||
|
'Programming Language :: Python :: 3',
|
||||||
|
'Development Status :: 5 - Production/Stable',
|
||||||
|
'License :: OSI Approved :: GNU General Public License (GPL)',
|
||||||
|
'Environment :: Console',
|
||||||
|
]
|
||||||
|
|
||||||
|
[project.urls]
|
||||||
|
Homepage = "https://pwnagotchi.org/"
|
||||||
|
Documentation = "https://pwnagotchi.org/"
|
||||||
|
Repository = "https://github.com/jayofelony/pwnagotchi.git"
|
||||||
|
Issues = "https://github.com/jayofelony/pwnagotchi/issues"
|
||||||
|
|
||||||
|
[project.scripts]
|
||||||
|
pwnagotchi_cli = "bin.pwnagotchi:pwnagotchi_cli"
|
@ -1,5 +1,5 @@
|
|||||||
gym
|
gym
|
||||||
shimmy; platform_machine!="armv6l"
|
shimmy
|
||||||
pycryptodome
|
pycryptodome
|
||||||
requests
|
requests
|
||||||
PyYAML
|
PyYAML
|
||||||
@ -18,17 +18,9 @@ dbus-python
|
|||||||
toml
|
toml
|
||||||
python-dateutil
|
python-dateutil
|
||||||
websockets
|
websockets
|
||||||
torch; platform_machine=="aarch64"
|
torch
|
||||||
torch>=2.0.1; platform_machine!="aarch64"
|
torchvision
|
||||||
|
stable_baselines3
|
||||||
torchvision; platform_machine=="aarch64"
|
RPi.GPIO
|
||||||
torchvision>=0.15.2; platform_machine!="aarch64"
|
rpi_hardware_pwm
|
||||||
|
|
||||||
stable_baselines3==1.8.0; platform_machine=="armv6l"
|
|
||||||
stable_baselines3; platform_machine!="armv6l"
|
|
||||||
|
|
||||||
RPi.GPIO; platform_release!="6.1.31-sun50iw9"
|
|
||||||
OPi.GPIO; platform_release=="6.1.31-sun50iw9"
|
|
||||||
|
|
||||||
rpi_hardware_pwm; platform_release!="6.1.31-sun50iw9"
|
|
||||||
pydrive2
|
pydrive2
|
Reference in New Issue
Block a user