Compare commits
90 Commits
Author | SHA1 | Date | |
---|---|---|---|
9fc737a590 | |||
be8809e62b | |||
3155a36fd9 | |||
eb77f29135 | |||
|
c34aed94c0 | ||
eb5e2f77c8 | |||
d2b22b2ff3 | |||
40cdfa3fdd | |||
29aa46a468 | |||
fe0b23625b | |||
e73fe60023 | |||
c35a707201 | |||
f82ac001b0 | |||
1097238319 | |||
ef717734af | |||
1976c8a850 | |||
5147d0f351 | |||
|
a6edbedbc8 | ||
|
4ad4796de7 | ||
|
584d8d30e0 | ||
|
9c6834f216 | ||
|
d5501d89ca | ||
|
47aee29806 | ||
|
9b95af1e52 | ||
|
c26f92d9dc | ||
|
fdef3bae97 | ||
|
bcb01ce046 | ||
|
8c5e84e1c7 | ||
|
7db5c10cd0 | ||
|
f0c2a39e2d | ||
|
6fb42fc8e8 | ||
|
66c4aea7ca | ||
|
ad1cac4755 | ||
|
77362bc530 | ||
|
3e0f3dcb8a | ||
|
71e248a8af | ||
|
366db3e095 | ||
|
001c1942c4 | ||
|
d8a4d930ea | ||
|
81699d3e35 | ||
|
578b15647f | ||
|
4350fa1204 | ||
|
c3093bc9aa | ||
|
3026ec9aa3 | ||
|
188cb6c62b | ||
|
21f4e35f73 | ||
|
7142e2bfd5 | ||
|
38e7eaae73 | ||
|
2ff553cbc6 | ||
|
3cdcd41fa2 | ||
|
88e8efa4df | ||
|
50a88efe15 | ||
|
b09eab24d8 | ||
|
ed6d59ab26 | ||
|
b04fba4bd2 | ||
|
95e96cc42b | ||
|
bb31085a73 | ||
|
be6c55a8cf | ||
|
5c5562f98f | ||
|
1842f88239 | ||
|
ba8f3bc4be | ||
|
7769f249f9 | ||
|
759c5832b4 | ||
|
3cf574bfc7 | ||
|
42e4a776ae | ||
|
bbbe086007 | ||
|
c9fe15005d | ||
|
334f2a4a5c | ||
|
c14019535f | ||
|
cd668f4dd4 | ||
|
cd50cf7418 | ||
|
bddb630b90 | ||
|
e862b24382 | ||
|
4c2d2196c4 | ||
|
b731f5808b | ||
|
4499e419f1 | ||
|
c3b0c9a032 | ||
|
5d93ad18c4 | ||
|
5738a532ec | ||
|
7998a84ea7 | ||
|
cde5396e85 | ||
|
a5d5533acf | ||
|
3c678104ef | ||
|
5ec621c5d7 | ||
|
9ec2646347 | ||
|
713fe6878b | ||
|
20d80bfb35 | ||
|
b2ecebc24a | ||
|
298ed24008 | ||
|
a9f07e9f8d |
@ -1,7 +1,6 @@
|
||||
maintainers:
|
||||
- evilsocket
|
||||
- caquino
|
||||
- dadav
|
||||
- justin-p
|
||||
|
||||
features:
|
||||
|
96
.github/workflows/CreateRelease.yml
vendored
Normal file
96
.github/workflows/CreateRelease.yml
vendored
Normal file
@ -0,0 +1,96 @@
|
||||
name: Release
|
||||
|
||||
on:
|
||||
workflow_dispatch:
|
||||
inputs:
|
||||
release_version:
|
||||
description: 'Release version'
|
||||
required: true
|
||||
type: string
|
||||
pishrink:
|
||||
description: 'pishrink Script'
|
||||
default: 'https://raw.githubusercontent.com/Drewsif/PiShrink/master/pishrink.sh'
|
||||
type: string
|
||||
z_compress_args:
|
||||
description: '7z compress args'
|
||||
default: '7z a -t7z -mx=9'
|
||||
type: string
|
||||
|
||||
jobs:
|
||||
build:
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@v3
|
||||
|
||||
# - name: Set VERSION variable
|
||||
# run: |
|
||||
# VERSION=$(awk '/__version__ /{print $NF}' ./pwnagotchi/_version.py | tr -d "'")
|
||||
# env:
|
||||
# VERSION: ${{ steps.set-version.outputs.version }} # Use the extracted version
|
||||
|
||||
- name: Show Version
|
||||
run: |
|
||||
# Use the $VERSION variable in your build or deployment steps
|
||||
echo "Using VERSION: ${{ inputs.release_version }}"
|
||||
|
||||
- name: Set _version.py correctly using the env variable
|
||||
run: |
|
||||
sed -i "s#.*__version__.*#__version__='$PWN_VERSION'#" pwnagotchi/_version.py
|
||||
env:
|
||||
PWN_VERSION: ${{ inputs.release_version }}
|
||||
|
||||
- name: Install language dependencies
|
||||
run: sudo apt-get install -y gettext
|
||||
|
||||
- name: Languages
|
||||
run: make langs
|
||||
|
||||
- name: Image
|
||||
run: make image
|
||||
env:
|
||||
PWN_VERSION: ${{ inputs.release_version }}
|
||||
|
||||
- name: Shrink Image
|
||||
run: |
|
||||
ls -a -s -h
|
||||
wget ${{ inputs.pishrink }}
|
||||
chmod +x pishrink.sh
|
||||
sudo mv pishrink.sh /usr/local/bin
|
||||
sudo pishrink.sh ./pwnagotchi-${{ inputs.release_version }}.img
|
||||
|
||||
- uses: edgarrc/action-7z@v1
|
||||
with:
|
||||
args: ${{ inputs.z_compress_args }} pwnagotchi-${{ inputs.release_version }}.7z ./pwnagotchi-${{ inputs.release_version }}.img
|
||||
|
||||
- name: sha256sum 7z
|
||||
run: |
|
||||
sudo sha256sum ./pwnagotchi-${{ inputs.release_version }}.7z > ./pwnagotchi-${{ inputs.release_version }}.sha256
|
||||
|
||||
- name: Create GitHub Release
|
||||
id: create_new_release
|
||||
uses: actions/create-release@v1
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
with:
|
||||
tag_name: ${{ inputs.release_version }}
|
||||
release_name: Release ${{ inputs.release_version }}
|
||||
- name: Upload GitHub Release sha
|
||||
uses: actions/upload-release-asset@v1
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
with:
|
||||
upload_url: ${{ steps.create_new_release.outputs.upload_url }}
|
||||
asset_path: ./pwnagotchi-${{ inputs.release_version }}.sha256
|
||||
asset_name: pwnagotchi-v${{ inputs.release_version }}.sha256
|
||||
asset_content_type: appliction/text
|
||||
- name: Upload GitHub Release Zip
|
||||
uses: actions/upload-release-asset@v1
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
with:
|
||||
upload_url: ${{ steps.create_new_release.outputs.upload_url }}
|
||||
asset_path: ./pwnagotchi-${{ inputs.release_version }}.7z
|
||||
asset_name: pwnagotchi-v${{ inputs.release_version }}.7z
|
||||
asset_content_type: appliction/zip
|
1
.gitignore
vendored
1
.gitignore
vendored
@ -2,6 +2,7 @@
|
||||
*.img.bmap
|
||||
*.pcap
|
||||
*.po~
|
||||
*.sha256
|
||||
preview.png
|
||||
__pycache__
|
||||
_backups
|
||||
|
@ -1,9 +1,10 @@
|
||||
exclude *.pyc .DS_Store .gitignore MANIFEST.in
|
||||
include requirements.txt
|
||||
include setup.py
|
||||
include distribute_setup.py
|
||||
include README.md
|
||||
include LICENSE
|
||||
recursive-include bin *
|
||||
recursive-include builder/data *
|
||||
recursive-include pwnagotchi *.py
|
||||
recursive-include pwnagotchi *.yml
|
||||
recursive-include pwnagotchi *.*
|
||||
|
85
Makefile
85
Makefile
@ -1,6 +1,31 @@
|
||||
PACKER_VERSION=1.7.2
|
||||
PWN_HOSTNAME=pwnagotchi
|
||||
PWN_VERSION=master
|
||||
PACKER_VERSION := 1.8.5
|
||||
PWN_HOSTNAME := pwnagotchi
|
||||
# PWN_VERSION := $(shell cut -d"'" -f2 < pwnagotchi/_version.py)
|
||||
PWN_VERSION := $(or ${PWN_VERSION},$(shell cut -d"'" -f2 < pwnagotchi/_version.py))
|
||||
PWN_RELEASE := pwnagotchi-$(PWN_VERSION)
|
||||
|
||||
MACHINE_TYPE := $(shell uname -m)
|
||||
ifneq (,$(filter x86_64,$(MACHINE_TYPE)))
|
||||
GOARCH := amd64
|
||||
else ifneq (,$(filter i686,$(MACHINE_TYPE)))
|
||||
GOARCH := 386
|
||||
else ifneq (,$(filter arm64% aarch64%,$(MACHINE_TYPE)))
|
||||
GOARCH := arm64
|
||||
else ifneq (,$(filter arm%,$(MACHINE_TYPE)))
|
||||
GOARCH := arm
|
||||
else
|
||||
GOARCH := amd64
|
||||
$(warning Unable to detect CPU arch from machine type $(MACHINE_TYPE), assuming $(GOARCH))
|
||||
endif
|
||||
|
||||
# The Ansible part of the build can inadvertently change the active hostname of
|
||||
# the build machine while updating the permanent hostname of the build image.
|
||||
# If the unshare command is available, use it to create a separate namespace
|
||||
# so hostname changes won't affect the build machine.
|
||||
UNSHARE := $(shell command -v unshare)
|
||||
ifneq (,$(UNSHARE))
|
||||
UNSHARE := $(UNSHARE) --uts
|
||||
endif
|
||||
|
||||
all: clean install image
|
||||
|
||||
@ -8,23 +33,47 @@ langs:
|
||||
@for lang in pwnagotchi/locale/*/; do\
|
||||
echo "compiling language: $$lang ..."; \
|
||||
./scripts/language.sh compile $$(basename $$lang); \
|
||||
done
|
||||
done
|
||||
|
||||
install:
|
||||
curl https://releases.hashicorp.com/packer/$(PACKER_VERSION)/packer_$(PACKER_VERSION)_linux_amd64.zip -o /tmp/packer.zip
|
||||
unzip /tmp/packer.zip -d /tmp
|
||||
sudo mv /tmp/packer /usr/bin/packer
|
||||
git clone https://github.com/solo-io/packer-builder-arm-image /tmp/packer-builder-arm-image
|
||||
cd /tmp/packer-builder-arm-image && go get -d ./... && go build
|
||||
sudo cp /tmp/packer-builder-arm-image/packer-builder-arm-image /usr/bin
|
||||
PACKER := /tmp/pwnagotchi/packer
|
||||
PACKER_URL := https://releases.hashicorp.com/packer/$(PACKER_VERSION)/packer_$(PACKER_VERSION)_linux_$(GOARCH).zip
|
||||
$(PACKER):
|
||||
mkdir -p $(@D)
|
||||
curl -L "$(PACKER_URL)" -o $(PACKER).zip
|
||||
unzip $(PACKER).zip -d $(@D)
|
||||
rm $(PACKER).zip
|
||||
chmod +x $@
|
||||
|
||||
image:
|
||||
cd builder && sudo /usr/bin/packer build -var "pwn_hostname=$(PWN_HOSTNAME)" -var "pwn_version=$(PWN_VERSION)" pwnagotchi.json
|
||||
sudo mv builder/output-pwnagotchi/image pwnagotchi-raspbian-lite-$(PWN_VERSION).img
|
||||
sudo sha256sum pwnagotchi-raspbian-lite-$(PWN_VERSION).img > pwnagotchi-raspbian-lite-$(PWN_VERSION).sha256
|
||||
sudo zip pwnagotchi-raspbian-lite-$(PWN_VERSION).zip pwnagotchi-raspbian-lite-$(PWN_VERSION).sha256 pwnagotchi-raspbian-lite-$(PWN_VERSION).img
|
||||
SDIST := dist/pwnagotchi-$(PWN_VERSION).tar.gz
|
||||
$(SDIST): setup.py pwnagotchi
|
||||
python3 setup.py sdist
|
||||
|
||||
# Building the image requires packer, but don't rebuild the image just because packer updated.
|
||||
$(PWN_RELEASE).img: | $(PACKER)
|
||||
|
||||
# If the packer or ansible files are updated, rebuild the image.
|
||||
$(PWN_RELEASE).img: $(SDIST) builder/pwnagotchi.json builder/pwnagotchi.yml $(shell find builder/data -type f)
|
||||
sudo $(PACKER) plugins install github.com/solo-io/arm-image
|
||||
cd builder && sudo $(UNSHARE) $(PACKER) build -var "pwn_hostname=$(PWN_HOSTNAME)" -var "pwn_version=$(PWN_VERSION)" pwnagotchi.json
|
||||
sudo chown -R $$USER:$$USER builder/output-pwnagotchi
|
||||
mv builder/output-pwnagotchi/image $@
|
||||
|
||||
# If any of these files are updated, rebuild the checksums.
|
||||
$(PWN_RELEASE).sha256: $(PWN_RELEASE).img
|
||||
sha256sum $^ > $@
|
||||
|
||||
# If any of the input files are updated, rebuild the archive.
|
||||
$(PWN_RELEASE).zip: $(PWN_RELEASE).img $(PWN_RELEASE).sha256
|
||||
zip $(PWN_RELEASE).zip $^
|
||||
|
||||
.PHONY: image
|
||||
image: $(PWN_RELEASE).zip
|
||||
|
||||
clean:
|
||||
rm -rf /tmp/packer-builder-arm-image
|
||||
rm -f pwnagotchi-raspbian-lite-*.zip pwnagotchi-raspbian-lite-*.img pwnagotchi-raspbian-lite-*.sha256
|
||||
rm -rf builder/output-pwnagotchi builder/packer_cache
|
||||
- python3 setup.py clean --all
|
||||
- rm -rf dist pwnagotchi.egg-info
|
||||
- rm -f $(PACKER)
|
||||
- rm -f $(PWN_RELEASE).*
|
||||
- sudo rm -rf builder/output-pwnagotchi builder/packer_cache
|
||||
|
||||
|
@ -19,7 +19,7 @@ Instead of merely playing [Super Mario or Atari games](https://becominghuman.ai/
|
||||
|
||||
More specifically, Pwnagotchi is using an [LSTM with MLP feature extractor](https://stable-baselines.readthedocs.io/en/master/modules/policies.html#stable_baselines.common.policies.MlpLstmPolicy) as its policy network for the [A2C agent](https://stable-baselines.readthedocs.io/en/master/modules/a2c.html). If you're unfamiliar with A2C, here is [a very good introductory explanation](https://hackernoon.com/intuitive-rl-intro-to-advantage-actor-critic-a2c-4ff545978752) (in comic form!) of the basic principles behind how Pwnagotchi learns. (You can read more about how Pwnagotchi learns in the [Usage](https://www.pwnagotchi.ai/usage/#training-the-ai) doc.)
|
||||
|
||||
**Keep in mind:** Unlike the usual RL simulations, Pwnagotchi learns over time. Time for a Pwnagotchi is measured in epochs; a single epoch can last from a few seconds to minutes, depending on how many access points and client stations are visible. Do not expect your Pwnagotchi to perform amazingly well at the very beginning, as it will be [exploring](https://hackernoon.com/intuitive-rl-intro-to-advantage-actor-critic-a2c-4ff545978752) several combinations of [key parameters](https://www.pwnagotchi.ai/usage/#training-the-ai) to determine ideal adjustments for pwning the particular environment you are exposing it to during its beginning epochs ... but ** listen to your Pwnagotchi when it tells you it's boring!** Bring it into novel WiFi environments with you and have it observe new networks and capture new handshakes—and you'll see. :)
|
||||
**Keep in mind:** Unlike the usual RL simulations, Pwnagotchi learns over time. Time for a Pwnagotchi is measured in epochs; a single epoch can last from a few seconds to minutes, depending on how many access points and client stations are visible. Do not expect your Pwnagotchi to perform amazingly well at the very beginning, as it will be [exploring](https://hackernoon.com/intuitive-rl-intro-to-advantage-actor-critic-a2c-4ff545978752) several combinations of [key parameters](https://www.pwnagotchi.ai/usage/#training-the-ai) to determine ideal adjustments for pwning the particular environment you are exposing it to during its beginning epochs ... but ** listen to your Pwnagotchi when it tells you it's bored!** Bring it into novel WiFi environments with you and have it observe new networks and capture new handshakes—and you'll see. :)
|
||||
|
||||
Multiple units within close physical proximity can "talk" to each other, advertising their presence to each other by broadcasting custom information elements using a parasite protocol I've built on top of the existing dot11 standard. Over time, two or more units trained together will learn to cooperate upon detecting each other's presence by dividing the available channels among them for optimal pwnage.
|
||||
|
||||
|
@ -83,8 +83,13 @@ def do_auto_mode(agent):
|
||||
plugins.on('internet_available', agent)
|
||||
|
||||
except Exception as e:
|
||||
logging.exception("main loop exception (%s)", e)
|
||||
|
||||
if str(e).find("wifi.interface not set") > 0:
|
||||
logging.exception("main loop exception due to unavailable wifi device, likely programmatically disabled (%s)", e)
|
||||
logging.info("sleeping 60 seconds then advancing to next epoch to allow for cleanup code to trigger")
|
||||
time.sleep(60)
|
||||
agent.next_epoch()
|
||||
else:
|
||||
logging.exception("main loop exception (%s)", e)
|
||||
|
||||
if __name__ == '__main__':
|
||||
parser = argparse.ArgumentParser()
|
||||
|
@ -1,2 +1,4 @@
|
||||
allow-hotplug wlan0
|
||||
iface wlan0 inet static
|
||||
iface wlan0 inet manual
|
||||
pre-up ifconfig $IFACE up
|
||||
post-down ifconfig $IFACE down
|
||||
|
@ -19,7 +19,12 @@ if ! check_brcm; then
|
||||
fi
|
||||
|
||||
# start mon0
|
||||
start_monitor_interface
|
||||
if ! is_interface_up 'mon0'; then
|
||||
start_monitor_interface
|
||||
else
|
||||
stop_monitor_interface
|
||||
start_monitor_interface
|
||||
fi
|
||||
|
||||
if is_auto_mode_no_delete; then
|
||||
/usr/bin/bettercap -no-colors -caplet pwnagotchi-auto -iface mon0
|
||||
|
@ -3,12 +3,30 @@
|
||||
# well ... it blinks the led
|
||||
blink_led() {
|
||||
for i in $(seq 1 "$1"); do
|
||||
echo 0 >/sys/class/leds/led0/brightness
|
||||
if [ -d /sys/class/leds/led0 ]
|
||||
then
|
||||
echo 0 | tee /sys/class/leds/led0/brightness
|
||||
else
|
||||
echo 0 | tee /sys/class/leds/ACT/brightness
|
||||
fi
|
||||
sleep 0.3
|
||||
echo 1 >/sys/class/leds/led0/brightness
|
||||
|
||||
if [ -d /sys/class/leds/led0 ]
|
||||
then
|
||||
echo 1 | tee /sys/class/leds/led0/brightness
|
||||
else
|
||||
echo 1 | tee /sys/class/leds/ACT/brightness
|
||||
fi
|
||||
sleep 0.3
|
||||
|
||||
done
|
||||
echo 0 >/sys/class/leds/led0/brightness
|
||||
|
||||
if [ -d /sys/class/leds/led0 ]
|
||||
then
|
||||
echo 0 | tee /sys/class/leds/led0/brightness
|
||||
else
|
||||
echo 0 | tee /sys/class/leds/ACT/brightness
|
||||
fi
|
||||
sleep 0.3
|
||||
}
|
||||
|
||||
@ -33,20 +51,31 @@ reload_brcm() {
|
||||
|
||||
# starts mon0
|
||||
start_monitor_interface() {
|
||||
rfkill unblock all
|
||||
iw dev wlan0 set power_save off
|
||||
|
||||
ifconfig wlan0 up
|
||||
|
||||
iw phy "$(iw phy | head -1 | cut -d" " -f2)" interface add mon0 type monitor && ifconfig mon0 up
|
||||
|
||||
# If wlan0 is NOT taken down after bringing up mon0, then when switching to AUTO you will get:
|
||||
# error 400: error while initializing mon0 to channel 1: iw: out=command failed: Device or resource busy (-16) err=exit status 240
|
||||
ifconfig wlan0 down
|
||||
}
|
||||
|
||||
# stops mon0
|
||||
stop_monitor_interface() {
|
||||
ifconfig mon0 down && iw dev mon0 del
|
||||
ifconfig wlan0 up
|
||||
}
|
||||
|
||||
# returns 0 if the specificed network interface is up
|
||||
is_interface_up() {
|
||||
if grep -qi 'up' /sys/class/net/$1/operstate; then
|
||||
return 0
|
||||
else
|
||||
return 1
|
||||
fi
|
||||
return 1
|
||||
}
|
||||
|
||||
# returns 0 if conditions for AUTO mode are met
|
||||
@ -158,7 +187,7 @@ network={
|
||||
frequency=2437
|
||||
}
|
||||
EOF
|
||||
>/dev/null 2>&1 wpa_supplicant -D nl80211 -i wlan0 -c /tmp/wpa_supplicant.conf &
|
||||
>/dev/null 2>&1 wpa_supplicant -u -s -O -D nl80211 -i wlan0 -c /tmp/wpa_supplicant.conf &
|
||||
fi
|
||||
|
||||
if ! pgrep dnsmasq >/dev/null 2>&1; then
|
||||
|
@ -3,95 +3,34 @@
|
||||
{
|
||||
"name": "pwnagotchi",
|
||||
"type": "arm-image",
|
||||
"iso_url": "https://downloads.raspberrypi.org/raspbian_lite/images/raspbian_lite-2019-07-12/2019-07-10-raspbian-buster-lite.zip",
|
||||
"iso_checksum": "9e5cf24ce483bb96e7736ea75ca422e3560e7b455eee63dd28f66fa1825db70e",
|
||||
"last_partition_extra_size": 3221225472
|
||||
"iso_url": "https://downloads.raspberrypi.org/raspios_oldstable_lite_armhf/images/raspios_oldstable_lite_armhf-2023-05-03/2023-05-03-raspios-buster-armhf-lite.img.xz",
|
||||
"iso_checksum": "3d210e61b057de4de90eadb46e28837585a9b24247c221998f5bead04f88624c",
|
||||
"target_image_size": 9368709120,
|
||||
"qemu_args": ["-cpu", "arm1176"]
|
||||
}
|
||||
],
|
||||
"provisioners": [
|
||||
{
|
||||
"type": "shell",
|
||||
"inline": [
|
||||
"sed -i 's/^\\([^#]\\)/#\\1/g' /etc/ld.so.preload",
|
||||
"mv /etc/ld.so.preload /etc/ld.so.preload.DISABLED",
|
||||
"uname -a",
|
||||
"dpkg-architecture",
|
||||
"apt-get -y update",
|
||||
"apt-get install -y ansible"
|
||||
"mkdir -p /usr/local/src/pwnagotchi"
|
||||
]
|
||||
},
|
||||
{
|
||||
"type": "file",
|
||||
"source": "data/usr/bin/pwnlib",
|
||||
"destination": "/usr/bin/pwnlib"
|
||||
},
|
||||
{
|
||||
"type": "file",
|
||||
"source": "data/usr/bin/bettercap-launcher",
|
||||
"destination": "/usr/bin/bettercap-launcher"
|
||||
},
|
||||
{
|
||||
"type": "file",
|
||||
"source": "data/usr/bin/pwnagotchi-launcher",
|
||||
"destination": "/usr/bin/pwnagotchi-launcher"
|
||||
},
|
||||
{
|
||||
"type": "file",
|
||||
"source": "data/usr/bin/monstop",
|
||||
"destination": "/usr/bin/monstop"
|
||||
},
|
||||
{
|
||||
"type": "file",
|
||||
"source": "data/usr/bin/monstart",
|
||||
"destination": "/usr/bin/monstart"
|
||||
},
|
||||
{
|
||||
"type": "file",
|
||||
"source": "data/usr/bin/hdmion",
|
||||
"destination": "/usr/bin/hdmion"
|
||||
},
|
||||
{
|
||||
"type": "file",
|
||||
"source": "data/usr/bin/hdmioff",
|
||||
"destination": "/usr/bin/hdmioff"
|
||||
},
|
||||
{
|
||||
"type": "file",
|
||||
"source": "data/etc/network/interfaces.d/lo-cfg",
|
||||
"destination": "/etc/network/interfaces.d/lo-cfg"
|
||||
},
|
||||
{
|
||||
"type": "file",
|
||||
"source": "data/etc/network/interfaces.d/wlan0-cfg",
|
||||
"destination": "/etc/network/interfaces.d/wlan0-cfg"
|
||||
},
|
||||
{
|
||||
"type": "file",
|
||||
"source": "data/etc/network/interfaces.d/usb0-cfg",
|
||||
"destination": "/etc/network/interfaces.d/usb0-cfg"
|
||||
},
|
||||
{
|
||||
"type": "file",
|
||||
"source": "data/etc/network/interfaces.d/eth0-cfg",
|
||||
"destination": "/etc/network/interfaces.d/eth0-cfg"
|
||||
},
|
||||
{
|
||||
"type": "file",
|
||||
"source": "data/etc/systemd/system/pwngrid-peer.service",
|
||||
"destination": "/etc/systemd/system/pwngrid-peer.service"
|
||||
},
|
||||
{
|
||||
"type": "file",
|
||||
"source": "data/etc/systemd/system/pwnagotchi.service",
|
||||
"destination": "/etc/systemd/system/pwnagotchi.service"
|
||||
},
|
||||
{
|
||||
"type": "file",
|
||||
"source": "data/etc/systemd/system/bettercap.service",
|
||||
"destination": "/etc/systemd/system/bettercap.service"
|
||||
"sources": [
|
||||
"../dist/pwnagotchi-{{user `pwn_version`}}.tar.gz"
|
||||
],
|
||||
"destination": "/usr/local/src/pwnagotchi/"
|
||||
},
|
||||
{
|
||||
"type": "shell",
|
||||
"inline": [
|
||||
"chmod +x /usr/bin/*"
|
||||
"apt-get -y --allow-releaseinfo-change update",
|
||||
"apt-get install -y --no-install-recommends ansible"
|
||||
]
|
||||
},
|
||||
{
|
||||
@ -103,7 +42,7 @@
|
||||
{
|
||||
"type": "shell",
|
||||
"inline": [
|
||||
"sed -i 's/^#\\(.+\\)/\\1/g' /etc/ld.so.preload"
|
||||
"mv /etc/ld.so.preload.DISABLED /etc/ld.so.preload"
|
||||
]
|
||||
}
|
||||
]
|
||||
|
@ -1,6 +1,7 @@
|
||||
---
|
||||
- hosts:
|
||||
- 127.0.0.1
|
||||
gather_facts: yes
|
||||
become: yes
|
||||
vars:
|
||||
pwnagotchi:
|
||||
@ -10,6 +11,7 @@
|
||||
boot_options:
|
||||
- "dtoverlay=dwc2"
|
||||
- "dtoverlay=spi1-3cs"
|
||||
- "dtoverlay=pwm-2chan,pin=12,func=4,pin2=13,func2=4"
|
||||
- "dtparam=spi=on"
|
||||
- "dtparam=i2c_arm=on"
|
||||
- "dtparam=i2c1=on"
|
||||
@ -33,9 +35,11 @@
|
||||
- bluetooth.service
|
||||
- triggerhappy.service
|
||||
- ifup@wlan0.service
|
||||
- dnsmasq.service
|
||||
packages:
|
||||
bettercap:
|
||||
url: "https://github.com/bettercap/bettercap/releases/download/v2.31.0/bettercap_linux_armhf_v2.31.0.zip"
|
||||
# We will install bettercap from source
|
||||
# url: "https://github.com/bettercap/bettercap/releases/download/v2.31.0/bettercap_linux_armhf_v2.31.0.zip"
|
||||
ui: "https://github.com/bettercap/ui/releases/download/v1.3.0/ui.zip"
|
||||
pwngrid:
|
||||
url: "https://github.com/evilsocket/pwngrid/releases/download/v1.10.3/pwngrid_linux_armhf_v1.10.3.zip"
|
||||
@ -52,11 +56,13 @@
|
||||
- triggerhappy
|
||||
- wpa_supplicant
|
||||
- nfs-common
|
||||
# Remove every golang package because we will install go-1.20.2
|
||||
- golang*
|
||||
- python2*
|
||||
install:
|
||||
- rsync
|
||||
- vim
|
||||
- screen
|
||||
- golang
|
||||
- git
|
||||
- build-essential
|
||||
- python3-pip
|
||||
@ -67,6 +73,7 @@
|
||||
- libopenmpi-dev
|
||||
- libatlas-base-dev
|
||||
- libjasper-dev
|
||||
- libgtk-3-0
|
||||
- libqtgui4
|
||||
- libqt4-test
|
||||
- libopenjp2-7
|
||||
@ -84,10 +91,6 @@
|
||||
- libnetfilter-queue-dev
|
||||
- libopenmpi3
|
||||
- dphys-swapfile
|
||||
- kalipi-kernel
|
||||
- kalipi-bootloader
|
||||
- kalipi-re4son-firmware
|
||||
- kalipi-kernel-headers
|
||||
- libraspberrypi0
|
||||
- libraspberrypi-dev
|
||||
- libraspberrypi-doc
|
||||
@ -104,8 +107,33 @@
|
||||
- fonts-ipaexfont-gothic
|
||||
- cryptsetup
|
||||
- dnsmasq
|
||||
- aircrack-ng
|
||||
- raspberrypi-kernel-headers
|
||||
- libgmp3-dev
|
||||
- qpdf
|
||||
- bison
|
||||
- flex
|
||||
- make
|
||||
- autoconf
|
||||
- libtool
|
||||
- texinfo
|
||||
- binutils
|
||||
- lnav
|
||||
- p7zip-full
|
||||
|
||||
environment:
|
||||
ARCHFLAGS: "-arch armv7l"
|
||||
|
||||
tasks:
|
||||
- name: System details
|
||||
debug:
|
||||
msg="{{ item }}"
|
||||
with_items:
|
||||
- "{{ ansible_distribution }}"
|
||||
- "{{ ansible_distribution_version }}"
|
||||
- "{{ ansible_distribution_major_version }}"
|
||||
- "{{ ansible_architecture }}"
|
||||
- "{{ ansible_machine }}"
|
||||
- name: change hostname
|
||||
hostname:
|
||||
name: "{{pwnagotchi.hostname}}"
|
||||
@ -127,16 +155,6 @@
|
||||
line: 'ExecStart=/usr/lib/bluetooth/bluetoothd --noplugin=sap'
|
||||
state: present
|
||||
|
||||
- name: Add re4son-kernel repo key
|
||||
apt_key:
|
||||
url: https://re4son-kernel.com/keys/http/archive-key.asc
|
||||
state: present
|
||||
|
||||
- name: Add re4son-kernel repository
|
||||
apt_repository:
|
||||
repo: deb http://http.re4son-kernel.com/re4son/ kali-pi main
|
||||
state: present
|
||||
|
||||
- name: add firmware packages to hold
|
||||
dpkg_selections:
|
||||
name: "{{ item }}"
|
||||
@ -162,16 +180,28 @@
|
||||
name: "{{ packages.apt.install }}"
|
||||
state: present
|
||||
|
||||
- name: Update .bashrc (root)
|
||||
blockinfile:
|
||||
dest: /root/.bashrc
|
||||
state: present
|
||||
block: |
|
||||
export MAKEFLAGS=-j$(nproc)
|
||||
insertafter: EOF
|
||||
|
||||
- name: configure dphys-swapfile
|
||||
file:
|
||||
lineinfile:
|
||||
path: /etc/dphys-swapfile
|
||||
content: "CONF_SWAPSIZE=1024"
|
||||
regexp: "^CONF_SWAPSIZE=.*$"
|
||||
line: "CONF_SWAPSIZE=512"
|
||||
|
||||
- name: clone papirus repository
|
||||
git:
|
||||
repo: https://github.com/repaper/gratis.git
|
||||
dest: /usr/local/src/gratis
|
||||
retries: 5000
|
||||
delay: 5
|
||||
register: gratisgit
|
||||
until: gratisgit is succeeded
|
||||
|
||||
- name: build papirus service
|
||||
make:
|
||||
@ -197,67 +227,58 @@
|
||||
regexp: "#EPD_SIZE=2.0"
|
||||
line: "EPD_SIZE=2.0"
|
||||
|
||||
- name: collect python pip package list
|
||||
command: "pip3 list"
|
||||
register: pip_output
|
||||
|
||||
- name: set python pip package facts
|
||||
set_fact:
|
||||
pip_packages: >
|
||||
{{ pip_packages | default({}) | combine( { item.split()[0]: item.split()[1] } ) }}
|
||||
with_items: "{{ pip_output.stdout_lines }}"
|
||||
|
||||
- name: acquire python3 pip target
|
||||
command: "python3 -c 'import sys;print(sys.path.pop())'"
|
||||
register: pip_target
|
||||
|
||||
- name: clone pwnagotchi repository
|
||||
git:
|
||||
repo: https://github.com/evilsocket/pwnagotchi.git
|
||||
dest: /usr/local/src/pwnagotchi
|
||||
register: pwnagotchigit
|
||||
|
||||
- name: create /usr/local/share/pwnagotchi/ folder
|
||||
- name: Delete papirus content & directory
|
||||
file:
|
||||
path: /usr/local/share/pwnagotchi/
|
||||
state: absent
|
||||
path: /usr/local/src/gratis
|
||||
when: gratisgit.changed
|
||||
|
||||
# pip v20.3 uses a newer dependency resolver that better handles our unique situation.
|
||||
# Specifically, it handles mismatches between direct requirements without extras and
|
||||
# indirect requirements that do want extras (e.g. gym vs stable-baselines->gym[atari]).
|
||||
- name: Upgrade pip and install rpi-hardware-pwm
|
||||
shell: "python3 -m pip install pip>=20.3 rpi-hardware-pwm --verbose --retries 5000"
|
||||
args:
|
||||
executable: /bin/bash
|
||||
|
||||
# We need the --ignore-installed option so that pip simply overwrites/upgrades existing
|
||||
# packages instead of trying to uninstall them first. While this sounds dangerous,
|
||||
# this matches the legacy behavior of pip. This is required to prevent pip from trying
|
||||
# (and failing) to uninstall python packages that were originally installed via apt.
|
||||
- name: Install pwnagotchi from source archive
|
||||
shell: "python3 -m pip install /usr/local/src/pwnagotchi/pwnagotchi-{{ pwnagotchi.version }}.tar.gz --verbose --ignore-installed --retries 5000"
|
||||
args:
|
||||
executable: /bin/bash
|
||||
|
||||
- name: create custom plugin directory
|
||||
file:
|
||||
path: /usr/local/share/pwnagotchi/custom-plugins/
|
||||
state: directory
|
||||
|
||||
- name: clone pwnagotchi plugins repository
|
||||
git:
|
||||
repo: https://github.com/evilsocket/pwnagotchi-plugins-contrib.git
|
||||
dest: /usr/local/share/pwnagotchi/availaible-plugins
|
||||
repo: https://git.chadwaltercummings.me/scifijunkie/pwnagotchi-plugins-contrib.git
|
||||
dest: /usr/local/share/pwnagotchi/available-plugins
|
||||
retries: 5000
|
||||
delay: 5
|
||||
register: pwnagotchipluginsgit
|
||||
until: pwnagotchipluginsgit is succeeded
|
||||
|
||||
- name: fetch pwnagotchi version
|
||||
set_fact:
|
||||
pwnagotchi_version: "{{ lookup('file', '/usr/local/src/pwnagotchi/pwnagotchi/_version.py') | regex_replace('.*__version__.*=.*''([0-9]+\\.[0-9]+\\.[0-9]+[A-Za-z0-9]*)''.*', '\\1') }}"
|
||||
- name: Copy aircrackonly.py
|
||||
copy:
|
||||
src: /usr/local/share/pwnagotchi/available-plugins/aircrackonly.py
|
||||
dest: /usr/local/share/pwnagotchi/custom-plugins/aircrackonly.py
|
||||
owner: root
|
||||
group: root
|
||||
mode: '644'
|
||||
|
||||
- name: pwnagotchi version found
|
||||
debug:
|
||||
msg: "{{ pwnagotchi_version }}"
|
||||
|
||||
- name: build pwnagotchi wheel
|
||||
command: "python3 setup.py sdist bdist_wheel"
|
||||
args:
|
||||
chdir: /usr/local/src/pwnagotchi
|
||||
when: (pwnagotchigit.changed) or (pip_packages['pwnagotchi'] is undefined) or (pip_packages['pwnagotchi'] != pwnagotchi_version)
|
||||
|
||||
- name: install opencv-python
|
||||
pip:
|
||||
name: "https://www.piwheels.org/simple/opencv-python/opencv_python-3.4.3.18-cp37-cp37m-linux_armv6l.whl"
|
||||
extra_args: "--no-deps --no-cache-dir --platform=linux_armv6l --only-binary=:all: --target={{ pip_target.stdout }}"
|
||||
when: (pip_packages['opencv-python'] is undefined) or (pip_packages['opencv-python'] != '3.4.3.18')
|
||||
|
||||
- name: install tensorflow
|
||||
pip:
|
||||
name: "https://www.piwheels.org/simple/tensorflow/tensorflow-1.13.1-cp37-none-linux_armv6l.whl"
|
||||
extra_args: "--no-deps --no-cache-dir --platform=linux_armv6l --only-binary=:all: --target={{ pip_target.stdout }}"
|
||||
when: (pip_packages['tensorflow'] is undefined) or (pip_packages['tensorflow'] != '1.13.1')
|
||||
|
||||
- name: install pwnagotchi wheel and dependencies
|
||||
pip:
|
||||
name: "{{ lookup('fileglob', '/usr/local/src/pwnagotchi/dist/pwnagotchi*.whl') }}"
|
||||
extra_args: "--no-cache-dir"
|
||||
when: (pwnagotchigit.changed) or (pip_packages['pwnagotchi'] is undefined) or (pip_packages['pwnagotchi'] != pwnagotchi_version)
|
||||
- name: Copy handshakes-dl.py
|
||||
copy:
|
||||
src: /usr/local/share/pwnagotchi/available-plugins/handshakes-dl.py
|
||||
dest: /usr/local/share/pwnagotchi/custom-plugins/handshakes-dl.py
|
||||
owner: root
|
||||
group: root
|
||||
mode: '644'
|
||||
|
||||
- name: download and install pwngrid
|
||||
unarchive:
|
||||
@ -266,21 +287,41 @@
|
||||
remote_src: yes
|
||||
mode: 0755
|
||||
|
||||
- name: download and install bettercap
|
||||
- name: Install go-1.24.1
|
||||
unarchive:
|
||||
src: "{{ packages.bettercap.url }}"
|
||||
dest: /usr/bin
|
||||
src: https://go.dev/dl/go1.24.1.linux-armv6l.tar.gz
|
||||
dest: /usr/local
|
||||
remote_src: yes
|
||||
exclude:
|
||||
- README.md
|
||||
- LICENSE.md
|
||||
mode: 0755
|
||||
register: golang
|
||||
|
||||
- name: Update .bashrc for go-1.24.1 (pi)
|
||||
blockinfile:
|
||||
dest: /home/pi/.bashrc
|
||||
state: present
|
||||
block: |
|
||||
export GOPATH=$HOME/go
|
||||
export PATH=/usr/local/go/bin:$PATH:$GOPATH/bin
|
||||
insertafter: EOF
|
||||
when: golang.changed
|
||||
|
||||
- name: Install bettercap
|
||||
shell: "export GOPATH=$HOME/go && export PATH=/usr/local/go/bin:$PATH:$GOPATH/bin && git clone https://github.com/bettercap/bettercap.git && cd bettercap/ && make build && make install"
|
||||
args:
|
||||
executable: /bin/bash
|
||||
register: bettercap
|
||||
|
||||
- name: Link bettercap
|
||||
command: ln -s /usr/local/bin/bettercap /usr/bin/bettercap
|
||||
when: bettercap.changed
|
||||
|
||||
- name: clone bettercap caplets
|
||||
git:
|
||||
repo: https://github.com/bettercap/caplets.git
|
||||
dest: /tmp/caplets
|
||||
retries: 5000
|
||||
delay: 5
|
||||
register: capletsgit
|
||||
until: capletsgit is succeeded
|
||||
|
||||
- name: install bettercap caplets
|
||||
make:
|
||||
@ -295,6 +336,227 @@
|
||||
remote_src: yes
|
||||
mode: 0755
|
||||
|
||||
# Install nexmon to fix wireless scanning (takes 2.5G of space)
|
||||
- name: clone nexmon repository
|
||||
git:
|
||||
repo: https://github.com/seemoo-lab/nexmon.git
|
||||
dest: /usr/local/src/nexmon
|
||||
# version: bfb3fe90c881498d7ee245b38f16722c1de26fa1
|
||||
retries: 5000
|
||||
delay: 5
|
||||
register: nexmongit
|
||||
until: nexmongit is succeeded
|
||||
|
||||
- name: configure libisl
|
||||
command: chdir=/usr/local/src/nexmon/buildtools/isl-0.10/ ./configure
|
||||
|
||||
- name: make libisl
|
||||
command: chdir=/usr/local/src/nexmon/buildtools/isl-0.10/ make
|
||||
|
||||
- name: install libisl
|
||||
command: chdir=/usr/local/src/nexmon/buildtools/isl-0.10/ make install
|
||||
|
||||
- name: link libisl
|
||||
command: ln -s /usr/local/lib/libisl.so /usr/lib/arm-linux-gnueabihf/libisl.so.10
|
||||
|
||||
- name: autoreconf libmpfr
|
||||
command: chdir=/usr/local/src/nexmon/buildtools/mpfr-3.1.4/ autoreconf -f -i
|
||||
|
||||
- name: configure libmpfr
|
||||
command: chdir=/usr/local/src/nexmon/buildtools/mpfr-3.1.4/ ./configure
|
||||
|
||||
- name: make libmpfr
|
||||
command: chdir=/usr/local/src/nexmon/buildtools/mpfr-3.1.4/ make
|
||||
|
||||
- name: install libmpfr
|
||||
command: chdir=/usr/local/src/nexmon/buildtools/mpfr-3.1.4/ make install
|
||||
|
||||
- name: link libmpfr
|
||||
command: ln -s /usr/local/lib/libmpfr.so /usr/lib/arm-linux-gnueabihf/libmpfr.so.4
|
||||
|
||||
- name: make firmware
|
||||
shell: "source ./setup_env.sh && make"
|
||||
args:
|
||||
executable: /bin/bash
|
||||
chdir: /usr/local/src/nexmon/
|
||||
|
||||
- name: choose the right kernel version (bcm43436b0)
|
||||
replace:
|
||||
dest: /usr/local/src/nexmon/patches/bcm43436b0/9_88_4_65/nexmon/Makefile
|
||||
backup: no
|
||||
regexp: "KERNEL_VERSION = .*$"
|
||||
replace: "KERNEL_VERSION = 5.10"
|
||||
|
||||
- name: choose the right kernel release (variable) (bcm43436b0)
|
||||
lineinfile:
|
||||
dest: /usr/local/src/nexmon/patches/bcm43436b0/9_88_4_65/nexmon/Makefile
|
||||
insertafter: "DRIVER_FOLDER_NAME = .*$"
|
||||
line: "KERNEL_RELEASE = 5.10.103-v7+"
|
||||
|
||||
- name: choose the right kernel release (replace string) (bcm43436b0)
|
||||
replace:
|
||||
dest: /usr/local/src/nexmon/patches/bcm43436b0/9_88_4_65/nexmon/Makefile
|
||||
backup: no
|
||||
regexp: "shell uname -r"
|
||||
replace: "KERNEL_RELEASE"
|
||||
|
||||
- name: make firmware patch (bcm43436b0)
|
||||
shell: "source ./setup_env.sh && cd /usr/local/src/nexmon/patches/bcm43436b0/9_88_4_65/nexmon/ && make"
|
||||
args:
|
||||
executable: /bin/bash
|
||||
chdir: /usr/local/src/nexmon/
|
||||
|
||||
# - name: backup original firmware
|
||||
# shell: "source ./setup_env.sh && cd /usr/local/src/nexmon/patches/bcm43436b0/9_88_4_65/nexmon/ && make backup-firmware"
|
||||
# args:
|
||||
# executable: /bin/bash
|
||||
# chdir: /usr/local/src/nexmon/
|
||||
|
||||
# - name: install new firmware
|
||||
# shell: "source ./setup_env.sh && cd /usr/local/src/nexmon/patches/bcm43436b0/9_88_4_65/nexmon/ && make install-firmware"
|
||||
# args:
|
||||
# executable: /bin/bash
|
||||
# chdir: /usr/local/src/nexmon/
|
||||
|
||||
- name: install new firmware (bcm43436b0)
|
||||
copy:
|
||||
src: /usr/local/src/nexmon/patches/bcm43436b0/9_88_4_65/nexmon/brcmfmac43436-sdio.bin
|
||||
dest: /lib/firmware/brcm/brcmfmac43436-sdio.bin
|
||||
|
||||
- name: choose the right kernel version (bcm43430a1)
|
||||
replace:
|
||||
dest: /usr/local/src/nexmon/patches/bcm43430a1/7_45_41_46/nexmon/Makefile
|
||||
backup: no
|
||||
regexp: "KERNEL_VERSION = .*$"
|
||||
replace: "KERNEL_VERSION = 5.10"
|
||||
|
||||
- name: choose the right kernel release (variable) (bcm43430a1)
|
||||
lineinfile:
|
||||
dest: /usr/local/src/nexmon/patches/bcm43430a1/7_45_41_46/nexmon/Makefile
|
||||
insertafter: "DRIVER_FOLDER_NAME = .*$"
|
||||
line: "KERNEL_RELEASE = 5.10.103-v7+"
|
||||
|
||||
- name: choose the right kernel release (replace string) (bcm43430a1)
|
||||
replace:
|
||||
dest: /usr/local/src/nexmon/patches/bcm43430a1/7_45_41_46/nexmon/Makefile
|
||||
backup: no
|
||||
regexp: "shell uname -r"
|
||||
replace: "KERNEL_RELEASE"
|
||||
|
||||
- name: make firmware patch (bcm43430a1)
|
||||
shell: "source ./setup_env.sh && cd /usr/local/src/nexmon/patches/bcm43430a1/7_45_41_46/nexmon/ && make"
|
||||
args:
|
||||
executable: /bin/bash
|
||||
chdir: /usr/local/src/nexmon/
|
||||
|
||||
# - name: backup original firmware
|
||||
# shell: "source ./setup_env.sh && cd /usr/local/src/nexmon/patches/bcm43430a1/7_45_41_46/nexmon/ && make backup-firmware"
|
||||
# args:
|
||||
# executable: /bin/bash
|
||||
# chdir: /usr/local/src/nexmon/
|
||||
|
||||
# - name: install new firmware
|
||||
# shell: "source ./setup_env.sh && cd /usr/local/src/nexmon/patches/bcm43430a1/7_45_41_46/nexmon/ && make install-firmware"
|
||||
# args:
|
||||
# executable: /bin/bash
|
||||
# chdir: /usr/local/src/nexmon/
|
||||
|
||||
- name: install new firmware (bcm43430a1)
|
||||
copy:
|
||||
src: /usr/local/src/nexmon/patches/bcm43430a1/7_45_41_46/nexmon/brcmfmac43430-sdio.bin
|
||||
dest: /lib/firmware/brcm/brcmfmac43430-sdio.bin
|
||||
|
||||
- name: Delete the firmware blob to avoid it crashing
|
||||
file:
|
||||
state: absent
|
||||
path: /lib/firmware/brcm/brcmfmac43430-sdio.clm_blob
|
||||
|
||||
- name: Delete the RPiZW firmware blob to avoid it crashing
|
||||
file:
|
||||
state: absent
|
||||
path: /lib/firmware/brcm/brcmfmac43430-sdio.raspberrypi,model-zero-w.clm_blob
|
||||
|
||||
- name: Delete the RPi3 firmware blob to avoid it crashing
|
||||
file:
|
||||
state: absent
|
||||
path: /lib/firmware/brcm/brcmfmac43430-sdio.raspberrypi,3-model-b.clm_blob
|
||||
|
||||
- name: choose the right kernel version (bcm43455c0)
|
||||
replace:
|
||||
dest: /usr/local/src/nexmon/patches/bcm43455c0/7_45_206/nexmon/Makefile
|
||||
backup: no
|
||||
regexp: "KERNEL_VERSION = .*$"
|
||||
replace: "KERNEL_VERSION = 5.10"
|
||||
|
||||
- name: choose the right kernel release (variable) (bcm43455c0)
|
||||
lineinfile:
|
||||
dest: /usr/local/src/nexmon/patches/bcm43455c0/7_45_206/nexmon/Makefile
|
||||
insertafter: "DRIVER_FOLDER_NAME = .*$"
|
||||
line: "KERNEL_RELEASE = 5.10.103-v7+"
|
||||
|
||||
- name: choose the right kernel release (replace string) (bcm43455c0)
|
||||
replace:
|
||||
dest: /usr/local/src/nexmon/patches/bcm43455c0/7_45_206/nexmon/Makefile
|
||||
backup: no
|
||||
regexp: "shell uname -r"
|
||||
replace: "KERNEL_RELEASE"
|
||||
|
||||
- name: make firmware patch (bcm43455c0)
|
||||
shell: "source ./setup_env.sh && cd /usr/local/src/nexmon/patches/bcm43455c0/7_45_206/nexmon/ && make"
|
||||
args:
|
||||
executable: /bin/bash
|
||||
chdir: /usr/local/src/nexmon/
|
||||
|
||||
# - name: backup original firmware
|
||||
# shell: "source ./setup_env.sh && cd /usr/local/src/nexmon/patches/bcm43455c0/7_45_206/nexmon/ && make backup-firmware"
|
||||
# args:
|
||||
# executable: /bin/bash
|
||||
# chdir: /usr/local/src/nexmon/
|
||||
|
||||
# - name: install new firmware
|
||||
# shell: "source ./setup_env.sh && cd /usr/local/src/nexmon/patches/bcm43455c0/7_45_206/nexmon/ && make install-firmware"
|
||||
# args:
|
||||
# executable: /bin/bash
|
||||
# chdir: /usr/local/src/nexmon/
|
||||
|
||||
- name: install new firmware (bcm43455c0)
|
||||
copy:
|
||||
src: /usr/local/src/nexmon/patches/bcm43455c0/7_45_206/nexmon/brcmfmac43455-sdio.bin
|
||||
dest: /lib/firmware/brcm/brcmfmac43455-sdio.bin
|
||||
|
||||
- name: make nexutil
|
||||
command: chdir=/usr/local/src/nexmon/utilities/nexutil/ make
|
||||
|
||||
- name: make install nexutil
|
||||
command: chdir=/usr/local/src/nexmon/utilities/nexutil/ make install
|
||||
|
||||
# - name: copy modified driver
|
||||
# shell: "cd /usr/local/src/nexmon/patches/driver/brcmfmac_5.10.y-nexmon/ && cp brcmfmac.ko /lib/modules/5.10.103-v7+/kernel/drivers/net/wireless/broadcom/brcm80211/brcmfmac/brcmfmac.ko && depmod -a"
|
||||
# args:
|
||||
# executable: /bin/bash
|
||||
|
||||
- name: copy modified driver (everyone but RPiZW)
|
||||
copy:
|
||||
src: /usr/local/src/nexmon/patches/driver/brcmfmac_5.10.y-nexmon/brcmfmac.ko
|
||||
dest: /lib/modules/5.10.103-v7+/kernel/drivers/net/wireless/broadcom/brcm80211/brcmfmac/brcmfmac.ko
|
||||
|
||||
- name: ensure depmod runs on reboot to load modified driver (brcmfmac)
|
||||
lineinfile:
|
||||
dest: /etc/rc.local
|
||||
line: "/sbin/depmod -a"
|
||||
|
||||
# To shrink the final image, remove the nexmon directory (takes 2.5G of space) post build and installation
|
||||
- name: Delete nexmon content & directory
|
||||
file:
|
||||
state: absent
|
||||
path: /usr/local/src/nexmon/
|
||||
|
||||
- name: Add pwnlog alias
|
||||
lineinfile:
|
||||
dest: /home/pi/.bashrc
|
||||
line: "\nalias pwnlog='tail -f -n300 /var/log/pwn*.log | sed --unbuffered \"s/,[[:digit:]]\\{3\\}\\]//g\" | cut -d \" \" -f 2-'"
|
||||
insertafter: EOF
|
||||
|
||||
- name: add HDMI powersave to rc.local
|
||||
blockinfile:
|
||||
path: /etc/rc.local
|
||||
@ -324,6 +586,30 @@
|
||||
# ui.display.type = "waveshare_2"
|
||||
when: not user_config.stat.exists
|
||||
|
||||
# - name: append commented out parameters for usb_hat_c.py
|
||||
# lineinfile:
|
||||
# dest: /etc/pwnagotchi/config.toml
|
||||
# line: "# main.plugins.ups_hat_c.enabled = true\n# main.plugins.ups_hat_c.label_on = true # show BAT label or just percentage\n# main.plugins.ups_hat_c.shutdown = 5 # battery percent at which the device will turn off\n# main.plugins.ups_hat_c.bat_x_coord = 140\n# main.plugins.ups_hat_c.bat_y_coord = 0"
|
||||
# insertafter: EOF
|
||||
|
||||
#bizzarely changing the plugin code directly reverts to the old string
|
||||
- name: Reconfigure auto-update to point to the scifijunk repo
|
||||
replace:
|
||||
dest: /usr/local/lib/python3.7/dist-packages/pwnagotchi/plugins/default/auto-update.py
|
||||
backup: no
|
||||
regexp: "evilsocket/pwnagotchi"
|
||||
replace: "scifijunk/pwnagotchi"
|
||||
|
||||
- name: Delete unnecessary large folder to save space (/root/go)
|
||||
file:
|
||||
state: absent
|
||||
path: /root/go
|
||||
|
||||
- name: Delete unnecessary large folder to save space (/root/.cache)
|
||||
file:
|
||||
state: absent
|
||||
path: /root/.cache
|
||||
|
||||
- name: enable ssh on boot
|
||||
file:
|
||||
path: /boot/ssh
|
||||
@ -391,9 +677,14 @@
|
||||
You learn more about me at https://pwnagotchi.ai/
|
||||
when: hostname.changed
|
||||
|
||||
# Ansible's apt module has an "autoclean" option but it only removes packages
|
||||
# that can no longer be downloaded. Ansible v2.13 added the "clean" option
|
||||
# which actually purges the apt cache, but that's newer than what we can
|
||||
# install from the RasPiOS repos. Instead, we'll manually clean the cache.
|
||||
- name: clean apt cache
|
||||
apt:
|
||||
autoclean: yes
|
||||
command: "apt-get clean"
|
||||
args:
|
||||
warn: false
|
||||
|
||||
- name: remove dependencies that are no longer required
|
||||
apt:
|
||||
|
@ -119,7 +119,7 @@ def shutdown():
|
||||
from pwnagotchi import fs
|
||||
for m in fs.mounts:
|
||||
m.sync()
|
||||
|
||||
|
||||
os.system("sync")
|
||||
os.system("halt")
|
||||
|
||||
@ -133,6 +133,7 @@ def restart(mode):
|
||||
os.system("touch /root/.pwnagotchi-manual")
|
||||
|
||||
os.system("service bettercap restart")
|
||||
time.sleep(2)
|
||||
os.system("service pwnagotchi restart")
|
||||
|
||||
|
||||
|
@ -1 +1 @@
|
||||
__version__ = '1.5.5'
|
||||
__version__='1.10.0'
|
||||
|
@ -324,6 +324,12 @@ class Agent(Client, Automata, AsyncAdvertiser, AsyncTrainer):
|
||||
found_handshake = False
|
||||
jmsg = json.loads(msg)
|
||||
|
||||
# give plugins access to all raw bettercap events
|
||||
try:
|
||||
plugins.on('bcap_%s' % re.sub(r"[^a-z0-9_]+", "_", jmsg['tag'].lower()), self, jmsg)
|
||||
except Exception as err:
|
||||
logging.error("Processing event: %s" % err)
|
||||
|
||||
if jmsg['tag'] == 'wifi.client.handshake':
|
||||
filename = jmsg['data']['file']
|
||||
sta_mac = jmsg['data']['station']
|
||||
|
@ -45,8 +45,17 @@ def load(config, agent, epoch, from_disk=True):
|
||||
if from_disk and os.path.exists(config['path']):
|
||||
logging.info("[ai] loading %s ..." % config['path'])
|
||||
start = time.time()
|
||||
a2c.load(config['path'], env)
|
||||
logging.debug("[ai] A2C loaded in %.2fs" % (time.time() - start))
|
||||
try:
|
||||
a2c.load(config['path'], env)
|
||||
except AssertionError as as_err:
|
||||
from fnmatch import fnmatch
|
||||
# Sometimes the model breaks...
|
||||
if not fnmatch(str(as_err), '* same * space as the model *'):
|
||||
raise as_err
|
||||
else:
|
||||
logging.warning("[ai] Model could not be loaded. Using new model.")
|
||||
else:
|
||||
logging.debug("[ai] A2C loaded in %.2fs" % (time.time() - start))
|
||||
else:
|
||||
logging.info("[ai] model created:")
|
||||
for key, value in config['params'].items():
|
||||
|
@ -2,9 +2,20 @@ import json
|
||||
import logging
|
||||
import requests
|
||||
import websockets
|
||||
import asyncio
|
||||
import random
|
||||
|
||||
from requests.auth import HTTPBasicAuth
|
||||
from time import sleep
|
||||
|
||||
requests.adapters.DEFAULT_RETRIES = 5 # increase retries number
|
||||
|
||||
ping_timeout = 180
|
||||
ping_interval = 15
|
||||
max_queue = 10000
|
||||
|
||||
min_sleep = 0.5
|
||||
max_sleep = 5.0
|
||||
|
||||
def decode(r, verbose_errors=True):
|
||||
try:
|
||||
@ -31,25 +42,78 @@ class Client(object):
|
||||
self.websocket = "ws://%s:%s@%s:%d/api" % (username, password, hostname, port)
|
||||
self.auth = HTTPBasicAuth(username, password)
|
||||
|
||||
def session(self):
|
||||
r = requests.get("%s/session" % self.url, auth=self.auth)
|
||||
# session takes optional argument to pull a sub-dictionary
|
||||
# ex.: "session/wifi", "session/ble"
|
||||
def session(self, sess="session"):
|
||||
r = requests.get("%s/%s" % (self.url, sess), auth=self.auth)
|
||||
return decode(r)
|
||||
|
||||
async def start_websocket(self, consumer):
|
||||
s = "%s/events" % self.websocket
|
||||
|
||||
# More modern version of the approach below
|
||||
# logging.info("Creating new websocket...")
|
||||
# async for ws in websockets.connect(s):
|
||||
# try:
|
||||
# async for msg in ws:
|
||||
# try:
|
||||
# await consumer(msg)
|
||||
# except Exception as ex:
|
||||
# logging.debug("Error while parsing event (%s)", ex)
|
||||
# except websockets.exceptions.ConnectionClosedError:
|
||||
# sleep_time = max_sleep*random.random()
|
||||
# logging.warning('Retrying websocket connection in {} sec'.format(sleep_time))
|
||||
# await asyncio.sleep(sleep_time)
|
||||
# continue
|
||||
|
||||
# restarted every time the connection fails
|
||||
while True:
|
||||
try:
|
||||
async with websockets.connect(s, ping_interval=60, ping_timeout=90) as ws:
|
||||
async for msg in ws:
|
||||
logging.info("creating new websocket...")
|
||||
try:
|
||||
async with websockets.connect(s, ping_interval=ping_interval, ping_timeout=ping_timeout, max_queue=max_queue) as ws:
|
||||
# listener loop
|
||||
while True:
|
||||
try:
|
||||
await consumer(msg)
|
||||
except Exception as ex:
|
||||
logging.debug("Error while parsing event (%s)", ex)
|
||||
except websockets.exceptions.ConnectionClosedError:
|
||||
logging.debug("Lost websocket connection. Reconnecting...")
|
||||
except websockets.exceptions.WebSocketException as wex:
|
||||
logging.debug("Websocket exception (%s)", wex)
|
||||
async for msg in ws:
|
||||
try:
|
||||
await consumer(msg)
|
||||
except Exception as ex:
|
||||
logging.debug("error while parsing event (%s)", ex)
|
||||
except websockets.exceptions.ConnectionClosedError:
|
||||
try:
|
||||
pong = await ws.ping()
|
||||
await asyncio.wait_for(pong, timeout=ping_timeout)
|
||||
logging.warning('ping OK, keeping connection alive...')
|
||||
continue
|
||||
except:
|
||||
sleep_time = min_sleep + max_sleep*random.random()
|
||||
logging.warning('ping error - retrying connection in {} sec'.format(sleep_time))
|
||||
await asyncio.sleep(sleep_time)
|
||||
break
|
||||
except ConnectionRefusedError:
|
||||
sleep_time = min_sleep + max_sleep*random.random()
|
||||
logging.warning('nobody seems to be listening at the bettercap endpoint...')
|
||||
logging.warning('retrying connection in {} sec'.format(sleep_time))
|
||||
await asyncio.sleep(sleep_time)
|
||||
continue
|
||||
except OSError:
|
||||
sleep_time = min_sleep + max_sleep*random.random()
|
||||
logging.warning('connection to the bettercap endpoint failed...')
|
||||
logging.warning('retrying connection in {} sec'.format(sleep_time))
|
||||
await asyncio.sleep(sleep_time)
|
||||
continue
|
||||
|
||||
|
||||
def run(self, command, verbose_errors=True):
|
||||
r = requests.post("%s/session" % self.url, auth=self.auth, json={'cmd': command})
|
||||
while True:
|
||||
try:
|
||||
r = requests.post("%s/session" % self.url, auth=self.auth, json={'cmd': command})
|
||||
except requests.exceptions.ConnectionError as e:
|
||||
sleep_time = min_sleep + max_sleep*random.random()
|
||||
logging.warning("can't run my request... connection to the bettercap endpoint failed...")
|
||||
logging.warning('retrying run in {} sec'.format(sleep_time))
|
||||
sleep(sleep_time)
|
||||
else:
|
||||
break
|
||||
|
||||
return decode(r, verbose_errors=verbose_errors)
|
||||
|
@ -1,9 +1,9 @@
|
||||
main.name = ""
|
||||
main.lang = "en"
|
||||
main.confd = "/etc/pwnagotchi/conf.d/"
|
||||
main.custom_plugins = ""
|
||||
main.custom_plugins = "/usr/local/share/pwnagotchi/custom-plugins"
|
||||
main.custom_plugin_repos = [
|
||||
"https://github.com/evilsocket/pwnagotchi-plugins-contrib/archive/master.zip"
|
||||
"https://git.chadwaltercummings.me/scifijunkie/pwnagotchi-plugins-contrib.git"
|
||||
]
|
||||
main.iface = "mon0"
|
||||
main.mon_start_cmd = "/usr/bin/monstart"
|
||||
@ -18,6 +18,8 @@ main.whitelist = [
|
||||
]
|
||||
main.filter = ""
|
||||
|
||||
main.log.debug = false
|
||||
|
||||
main.plugins.grid.enabled = true
|
||||
main.plugins.grid.report = false
|
||||
main.plugins.grid.exclude = [
|
||||
@ -33,7 +35,7 @@ main.plugins.net-pos.api_key = "test"
|
||||
|
||||
main.plugins.gps.enabled = false
|
||||
main.plugins.gps.speed = 19200
|
||||
main.plugins.gps.device = "/dev/ttyUSB0"
|
||||
main.plugins.gps.device = "/dev/ttyUSB0" # for GPSD: "localhost:2947"
|
||||
|
||||
main.plugins.webgpsmap.enabled = false
|
||||
|
||||
@ -47,6 +49,7 @@ main.plugins.wpa-sec.enabled = false
|
||||
main.plugins.wpa-sec.api_key = ""
|
||||
main.plugins.wpa-sec.api_url = "https://wpa-sec.stanev.org"
|
||||
main.plugins.wpa-sec.download_results = false
|
||||
main.plugins.wpa-sec.download_interval = 3600
|
||||
main.plugins.wpa-sec.whitelist = []
|
||||
|
||||
main.plugins.wigle.enabled = false
|
||||
@ -83,7 +86,10 @@ main.plugins.memtemp.scale = "celsius"
|
||||
main.plugins.memtemp.orientation = "horizontal"
|
||||
|
||||
main.plugins.paw-gps.enabled = false
|
||||
main.plugins.paw-gps.ip = ""
|
||||
main.plugins.paw-gps.ip = "192.168.44.1:8080"
|
||||
|
||||
main.plugins.ups_lite.enabled = false
|
||||
main.plugins.ups_lite.shutdown = 2
|
||||
|
||||
main.plugins.gpio_buttons.enabled = false
|
||||
|
||||
|
BIN
pwnagotchi/locale/hr/LC_MESSAGES/voice.mo
Normal file
BIN
pwnagotchi/locale/hr/LC_MESSAGES/voice.mo
Normal file
Binary file not shown.
253
pwnagotchi/locale/hr/LC_MESSAGES/voice.po
Normal file
253
pwnagotchi/locale/hr/LC_MESSAGES/voice.po
Normal file
@ -0,0 +1,253 @@
|
||||
# Croatian translation
|
||||
# Copyright (C) 2021
|
||||
# This file is distributed under the same license as the pwnagotchi package.
|
||||
# FIRST AUTHOR dbukovac <37124354+dbukovac@users.noreply.github.com>, 2021.
|
||||
#
|
||||
#, fuzzy
|
||||
msgid ""
|
||||
msgstr ""
|
||||
"Project-Id-Version: 0.0.1\n"
|
||||
"Report-Msgid-Bugs-To: \n"
|
||||
"POT-Creation-Date: 2019-11-29 21:50+0100\n"
|
||||
"PO-Revision-Date: 2021-07-16 00:20+0100\n"
|
||||
"Last-Translator: dbukovac <37124354+dbukovac@users.noreply.github.com>\n"
|
||||
"Language-Team: HR <37124354+dbukovac@users.noreply.github.com>\n"
|
||||
"Language: Croatian\n"
|
||||
"MIME-Version: 1.0\n"
|
||||
"Content-Type: text/plain; charset=CHARSET\n"
|
||||
"Content-Transfer-Encoding: 8bit\n"
|
||||
|
||||
msgid "ZzzzZZzzzzZzzz"
|
||||
msgstr "ZzzzZZzzzzZzzz"
|
||||
|
||||
msgid "Hi, I'm Pwnagotchi! Starting ..."
|
||||
msgstr "Zdravo, ja sam Pwnagotchi! Pokrećem se ..."
|
||||
|
||||
msgid "New day, new hunt, new pwns!"
|
||||
msgstr "Novi dan, novi lov, nove pobjede!"
|
||||
|
||||
msgid "Hack the Planet!"
|
||||
msgstr "Hakiraj planet!"
|
||||
|
||||
msgid "AI ready."
|
||||
msgstr "UI spremna."
|
||||
|
||||
msgid "The neural network is ready."
|
||||
msgstr "Neuralna mreža je spremna."
|
||||
|
||||
msgid "Generating keys, do not turn off ..."
|
||||
msgstr "Generiram ključeve, nemoj me gasiti ..."
|
||||
|
||||
#, python-brace-format
|
||||
msgid "Hey, channel {channel} is free! Your AP will say thanks."
|
||||
msgstr "Hej, kanal {channel} je slobodan! Tvoj AP ti zahvaljuje."
|
||||
|
||||
msgid "Reading last session logs ..."
|
||||
msgstr "Čitam logove zadnje sesije ..."
|
||||
|
||||
#, python-brace-format
|
||||
msgid "Read {lines_so_far} log lines so far ..."
|
||||
msgstr "Pročitao {lines_so_far} linija loga zasad ..."
|
||||
|
||||
msgid "I'm bored ..."
|
||||
msgstr "Dosadno mi je ..."
|
||||
|
||||
msgid "Let's go for a walk!"
|
||||
msgstr "Ajmo u šetnju!"
|
||||
|
||||
msgid "This is the best day of my life!"
|
||||
msgstr "Ovo je najbolji dan u mom životu!"
|
||||
|
||||
msgid "Shitty day :/"
|
||||
msgstr "Usrani dan :/"
|
||||
|
||||
msgid "I'm extremely bored ..."
|
||||
msgstr "Strašno mi je dosadno ..."
|
||||
|
||||
msgid "I'm very sad ..."
|
||||
msgstr "Jako sam tužan ..."
|
||||
|
||||
msgid "I'm sad"
|
||||
msgstr "Tužan sam ..."
|
||||
|
||||
msgid "Leave me alone ..."
|
||||
msgstr "Pusti me na miru ..."
|
||||
|
||||
msgid "I'm mad at you!"
|
||||
msgstr "Ljut sam na tebe!"
|
||||
|
||||
msgid "I'm living the life!"
|
||||
msgstr "To se zove život!"
|
||||
|
||||
msgid "I pwn therefore I am."
|
||||
msgstr "Pwnam dakle postojim."
|
||||
|
||||
msgid "So many networks!!!"
|
||||
msgstr "Toliko mreža!!!"
|
||||
|
||||
msgid "I'm having so much fun!"
|
||||
msgstr "Super se zabavljam!"
|
||||
|
||||
msgid "My crime is that of curiosity ..."
|
||||
msgstr "Znatiželja je moja jedina mana ..."
|
||||
|
||||
#, python-brace-format
|
||||
msgid "Hello {name}! Nice to meet you."
|
||||
msgstr "Bok {name}! Drago mi je da smo se upoznali. "
|
||||
|
||||
#, python-brace-format
|
||||
msgid "Yo {name}! Sup?"
|
||||
msgstr "Di si {name}! Šta ima?"
|
||||
|
||||
#, python-brace-format
|
||||
msgid "Hey {name} how are you doing?"
|
||||
msgstr "Bok {name} kako ide?"
|
||||
|
||||
#, python-brace-format
|
||||
msgid "Unit {name} is nearby!"
|
||||
msgstr "Jedinica {name} je u blizini!"
|
||||
|
||||
#, python-brace-format
|
||||
msgid "Uhm ... goodbye {name}"
|
||||
msgstr "Uhm ... doviđenja {name}"
|
||||
|
||||
#, python-brace-format
|
||||
msgid "{name} is gone ..."
|
||||
msgstr "{name} je nestao ..."
|
||||
|
||||
#, python-brace-format
|
||||
msgid "Whoops ... {name} is gone."
|
||||
msgstr "Ups ... {name} je nestao."
|
||||
|
||||
#, python-brace-format
|
||||
msgid "{name} missed!"
|
||||
msgstr "{name} mi nedostaje!"
|
||||
|
||||
msgid "Missed!"
|
||||
msgstr "Nedostaje mi!"
|
||||
|
||||
msgid "Good friends are a blessing!"
|
||||
msgstr "Imati dobre prijatelje je blagoslov!"
|
||||
|
||||
msgid "I love my friends!"
|
||||
msgstr "Volim svoj prijatelje!"
|
||||
|
||||
msgid "Nobody wants to play with me ..."
|
||||
msgstr "Nitko se ne želi igrati samnom ..."
|
||||
|
||||
msgid "I feel so alone ..."
|
||||
msgstr "Tako sam usamljen ..."
|
||||
|
||||
msgid "Where's everybody?!"
|
||||
msgstr "Gdje su svi nestali?!"
|
||||
|
||||
#, python-brace-format
|
||||
msgid "Napping for {secs}s ..."
|
||||
msgstr "Pajkim {secs}s ..."
|
||||
|
||||
msgid "Zzzzz"
|
||||
msgstr "Zzzzz"
|
||||
|
||||
#, python-brace-format
|
||||
msgid "ZzzZzzz ({secs}s)"
|
||||
msgstr "ZzzZzzz ({secs}s)"
|
||||
|
||||
msgid "Good night."
|
||||
msgstr "Laku noć."
|
||||
|
||||
msgid "Zzz"
|
||||
msgstr "Zzz"
|
||||
|
||||
#, python-brace-format
|
||||
msgid "Waiting for {secs}s ..."
|
||||
msgstr "Čekam {secs}s ..."
|
||||
|
||||
#, python-brace-format
|
||||
msgid "Looking around ({secs}s)"
|
||||
msgstr "Gledam uokolo {secs}s ..."
|
||||
|
||||
#, python-brace-format
|
||||
msgid "Hey {what} let's be friends!"
|
||||
msgstr "Bok {what} ajmo biti prijatelji!"
|
||||
|
||||
#, python-brace-format
|
||||
msgid "Associating to {what}"
|
||||
msgstr "Asociram se sa {what}"
|
||||
|
||||
#, python-brace-format
|
||||
msgid "Yo {what}!"
|
||||
msgstr "Šta ima {what}!"
|
||||
|
||||
#, python-brace-format
|
||||
msgid "Just decided that {mac} needs no WiFi!"
|
||||
msgstr "Upravo sam odlučio da {mac} ne treba WiFI!"
|
||||
|
||||
#, python-brace-format
|
||||
msgid "Deauthenticating {mac}"
|
||||
msgstr "Deautenticiram {mac}"
|
||||
|
||||
#, python-brace-format
|
||||
msgid "Kickbanning {mac}!"
|
||||
msgstr "Kickbannam {mac}!"
|
||||
|
||||
#, python-brace-format
|
||||
msgid "Cool, we got {num} new handshake{plural}!"
|
||||
msgstr "Fora, imamo {num} novih handshakeova!"
|
||||
|
||||
#, python-brace-format
|
||||
msgid "You have {count} new message{plural}!"
|
||||
msgstr "Imate {count} novih poruka!"
|
||||
|
||||
msgid "Oops, something went wrong ... Rebooting ..."
|
||||
msgstr "Ups, nešto je krepalo ... Rebooting ..."
|
||||
|
||||
#, python-brace-format
|
||||
msgid "Kicked {num} stations\n"
|
||||
msgstr "Šutnuo {num} stanica\n"
|
||||
|
||||
#, python-brace-format
|
||||
msgid "Made {num} new friends\n"
|
||||
msgstr "Upoznao {num} novih prijatelja\n"
|
||||
|
||||
#, python-brace-format
|
||||
msgid "Got {num} handshakes\n"
|
||||
msgstr "Pokupio {num} handshakeova\n"
|
||||
|
||||
msgid "Met 1 peer"
|
||||
msgstr "Sreo 1 novog druga"
|
||||
|
||||
#, python-brace-format
|
||||
msgid "Met {num} peers"
|
||||
msgstr "Sreo {num} druga"
|
||||
|
||||
#, python-brace-format
|
||||
msgid ""
|
||||
"I've been pwning for {duration} and kicked {deauthed} clients! I've also met "
|
||||
"{associated} new friends and ate {handshakes} handshakes! #pwnagotchi "
|
||||
"#pwnlog #pwnlife #hacktheplanet #skynet"
|
||||
msgstr ""
|
||||
"Pwnam {duration} vremena i šutnuo sam {deauthed} klijenata! Sreo sam"
|
||||
"{associated} novih prijatelja i pojeo {handshakes} handshakeova! #pwnagotchi "
|
||||
"#pwnlog #pwnlife #hacktheplanet #skynet"
|
||||
|
||||
msgid "hours"
|
||||
msgstr "sati"
|
||||
|
||||
msgid "minutes"
|
||||
msgstr "minuta"
|
||||
|
||||
msgid "seconds"
|
||||
msgstr "sekundi"
|
||||
|
||||
msgid "hour"
|
||||
msgstr "sat"
|
||||
|
||||
msgid "minute"
|
||||
msgstr "minuta"
|
||||
|
||||
msgid "second"
|
||||
msgstr "sekunda"
|
||||
|
||||
#, python-brace-format
|
||||
msgid "Uploading data to {to} ..."
|
||||
msgstr "Šaljem podatke na {to} ..."
|
BIN
pwnagotchi/locale/tr/LC_MESSAGES/voice.mo
Normal file
BIN
pwnagotchi/locale/tr/LC_MESSAGES/voice.mo
Normal file
Binary file not shown.
252
pwnagotchi/locale/tr/LC_MESSAGES/voice.po
Normal file
252
pwnagotchi/locale/tr/LC_MESSAGES/voice.po
Normal file
@ -0,0 +1,252 @@
|
||||
# Pwnagotchi Turkish translation.
|
||||
# Copyright (C) 2021
|
||||
# This file is distributed under the same license as the PACKAGE package.
|
||||
# FIRST AUTHOR <abtonc@icloud.com>, 2021.
|
||||
#
|
||||
msgid ""
|
||||
msgstr ""
|
||||
"Project-Id-Version: 0.0.1\n"
|
||||
"Report-Msgid-Bugs-To: \n"
|
||||
"POT-Creation-Date: 2019-11-29 21:50+0100\n"
|
||||
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
|
||||
"Last-Translator: Arda Barış Tonç <abtonc@icloud.com>\n"
|
||||
"Language-Team: \n"
|
||||
"Language: Turkish\n"
|
||||
"MIME-Version: 1.0\n"
|
||||
"Content-Type: text/plain; charset=utf-8\n"
|
||||
"Content-Transfer-Encoding: 8bit\n"
|
||||
|
||||
msgid "ZzzzZZzzzzZzzz"
|
||||
msgstr "ZzzzZzzZzZZzzzzZ"
|
||||
|
||||
msgid "Hi, I'm Pwnagotchi! Starting ..."
|
||||
msgstr "Merhaba, ben Pwnagotchi! Başlatılıyorum ..."
|
||||
|
||||
msgid "New day, new hunt, new pwns!"
|
||||
msgstr "Yeni bir gün, yeni bir av, yeni pwn'lar!"
|
||||
|
||||
msgid "Hack the Planet!"
|
||||
msgstr "Dünyayı Hackle!"
|
||||
|
||||
msgid "AI ready."
|
||||
msgstr "Yapay zeka hazır."
|
||||
|
||||
msgid "The neural network is ready."
|
||||
msgstr "Nöral ağ hazır."
|
||||
|
||||
msgid "Generating keys, do not turn off ..."
|
||||
msgstr "Anahatarlar oluşturuluyor, lütfen kapatmayın ..."
|
||||
|
||||
#, python-brace-format
|
||||
msgid "Hey, channel {channel} is free! Your AP will say thanks."
|
||||
msgstr "Hey, {channel} kanalı boş! AP'niz teşekkür edecek."
|
||||
|
||||
msgid "Reading last session logs ..."
|
||||
msgstr "Son oturum kayıtları okunuyor ..."
|
||||
|
||||
#, python-brace-format
|
||||
msgid "Read {lines_so_far} log lines so far ..."
|
||||
msgstr "Şimdiye kadar {lines_so_far} kayıt satırı okundu ..."
|
||||
|
||||
msgid "I'm bored ..."
|
||||
msgstr "Sıkıldım ..."
|
||||
|
||||
msgid "Let's go for a walk!"
|
||||
msgstr "Yürüyüşe çıkalım!"
|
||||
|
||||
msgid "This is the best day of my life!"
|
||||
msgstr "Bugün hayatımın en iyi günü!"
|
||||
|
||||
msgid "Shitty day :/"
|
||||
msgstr "Bok gibi bir gün :/"
|
||||
|
||||
msgid "I'm extremely bored ..."
|
||||
msgstr "Çook sıkıldım ..."
|
||||
|
||||
msgid "I'm very sad ..."
|
||||
msgstr "Çok mutsuzum ..."
|
||||
|
||||
msgid "I'm sad"
|
||||
msgstr "Mutsuzum"
|
||||
|
||||
msgid "Leave me alone ..."
|
||||
msgstr "Beni yalnız bırak ..."
|
||||
|
||||
msgid "I'm mad at you!"
|
||||
msgstr "Sana kızgınım!"
|
||||
|
||||
msgid "I'm living the life!"
|
||||
msgstr "Bu hayatı yaşıyorum!"
|
||||
|
||||
msgid "I pwn therefore I am."
|
||||
msgstr "Ben, pwn'ladığım için benim."
|
||||
|
||||
msgid "So many networks!!!"
|
||||
msgstr "Çok fazla ağ var!!!"
|
||||
|
||||
msgid "I'm having so much fun!"
|
||||
msgstr "Çok eğleniyorum!"
|
||||
|
||||
msgid "My crime is that of curiosity ..."
|
||||
msgstr "Tek suçum merak etmek ..."
|
||||
|
||||
#, python-brace-format
|
||||
msgid "Hello {name}! Nice to meet you."
|
||||
msgstr "Merhaba {name}! Tanıştığıma memnun oldum."
|
||||
|
||||
#, python-brace-format
|
||||
msgid "Yo {name}! Sup?"
|
||||
msgstr "{name}, kanka! Naber?"
|
||||
|
||||
#, python-brace-format
|
||||
msgid "Hey {name} how are you doing?"
|
||||
msgstr "Nasılsın {name}?"
|
||||
|
||||
#, python-brace-format
|
||||
msgid "Unit {name} is nearby!"
|
||||
msgstr "{name} birimi yakında!"
|
||||
|
||||
#, python-brace-format
|
||||
msgid "Uhm ... goodbye {name}"
|
||||
msgstr "ııı ... görüşürüz {name}"
|
||||
|
||||
#, python-brace-format
|
||||
msgid "{name} is gone ..."
|
||||
msgstr "{name} gitti."
|
||||
|
||||
#, python-brace-format
|
||||
msgid "Whoops ... {name} is gone."
|
||||
msgstr "Hoppala ... {name} gitti."
|
||||
|
||||
#, python-brace-format
|
||||
msgid "{name} missed!"
|
||||
msgstr "{name}'i kaçırdık ya!"
|
||||
|
||||
msgid "Missed!"
|
||||
msgstr "Kaçırdık!"
|
||||
|
||||
msgid "Good friends are a blessing!"
|
||||
msgstr "İyi arkadaşlar nimettir!"
|
||||
|
||||
msgid "I love my friends!"
|
||||
msgstr "Arkadaşlarımı seviyorum!"
|
||||
|
||||
msgid "Nobody wants to play with me ..."
|
||||
msgstr "Hiç kimse benimle birlikte oynamak istemiyor ..."
|
||||
|
||||
msgid "I feel so alone ..."
|
||||
msgstr "Çok yalnız hissediyorum ..."
|
||||
|
||||
msgid "Where's everybody?!"
|
||||
msgstr "Herkes nerede!?"
|
||||
|
||||
#, python-brace-format
|
||||
msgid "Napping for {secs}s ..."
|
||||
msgstr "{secs}dir kestiriyorum ..."
|
||||
|
||||
msgid "Zzzzz"
|
||||
msgstr "ZzzzZz"
|
||||
|
||||
#, python-brace-format
|
||||
msgid "ZzzZzzz ({secs}s)"
|
||||
msgstr "ZzzZzzZ ({secs})"
|
||||
|
||||
msgid "Good night."
|
||||
msgstr "İyi geceler."
|
||||
|
||||
msgid "Zzz"
|
||||
msgstr "ZzZ"
|
||||
|
||||
#, python-brace-format
|
||||
msgid "Waiting for {secs}s ..."
|
||||
msgstr "{secs}dir bekleniyor ..."
|
||||
|
||||
#, python-brace-format
|
||||
msgid "Looking around ({secs}s)"
|
||||
msgstr "Etrafa bakıyorum ({secs})"
|
||||
|
||||
#, python-brace-format
|
||||
msgid "Hey {what} let's be friends!"
|
||||
msgstr "Arkadaş olalım {what}!"
|
||||
|
||||
#, python-brace-format
|
||||
msgid "Associating to {what}"
|
||||
msgstr "{what} ile tanışıyoruz"
|
||||
|
||||
#, python-brace-format
|
||||
msgid "Yo {what}!"
|
||||
msgstr "Hey {what}!"
|
||||
|
||||
#, python-brace-format
|
||||
msgid "Just decided that {mac} needs no WiFi!"
|
||||
msgstr "Sanırım {mac}'in WiFi'a ihtiyacı yok!"
|
||||
|
||||
#, python-brace-format
|
||||
msgid "Deauthenticating {mac}"
|
||||
msgstr "{mac} ağdan çıkarılıyor"
|
||||
|
||||
#, python-brace-format
|
||||
msgid "Kickbanning {mac}!"
|
||||
msgstr "{mac} atılıp yasaklanıyor!"
|
||||
|
||||
#, python-brace-format
|
||||
msgid "Cool, we got {num} new handshake{plural}!"
|
||||
msgstr "Güzel, yeni {num} el sıkıştık!"
|
||||
|
||||
#, python-brace-format
|
||||
msgid "You have {count} new message{plural}!"
|
||||
msgstr "{count} Tane yeni mesajınız var!"
|
||||
|
||||
msgid "Oops, something went wrong ... Rebooting ..."
|
||||
msgstr "Haydaa, bir şeyler ters gitti ... Yeniden başlatılıyor ..."
|
||||
|
||||
#, python-brace-format
|
||||
msgid "Kicked {num} stations\n"
|
||||
msgstr "{num} İstasyon atıldı\n"
|
||||
|
||||
#, python-brace-format
|
||||
msgid "Made {num} new friends\n"
|
||||
msgstr "{num} Yeni arkadaş edindim\n"
|
||||
|
||||
#, python-brace-format
|
||||
msgid "Got {num} handshakes\n"
|
||||
msgstr "{num} El sıkıştım\n"
|
||||
|
||||
msgid "Met 1 peer"
|
||||
msgstr "1 Kişiyle tanıştım"
|
||||
|
||||
#, python-brace-format
|
||||
msgid "Met {num} peers"
|
||||
msgstr "{num} Kişiyle tanıştım"
|
||||
|
||||
#, python-brace-format
|
||||
msgid ""
|
||||
"I've been pwning for {duration} and kicked {deauthed} clients! I've also met "
|
||||
"{associated} new friends and ate {handshakes} handshakes! #pwnagotchi "
|
||||
"#pwnlog #pwnlife #hacktheplanet #skynet"
|
||||
msgstr ""
|
||||
"{duration}'dır pwn'lıyorum ve {deauthed} kişiyi attım. Hem de {associated}"
|
||||
"yeni kişiyle tanıştım ve {handshakes} el sıkıştım! #pwnagotchi "
|
||||
"#pwnlog #pwnyaşam #dünyayıhackle #skynet"
|
||||
|
||||
msgid "hours"
|
||||
msgstr "saat"
|
||||
|
||||
msgid "minutes"
|
||||
msgstr "dakika"
|
||||
|
||||
msgid "seconds"
|
||||
msgstr "saniye"
|
||||
|
||||
msgid "hour"
|
||||
msgstr "saat"
|
||||
|
||||
msgid "minute"
|
||||
msgstr "dakika"
|
||||
|
||||
msgid "second"
|
||||
msgstr "saniye"
|
||||
|
||||
#, python-brace-format
|
||||
msgid "Uploading data to {to} ..."
|
||||
msgstr "{to}'ye veri yükleniyor ..."
|
@ -221,7 +221,7 @@ def setup_logging(args, config):
|
||||
formatter = logging.Formatter("[%(asctime)s] [%(levelname)s] %(message)s")
|
||||
root = logging.getLogger()
|
||||
|
||||
root.setLevel(logging.DEBUG if args.debug else logging.INFO)
|
||||
root.setLevel(logging.DEBUG if args.debug or cfg['debug']==True else logging.INFO)
|
||||
|
||||
if filename:
|
||||
# since python default log rotation might break session data in different files,
|
||||
@ -307,3 +307,5 @@ def do_rotate(filename, stats, cfg):
|
||||
with open(log_filename, 'rb') as src:
|
||||
with gzip.open(archive_filename, 'wb') as dst:
|
||||
dst.writelines(src)
|
||||
|
||||
os.remove(log_filename)
|
||||
|
@ -1,17 +1,17 @@
|
||||
import os
|
||||
import glob
|
||||
import _thread
|
||||
import threading
|
||||
import importlib, importlib.util
|
||||
import logging
|
||||
|
||||
|
||||
from concurrent.futures import ThreadPoolExecutor
|
||||
|
||||
default_path = os.path.join(os.path.dirname(os.path.realpath(__file__)), "default")
|
||||
loaded = {}
|
||||
database = {}
|
||||
locks = {}
|
||||
|
||||
THREAD_POOL_SIZE = 10
|
||||
executor = ThreadPoolExecutor(max_workers=THREAD_POOL_SIZE)
|
||||
|
||||
class Plugin:
|
||||
@classmethod
|
||||
@ -30,7 +30,6 @@ class Plugin:
|
||||
if cb is not None and callable(cb):
|
||||
locks["%s::%s" % (plugin_name, attr_name)] = threading.Lock()
|
||||
|
||||
|
||||
def toggle_plugin(name, enable=True):
|
||||
"""
|
||||
Load or unload a plugin
|
||||
@ -69,12 +68,10 @@ def toggle_plugin(name, enable=True):
|
||||
|
||||
return False
|
||||
|
||||
|
||||
def on(event_name, *args, **kwargs):
|
||||
for plugin_name in loaded.keys():
|
||||
one(plugin_name, event_name, *args, **kwargs)
|
||||
|
||||
|
||||
def locked_cb(lock_name, cb, *args, **kwargs):
|
||||
global locks
|
||||
|
||||
@ -84,7 +81,6 @@ def locked_cb(lock_name, cb, *args, **kwargs):
|
||||
with locks[lock_name]:
|
||||
cb(*args, *kwargs)
|
||||
|
||||
|
||||
def one(plugin_name, event_name, *args, **kwargs):
|
||||
global loaded
|
||||
|
||||
@ -96,12 +92,11 @@ def one(plugin_name, event_name, *args, **kwargs):
|
||||
try:
|
||||
lock_name = "%s::%s" % (plugin_name, cb_name)
|
||||
locked_cb_args = (lock_name, callback, *args, *kwargs)
|
||||
_thread.start_new_thread(locked_cb, locked_cb_args)
|
||||
executor.submit(locked_cb, *locked_cb_args)
|
||||
except Exception as e:
|
||||
logging.error("error while running %s.%s : %s" % (plugin_name, cb_name, e))
|
||||
logging.error(e, exc_info=True)
|
||||
|
||||
|
||||
def load_from_file(filename):
|
||||
logging.debug("loading %s" % filename)
|
||||
plugin_name = os.path.basename(filename.replace(".py", ""))
|
||||
@ -110,7 +105,6 @@ def load_from_file(filename):
|
||||
spec.loader.exec_module(instance)
|
||||
return plugin_name, instance
|
||||
|
||||
|
||||
def load_from_path(path, enabled=()):
|
||||
global loaded, database
|
||||
logging.debug("loading plugins from %s - enabled: %s" % (path, enabled))
|
||||
@ -126,7 +120,6 @@ def load_from_path(path, enabled=()):
|
||||
|
||||
return loaded
|
||||
|
||||
|
||||
def load(config):
|
||||
enabled = [name for name, options in config['main']['plugins'].items() if
|
||||
'enabled' in options and options['enabled']]
|
||||
|
@ -10,7 +10,7 @@ from pwnagotchi.utils import download_file, unzip, save_config, parse_version, m
|
||||
from pwnagotchi.plugins import default_path
|
||||
|
||||
|
||||
SAVE_DIR = '/usr/local/share/pwnagotchi/availaible-plugins/'
|
||||
SAVE_DIR = '/usr/local/share/pwnagotchi/available-plugins/'
|
||||
DEFAULT_INSTALL_PATH = '/usr/local/share/pwnagotchi/installed-plugins/'
|
||||
|
||||
|
||||
|
@ -7,14 +7,15 @@ import platform
|
||||
import shutil
|
||||
import glob
|
||||
from threading import Lock
|
||||
import time
|
||||
|
||||
import pwnagotchi
|
||||
import pwnagotchi.plugins as plugins
|
||||
from pwnagotchi.utils import StatusFile, parse_version as version_to_tuple
|
||||
|
||||
|
||||
def check(version, repo, native=True):
|
||||
logging.debug("checking remote version for %s, local is %s" % (repo, version))
|
||||
def check_remote_version(version, repo, native=True):
|
||||
logging.debug("Checking remote version for %s, local is %s" % (repo, version))
|
||||
info = {
|
||||
'repo': repo,
|
||||
'current': version,
|
||||
@ -24,81 +25,92 @@ def check(version, repo, native=True):
|
||||
'arch': platform.machine()
|
||||
}
|
||||
|
||||
resp = requests.get("https://api.github.com/repos/%s/releases/latest" % repo)
|
||||
latest = resp.json()
|
||||
info['available'] = latest_ver = latest['tag_name'].replace('v', '')
|
||||
is_arm = info['arch'].startswith('arm')
|
||||
try:
|
||||
resp = requests.get(f"https://api.github.com/repos/{repo}/releases/latest")
|
||||
resp.raise_for_status()
|
||||
latest = resp.json()
|
||||
info['available'] = latest_ver = latest['tag_name'].replace('v', '')
|
||||
|
||||
local = version_to_tuple(info['current'])
|
||||
remote = version_to_tuple(latest_ver)
|
||||
if remote > local:
|
||||
if not native:
|
||||
info['url'] = "https://github.com/%s/archive/%s.zip" % (repo, latest['tag_name'])
|
||||
else:
|
||||
# check if this release is compatible with arm6
|
||||
for asset in latest['assets']:
|
||||
download_url = asset['browser_download_url']
|
||||
if download_url.endswith('.zip') and (
|
||||
info['arch'] in download_url or (is_arm and 'armhf' in download_url)):
|
||||
info['url'] = download_url
|
||||
break
|
||||
is_arm = info['arch'].startswith('arm')
|
||||
local = version_to_tuple(info['current'])
|
||||
remote = version_to_tuple(latest_ver)
|
||||
|
||||
if remote > local:
|
||||
if not native:
|
||||
info['url'] = f"https://github.com/{repo}/archive/{latest['tag_name']}.zip"
|
||||
else:
|
||||
for asset in latest['assets']:
|
||||
download_url = asset['browser_download_url']
|
||||
if download_url.endswith('.zip') and (
|
||||
info['arch'] in download_url or (is_arm and 'armhf' in download_url)):
|
||||
info['url'] = download_url
|
||||
break
|
||||
except Exception as e:
|
||||
logging.error(f"Error checking remote version for {repo}: {e}")
|
||||
|
||||
return info
|
||||
|
||||
|
||||
def make_path_for(name):
|
||||
path = os.path.join("/tmp/updates/", name)
|
||||
if os.path.exists(path):
|
||||
logging.debug("[update] deleting %s" % path)
|
||||
shutil.rmtree(path, ignore_errors=True, onerror=None)
|
||||
os.makedirs(path)
|
||||
try:
|
||||
if os.path.exists(path):
|
||||
logging.debug("[update] Deleting %s" % path)
|
||||
shutil.rmtree(path, ignore_errors=True, onerror=None)
|
||||
os.makedirs(path)
|
||||
except Exception as e:
|
||||
logging.error(f"Error creating path for {name}: {e}")
|
||||
return path
|
||||
|
||||
|
||||
def download_and_unzip(name, path, display, update):
|
||||
target = "%s_%s.zip" % (name, update['available'])
|
||||
target = f"{name}_{update['available']}.zip"
|
||||
target_path = os.path.join(path, target)
|
||||
|
||||
logging.info("[update] downloading %s to %s ..." % (update['url'], target_path))
|
||||
display.update(force=True, new_data={'status': 'Downloading %s %s ...' % (name, update['available'])})
|
||||
try:
|
||||
logging.info("[update] Downloading %s to %s ..." % (update['url'], target_path))
|
||||
display.update(force=True, new_data={'status': f'Downloading {name} {update["available"]} ...'})
|
||||
subprocess.run(['wget', '-q', update['url'], '-O', target_path], check=True)
|
||||
|
||||
os.system('wget -q "%s" -O "%s"' % (update['url'], target_path))
|
||||
logging.info("[update] Extracting %s to %s ..." % (target_path, path))
|
||||
display.update(force=True, new_data={'status': f'Extracting {name} {update["available"]} ...'})
|
||||
subprocess.run(['unzip', target_path, '-d', path], check=True)
|
||||
|
||||
logging.info("[update] extracting %s to %s ..." % (target_path, path))
|
||||
display.update(force=True, new_data={'status': 'Extracting %s %s ...' % (name, update['available'])})
|
||||
|
||||
os.system('unzip "%s" -d "%s"' % (target_path, path))
|
||||
except Exception as e:
|
||||
logging.error(f"Error downloading and unzipping {name} update: {e}")
|
||||
|
||||
|
||||
def verify(name, path, source_path, display, update):
|
||||
display.update(force=True, new_data={'status': 'Verifying %s %s ...' % (name, update['available'])})
|
||||
display.update(force=True, new_data={'status': f'Verifying {name} {update["available"]} ...'})
|
||||
|
||||
checksums = glob.glob("%s/*.sha256" % path)
|
||||
if len(checksums) == 0:
|
||||
if update['native']:
|
||||
logging.warning("[update] native update without SHA256 checksum file")
|
||||
return False
|
||||
try:
|
||||
checksums = glob.glob(f"{path}/*.sha256")
|
||||
if len(checksums) == 0:
|
||||
if update['native']:
|
||||
logging.warning("[update] Native update without SHA256 checksum file")
|
||||
return False
|
||||
else:
|
||||
checksum = checksums[0]
|
||||
logging.info(f"[update] Verifying {checksum} for {source_path} ...")
|
||||
|
||||
else:
|
||||
checksum = checksums[0]
|
||||
with open(checksum, 'rt') as fp:
|
||||
expected = fp.read().split('=')[1].strip().lower()
|
||||
|
||||
logging.info("[update] verifying %s for %s ..." % (checksum, source_path))
|
||||
real = subprocess.getoutput(f'sha256sum "{source_path}"').split(' ')[0].strip().lower()
|
||||
|
||||
with open(checksum, 'rt') as fp:
|
||||
expected = fp.read().split('=')[1].strip().lower()
|
||||
if real != expected:
|
||||
logging.warning(f"[update] Checksum mismatch for {source_path}: expected={expected} got={real}")
|
||||
return False
|
||||
|
||||
real = subprocess.getoutput('sha256sum "%s"' % source_path).split(' ')[0].strip().lower()
|
||||
|
||||
if real != expected:
|
||||
logging.warning("[update] checksum mismatch for %s: expected=%s got=%s" % (source_path, expected, real))
|
||||
return False
|
||||
except Exception as e:
|
||||
logging.error(f"Error verifying {name} update: {e}")
|
||||
return False
|
||||
|
||||
return True
|
||||
|
||||
|
||||
def install(display, update):
|
||||
name = update['repo'].split('/')[1]
|
||||
|
||||
path = make_path_for(name)
|
||||
|
||||
download_and_unzip(name, path, display, update)
|
||||
@ -107,37 +119,70 @@ def install(display, update):
|
||||
if not verify(name, path, source_path, display, update):
|
||||
return False
|
||||
|
||||
logging.info("[update] installing %s ..." % name)
|
||||
display.update(force=True, new_data={'status': 'Installing %s %s ...' % (name, update['available'])})
|
||||
try:
|
||||
logging.info("[update] Installing %s ..." % name)
|
||||
display.update(force=True, new_data={'status': f'Installing {name} {update["available"]} ...'})
|
||||
|
||||
if update['native']:
|
||||
dest_path = subprocess.getoutput("which %s" % name)
|
||||
if dest_path == "":
|
||||
logging.warning("[update] can't find path for %s" % name)
|
||||
return False
|
||||
if update['native']:
|
||||
dest_path = subprocess.getoutput(f"which {name}")
|
||||
if dest_path == "":
|
||||
logging.warning(f"[update] Can't find path for {name}")
|
||||
return False
|
||||
|
||||
logging.info("[update] stopping %s ..." % update['service'])
|
||||
os.system("service %s stop" % update['service'])
|
||||
os.system("mv %s %s" % (source_path, dest_path))
|
||||
logging.info("[update] restarting %s ..." % update['service'])
|
||||
os.system("service %s start" % update['service'])
|
||||
else:
|
||||
if not os.path.exists(source_path):
|
||||
source_path = "%s-%s" % (source_path, update['available'])
|
||||
logging.info(f"[update] Stopping {update['service']} ...")
|
||||
subprocess.run(["service", update['service'], "stop"], check=True)
|
||||
|
||||
# setup.py is going to install data files for us
|
||||
os.system("cd %s && pip3 install ." % source_path)
|
||||
subprocess.run(["mv", source_path, dest_path], check=True)
|
||||
logging.info(f"[update] Restarting {update['service']} ...")
|
||||
subprocess.run(["service", update['service'], "start"], check=True)
|
||||
else:
|
||||
if not os.path.exists(source_path):
|
||||
source_path = f"{source_path}-{update['available']}"
|
||||
|
||||
subprocess.run(["cd", source_path, "&&", "pip3", "install", "."], check=True, shell=True)
|
||||
|
||||
except Exception as e:
|
||||
logging.error(f"Error installing {name} update: {e}")
|
||||
return False
|
||||
|
||||
return True
|
||||
|
||||
|
||||
def parse_version(cmd):
|
||||
out = subprocess.getoutput(cmd)
|
||||
for part in out.split(' '):
|
||||
part = part.replace('v', '').strip()
|
||||
if re.search(r'^\d+\.\d+\.\d+.*$', part):
|
||||
return part
|
||||
raise Exception('could not parse version from "%s": output=\n%s' % (cmd, out))
|
||||
try:
|
||||
out = subprocess.getoutput(cmd)
|
||||
for part in out.split(' '):
|
||||
part = part.replace('v', '').strip()
|
||||
if re.search(r'^\d+\.\d+\.\d+.*$', part):
|
||||
return part
|
||||
except Exception as e:
|
||||
logging.error(f"Error parsing version from '{cmd}': {e}")
|
||||
raise Exception(f'Could not parse version from "{cmd}": output=\n{out}')
|
||||
|
||||
|
||||
def check_remote_version_with_retry(version, repo, native=True, max_retries=3):
|
||||
retries = 0
|
||||
while retries < max_retries:
|
||||
try:
|
||||
resp = requests.get(f"https://api.github.com/repos/{repo}/releases/latest")
|
||||
resp.raise_for_status()
|
||||
latest = resp.json()
|
||||
return check_remote_version(version, repo, native)
|
||||
except requests.exceptions.HTTPError as e:
|
||||
if e.response.status_code == 403:
|
||||
wait_time = 2 ** retries
|
||||
print(f"Rate limit exceeded. Retrying after {wait_time} seconds...")
|
||||
time.sleep(wait_time)
|
||||
retries += 1
|
||||
else:
|
||||
print(f"Error checking remote version for {repo}: {e}")
|
||||
raise e
|
||||
except requests.exceptions.ConnectionError as ce:
|
||||
wait_time = 2 ** retries
|
||||
print(f"Connection error. Retrying after {wait_time} seconds...")
|
||||
time.sleep(wait_time)
|
||||
retries += 1
|
||||
raise Exception(f"Failed to check remote version for {repo} after {max_retries} retries.")
|
||||
|
||||
|
||||
class AutoUpdate(plugins.Plugin):
|
||||
@ -157,23 +202,23 @@ class AutoUpdate(plugins.Plugin):
|
||||
logging.error("[update] main.plugins.auto-update.interval is not set")
|
||||
return
|
||||
self.ready = True
|
||||
logging.info("[update] plugin loaded.")
|
||||
logging.info("[update] Plugin loaded.")
|
||||
|
||||
def on_internet_available(self, agent):
|
||||
if self.lock.locked():
|
||||
return
|
||||
|
||||
with self.lock:
|
||||
logging.debug("[update] internet connectivity is available (ready %s)" % self.ready)
|
||||
logging.debug("[update] Internet connectivity is available (ready %s)" % self.ready)
|
||||
|
||||
if not self.ready:
|
||||
return
|
||||
|
||||
if self.status.newer_then_hours(self.options['interval']):
|
||||
logging.debug("[update] last check happened less than %d hours ago" % self.options['interval'])
|
||||
logging.debug("[update] Last check happened less than %d hours ago" % self.options['interval'])
|
||||
return
|
||||
|
||||
logging.info("[update] checking for updates ...")
|
||||
logging.info("[update] Checking for updates ...")
|
||||
|
||||
display = agent.view()
|
||||
prev_status = display.get('status')
|
||||
@ -185,15 +230,14 @@ class AutoUpdate(plugins.Plugin):
|
||||
to_check = [
|
||||
('bettercap/bettercap', parse_version('bettercap -version'), True, 'bettercap'),
|
||||
('evilsocket/pwngrid', parse_version('pwngrid -version'), True, 'pwngrid-peer'),
|
||||
('evilsocket/pwnagotchi', pwnagotchi.__version__, False, 'pwnagotchi')
|
||||
('scifijunk/pwnagotchi', pwnagotchi.__version__, False, 'pwnagotchi')
|
||||
]
|
||||
|
||||
for repo, local_version, is_native, svc_name in to_check:
|
||||
info = check(local_version, repo, is_native)
|
||||
info = check_remote_version_with_retry(local_version, repo, is_native)
|
||||
if info['url'] is not None:
|
||||
logging.warning(
|
||||
"update for %s available (local version is '%s'): %s" % (
|
||||
repo, info['current'], info['url']))
|
||||
f"Update for {repo} available (local version is '{info['current']}'): {info['url']}")
|
||||
info['service'] = svc_name
|
||||
to_install.append(info)
|
||||
|
||||
@ -207,9 +251,9 @@ class AutoUpdate(plugins.Plugin):
|
||||
if install(display, update):
|
||||
num_installed += 1
|
||||
else:
|
||||
prev_status = '%d new update%c available!' % (num_updates, 's' if num_updates > 1 else '')
|
||||
prev_status = f"{num_updates} new update{'s' if num_updates > 1 else ''} available!"
|
||||
|
||||
logging.info("[update] done")
|
||||
logging.info("[update] Done")
|
||||
|
||||
self.status.update()
|
||||
|
||||
|
@ -25,18 +25,20 @@ class GPS(plugins.Plugin):
|
||||
logging.info(f"gps plugin loaded for {self.options['device']}")
|
||||
|
||||
def on_ready(self, agent):
|
||||
if os.path.exists(self.options["device"]):
|
||||
if os.path.exists(self.options["device"]) or ":" in self.options["device"]:
|
||||
logging.info(
|
||||
f"enabling bettercap's gps module for {self.options['device']}"
|
||||
)
|
||||
try:
|
||||
agent.run("gps off")
|
||||
except Exception:
|
||||
logging.info(f"bettercap gps module was already off")
|
||||
pass
|
||||
|
||||
agent.run(f"set gps.device {self.options['device']}")
|
||||
agent.run(f"set gps.baudrate {self.options['speed']}")
|
||||
agent.run("gps on")
|
||||
logging.info(f"bettercap gps module enabled on {self.options['device']}")
|
||||
self.running = True
|
||||
else:
|
||||
logging.warning("no GPS detected")
|
||||
|
@ -10,25 +10,30 @@ GUIDE HERE: https://community.pwnagotchi.ai/t/setting-up-paw-gps-on-android
|
||||
|
||||
class PawGPS(plugins.Plugin):
|
||||
__author__ = 'leont'
|
||||
__version__ = '1.0.0'
|
||||
__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 '
|
||||
__description__ = 'Saves GPS coordinates whenever an handshake is captured. The GPS data is get from PAW on android.'
|
||||
|
||||
def on_loaded(self):
|
||||
logging.info("PAW-GPS loaded")
|
||||
if 'ip' not in self.options or ('ip' in self.options and self.options['ip'] is None):
|
||||
logging.info("PAW-GPS: No IP Address in the config file is defined, it uses the default (192.168.44.1:8080)")
|
||||
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):
|
||||
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']
|
||||
|
||||
gps = requests.get('http://' + ip + '/gps.xhtml')
|
||||
gps_filename = filename.replace('.pcap', '.paw-gps.json')
|
||||
|
||||
logging.info("saving GPS to %s (%s)" % (gps_filename, gps))
|
||||
with open(gps_filename, 'w+t') as f:
|
||||
f.write(gps.text)
|
||||
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}")
|
||||
|
@ -11,6 +11,7 @@
|
||||
# To display external power supply status you need to bridge the necessary pins on the UPS-Lite board. See instructions in the UPS-Lite repo.
|
||||
import logging
|
||||
import struct
|
||||
import subprocess
|
||||
|
||||
import RPi.GPIO as GPIO
|
||||
|
||||
@ -28,11 +29,16 @@ class UPS:
|
||||
import smbus
|
||||
# 0 = /dev/i2c-0 (port I2C0), 1 = /dev/i2c-1 (port I2C1)
|
||||
self._bus = smbus.SMBus(1)
|
||||
# Version v1.1 and v1.2
|
||||
self.address = 0x36
|
||||
if subprocess.run(['i2cget', '-y', '1', '0x62']).returncode == 0:
|
||||
# Version v1.3
|
||||
self.address = 0X62
|
||||
self._bus.write_word_data(self.address, 0X0A, 0x30)
|
||||
|
||||
def voltage(self):
|
||||
try:
|
||||
address = 0x36
|
||||
read = self._bus.read_word_data(address, 2)
|
||||
read = self._bus.read_word_data(self.address, 2)
|
||||
swapped = struct.unpack("<H", struct.pack(">H", read))[0]
|
||||
return swapped * 1.25 / 1000 / 16
|
||||
except:
|
||||
@ -40,8 +46,7 @@ class UPS:
|
||||
|
||||
def capacity(self):
|
||||
try:
|
||||
address = 0x36
|
||||
read = self._bus.read_word_data(address, 4)
|
||||
read = self._bus.read_word_data(self.address, 4)
|
||||
swapped = struct.unpack("<H", struct.pack(">H", read))[0]
|
||||
return swapped / 256
|
||||
except:
|
||||
@ -60,7 +65,7 @@ class UPSLite(plugins.Plugin):
|
||||
__author__ = 'evilsocket@gmail.com'
|
||||
__version__ = '1.0.0'
|
||||
__license__ = 'GPL3'
|
||||
__description__ = 'A plugin that will add a voltage indicator for the UPS Lite v1.1'
|
||||
__description__ = 'A plugin that will add a voltage indicator for the UPS Lite v1.1, v1.2, v1.3'
|
||||
|
||||
def __init__(self):
|
||||
self.ups = None
|
||||
|
@ -20,8 +20,14 @@ def _extract_gps_data(path):
|
||||
"""
|
||||
|
||||
try:
|
||||
with open(path, 'r') as json_file:
|
||||
return json.load(json_file)
|
||||
if path.endswith('.geo.json'):
|
||||
with open(path, 'r') as json_file:
|
||||
tempJson = json.load(json_file)
|
||||
d = datetime.utcfromtimestamp(int(tempJson["ts"]))
|
||||
return {"Latitude": tempJson["location"]["lat"], "Longitude": tempJson["location"]["lng"], "Altitude": 10, "Updated": d.strftime('%Y-%m-%dT%H:%M:%S.%f')}
|
||||
else:
|
||||
with open(path, 'r') as json_file:
|
||||
return json.load(json_file)
|
||||
except OSError as os_err:
|
||||
raise os_err
|
||||
except json.JSONDecodeError as json_err:
|
||||
@ -79,7 +85,6 @@ def _send_to_wigle(lines, api_key, donate=True, timeout=30):
|
||||
'Accept': 'application/json'}
|
||||
data = {'donate': 'on' if donate else 'false'}
|
||||
payload = {'file': dummy, 'type': 'text/csv'}
|
||||
|
||||
try:
|
||||
res = requests.post('https://api.wigle.net/api/v2/file/upload',
|
||||
data=data,
|
||||
@ -117,6 +122,7 @@ class Wigle(plugins.Plugin):
|
||||
self.options['donate'] = True
|
||||
|
||||
self.ready = True
|
||||
logging.info("WIGLE: ready")
|
||||
|
||||
def on_internet_available(self, agent):
|
||||
"""
|
||||
@ -134,7 +140,7 @@ class Wigle(plugins.Plugin):
|
||||
all_files = os.listdir(handshake_dir)
|
||||
all_gps_files = [os.path.join(handshake_dir, filename)
|
||||
for filename in all_files
|
||||
if filename.endswith('.gps.json')]
|
||||
if filename.endswith('.gps.json') or filename.endswith('.paw-gps.json') or filename.endswith('.geo.json')]
|
||||
|
||||
all_gps_files = remove_whitelisted(all_gps_files, self.options['whitelist'])
|
||||
new_gps_files = set(all_gps_files) - set(reported) - set(self.skip)
|
||||
@ -143,7 +149,12 @@ class Wigle(plugins.Plugin):
|
||||
csv_entries = list()
|
||||
no_err_entries = list()
|
||||
for gps_file in new_gps_files:
|
||||
pcap_filename = gps_file.replace('.gps.json', '.pcap')
|
||||
if gps_file.endswith('.gps.json'):
|
||||
pcap_filename = gps_file.replace('.gps.json', '.pcap')
|
||||
if gps_file.endswith('.paw-gps.json'):
|
||||
pcap_filename = gps_file.replace('.paw-gps.json', '.pcap')
|
||||
if gps_file.endswith('.geo.json'):
|
||||
pcap_filename = gps_file.replace('.geo.json', '.pcap')
|
||||
if not os.path.exists(pcap_filename):
|
||||
logging.debug("WIGLE: Can't find pcap for %s", gps_file)
|
||||
self.skip.append(gps_file)
|
||||
|
@ -132,7 +132,8 @@ class WpaSec(plugins.Plugin):
|
||||
cracked_file = os.path.join(handshake_dir, 'wpa-sec.cracked.potfile')
|
||||
if os.path.exists(cracked_file):
|
||||
last_check = datetime.fromtimestamp(os.path.getmtime(cracked_file))
|
||||
if last_check is not None and ((datetime.now() - last_check).seconds / (60 * 60)) < 1:
|
||||
download_interval = int(self.options['download_interval'])
|
||||
if last_check is not None and ((datetime.now() - last_check).seconds / download_interval) < 1:
|
||||
return
|
||||
try:
|
||||
self._download_from_wpasec(os.path.join(handshake_dir, 'wpa-sec.cracked.potfile'))
|
||||
|
@ -37,9 +37,18 @@ class Display(View):
|
||||
def is_waveshare_v2(self):
|
||||
return self._implementation.name == 'waveshare_2'
|
||||
|
||||
def is_waveshare_v3(self):
|
||||
return self._implementation.name == 'waveshare_3'
|
||||
|
||||
def is_waveshare_v4(self):
|
||||
return self._implementation.name == 'waveshare_4'
|
||||
|
||||
def is_waveshare27inch(self):
|
||||
return self._implementation.name == 'waveshare27inch'
|
||||
|
||||
def is_waveshare27inchv2(self):
|
||||
return self._implementation.name == 'waveshare27inchv2'
|
||||
|
||||
def is_waveshare29inch(self):
|
||||
return self._implementation.name == 'waveshare29inch'
|
||||
|
||||
@ -64,12 +73,24 @@ class Display(View):
|
||||
def is_waveshare213d(self):
|
||||
return self._implementation.name == 'waveshare213d'
|
||||
|
||||
def is_waveshare213g(self):
|
||||
return self._implementation.name == 'waveshare213g'
|
||||
|
||||
def is_waveshare213bc(self):
|
||||
return self._implementation.name == 'waveshare213bc'
|
||||
|
||||
def is_waveshare213inb_v4(self):
|
||||
return self._implementation.name == 'waveshare213inb_v4'
|
||||
|
||||
def is_waveshare35lcd(self):
|
||||
return self._implementation.name == 'waveshare35lcd'
|
||||
|
||||
def is_spotpear24inch(self):
|
||||
return self._implementation.name == 'spotpear24inch'
|
||||
|
||||
def is_displayhatmini(self):
|
||||
return self._implementation.name == 'displayhatmini'
|
||||
|
||||
def is_waveshare_any(self):
|
||||
return self.is_waveshare_v1() or self.is_waveshare_v2()
|
||||
|
||||
|
@ -6,13 +6,20 @@ from pwnagotchi.ui.hw.dfrobot1 import DFRobotV1
|
||||
from pwnagotchi.ui.hw.dfrobot2 import DFRobotV2
|
||||
from pwnagotchi.ui.hw.waveshare1 import WaveshareV1
|
||||
from pwnagotchi.ui.hw.waveshare2 import WaveshareV2
|
||||
from pwnagotchi.ui.hw.waveshare3 import WaveshareV3
|
||||
from pwnagotchi.ui.hw.waveshare4 import WaveshareV4
|
||||
from pwnagotchi.ui.hw.waveshare27inch import Waveshare27inch
|
||||
from pwnagotchi.ui.hw.waveshare27inchv2 import Waveshare27inchV2
|
||||
from pwnagotchi.ui.hw.waveshare29inch import Waveshare29inch
|
||||
from pwnagotchi.ui.hw.waveshare144lcd import Waveshare144lcd
|
||||
from pwnagotchi.ui.hw.waveshare154inch import Waveshare154inch
|
||||
from pwnagotchi.ui.hw.waveshare213d import Waveshare213d
|
||||
from pwnagotchi.ui.hw.waveshare213g import Waveshare213g
|
||||
from pwnagotchi.ui.hw.waveshare213bc import Waveshare213bc
|
||||
from pwnagotchi.ui.hw.waveshare213inb_v4 import Waveshare213bV4
|
||||
from pwnagotchi.ui.hw.waveshare35lcd import Waveshare35lcd
|
||||
from pwnagotchi.ui.hw.spotpear24inch import Spotpear24inch
|
||||
from pwnagotchi.ui.hw.displayhatmini import DisplayHatMini
|
||||
|
||||
def display_for(config):
|
||||
# config has been normalized already in utils.load_config
|
||||
@ -40,9 +47,18 @@ def display_for(config):
|
||||
elif config['ui']['display']['type'] == 'waveshare_2':
|
||||
return WaveshareV2(config)
|
||||
|
||||
elif config['ui']['display']['type'] == 'waveshare_3':
|
||||
return WaveshareV3(config)
|
||||
|
||||
elif config['ui']['display']['type'] == 'waveshare_4':
|
||||
return WaveshareV4(config)
|
||||
|
||||
elif config['ui']['display']['type'] == 'waveshare27inch':
|
||||
return Waveshare27inch(config)
|
||||
|
||||
elif config['ui']['display']['type'] == 'waveshare27inchv2':
|
||||
return Waveshare27inchV2(config)
|
||||
|
||||
elif config['ui']['display']['type'] == 'waveshare29inch':
|
||||
return Waveshare29inch(config)
|
||||
|
||||
@ -55,8 +71,20 @@ def display_for(config):
|
||||
elif config['ui']['display']['type'] == 'waveshare213d':
|
||||
return Waveshare213d(config)
|
||||
|
||||
elif config['ui']['display']['type'] == 'waveshare213g':
|
||||
return Waveshare213g(config)
|
||||
|
||||
elif config['ui']['display']['type'] == 'waveshare213bc':
|
||||
return Waveshare213bc(config)
|
||||
|
||||
elif config['ui']['display']['type'] == 'waveshare213inb_v4':
|
||||
return Waveshare213bV4(config)
|
||||
|
||||
elif config['ui']['display']['type'] == 'waveshare35lcd':
|
||||
return Waveshare35lcd(config)
|
||||
|
||||
elif config['ui']['display']['type'] == 'spotpear24inch':
|
||||
return Spotpear24inch(config)
|
||||
|
||||
elif config['ui']['display']['type'] == 'displayhatmini':
|
||||
return DisplayHatMini(config)
|
||||
|
@ -3,6 +3,8 @@ import pwnagotchi.ui.fonts as fonts
|
||||
|
||||
class DisplayImpl(object):
|
||||
def __init__(self, config, name):
|
||||
if fonts.Medium is None:
|
||||
fonts.init(config)
|
||||
self.name = name
|
||||
self.config = config['ui']['display']
|
||||
self._layout = {
|
||||
|
52
pwnagotchi/ui/hw/displayhatmini.py
Normal file
52
pwnagotchi/ui/hw/displayhatmini.py
Normal file
@ -0,0 +1,52 @@
|
||||
import logging
|
||||
|
||||
import pwnagotchi.ui.fonts as fonts
|
||||
from pwnagotchi.ui.hw.base import DisplayImpl
|
||||
|
||||
|
||||
class DisplayHatMini(DisplayImpl):
|
||||
def __init__(self, config):
|
||||
super(DisplayHatMini, self).__init__(config, 'displayhatmini')
|
||||
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'] = (35, 50)
|
||||
self._layout['name'] = (5, 20)
|
||||
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': (80, 160),
|
||||
'font': fonts.status_font(fonts.Medium),
|
||||
'max': 20
|
||||
}
|
||||
|
||||
return self._layout
|
||||
|
||||
def initialize(self):
|
||||
logging.info("initializing Display Hat Mini")
|
||||
from pwnagotchi.ui.hw.libs.pimoroni.displayhatmini.ST7789 import ST7789
|
||||
self._display = ST7789(0,1,9,13)
|
||||
|
||||
def render(self, canvas):
|
||||
self._display.display(canvas)
|
||||
|
||||
def clear(self):
|
||||
self._display.clear()
|
||||
|
||||
def set_backlight(self, value):
|
||||
if self._display:
|
||||
self._display.set_backlight(value)
|
||||
|
||||
def get_backlight(self):
|
||||
if self._display:
|
||||
self._display.get_backlight(value)
|
388
pwnagotchi/ui/hw/libs/pimoroni/displayhatmini/ST7789.py
Normal file
388
pwnagotchi/ui/hw/libs/pimoroni/displayhatmini/ST7789.py
Normal file
@ -0,0 +1,388 @@
|
||||
# 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
|
||||
|
||||
# Hardware PWM for the backlight LED requires and external library installed
|
||||
# with pip3, and a dtoverlay added to /boot/config.txt to enable hardware PWM
|
||||
# run the following two commands in the shell:
|
||||
#
|
||||
# sudo pip3 install rpi-hardware-pwm
|
||||
# echo "dtoverlay=pwm-2chan,pin=12,func=4,pin2=13,func2=4" | sudo tee -a /boot/config.txt
|
||||
#
|
||||
from rpi_hardware_pwm import HardwarePWM
|
||||
|
||||
__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=0, invert=True, spi_speed_hz=60 * 1000 * 1000,
|
||||
backlight_pwm=True,
|
||||
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 backlight_pwm: If true use PWM for backlight, if false then full on
|
||||
: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
|
||||
self._brightness = 0
|
||||
if backlight is not None:
|
||||
if backlight_pwm:
|
||||
self._backlight_pwm = HardwarePWM(pwm_channel=1, hz=100)
|
||||
self._brightness = 50
|
||||
self._backlight_pwm.start(self._brightness)
|
||||
else:
|
||||
self._backlight_pwm = None
|
||||
GPIO.setup(backlight, GPIO.OUT)
|
||||
GPIO.output(backlight, GPIO.LOW)
|
||||
time.sleep(0.1)
|
||||
GPIO.output(backlight, GPIO.HIGH)
|
||||
self._brightness = 100
|
||||
|
||||
# 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. PWM 0.0-1.0, otherwise 1 or 0."""
|
||||
if self._backlight is not None:
|
||||
if self._backlight_pwm:
|
||||
self._backlight_pwm.change_duty_cycle(value * 100)
|
||||
self._brightness = value
|
||||
else:
|
||||
GPIO.output(self._backlight, value)
|
||||
self._brightness = value
|
||||
|
||||
def get_backlight(self):
|
||||
"""Get the current backlight value."""
|
||||
if self._backlight is not None:
|
||||
return self._brightness
|
||||
|
||||
@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()
|
241
pwnagotchi/ui/hw/libs/waveshare/v213g/epd2in13g.py
Normal file
241
pwnagotchi/ui/hw/libs/waveshare/v213g/epd2in13g.py
Normal file
@ -0,0 +1,241 @@
|
||||
# *****************************************************************************
|
||||
# * | File : epd2in13g.py
|
||||
# * | Author : Waveshare team
|
||||
# * | Function : Electronic paper driver
|
||||
# * | Info :
|
||||
# *----------------
|
||||
# * | This version: V1.0
|
||||
# * | Date : 2023-05-29
|
||||
# # | 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
|
||||
|
||||
import PIL
|
||||
from PIL import Image
|
||||
import io
|
||||
|
||||
# Display resolution
|
||||
EPD_WIDTH = 122
|
||||
EPD_HEIGHT = 250
|
||||
|
||||
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.BLACK = 0x000000 # 00 BGR
|
||||
self.WHITE = 0xffffff # 01
|
||||
self.YELLOW = 0x00ffff # 10
|
||||
self.RED = 0x0000ff # 11
|
||||
self.Gate_BITS = EPD_HEIGHT
|
||||
if self.width < 128:
|
||||
self.Source_BITS = 128
|
||||
else:
|
||||
self.Source_BITS = self.width
|
||||
|
||||
|
||||
# Hardware reset
|
||||
def reset(self):
|
||||
epdconfig.digital_write(self.reset_pin, 1)
|
||||
epdconfig.delay_ms(200)
|
||||
epdconfig.digital_write(self.reset_pin, 0) # module reset
|
||||
epdconfig.delay_ms(2)
|
||||
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)
|
||||
|
||||
def ReadBusy(self):
|
||||
logger.debug("e-Paper busy H")
|
||||
epdconfig.delay_ms(100)
|
||||
while(epdconfig.digital_read(self.busy_pin) == 0): # 0: idle, 1: busy
|
||||
epdconfig.delay_ms(5)
|
||||
logger.debug("e-Paper busy release")
|
||||
|
||||
def SetWindow(self):
|
||||
self.send_command(0x61) # 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(int(self.Source_BITS/256))
|
||||
self.send_data(self.Source_BITS%256)
|
||||
self.send_data(int(self.Gate_BITS/256))
|
||||
self.send_data(self.Gate_BITS%256)
|
||||
|
||||
def TurnOnDisplay(self):
|
||||
self.send_command(0x12) # DISPLAY_REFRESH
|
||||
self.send_data(0X00)
|
||||
self.ReadBusy()
|
||||
|
||||
def init(self):
|
||||
if (epdconfig.module_init() != 0):
|
||||
return -1
|
||||
# EPD hardware init start
|
||||
|
||||
self.reset()
|
||||
|
||||
self.ReadBusy()
|
||||
self.send_command(0x4D)
|
||||
self.send_data(0x78)
|
||||
|
||||
self.send_command(0x00)
|
||||
self.send_data(0x0F)
|
||||
self.send_data(0x29)
|
||||
|
||||
self.send_command(0x01)
|
||||
self.send_data(0x07)
|
||||
self.send_data(0x00)
|
||||
|
||||
self.send_command(0x03)
|
||||
self.send_data(0x10)
|
||||
self.send_data(0x54)
|
||||
self.send_data(0x44)
|
||||
|
||||
self.send_command(0x06)
|
||||
self.send_data(0x05)
|
||||
self.send_data(0x00)
|
||||
self.send_data(0x3F)
|
||||
self.send_data(0x0A)
|
||||
self.send_data(0x25)
|
||||
self.send_data(0x12)
|
||||
self.send_data(0x1A)
|
||||
|
||||
self.send_command(0x50)
|
||||
self.send_data(0x37)
|
||||
|
||||
self.send_command(0x60)
|
||||
self.send_data(0x02)
|
||||
self.send_data(0x02)
|
||||
|
||||
self.SetWindow()
|
||||
|
||||
self.send_command(0xE7)
|
||||
self.send_data(0x1C)
|
||||
|
||||
self.send_command(0xE3)
|
||||
self.send_data(0x22)
|
||||
|
||||
self.send_command(0xB4)
|
||||
self.send_data(0xD0)
|
||||
self.send_command(0xB5)
|
||||
self.send_data(0x03)
|
||||
|
||||
self.send_command(0xE9)
|
||||
self.send_data(0x01)
|
||||
|
||||
self.send_command(0x30)
|
||||
self.send_data(0x08)
|
||||
|
||||
self.send_command(0x04)
|
||||
self.ReadBusy()
|
||||
return 0
|
||||
|
||||
def getbuffer(self, image):
|
||||
# Create a pallette with the 4 colors supported by the panel
|
||||
pal_image = Image.new("P", (1,1))
|
||||
pal_image.putpalette( (0,0,0, 255,255,255, 255,255,0, 255,0,0) + (0,0,0)*252)
|
||||
|
||||
# Check if we need to rotate the image
|
||||
imwidth, imheight = image.size
|
||||
if(imwidth == self.width and imheight == self.height):
|
||||
image_temp = image
|
||||
elif(imwidth == self.height and imheight == self.width):
|
||||
image_temp = image.rotate(90, expand=True)
|
||||
else:
|
||||
logger.warning("Invalid image dimensions: %d x %d, expected %d x %d" % (imwidth, imheight, self.width, self.height))
|
||||
|
||||
# Convert the soruce image to the 4 colors, dithering if needed
|
||||
image_4color = image_temp.convert("RGB").quantize(palette=pal_image)
|
||||
buf_4color = bytearray(image_4color.tobytes('raw'))
|
||||
|
||||
# into a single byte to transfer to the panel
|
||||
if self.width % 4 == 0 :
|
||||
Width = self.width // 4
|
||||
else :
|
||||
Width = self.width // 4 + 1
|
||||
Height = self.height
|
||||
buf = [0x00] * int(Width * Height)
|
||||
idx = 0
|
||||
for j in range(0, Height):
|
||||
for i in range(0, Width):
|
||||
if i == Width -1:
|
||||
buf[i + j * Width] = (buf_4color[idx] << 6) + (buf_4color[idx+1] << 4)
|
||||
idx = idx + 2
|
||||
else:
|
||||
buf[i + j * Width] = (buf_4color[idx] << 6) + (buf_4color[idx+1] << 4) + (buf_4color[idx+2] << 2) + buf_4color[idx+3]
|
||||
idx = idx + 4
|
||||
return buf
|
||||
|
||||
def display(self, image):
|
||||
if self.width % 4 == 0 :
|
||||
Width = self.width // 4
|
||||
else :
|
||||
Width = self.width // 4 + 1
|
||||
Height = self.height
|
||||
|
||||
self.send_command(0x10)
|
||||
for j in range(0, Height):
|
||||
for i in range(0, self.Source_BITS//4):
|
||||
if i < 31 :
|
||||
self.send_data(image[i + j * Width])
|
||||
else :
|
||||
self.send_data(0x00)
|
||||
|
||||
self.TurnOnDisplay()
|
||||
|
||||
def Clear(self, color=0x55):
|
||||
Width = self.Source_BITS//4
|
||||
Height = self.height
|
||||
|
||||
|
||||
self.send_command(0x10)
|
||||
for j in range(0, Height):
|
||||
for i in range(0, Width):
|
||||
self.send_data(color)
|
||||
self.TurnOnDisplay()
|
||||
|
||||
def sleep(self):
|
||||
self.send_command(0x02) # POWER_OFF
|
||||
self.ReadBusy()
|
||||
epdconfig.delay_ms(100)
|
||||
|
||||
self.send_command(0x07) # DEEP_SLEEP
|
||||
self.send_data(0XA5)
|
||||
|
||||
epdconfig.delay_ms(2000)
|
||||
epdconfig.module_exit()
|
||||
### END OF FILE ###
|
285
pwnagotchi/ui/hw/libs/waveshare/v213g/epdconfig.py
Normal file
285
pwnagotchi/ui/hw/libs/waveshare/v213g/epdconfig.py
Normal file
@ -0,0 +1,285 @@
|
||||
# /*****************************************************************************
|
||||
# * | File : epdconfig.py
|
||||
# * | Author : Waveshare team
|
||||
# * | Function : Hardware underlying interface
|
||||
# * | Info :
|
||||
# *----------------
|
||||
# * | This version: V1.2
|
||||
# * | Date : 2022-10-29
|
||||
# * | Info :
|
||||
# ******************************************************************************
|
||||
# Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
# of this software and associated documnetation files (the "Software"), to deal
|
||||
# in the Software without restriction, including without limitation the rights
|
||||
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
# copies of the Software, and to permit persons to whom the Software is
|
||||
# furished to do so, subject to the following conditions:
|
||||
#
|
||||
# The above copyright notice and this permission notice shall be included in
|
||||
# all copies or substantial portions of the Software.
|
||||
#
|
||||
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
# FITNESS OR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
# LIABILITY WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
# THE SOFTWARE.
|
||||
#
|
||||
|
||||
import os
|
||||
import logging
|
||||
import sys
|
||||
import time
|
||||
import subprocess
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class RaspberryPi:
|
||||
# Pin definition
|
||||
RST_PIN = 17
|
||||
DC_PIN = 25
|
||||
CS_PIN = 8
|
||||
BUSY_PIN = 24
|
||||
PWR_PIN = 18
|
||||
|
||||
def __init__(self):
|
||||
import spidev
|
||||
import gpiozero
|
||||
|
||||
self.SPI = spidev.SpiDev()
|
||||
self.GPIO_RST_PIN = gpiozero.LED(self.RST_PIN)
|
||||
self.GPIO_DC_PIN = gpiozero.LED(self.DC_PIN)
|
||||
# self.GPIO_CS_PIN = gpiozero.LED(self.CS_PIN)
|
||||
self.GPIO_PWR_PIN = gpiozero.LED(self.PWR_PIN)
|
||||
self.GPIO_BUSY_PIN = gpiozero.Button(self.BUSY_PIN, pull_up = False)
|
||||
|
||||
def digital_write(self, pin, value):
|
||||
if pin == self.RST_PIN:
|
||||
if value:
|
||||
self.GPIO_RST_PIN.on()
|
||||
else:
|
||||
self.GPIO_RST_PIN.off()
|
||||
elif pin == self.DC_PIN:
|
||||
if value:
|
||||
self.GPIO_DC_PIN.on()
|
||||
else:
|
||||
self.GPIO_DC_PIN.off()
|
||||
# elif pin == self.CS_PIN:
|
||||
# if value:
|
||||
# self.GPIO_CS_PIN.on()
|
||||
# else:
|
||||
# self.GPIO_CS_PIN.off()
|
||||
elif pin == self.PWR_PIN:
|
||||
if value:
|
||||
self.GPIO_PWR_PIN.on()
|
||||
else:
|
||||
self.GPIO_PWR_PIN.off()
|
||||
|
||||
def digital_read(self, pin):
|
||||
if pin == self.BUSY_PIN:
|
||||
return self.GPIO_BUSY_PIN.value
|
||||
elif pin == self.RST_PIN:
|
||||
return self.RST_PIN.value
|
||||
elif pin == self.DC_PIN:
|
||||
return self.DC_PIN.value
|
||||
# elif pin == self.CS_PIN:
|
||||
# return self.CS_PIN.value
|
||||
elif pin == self.PWR_PIN:
|
||||
return self.PWR_PIN.value
|
||||
|
||||
def delay_ms(self, delaytime):
|
||||
time.sleep(delaytime / 1000.0)
|
||||
|
||||
def spi_writebyte(self, data):
|
||||
self.SPI.writebytes(data)
|
||||
|
||||
def spi_writebyte2(self, data):
|
||||
self.SPI.writebytes2(data)
|
||||
|
||||
def module_init(self):
|
||||
self.GPIO_PWR_PIN.on()
|
||||
|
||||
# SPI device, bus = 0, device = 0
|
||||
self.SPI.open(0, 0)
|
||||
self.SPI.max_speed_hz = 4000000
|
||||
self.SPI.mode = 0b00
|
||||
return 0
|
||||
|
||||
def module_exit(self, cleanup=False):
|
||||
logger.debug("spi end")
|
||||
self.SPI.close()
|
||||
|
||||
|
||||
self.GPIO_RST_PIN.off()
|
||||
self.GPIO_DC_PIN.off()
|
||||
self.GPIO_PWR_PIN.off()
|
||||
logger.debug("close 5V, Module enters 0 power consumption ...")
|
||||
|
||||
if cleanup:
|
||||
self.GPIO_RST_PIN.close()
|
||||
self.GPIO_DC_PIN.close()
|
||||
# self.GPIO_CS_PIN.close()
|
||||
self.GPIO_PWR_PIN.close()
|
||||
self.GPIO_BUSY_PIN.close()
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
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 sys.version_info[0] == 2:
|
||||
process = subprocess.Popen("cat /proc/cpuinfo | grep Raspberry", shell=True, stdout=subprocess.PIPE)
|
||||
else:
|
||||
process = subprocess.Popen("cat /proc/cpuinfo | grep Raspberry", shell=True, stdout=subprocess.PIPE, text=True)
|
||||
output, _ = process.communicate()
|
||||
if sys.version_info[0] == 2:
|
||||
output = output.decode(sys.stdout.encoding)
|
||||
|
||||
if "Raspberry" in output:
|
||||
implementation = RaspberryPi()
|
||||
elif os.path.exists('/sys/bus/platform/drivers/gpio-x3'):
|
||||
implementation = SunriseX3()
|
||||
else:
|
||||
implementation = JetsonNano()
|
||||
|
||||
for func in [x for x in dir(implementation) if not x.startswith('_')]:
|
||||
setattr(sys.modules[__name__], func, getattr(implementation, func))
|
||||
|
||||
### END OF FILE ###
|
204
pwnagotchi/ui/hw/libs/waveshare/v213inb_v4/epd2in13b_V4.py
Normal file
204
pwnagotchi/ui/hw/libs/waveshare/v213inb_v4/epd2in13b_V4.py
Normal file
@ -0,0 +1,204 @@
|
||||
# *****************************************************************************
|
||||
# * | File : epd2in13b_V4.py
|
||||
# * | Author : Waveshare team
|
||||
# * | Function : Electronic paper driver
|
||||
# * | Info :
|
||||
# *----------------
|
||||
# * | This version: V1.0
|
||||
# * | Date : 2022-04-21
|
||||
# # | 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 = 122
|
||||
EPD_HEIGHT = 250
|
||||
|
||||
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(20)
|
||||
epdconfig.digital_write(self.reset_pin, 0)
|
||||
epdconfig.delay_ms(2)
|
||||
epdconfig.digital_write(self.reset_pin, 1)
|
||||
epdconfig.delay_ms(20)
|
||||
|
||||
# send 1 byte command
|
||||
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)
|
||||
|
||||
# send 1 byte 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)
|
||||
|
||||
# judge e-Paper whether is busy
|
||||
def busy(self):
|
||||
logger.debug("e-Paper busy")
|
||||
while (epdconfig.digital_read(self.busy_pin) != 0):
|
||||
epdconfig.delay_ms(10)
|
||||
logger.debug("e-Paper busy release")
|
||||
|
||||
# set the display window
|
||||
def set_windows(self, xstart, ystart, xend, yend):
|
||||
self.send_command(0x44) # SET_RAM_X_ADDRESS_START_END_POSITION
|
||||
self.send_data((xstart >> 3) & 0xff)
|
||||
self.send_data((xend >> 3) & 0xff)
|
||||
|
||||
self.send_command(0x45) # SET_RAM_Y_ADDRESS_START_END_POSITION
|
||||
self.send_data(ystart & 0xff)
|
||||
self.send_data((ystart >> 8) & 0xff)
|
||||
self.send_data(yend & 0xff)
|
||||
self.send_data((yend >> 8) & 0xff)
|
||||
|
||||
# set the display cursor(origin)
|
||||
def set_cursor(self, xstart, ystart):
|
||||
self.send_command(0x4E) # SET_RAM_X_ADDRESS_COUNTER
|
||||
self.send_data(xstart & 0xff)
|
||||
|
||||
self.send_command(0x4F) # SET_RAM_Y_ADDRESS_COUNTER
|
||||
self.send_data(ystart & 0xff)
|
||||
self.send_data((ystart >> 8) & 0xff)
|
||||
|
||||
# initialize
|
||||
def init(self):
|
||||
if (epdconfig.module_init() != 0):
|
||||
return -1
|
||||
|
||||
self.reset()
|
||||
|
||||
self.busy()
|
||||
self.send_command(0x12) # SWRESET
|
||||
self.busy()
|
||||
|
||||
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.set_windows(0, 0, self.width - 1, self.height - 1)
|
||||
self.set_cursor(0, 0)
|
||||
|
||||
self.send_command(0x3C) # BorderWavefrom
|
||||
self.send_data(0x05)
|
||||
|
||||
self.send_command(0x18) # Read built-in temperature sensor
|
||||
self.send_data(0x80)
|
||||
|
||||
self.send_command(0x21) # Display update control
|
||||
self.send_data(0x80)
|
||||
self.send_data(0x80)
|
||||
|
||||
self.busy()
|
||||
|
||||
return 0
|
||||
|
||||
# turn on display
|
||||
def ondisplay(self):
|
||||
self.send_command(0x20)
|
||||
self.busy()
|
||||
|
||||
# image converted to bytearray
|
||||
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
|
||||
|
||||
# display image
|
||||
def display(self, imageblack, imagered):
|
||||
self.send_command(0x24)
|
||||
self.send_data2(imageblack)
|
||||
|
||||
self.send_command(0x26)
|
||||
self.send_data2(imagered)
|
||||
|
||||
self.ondisplay()
|
||||
|
||||
# display white image
|
||||
def clear(self):
|
||||
if self.width % 8 == 0:
|
||||
linewidth = int(self.width/8)
|
||||
else:
|
||||
linewidth = int(self.width/8) + 1
|
||||
|
||||
buf = [0xff] * (int(linewidth * self.height))
|
||||
|
||||
self.send_command(0x24)
|
||||
self.send_data2(buf)
|
||||
|
||||
self.send_command(0x26)
|
||||
self.send_data2(buf)
|
||||
|
||||
self.ondisplay()
|
||||
|
||||
# Compatible with older version functions
|
||||
def Clear(self):
|
||||
self.clear()
|
||||
|
||||
# sleep
|
||||
def sleep(self):
|
||||
self.send_command(0x10) # DEEP_SLEEP
|
||||
self.send_data(0x01) # check code
|
||||
|
||||
epdconfig.delay_ms(2000)
|
||||
epdconfig.module_exit()
|
||||
### END OF FILE ###
|
227
pwnagotchi/ui/hw/libs/waveshare/v213inb_v4/epdconfig.py
Normal file
227
pwnagotchi/ui/hw/libs/waveshare/v213inb_v4/epdconfig.py
Normal file
@ -0,0 +1,227 @@
|
||||
# /*****************************************************************************
|
||||
# * | 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
|
||||
|
||||
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.BUSY_PIN, self.GPIO.IN)
|
||||
|
||||
# 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.cleanup([self.RST_PIN, self.DC_PIN, self.CS_PIN, self.BUSY_PIN])
|
||||
|
||||
|
||||
class JetsonNano:
|
||||
# Pin definition
|
||||
RST_PIN = 17
|
||||
DC_PIN = 25
|
||||
CS_PIN = 8
|
||||
BUSY_PIN = 24
|
||||
|
||||
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.BUSY_PIN, self.GPIO.IN)
|
||||
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.cleanup([self.RST_PIN, self.DC_PIN, self.CS_PIN, self.BUSY_PIN])
|
||||
|
||||
|
||||
class SunriseX3:
|
||||
# Pin definition
|
||||
RST_PIN = 17
|
||||
DC_PIN = 25
|
||||
CS_PIN = 8
|
||||
BUSY_PIN = 24
|
||||
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.BUSY_PIN, self.GPIO.IN)
|
||||
|
||||
# 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.cleanup([self.RST_PIN, self.DC_PIN, self.CS_PIN, self.BUSY_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 = JetsonNano()
|
||||
|
||||
for func in [x for x in dir(implementation) if not x.startswith('_')]:
|
||||
setattr(sys.modules[__name__], func, getattr(implementation, func))
|
||||
|
||||
### END OF FILE ###
|
518
pwnagotchi/ui/hw/libs/waveshare/v27inchv2/epd2in7_V2.py
Normal file
518
pwnagotchi/ui/hw/libs/waveshare/v27inchv2/epd2in7_V2.py
Normal file
@ -0,0 +1,518 @@
|
||||
# *****************************************************************************
|
||||
# * | File : epd2in7_V2.py
|
||||
# * | Author : Waveshare team
|
||||
# * | Function : Electronic paper driver
|
||||
# * | Info :
|
||||
# *----------------
|
||||
# * | This version: V1.0
|
||||
# * | Date : 2022-09-17
|
||||
# # | 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 = 176
|
||||
EPD_HEIGHT = 264
|
||||
|
||||
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
|
||||
|
||||
LUT_DATA_4Gray = [
|
||||
0x40,0x48,0x80,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,
|
||||
0x8,0x48,0x10,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,
|
||||
0x2,0x48,0x4,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,
|
||||
0x20,0x48,0x1,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,
|
||||
0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,
|
||||
0xA,0x19,0x0,0x3,0x8,0x0,0x0,
|
||||
0x14,0x1,0x0,0x14,0x1,0x0,0x3,
|
||||
0xA,0x3,0x0,0x8,0x19,0x0,0x0,
|
||||
0x1,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,
|
||||
0x22,0x22,0x22,0x22,0x22,0x22,0x0,0x0,0x0,
|
||||
0x22,0x17,0x41,0x0,0x32,0x1C,
|
||||
]
|
||||
|
||||
# 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(2)
|
||||
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)
|
||||
|
||||
def ReadBusy(self):
|
||||
logger.debug("e-Paper busy")
|
||||
while(epdconfig.digital_read(self.busy_pin) == 1): # 1: idle, 0: busy
|
||||
epdconfig.delay_ms(20)
|
||||
logger.debug("e-Paper busy release")
|
||||
|
||||
def TurnOnDisplay(self):
|
||||
self.send_command(0x22) #Display Update Control
|
||||
self.send_data(0xF7)
|
||||
self.send_command(0x20) #Activate Display Update Sequence
|
||||
self.ReadBusy()
|
||||
|
||||
def TurnOnDisplay_Fast(self):
|
||||
self.send_command(0x22) #Display Update Control
|
||||
self.send_data(0xC7)
|
||||
self.send_command(0x20) #Activate Display Update Sequence
|
||||
self.ReadBusy()
|
||||
|
||||
def TurnOnDisplay_Partial(self):
|
||||
self.send_command(0x22) #Display Update Control
|
||||
self.send_data(0xFF)
|
||||
self.send_command(0x20) #Activate Display Update Sequence
|
||||
self.ReadBusy()
|
||||
|
||||
def TurnOnDisplay_4GRAY(self):
|
||||
self.send_command(0x22) #Display Update Control
|
||||
self.send_data(0xC7)
|
||||
self.send_command(0x20) #Activate Display Update Sequence
|
||||
self.ReadBusy()
|
||||
|
||||
def Lut(self):
|
||||
self.send_command(0x32)
|
||||
for i in range(159):
|
||||
self.send_data(self.LUT_DATA_4Gray[i])
|
||||
|
||||
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(0x45) #set Ram-Y address start/end position
|
||||
self.send_data(0x00)
|
||||
self.send_data(0x00)
|
||||
self.send_data(0x07) #0x0107-->(263+1)=264
|
||||
self.send_data(0x01)
|
||||
|
||||
self.send_command(0x4F) # set RAM y address count to 0;
|
||||
self.send_data(0x00)
|
||||
self.send_data(0x00)
|
||||
|
||||
self.send_command(0x11) # data entry mode
|
||||
self.send_data(0x03)
|
||||
return 0
|
||||
|
||||
def init_Fast(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(0x12) #SWRESET
|
||||
self.ReadBusy()
|
||||
|
||||
self.send_command(0x18) #Read built-in temperature sensor
|
||||
self.send_data(0x80)
|
||||
|
||||
self.send_command(0x22) # Load temperature value
|
||||
self.send_data(0xB1)
|
||||
self.send_command(0x20)
|
||||
self.ReadBusy()
|
||||
|
||||
self.send_command(0x1A) # Write to temperature register
|
||||
self.send_data(0x64)
|
||||
self.send_data(0x00)
|
||||
|
||||
self.send_command(0x45) #set Ram-Y address start/end position
|
||||
self.send_data(0x00)
|
||||
self.send_data(0x00)
|
||||
self.send_data(0x07) #0x0107-->(263+1)=264
|
||||
self.send_data(0x01)
|
||||
|
||||
self.send_command(0x4F) # set RAM y address count to 0;
|
||||
self.send_data(0x00)
|
||||
self.send_data(0x00)
|
||||
|
||||
self.send_command(0x11) # data entry mode
|
||||
self.send_data(0x03)
|
||||
|
||||
self.send_command(0x22) # Load temperature value
|
||||
self.send_data(0x91)
|
||||
self.send_command(0x20)
|
||||
self.ReadBusy()
|
||||
return 0
|
||||
|
||||
def Init_4Gray(self):
|
||||
if (epdconfig.module_init() != 0):
|
||||
return -1
|
||||
self.reset()
|
||||
|
||||
self.send_command(0x12) # soft reset
|
||||
self.ReadBusy();
|
||||
|
||||
self.send_command(0x74) #set analog block control
|
||||
self.send_data(0x54)
|
||||
self.send_command(0x7E) #set digital block control
|
||||
self.send_data(0x3B)
|
||||
|
||||
self.send_command(0x01) #Driver output control
|
||||
self.send_data(0x07)
|
||||
self.send_data(0x01)
|
||||
self.send_data(0x00)
|
||||
|
||||
self.send_command(0x11) #data entry mode
|
||||
self.send_data(0x03)
|
||||
|
||||
self.send_command(0x44) #set Ram-X address start/end position
|
||||
self.send_data(0x00)
|
||||
self.send_data(0x15) #0x15-->(21+1)*8=176
|
||||
|
||||
self.send_command(0x45) #set Ram-Y address start/end position
|
||||
self.send_data(0x00)
|
||||
self.send_data(0x00)
|
||||
self.send_data(0x07) #0x0107-->(263+1)=264
|
||||
self.send_data(0x01)
|
||||
|
||||
|
||||
self.send_command(0x3C) #BorderWavefrom
|
||||
self.send_data(0x00)
|
||||
|
||||
|
||||
self.send_command(0x2C) #VCOM Voltage
|
||||
self.send_data(self.LUT_DATA_4Gray[158]) #0x1C
|
||||
|
||||
|
||||
self.send_command(0x3F) #EOPQ
|
||||
self.send_data(self.LUT_DATA_4Gray[153])
|
||||
|
||||
self.send_command(0x03) #VGH
|
||||
self.send_data(self.LUT_DATA_4Gray[154])
|
||||
|
||||
self.send_command(0x04) #
|
||||
self.send_data(self.LUT_DATA_4Gray[155]) #VSH1
|
||||
self.send_data(self.LUT_DATA_4Gray[156]) #VSH2
|
||||
self.send_data(self.LUT_DATA_4Gray[157]) #VSL
|
||||
|
||||
self.Lut() #LUT
|
||||
|
||||
|
||||
self.send_command(0x4E) # set RAM x address count to 0;
|
||||
self.send_data(0x00)
|
||||
self.send_command(0x4F) # set RAM y address count to 0X199;
|
||||
self.send_data(0x00)
|
||||
self.send_data(0x00)
|
||||
self.ReadBusy()
|
||||
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 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 Clear(self):
|
||||
if(self.width % 8 == 0):
|
||||
Width = self.width // 8
|
||||
else:
|
||||
Width = self.width // 8 +1
|
||||
Height = self.height
|
||||
self.send_command(0x24)
|
||||
for j in range(Height):
|
||||
for i in range(Width):
|
||||
self.send_data(0XFF)
|
||||
self.TurnOnDisplay()
|
||||
|
||||
def display(self, image):
|
||||
if(self.width % 8 == 0):
|
||||
Width = self.width // 8
|
||||
else:
|
||||
Width = self.width // 8 +1
|
||||
Height = self.height
|
||||
self.send_command(0x24)
|
||||
for j in range(Height):
|
||||
for i in range(Width):
|
||||
self.send_data(image[i + j * Width])
|
||||
self.TurnOnDisplay()
|
||||
|
||||
def display_Fast(self, image):
|
||||
if(self.width % 8 == 0):
|
||||
Width = self.width // 8
|
||||
else:
|
||||
Width = self.width // 8 +1
|
||||
Height = self.height
|
||||
self.send_command(0x24)
|
||||
for j in range(Height):
|
||||
for i in range(Width):
|
||||
self.send_data(image[i + j * Width])
|
||||
self.TurnOnDisplay_Fast()
|
||||
|
||||
def display_Base(self, image):
|
||||
if(self.width % 8 == 0):
|
||||
Width = self.width // 8
|
||||
else:
|
||||
Width = self.width // 8 +1
|
||||
Height = self.height
|
||||
self.send_command(0x24) #Write Black and White image to RAM
|
||||
for j in range(Height):
|
||||
for i in range(Width):
|
||||
self.send_data(image[i + j * Width])
|
||||
|
||||
self.send_command(0x26) #Write Black and White image to RAM
|
||||
for j in range(Height):
|
||||
for i in range(Width):
|
||||
self.send_data(image[i + j * Width])
|
||||
self.TurnOnDisplay()
|
||||
|
||||
def display_Base_color(self, color):
|
||||
if(self.width % 8 == 0):
|
||||
Width = self.width // 8
|
||||
else:
|
||||
Width = self.width // 8 +1
|
||||
Height = self.height
|
||||
self.send_command(0x24) #Write Black and White image to RAM
|
||||
for j in range(Height):
|
||||
for i in range(Width):
|
||||
self.send_data(color)
|
||||
|
||||
self.send_command(0x26) #Write Black and White image to RAM
|
||||
for j in range(Height):
|
||||
for i in range(Width):
|
||||
self.send_data(color)
|
||||
# self.TurnOnDisplay()
|
||||
|
||||
def display_Partial(self, Image, Xstart, Ystart, Xend, Yend):
|
||||
if((Xstart % 8 + Xend % 8 == 8 & Xstart % 8 > Xend % 8) | Xstart % 8 + Xend % 8 == 0 | (Xend - Xstart)%8 == 0):
|
||||
Xstart = Xstart // 8
|
||||
Xend = Xend // 8
|
||||
else:
|
||||
Xstart = Xstart // 8
|
||||
if Xend % 8 == 0:
|
||||
Xend = Xend // 8
|
||||
else:
|
||||
Xend = Xend // 8 + 1
|
||||
|
||||
if(self.width % 8 == 0):
|
||||
Width = self.width // 8
|
||||
else:
|
||||
Width = self.width // 8 +1
|
||||
Height = self.height
|
||||
|
||||
Xend -= 1
|
||||
Yend -= 1
|
||||
|
||||
# Reset
|
||||
self.reset()
|
||||
|
||||
self.send_command(0x3C) #BorderWavefrom
|
||||
self.send_data(0x80)
|
||||
|
||||
self.send_command(0x44) # set RAM x address start/end, in page 35
|
||||
self.send_data(Xstart & 0xff) # RAM x address start at 00h;
|
||||
self.send_data(Xend & 0xff) # RAM x address end at 0fh(15+1)*8->128
|
||||
self.send_command(0x45) # set RAM y address start/end, in page 35
|
||||
self.send_data(Ystart & 0xff) # RAM y address start at 0127h;
|
||||
self.send_data((Ystart>>8) & 0x01) # RAM y address start at 0127h;
|
||||
self.send_data(Yend & 0xff) # RAM y address end at 00h;
|
||||
self.send_data((Yend>>8) & 0x01)
|
||||
|
||||
self.send_command(0x4E) # set RAM x address count to 0;
|
||||
self.send_data(Xstart & 0xff)
|
||||
self.send_command(0x4F) # set RAM y address count to 0X127;
|
||||
self.send_data(Ystart & 0xff)
|
||||
self.send_data((Ystart>>8) & 0x01)
|
||||
|
||||
self.send_command(0x24) #Write Black and White image to RAM
|
||||
for j in range(Height):
|
||||
for i in range(Width):
|
||||
if((j > Ystart-1) & (j < (Yend + 1)) & (i > Xstart-1) & (i < (Xend + 1))):
|
||||
self.send_data(Image[i + j * Width])
|
||||
self.TurnOnDisplay_Partial()
|
||||
|
||||
def display_4Gray(self, image):
|
||||
self.send_command(0x24)
|
||||
for i in range(0, 5808): #5808*4 46464
|
||||
temp3=0
|
||||
for j in range(0, 2):
|
||||
temp1 = image[i*2+j]
|
||||
for k in range(0, 2):
|
||||
temp2 = temp1&0xC0
|
||||
if(temp2 == 0xC0):
|
||||
temp3 |= 0x00
|
||||
elif(temp2 == 0x00):
|
||||
temp3 |= 0x01
|
||||
elif(temp2 == 0x80):
|
||||
temp3 |= 0x01
|
||||
else: #0x40
|
||||
temp3 |= 0x00
|
||||
temp3 <<= 1
|
||||
|
||||
temp1 <<= 2
|
||||
temp2 = temp1&0xC0
|
||||
if(temp2 == 0xC0):
|
||||
temp3 |= 0x00
|
||||
elif(temp2 == 0x00):
|
||||
temp3 |= 0x01
|
||||
elif(temp2 == 0x80):
|
||||
temp3 |= 0x01
|
||||
else : #0x40
|
||||
temp3 |= 0x00
|
||||
if(j!=1 or k!=1):
|
||||
temp3 <<= 1
|
||||
temp1 <<= 2
|
||||
self.send_data(temp3)
|
||||
|
||||
self.send_command(0x26)
|
||||
for i in range(0, 5808): #5808*4 46464
|
||||
temp3=0
|
||||
for j in range(0, 2):
|
||||
temp1 = image[i*2+j]
|
||||
for k in range(0, 2):
|
||||
temp2 = temp1&0xC0
|
||||
if(temp2 == 0xC0):
|
||||
temp3 |= 0x00
|
||||
elif(temp2 == 0x00):
|
||||
temp3 |= 0x01
|
||||
elif(temp2 == 0x80):
|
||||
temp3 |= 0x00
|
||||
else: #0x40
|
||||
temp3 |= 0x01
|
||||
temp3 <<= 1
|
||||
|
||||
temp1 <<= 2
|
||||
temp2 = temp1&0xC0
|
||||
if(temp2 == 0xC0):
|
||||
temp3 |= 0x00
|
||||
elif(temp2 == 0x00):
|
||||
temp3 |= 0x01
|
||||
elif(temp2 == 0x80):
|
||||
temp3 |= 0x00
|
||||
else: #0x40
|
||||
temp3 |= 0x01
|
||||
if(j!=1 or k!=1):
|
||||
temp3 <<= 1
|
||||
temp1 <<= 2
|
||||
self.send_data(temp3)
|
||||
|
||||
self.TurnOnDisplay_4GRAY()
|
||||
|
||||
def sleep(self):
|
||||
self.send_command(0X10)
|
||||
self.send_data(0x01)
|
||||
|
||||
epdconfig.delay_ms(2000)
|
||||
epdconfig.module_exit()
|
||||
### END OF FILE ###
|
243
pwnagotchi/ui/hw/libs/waveshare/v27inchv2/epdconfig.py
Normal file
243
pwnagotchi/ui/hw/libs/waveshare/v27inchv2/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 = JetsonNano()
|
||||
|
||||
for func in [x for x in dir(implementation) if not x.startswith('_')]:
|
||||
setattr(sys.modules[__name__], func, getattr(implementation, func))
|
||||
|
||||
### END OF FILE ###
|
396
pwnagotchi/ui/hw/libs/waveshare/v3/epd2in13_V3.py
Normal file
396
pwnagotchi/ui/hw/libs/waveshare/v3/epd2in13_V3.py
Normal file
@ -0,0 +1,396 @@
|
||||
# *****************************************************************************
|
||||
# * | File : epd2in13_V3.py
|
||||
# * | Author : Waveshare team
|
||||
# * | Function : Electronic paper driver
|
||||
# * | Info :
|
||||
# *----------------
|
||||
# * | This version: V1.1
|
||||
# * | Date : 2021-10-30
|
||||
# # | 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
|
||||
import numpy as np
|
||||
|
||||
# Display resolution
|
||||
EPD_WIDTH = 122
|
||||
EPD_HEIGHT = 250
|
||||
|
||||
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)
|
||||
|
||||
'''
|
||||
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):
|
||||
if self.width%8 == 0:
|
||||
linewidth = int(self.width/8)
|
||||
else:
|
||||
linewidth = int(self.width/8) + 1
|
||||
|
||||
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.TurnOnDisplayPart()
|
||||
|
||||
'''
|
||||
function : Refresh a base image
|
||||
parameter:
|
||||
image : Image data
|
||||
'''
|
||||
def displayPartBaseImage(self, image):
|
||||
if self.width%8 == 0:
|
||||
linewidth = int(self.width/8)
|
||||
else:
|
||||
linewidth = int(self.width/8) + 1
|
||||
|
||||
self.send_command(0x24)
|
||||
for j in range(0, self.height):
|
||||
for i in range(0, linewidth):
|
||||
self.send_data(image[i + j * linewidth])
|
||||
|
||||
self.send_command(0x26)
|
||||
for j in range(0, self.height):
|
||||
for i in range(0, linewidth):
|
||||
self.send_data(image[i + j * linewidth])
|
||||
self.TurnOnDisplay()
|
||||
|
||||
'''
|
||||
function : Clear screen
|
||||
parameter:
|
||||
'''
|
||||
def Clear(self, color):
|
||||
if self.width%8 == 0:
|
||||
linewidth = int(self.width/8)
|
||||
else:
|
||||
linewidth = int(self.width/8) + 1
|
||||
# logger.debug(linewidth)
|
||||
|
||||
self.send_command(0x24)
|
||||
for j in range(0, self.height):
|
||||
for i in range(0, linewidth):
|
||||
self.send_data(color)
|
||||
|
||||
self.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 ###
|
154
pwnagotchi/ui/hw/libs/waveshare/v3/epdconfig.py
Normal file
154
pwnagotchi/ui/hw/libs/waveshare/v3/epdconfig.py
Normal file
@ -0,0 +1,154 @@
|
||||
# /*****************************************************************************
|
||||
# * | File : epdconfig.py
|
||||
# * | Author : Waveshare team
|
||||
# * | Function : Hardware underlying interface
|
||||
# * | Info :
|
||||
# *----------------
|
||||
# * | This version: V1.0
|
||||
# * | Date : 2019-06-21
|
||||
# * | 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
|
||||
|
||||
|
||||
class RaspberryPi:
|
||||
# Pin definition
|
||||
RST_PIN = 17
|
||||
DC_PIN = 25
|
||||
CS_PIN = 8
|
||||
BUSY_PIN = 24
|
||||
|
||||
def __init__(self):
|
||||
import spidev
|
||||
import RPi.GPIO
|
||||
|
||||
self.GPIO = RPi.GPIO
|
||||
|
||||
# SPI device, bus = 0, device = 0
|
||||
self.SPI = spidev.SpiDev(0, 0)
|
||||
|
||||
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 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.BUSY_PIN, self.GPIO.IN)
|
||||
self.SPI.max_speed_hz = 4000000
|
||||
self.SPI.mode = 0b00
|
||||
return 0
|
||||
|
||||
def module_exit(self):
|
||||
logging.debug("spi end")
|
||||
self.SPI.close()
|
||||
|
||||
logging.debug("close 5V, Module enters 0 power consumption ...")
|
||||
self.GPIO.output(self.RST_PIN, 0)
|
||||
self.GPIO.output(self.DC_PIN, 0)
|
||||
|
||||
self.GPIO.cleanup()
|
||||
|
||||
|
||||
class JetsonNano:
|
||||
# Pin definition
|
||||
RST_PIN = 17
|
||||
DC_PIN = 25
|
||||
CS_PIN = 8
|
||||
BUSY_PIN = 24
|
||||
|
||||
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 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.BUSY_PIN, self.GPIO.IN)
|
||||
self.SPI.SYSFS_software_spi_begin()
|
||||
return 0
|
||||
|
||||
def module_exit(self):
|
||||
logging.debug("spi end")
|
||||
self.SPI.SYSFS_software_spi_end()
|
||||
|
||||
logging.debug("close 5V, Module enters 0 power consumption ...")
|
||||
self.GPIO.output(self.RST_PIN, 0)
|
||||
self.GPIO.output(self.DC_PIN, 0)
|
||||
|
||||
self.GPIO.cleanup()
|
||||
|
||||
|
||||
if os.path.exists('/sys/bus/platform/drivers/gpiomem-bcm2835'):
|
||||
implementation = RaspberryPi()
|
||||
else:
|
||||
implementation = JetsonNano()
|
||||
|
||||
for func in [x for x in dir(implementation) if not x.startswith('_')]:
|
||||
setattr(sys.modules[__name__], func, getattr(implementation, func))
|
||||
|
||||
|
||||
### END OF FILE ###
|
349
pwnagotchi/ui/hw/libs/waveshare/v4/epd2in13_V4.py
Normal file
349
pwnagotchi/ui/hw/libs/waveshare/v4/epd2in13_V4.py
Normal file
@ -0,0 +1,349 @@
|
||||
# *****************************************************************************
|
||||
# * | File : epd2in13_V4.py
|
||||
# * | Author : Waveshare team
|
||||
# * | Function : Electronic paper driver
|
||||
# * | Info :
|
||||
# *----------------
|
||||
# * | This version: V1.0
|
||||
# * | Date : 2023-06-25
|
||||
# # | 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 = 122
|
||||
EPD_HEIGHT = 250
|
||||
|
||||
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
|
||||
|
||||
'''
|
||||
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(0xf7)
|
||||
self.send_command(0x20) # Activate Display Update Sequence
|
||||
self.ReadBusy()
|
||||
|
||||
'''
|
||||
function : Turn On Display Fast
|
||||
parameter:
|
||||
'''
|
||||
def TurnOnDisplay_Fast(self):
|
||||
self.send_command(0x22) # Display Update Control
|
||||
self.send_data(0xC7) # fast:0x0c, quality:0x0f, 0xcf
|
||||
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(0xff) # fast:0x0c, quality:0x0f, 0xcf
|
||||
self.send_command(0x20) # Activate Display Update Sequence
|
||||
self.ReadBusy()
|
||||
|
||||
|
||||
'''
|
||||
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()
|
||||
|
||||
return 0
|
||||
|
||||
'''
|
||||
function : Initialize the e-Paper fast register
|
||||
parameter:
|
||||
'''
|
||||
def init_fast(self):
|
||||
if (epdconfig.module_init() != 0):
|
||||
return -1
|
||||
# EPD hardware init start
|
||||
self.reset()
|
||||
|
||||
self.send_command(0x12) #SWRESET
|
||||
self.ReadBusy()
|
||||
|
||||
self.send_command(0x18) # Read built-in temperature sensor
|
||||
self.send_command(0x80)
|
||||
|
||||
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(0x22) # Load temperature value
|
||||
self.send_data(0xB1)
|
||||
self.send_command(0x20)
|
||||
self.ReadBusy()
|
||||
|
||||
self.send_command(0x1A) # Write to temperature register
|
||||
self.send_data(0x64)
|
||||
self.send_data(0x00)
|
||||
|
||||
self.send_command(0x22) # Load temperature value
|
||||
self.send_data(0x91)
|
||||
self.send_command(0x20)
|
||||
self.ReadBusy()
|
||||
|
||||
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):
|
||||
self.send_command(0x24)
|
||||
self.send_data2(image)
|
||||
self.TurnOnDisplay()
|
||||
|
||||
'''
|
||||
function : Sends the image buffer in RAM to e-Paper and fast displays
|
||||
parameter:
|
||||
image : Image data
|
||||
'''
|
||||
def display_fast(self, image):
|
||||
self.send_command(0x24)
|
||||
self.send_data2(image)
|
||||
self.TurnOnDisplay_Fast()
|
||||
'''
|
||||
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.send_command(0x3C) # BorderWavefrom
|
||||
self.send_data(0x80)
|
||||
|
||||
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(0x24) # WRITE_RAM
|
||||
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 ###
|
243
pwnagotchi/ui/hw/libs/waveshare/v4/epdconfig.py
Normal file
243
pwnagotchi/ui/hw/libs/waveshare/v4/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 = JetsonNano()
|
||||
|
||||
for func in [x for x in dir(implementation) if not x.startswith('_')]:
|
||||
setattr(sys.modules[__name__], func, getattr(implementation, func))
|
||||
|
||||
### END OF FILE ###
|
@ -10,22 +10,22 @@ class OledHat(DisplayImpl):
|
||||
self._display = None
|
||||
|
||||
def layout(self):
|
||||
fonts.setup(8, 8, 8, 8, 25, 9)
|
||||
fonts.setup(8, 8, 8, 10, 10, 8)
|
||||
self._layout['width'] = 128
|
||||
self._layout['height'] = 64
|
||||
self._layout['face'] = (0, 32)
|
||||
self._layout['face'] = (0, 30)
|
||||
self._layout['name'] = (0, 10)
|
||||
self._layout['channel'] = (0, 0)
|
||||
self._layout['aps'] = (25, 0)
|
||||
self._layout['uptime'] = (65, 0)
|
||||
self._layout['channel'] = (72, 10)
|
||||
self._layout['aps'] = (0, 0)
|
||||
self._layout['uptime'] = (87, 0)
|
||||
self._layout['line1'] = [0, 9, 128, 9]
|
||||
self._layout['line2'] = [0, 53, 128, 53]
|
||||
self._layout['line2'] = [0, 54, 128, 54]
|
||||
self._layout['friend_face'] = (0, 41)
|
||||
self._layout['friend_name'] = (40, 43)
|
||||
self._layout['shakes'] = (0, 53)
|
||||
self._layout['mode'] = (103, 10)
|
||||
self._layout['shakes'] = (0, 55)
|
||||
self._layout['mode'] = (107, 10)
|
||||
self._layout['status'] = {
|
||||
'pos': (30, 18),
|
||||
'pos': (37, 19),
|
||||
'font': fonts.status_font(fonts.Small),
|
||||
'max': 18
|
||||
}
|
||||
|
46
pwnagotchi/ui/hw/waveshare213g.py
Normal file
46
pwnagotchi/ui/hw/waveshare213g.py
Normal file
@ -0,0 +1,46 @@
|
||||
import logging
|
||||
|
||||
import pwnagotchi.ui.fonts as fonts
|
||||
from pwnagotchi.ui.hw.base import DisplayImpl
|
||||
|
||||
|
||||
class Waveshare213g(DisplayImpl):
|
||||
def __init__(self, config):
|
||||
super(Waveshare213g, self).__init__(config, 'waveshare213g')
|
||||
self._display = None
|
||||
|
||||
def layout(self):
|
||||
fonts.setup(10, 8, 10, 35, 25, 9)
|
||||
self._layout['width'] = 250
|
||||
self._layout['height'] = 122
|
||||
self._layout['face'] = (0, 40)
|
||||
self._layout['name'] = (5, 20)
|
||||
self._layout['channel'] = (0, 0)
|
||||
self._layout['aps'] = (28, 0)
|
||||
self._layout['uptime'] = (185, 0)
|
||||
self._layout['line1'] = [0, 14, 250, 14]
|
||||
self._layout['line2'] = [0, 108, 250, 108]
|
||||
self._layout['friend_face'] = (0, 92)
|
||||
self._layout['friend_name'] = (40, 94)
|
||||
self._layout['shakes'] = (0, 109)
|
||||
self._layout['mode'] = (225, 109)
|
||||
self._layout['status'] = {
|
||||
'pos': (125, 20),
|
||||
'font': fonts.status_font(fonts.Medium),
|
||||
'max': 20
|
||||
}
|
||||
return self._layout
|
||||
|
||||
def initialize(self):
|
||||
logging.info("initializing waveshare g version display")
|
||||
from pwnagotchi.ui.hw.libs.waveshare.v213g.epd2in13g import EPD
|
||||
self._display = EPD()
|
||||
self._display.init()
|
||||
self._display.Clear()
|
||||
|
||||
def render(self, canvas):
|
||||
buf = self._display.getbuffer(canvas)
|
||||
self._display.display(buf)
|
||||
|
||||
def clear(self):
|
||||
self._display.Clear()
|
71
pwnagotchi/ui/hw/waveshare213inb_v4.py
Normal file
71
pwnagotchi/ui/hw/waveshare213inb_v4.py
Normal file
@ -0,0 +1,71 @@
|
||||
import logging
|
||||
|
||||
import pwnagotchi.ui.fonts as fonts
|
||||
from pwnagotchi.ui.hw.base import DisplayImpl
|
||||
from PIL import Image
|
||||
|
||||
class Waveshare213bV4(DisplayImpl):
|
||||
def __init__(self, config):
|
||||
super(Waveshare213bV4, self).__init__(config, 'waveshare213inb_v4')
|
||||
self._display = None
|
||||
|
||||
def layout(self):
|
||||
if self.config['color'] == 'black':
|
||||
fonts.setup(10, 9, 10, 35, 25, 9)
|
||||
self._layout['width'] = 250
|
||||
self._layout['height'] = 122
|
||||
self._layout['face'] = (0, 40)
|
||||
self._layout['name'] = (5, 20)
|
||||
self._layout['channel'] = (0, 0)
|
||||
self._layout['aps'] = (28, 0)
|
||||
self._layout['uptime'] = (185, 0)
|
||||
self._layout['line1'] = [0, 14, 250, 14]
|
||||
self._layout['line2'] = [0, 108, 250, 108]
|
||||
self._layout['friend_face'] = (0, 92)
|
||||
self._layout['friend_name'] = (40, 94)
|
||||
self._layout['shakes'] = (0, 109)
|
||||
self._layout['mode'] = (225, 109)
|
||||
self._layout['status'] = {
|
||||
'pos': (125, 20),
|
||||
'font': fonts.status_font(fonts.Medium),
|
||||
'max': 20
|
||||
}
|
||||
else:
|
||||
fonts.setup(10, 8, 10, 25, 25, 9)
|
||||
self._layout['width'] = 212
|
||||
self._layout['height'] = 104
|
||||
self._layout['face'] = (0, 26)
|
||||
self._layout['name'] = (5, 15)
|
||||
self._layout['channel'] = (0, 0)
|
||||
self._layout['aps'] = (28, 0)
|
||||
self._layout['status'] = (91, 15)
|
||||
self._layout['uptime'] = (147, 0)
|
||||
self._layout['line1'] = [0, 12, 212, 12]
|
||||
self._layout['line2'] = [0, 92, 212, 92]
|
||||
self._layout['friend_face'] = (0, 76)
|
||||
self._layout['friend_name'] = (40, 78)
|
||||
self._layout['shakes'] = (0, 93)
|
||||
self._layout['mode'] = (187, 93)
|
||||
self._layout['status'] = {
|
||||
'pos': (125, 20),
|
||||
'font': fonts.status_font(fonts.Medium),
|
||||
'max': 14
|
||||
}
|
||||
return self._layout
|
||||
|
||||
def initialize(self):
|
||||
logging.info("initializing waveshare 2.13inb v4 display")
|
||||
from pwnagotchi.ui.hw.libs.waveshare.v213inb_v4.epd2in13b_V4 import EPD
|
||||
self._display = EPD()
|
||||
self._display.init()
|
||||
self._display.Clear()
|
||||
|
||||
def render(self, canvasBlack = None, canvasRed = None):
|
||||
buffer = self._display.getbuffer
|
||||
image = Image.new('1', (self._layout['height'], self._layout['width']))
|
||||
imageBlack = image if canvasBlack is None else canvasBlack
|
||||
imageRed = image if canvasRed is None else canvasRed
|
||||
self._display.display(buffer(imageBlack), buffer(imageRed))
|
||||
|
||||
def clear(self):
|
||||
self._display.Clear()
|
46
pwnagotchi/ui/hw/waveshare27inchv2.py
Normal file
46
pwnagotchi/ui/hw/waveshare27inchv2.py
Normal file
@ -0,0 +1,46 @@
|
||||
import logging
|
||||
|
||||
import pwnagotchi.ui.fonts as fonts
|
||||
from pwnagotchi.ui.hw.base import DisplayImpl
|
||||
|
||||
|
||||
class Waveshare27inchV2(DisplayImpl):
|
||||
def __init__(self, config):
|
||||
super(Waveshare27inchV2, self).__init__(config, 'waveshare27inchv2')
|
||||
self._display = None
|
||||
|
||||
def layout(self):
|
||||
fonts.setup(10, 9, 10, 35, 25, 9)
|
||||
self._layout['width'] = 264
|
||||
self._layout['height'] = 176
|
||||
self._layout['face'] = (66, 27)
|
||||
self._layout['name'] = (5, 73)
|
||||
self._layout['channel'] = (0, 0)
|
||||
self._layout['aps'] = (28, 0)
|
||||
self._layout['uptime'] = (199, 0)
|
||||
self._layout['line1'] = [0, 14, 264, 14]
|
||||
self._layout['line2'] = [0, 162, 264, 162]
|
||||
self._layout['friend_face'] = (0, 146)
|
||||
self._layout['friend_name'] = (40, 146)
|
||||
self._layout['shakes'] = (0, 163)
|
||||
self._layout['mode'] = (239, 163)
|
||||
self._layout['status'] = {
|
||||
'pos': (38, 93),
|
||||
'font': fonts.status_font(fonts.Medium),
|
||||
'max': 40
|
||||
}
|
||||
return self._layout
|
||||
|
||||
def initialize(self):
|
||||
logging.info("initializing waveshare v2 2.7 inch display")
|
||||
from pwnagotchi.ui.hw.libs.waveshare.v27inchv2.epd2in7_V2 import EPD
|
||||
self._display = EPD()
|
||||
self._display.init()
|
||||
self._display.Clear()
|
||||
|
||||
def render(self, canvas):
|
||||
buf = self._display.getbuffer(canvas)
|
||||
self._display.display(buf)
|
||||
|
||||
def clear(self):
|
||||
self._display.Clear()
|
48
pwnagotchi/ui/hw/waveshare3.py
Normal file
48
pwnagotchi/ui/hw/waveshare3.py
Normal file
@ -0,0 +1,48 @@
|
||||
import logging
|
||||
|
||||
import pwnagotchi.ui.fonts as fonts
|
||||
from pwnagotchi.ui.hw.base import DisplayImpl
|
||||
|
||||
|
||||
class WaveshareV3(DisplayImpl):
|
||||
def __init__(self, config):
|
||||
super(WaveshareV3, self).__init__(config, 'waveshare_3')
|
||||
self._display = None
|
||||
|
||||
def layout(self):
|
||||
fonts.setup(10, 8, 10, 35, 25, 9)
|
||||
self._layout['width'] = 250
|
||||
self._layout['height'] = 122
|
||||
self._layout['face'] = (0, 40)
|
||||
self._layout['name'] = (5, 20)
|
||||
self._layout['channel'] = (0, 0)
|
||||
self._layout['aps'] = (28, 0)
|
||||
self._layout['uptime'] = (185, 0)
|
||||
self._layout['line1'] = [0, 14, 250, 14]
|
||||
self._layout['line2'] = [0, 108, 250, 108]
|
||||
self._layout['friend_face'] = (0, 92)
|
||||
self._layout['friend_name'] = (40, 94)
|
||||
self._layout['shakes'] = (0, 109)
|
||||
self._layout['mode'] = (225, 109)
|
||||
self._layout['status'] = {
|
||||
'pos': (125, 20),
|
||||
'font': fonts.status_font(fonts.Medium),
|
||||
'max': 20
|
||||
}
|
||||
|
||||
return self._layout
|
||||
|
||||
def initialize(self):
|
||||
logging.info("initializing waveshare v3 display")
|
||||
from pwnagotchi.ui.hw.libs.waveshare.v3.epd2in13_V3 import EPD
|
||||
self._display = EPD()
|
||||
self._display.init()
|
||||
self._display.Clear(0xFF)
|
||||
|
||||
def render(self, canvas):
|
||||
buf = self._display.getbuffer(canvas)
|
||||
self._display.displayPartial(buf)
|
||||
|
||||
def clear(self):
|
||||
#pass
|
||||
self._display.Clear(0xFF)
|
52
pwnagotchi/ui/hw/waveshare35lcd.py
Normal file
52
pwnagotchi/ui/hw/waveshare35lcd.py
Normal file
@ -0,0 +1,52 @@
|
||||
import logging
|
||||
|
||||
import pwnagotchi.ui.fonts as fonts
|
||||
from pwnagotchi.ui.hw.base import DisplayImpl
|
||||
|
||||
import os,time
|
||||
|
||||
class Waveshare35lcd(DisplayImpl):
|
||||
def __init__(self, config):
|
||||
super(Waveshare35lcd, self).__init__(config, 'waveshare35lcd')
|
||||
self._display = None
|
||||
|
||||
def layout(self):
|
||||
fonts.setup(12, 10, 12, 70, 25, 9)
|
||||
self._layout['width'] = 480
|
||||
self._layout['height'] = 320
|
||||
self._layout['face'] = (110, 60)
|
||||
self._layout['name'] = (10, 30)
|
||||
self._layout['channel'] = (0, 0)
|
||||
self._layout['aps'] = (80, 0)
|
||||
self._layout['uptime'] = (400, 0)
|
||||
self._layout['line1'] = [0, 14, 480, 14]
|
||||
self._layout['line2'] = [0,300, 480, 300]
|
||||
self._layout['friend_face'] = (0, 220)
|
||||
self._layout['friend_name'] = (50, 225)
|
||||
self._layout['shakes'] = (10, 300)
|
||||
self._layout['mode'] = (440, 300)
|
||||
self._layout['status'] = {
|
||||
'pos': (80, 180),
|
||||
'font': fonts.status_font(fonts.Medium),
|
||||
'max': 100
|
||||
}
|
||||
|
||||
return self._layout
|
||||
|
||||
def refresh(self):
|
||||
time.sleep(0.1)
|
||||
|
||||
def initialize(self):
|
||||
from pwnagotchi.ui.hw.libs.fb import fb
|
||||
self._display = fb
|
||||
logging.info("initializing waveshare 3,5inch lcd display")
|
||||
self._display.ready_fb(i=1)
|
||||
self._display.black_scr()
|
||||
|
||||
def render(self, canvas):
|
||||
self._display.show_img(canvas.rotate(0))
|
||||
self.refresh()
|
||||
|
||||
def clear(self):
|
||||
self._display.black_scr()
|
||||
self.refresh()
|
50
pwnagotchi/ui/hw/waveshare4.py
Normal file
50
pwnagotchi/ui/hw/waveshare4.py
Normal file
@ -0,0 +1,50 @@
|
||||
import logging
|
||||
|
||||
import pwnagotchi.ui.fonts as fonts
|
||||
from pwnagotchi.ui.hw.base import DisplayImpl
|
||||
from PIL import Image
|
||||
|
||||
class WaveshareV4(DisplayImpl):
|
||||
def __init__(self, config):
|
||||
super(WaveshareV4, self).__init__(config, 'waveshare_4')
|
||||
self._display = None
|
||||
|
||||
def layout(self):
|
||||
fonts.setup(10, 9, 10, 35, 25, 9)
|
||||
self._layout['width'] = 250
|
||||
self._layout['height'] = 122
|
||||
self._layout['face'] = (0, 40)
|
||||
self._layout['name'] = (5, 20)
|
||||
self._layout['channel'] = (0, 0)
|
||||
self._layout['aps'] = (28, 0)
|
||||
self._layout['uptime'] = (185, 0)
|
||||
self._layout['line1'] = [0, 14, 250, 14]
|
||||
self._layout['line2'] = [0, 108, 250, 108]
|
||||
self._layout['friend_face'] = (0, 92)
|
||||
self._layout['friend_name'] = (40, 94)
|
||||
self._layout['shakes'] = (0, 109)
|
||||
self._layout['mode'] = (225, 109)
|
||||
self._layout['status'] = {
|
||||
'pos': (125, 20),
|
||||
'font': fonts.status_font(fonts.Medium),
|
||||
'max': 20
|
||||
}
|
||||
return self._layout
|
||||
|
||||
|
||||
|
||||
def initialize(self):
|
||||
logging.info("initializing waveshare v4 display")
|
||||
from pwnagotchi.ui.hw.libs.waveshare.v4.epd2in13_V4 import EPD
|
||||
self._display = EPD()
|
||||
self._display.init()
|
||||
self._display.Clear(0xFF)
|
||||
|
||||
|
||||
def render(self, canvas):
|
||||
buf = self._display.getbuffer(canvas)
|
||||
self._display.displayPartial(buf)
|
||||
|
||||
|
||||
def clear(self):
|
||||
self._display.Clear(0xFF)
|
BIN
pwnagotchi/ui/web/static/images/pwnagotchi.png
Normal file
BIN
pwnagotchi/ui/web/static/images/pwnagotchi.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 1.2 KiB |
@ -4,6 +4,7 @@
|
||||
<head>
|
||||
{% block meta %}
|
||||
<meta charset="utf-8">
|
||||
<meta name="apple-mobile-web-app-capable" content="yes">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
{% endblock %}
|
||||
|
||||
@ -15,6 +16,8 @@
|
||||
{% block styles %}
|
||||
<link rel="stylesheet" href="/js/jquery.mobile/jquery.mobile-1.4.5.min.css"/>
|
||||
<link rel="stylesheet" type="text/css" href="/css/style.css"/>
|
||||
<link rel="apple-touch-icon" href="/images/pwnagotchi.png">
|
||||
<link rel="icon" type="image/png" href="/images/pwnagotchi.png">
|
||||
{% endblock %}
|
||||
|
||||
</head>
|
||||
|
@ -8,54 +8,61 @@ Plugins
|
||||
{% block styles %}
|
||||
{{ super() }}
|
||||
<style>
|
||||
.tooltip {
|
||||
position: relative;
|
||||
display: inline-block;
|
||||
}
|
||||
.tooltip .tooltiptext {
|
||||
visibility: hidden;
|
||||
width: 200px;
|
||||
background-color: #3388cc;
|
||||
color: #fff;
|
||||
text-align: center;
|
||||
border-radius: 10px;
|
||||
border: 2px solid black;
|
||||
padding: 20px 0;
|
||||
.plugins-box {
|
||||
position: relative;
|
||||
display: inline-block;
|
||||
margin: 10px;
|
||||
}
|
||||
|
||||
position: absolute;
|
||||
z-index: 1;
|
||||
top: 100%;
|
||||
left: 50%;
|
||||
margin-left: -100px;
|
||||
}
|
||||
.tooltip {
|
||||
position: relative;
|
||||
display: inline-block;
|
||||
}
|
||||
|
||||
.tooltip:hover .tooltiptext {
|
||||
visibility: visible;
|
||||
}
|
||||
.tooltip .tooltiptext {
|
||||
visibility: hidden;
|
||||
width: 100%;
|
||||
max-width: 400px;
|
||||
background-color: #3388cc;
|
||||
color: #fff;
|
||||
text-align: center;
|
||||
border-radius: 10px;
|
||||
border: 2px solid black;
|
||||
padding: 20px 0;
|
||||
position: fixed;
|
||||
z-index: 1;
|
||||
top: 50%;
|
||||
left: 50%;
|
||||
transform: translate(-50%, -50%);
|
||||
}
|
||||
|
||||
.tooltip:hover .tooltiptext {
|
||||
visibility: visible;
|
||||
}
|
||||
</style>
|
||||
{% endblock %}
|
||||
|
||||
{% block script %}
|
||||
$(function(){
|
||||
$('input[type=checkbox]').change(function(e) {
|
||||
$(function(){
|
||||
$('input[type=checkbox]').change(function(e) {
|
||||
var checkbox = $(this);
|
||||
var form = checkbox.closest('form');
|
||||
var url = form.attr('action');
|
||||
|
||||
$.ajax({
|
||||
type: 'POST',
|
||||
url: url,
|
||||
data: form.serialize(),
|
||||
success: function(data) {
|
||||
if( data.indexOf('failed') != -1 ) {
|
||||
type: 'POST',
|
||||
url: url,
|
||||
data: form.serialize(),
|
||||
success: function(data) {
|
||||
if (data.indexOf('failed') != -1) {
|
||||
alert('Could not be toggled.');
|
||||
}
|
||||
}
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
||||
});
|
||||
});
|
||||
});
|
||||
{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
<div id="container">
|
||||
{% for name in database.keys() | sort %}
|
||||
@ -65,6 +72,11 @@ $(function(){
|
||||
<h4>
|
||||
<a {% if name in loaded and loaded[name].on_webhook is defined %} href="/plugins/{{name}}" {% endif %}>{{name}}</a>
|
||||
</h4>
|
||||
{% if has_info %}
|
||||
{% if loaded[name].__version__ is defined %}
|
||||
<p>v{{ loaded[name].__version__ }}</p>
|
||||
{% endif %}
|
||||
{% endif %}
|
||||
{% if has_info %}
|
||||
<span class="tooltiptext">{{ loaded[name].__description__ }}</span>
|
||||
{% else %}
|
||||
|
@ -248,9 +248,18 @@ def load_config(args):
|
||||
elif config['ui']['display']['type'] in ('ws_2', 'ws2', 'waveshare_2', 'waveshare2'):
|
||||
config['ui']['display']['type'] = 'waveshare_2'
|
||||
|
||||
elif config['ui']['display']['type'] in ('ws_3', 'ws3', 'waveshare_3', 'waveshare3'):
|
||||
config['ui']['display']['type'] = 'waveshare_3'
|
||||
|
||||
elif config['ui']['display']['type'] in ('ws_4', 'ws4', 'waveshare_4', 'waveshare4'):
|
||||
config['ui']['display']['type'] = 'waveshare_4'
|
||||
|
||||
elif config['ui']['display']['type'] in ('ws_27inch', 'ws27inch', 'waveshare_27inch', 'waveshare27inch'):
|
||||
config['ui']['display']['type'] = 'waveshare27inch'
|
||||
|
||||
elif config['ui']['display']['type'] in ('ws_27inchv2', 'ws27inchv2', 'waveshare_27inchv2', 'waveshare27inchv2'):
|
||||
config['ui']['display']['type'] = 'waveshare27inchv2'
|
||||
|
||||
elif config['ui']['display']['type'] in ('ws_29inch', 'ws29inch', 'waveshare_29inch', 'waveshare29inch'):
|
||||
config['ui']['display']['type'] = 'waveshare29inch'
|
||||
|
||||
@ -272,12 +281,24 @@ def load_config(args):
|
||||
elif config['ui']['display']['type'] in ('ws_213d', 'ws213d', 'waveshare_213d', 'waveshare213d'):
|
||||
config['ui']['display']['type'] = 'waveshare213d'
|
||||
|
||||
elif config['ui']['display']['type'] in ('ws_213g', 'ws213g', 'waveshare_213g', 'waveshare213g'):
|
||||
config['ui']['display']['type'] = 'waveshare213g'
|
||||
|
||||
elif config['ui']['display']['type'] in ('ws_213bc', 'ws213bc', 'waveshare_213bc', 'waveshare213bc'):
|
||||
config['ui']['display']['type'] = 'waveshare213bc'
|
||||
|
||||
elif config['ui']['display']['type'] in ('ws_213bv4', 'ws213bv4', 'waveshare_213bv4', 'waveshare213inb_v4'):
|
||||
config['ui']['display']['type'] = 'waveshare213inb_v4'
|
||||
|
||||
elif config['ui']['display']['type'] in ('waveshare35lcd'):
|
||||
config['ui']['display']['type'] = 'waveshare35lcd'
|
||||
|
||||
elif config['ui']['display']['type'] in ('spotpear24inch'):
|
||||
config['ui']['display']['type'] = 'spotpear24inch'
|
||||
|
||||
elif config['ui']['display']['type'] in ('displayhatmini'):
|
||||
config['ui']['display']['type'] = 'displayhatmini'
|
||||
|
||||
else:
|
||||
print("unsupported display type %s" % config['ui']['display']['type'])
|
||||
sys.exit(1)
|
||||
|
93
requirements.in
Normal file
93
requirements.in
Normal file
@ -0,0 +1,93 @@
|
||||
# To convert into requirements.txt use 'pip-compile --resolver=backtracking --strip-extras --output-file=requirements.txt requirements.in'
|
||||
|
||||
# If you get "error: no such option: --prefer-binary" then you need to run:
|
||||
# pip3 install --upgrade "pip>=20.2"
|
||||
--prefer-binary
|
||||
#--index-url "https://nexus.chadwaltercummings.me/repository/www.piwheels.org/simple"
|
||||
#--extra-index-url "https://nexus.chadwaltercummings.me/repository/pypi.org/simple"
|
||||
|
||||
# Used for bluetooth tethering plugin.
|
||||
dbus-python~=1.2
|
||||
|
||||
# Used for parsing LastSession logs in manual mode.
|
||||
file-read-backwards~=2.0
|
||||
|
||||
# Only using basic Flask and Flask plugin features.
|
||||
# Should be kept up-to-date as Flask is notorious for breaking
|
||||
# environments with their extremely loose dependency definitions.
|
||||
flask-cors~=3.0
|
||||
flask-wtf~=1.0
|
||||
flask~=1.0
|
||||
|
||||
# Used for modeling AI parameters.
|
||||
# NOTE: stable-baselines wants gym[atari,classic_control] but we
|
||||
# can't satisfy the "atari" extra because it requires ale-py
|
||||
# which has no source distributions or RasPi wheels.
|
||||
# Using pip's new backtracking resolver from pip>=20.3 is required
|
||||
# as it improves handling of extras required by indirect dependencies.
|
||||
# NOTE: gym v0.22 modified the gym.Env API.
|
||||
gym~=0.14,<0.22
|
||||
|
||||
# Used for Inky pHAT and wHAT displays.
|
||||
inky~=1.2
|
||||
|
||||
# Used in the AI and UI layers.
|
||||
# Only using basic numpy features.
|
||||
numpy~=1.21.4 # Moved to 1.21.4 from 1.20
|
||||
|
||||
# Used in the UI layer.
|
||||
# Only using core PIL features (Image, ImageFont, ImageDraw).
|
||||
# Very stable library, should be safe to upgrade.
|
||||
Pillow>=5.4
|
||||
|
||||
# Used for pwngrid identity verification (PKCS1, RSA, SHA256).
|
||||
# Very stable library, should be safe to upgrade.
|
||||
pycryptodome~=3.9
|
||||
|
||||
# Used for GPS plugin to parse a GPS datetime string.
|
||||
python-dateutil~=2.8
|
||||
|
||||
# Used exclusively to convert legacy YAML configs to TOML.
|
||||
PyYAML>=5.3
|
||||
|
||||
# Used for HTTP requests with bettercap, pwngrid, and plugins.
|
||||
# Only using core library features (GET, POST, Sessions).
|
||||
# Very stable library, should be safe to upgrade.
|
||||
requests~=2.21
|
||||
|
||||
# Used for WiFi pwnage and WiGLE plugin.
|
||||
scapy~=2.4
|
||||
|
||||
# I2C/SPI communication with displays, also used by some plugins.
|
||||
smbus2~=0.4
|
||||
spidev~=3.5
|
||||
|
||||
# Primary AI library. Safe to upgrade as v3 is a different package.
|
||||
# Upgrading to stable-baselines3 is currently impossible because
|
||||
# it depends on PyTorch which requires a 64-bit processor.
|
||||
stable-baselines~=2.7
|
||||
|
||||
# stable-baselines made a mistake.
|
||||
# stable-baselines has a tensorflow requirement of ">=1.8.0,<2.0.0",
|
||||
# but the requirement is the result of a calculation during setup.
|
||||
# As a result, the requirement is entirely missing from the wheel file.
|
||||
# Furthermore, "<2.0.0" will fail because tensorflow v1.14 contains
|
||||
# breaking API changes in preparation for their v2.X release.
|
||||
tensorflow>=1.8.0,<1.14.0
|
||||
|
||||
# Used for loading and writing configs.
|
||||
toml~=0.10
|
||||
|
||||
# Used for communicating with bettercap.
|
||||
websockets~=8.1
|
||||
|
||||
# WARNING: conflict prevention hack!
|
||||
# flask v1.X requires "Jinja2 >= 2.10, < 3.0"
|
||||
# Jinja2 v2.X requires "MarkupSafe >= 0.23" for a deprecated
|
||||
# function that was later removed in MarkupSafe v2.1.0.
|
||||
# Jinja2 v3.0 no longer uses the deprecated function but
|
||||
# falls outside the version range requested by flask.
|
||||
MarkupSafe<2.1.0
|
||||
|
||||
# Addressing the "TypeError: Descriptors cannot not be created directly." error.
|
||||
protobuf==3.20.*
|
213
requirements.txt
213
requirements.txt
@ -1,24 +1,193 @@
|
||||
pycryptodome==3.9.4
|
||||
requests==2.21.0
|
||||
PyYAML==5.3.1
|
||||
scapy==2.4.3
|
||||
gym==0.14.0
|
||||
scipy==1.3.1
|
||||
stable-baselines==2.7.0
|
||||
tensorflow==1.13.1
|
||||
tensorflow-estimator==1.14.0
|
||||
tweepy==3.7.0
|
||||
#
|
||||
# This file is autogenerated by pip-compile with Python 3.7
|
||||
# by the following command:
|
||||
#
|
||||
# pip-compile --output-file=requirements.txt --pip-args='--retries 5000' --resolver=backtracking --strip-extras requirements.in
|
||||
#
|
||||
--extra-index-url https://www.piwheels.org/simple
|
||||
|
||||
absl-py==2.1.0
|
||||
# via
|
||||
# tensorboard
|
||||
# tensorflow
|
||||
astor==0.8.1
|
||||
# via tensorflow
|
||||
atari-py==0.2.6
|
||||
# via gym
|
||||
certifi==2025.1.31
|
||||
# via requests
|
||||
charset-normalizer==3.4.1
|
||||
# via requests
|
||||
click==7.1.2
|
||||
# via flask
|
||||
cloudpickle==1.6.0
|
||||
# via
|
||||
# gym
|
||||
# stable-baselines
|
||||
cycler==0.11.0
|
||||
# via matplotlib
|
||||
dbus-python==1.3.2
|
||||
# via -r requirements.in
|
||||
file-read-backwards==2.0.0
|
||||
numpy==1.17.2
|
||||
inky==0.0.5
|
||||
smbus2==0.3.0
|
||||
Pillow==5.4.1
|
||||
spidev==3.4
|
||||
gast==0.2.2
|
||||
flask==1.0.2
|
||||
flask-cors==3.0.7
|
||||
flask-wtf==0.14.3
|
||||
dbus-python==1.2.12
|
||||
toml==0.10.0
|
||||
python-dateutil==2.8.1
|
||||
# via -r requirements.in
|
||||
flask==1.1.4
|
||||
# via
|
||||
# -r requirements.in
|
||||
# flask-cors
|
||||
# flask-wtf
|
||||
flask-cors==3.0.10
|
||||
# via -r requirements.in
|
||||
flask-wtf==1.1.1
|
||||
# via -r requirements.in
|
||||
fonttools==4.38.0
|
||||
# via matplotlib
|
||||
gast==0.6.0
|
||||
# via tensorflow
|
||||
google-pasta==0.2.0
|
||||
# via tensorflow
|
||||
grpcio==1.62.3
|
||||
# via
|
||||
# tensorboard
|
||||
# tensorflow
|
||||
gym==0.19.0
|
||||
# via
|
||||
# -r requirements.in
|
||||
# stable-baselines
|
||||
h5py==3.8.0
|
||||
# via keras-applications
|
||||
idna==3.10
|
||||
# via requests
|
||||
importlib-metadata==6.7.0
|
||||
# via
|
||||
# gym
|
||||
# markdown
|
||||
inky==1.5.0
|
||||
# via -r requirements.in
|
||||
itsdangerous==1.1.0
|
||||
# via
|
||||
# flask
|
||||
# flask-wtf
|
||||
jinja2==2.11.3
|
||||
# via flask
|
||||
joblib==1.3.2
|
||||
# via stable-baselines
|
||||
keras-applications==1.0.8
|
||||
# via tensorflow
|
||||
keras-preprocessing==1.1.2
|
||||
# via tensorflow
|
||||
kiwisolver==1.4.5
|
||||
# via matplotlib
|
||||
markdown==3.4.4
|
||||
# via tensorboard
|
||||
markupsafe==2.0.1
|
||||
# via
|
||||
# -r requirements.in
|
||||
# jinja2
|
||||
# wtforms
|
||||
matplotlib==3.5.3
|
||||
# via stable-baselines
|
||||
numpy==1.21.4
|
||||
# via
|
||||
# -r requirements.in
|
||||
# atari-py
|
||||
# gym
|
||||
# h5py
|
||||
# inky
|
||||
# keras-applications
|
||||
# keras-preprocessing
|
||||
# matplotlib
|
||||
# opencv-python
|
||||
# pandas
|
||||
# scipy
|
||||
# stable-baselines
|
||||
# tensorboard
|
||||
# tensorflow
|
||||
opencv-python==4.7.0.72
|
||||
# via
|
||||
# gym
|
||||
# stable-baselines
|
||||
packaging==24.0
|
||||
# via matplotlib
|
||||
pandas==1.3.5
|
||||
# via stable-baselines
|
||||
pillow==9.5.0
|
||||
# via
|
||||
# -r requirements.in
|
||||
# matplotlib
|
||||
protobuf==3.20.3
|
||||
# via
|
||||
# -r requirements.in
|
||||
# tensorboard
|
||||
# tensorflow
|
||||
pycryptodome==3.22.0
|
||||
# via -r requirements.in
|
||||
pyglet==2.0.10
|
||||
# via gym
|
||||
pyparsing==3.1.4
|
||||
# via matplotlib
|
||||
python-dateutil==2.9.0.post0
|
||||
# via
|
||||
# -r requirements.in
|
||||
# matplotlib
|
||||
# pandas
|
||||
pytz==2025.1
|
||||
# via pandas
|
||||
pyyaml==6.0.1
|
||||
# via -r requirements.in
|
||||
requests==2.31.0
|
||||
# via -r requirements.in
|
||||
scapy==2.6.1
|
||||
# via -r requirements.in
|
||||
scipy==1.7.3
|
||||
# via stable-baselines
|
||||
six==1.17.0
|
||||
# via
|
||||
# atari-py
|
||||
# flask-cors
|
||||
# google-pasta
|
||||
# keras-preprocessing
|
||||
# python-dateutil
|
||||
# tensorboard
|
||||
# tensorflow
|
||||
smbus2==0.5.0
|
||||
# via
|
||||
# -r requirements.in
|
||||
# inky
|
||||
spidev==3.6
|
||||
# via
|
||||
# -r requirements.in
|
||||
# inky
|
||||
stable-baselines==2.10.2
|
||||
# via -r requirements.in
|
||||
tensorboard==1.13.1
|
||||
# via tensorflow
|
||||
tensorflow==1.13.1
|
||||
# via -r requirements.in
|
||||
tensorflow-estimator==1.14.0
|
||||
# via tensorflow
|
||||
termcolor==2.3.0
|
||||
# via tensorflow
|
||||
toml==0.10.2
|
||||
# via -r requirements.in
|
||||
typing-extensions==4.7.1
|
||||
# via
|
||||
# importlib-metadata
|
||||
# kiwisolver
|
||||
urllib3==2.0.7
|
||||
# via requests
|
||||
websockets==8.1
|
||||
# via -r requirements.in
|
||||
werkzeug==1.0.1
|
||||
# via
|
||||
# flask
|
||||
# tensorboard
|
||||
wheel==0.42.0
|
||||
# via
|
||||
# tensorboard
|
||||
# tensorflow
|
||||
wrapt==1.16.0
|
||||
# via tensorflow
|
||||
wtforms==3.0.1
|
||||
# via flask-wtf
|
||||
zipp==3.15.0
|
||||
# via importlib-metadata
|
||||
|
@ -46,7 +46,14 @@ FILES_TO_BACKUP="/root/brain.nn \
|
||||
/var/log/pwnagotchi*.gz \
|
||||
/home/pi/.ssh \
|
||||
/home/pi/.bashrc \
|
||||
/home/pi/.profile"
|
||||
/home/pi/.profile \
|
||||
/root/.api-report.json \
|
||||
/root/.auto-update \
|
||||
/root/.bt-tether* \
|
||||
/root/.net_pos_saved \
|
||||
/root/.ohc_uploads \
|
||||
/root/.wigle_uploads \
|
||||
/root/.wpa_sec_uploads"
|
||||
|
||||
ping -c 1 "${UNIT_HOSTNAME}" > /dev/null 2>&1 || {
|
||||
echo "@ unit ${UNIT_HOSTNAME} can't be reached, make sure it's connected and a static IP assigned to the USB interface."
|
||||
|
20
scripts/openbsd_connection_share.sh
Normal file
20
scripts/openbsd_connection_share.sh
Normal file
@ -0,0 +1,20 @@
|
||||
#!/bin/sh
|
||||
|
||||
USB_IFACE=$(ifconfig urndis0 | grep urndis0 | awk '{print $1}' | tr -d ':')
|
||||
USB_IP=${2:-10.0.0.1}
|
||||
|
||||
if test $(whoami) != root; then
|
||||
doas "$0" "$@"
|
||||
exit $?
|
||||
fi
|
||||
|
||||
if [ "${USB_IFACE}" == "urndis0" ]; then
|
||||
ifconfig ${USB_IFACE} ${USB_IP}
|
||||
sysctl -w net.inet.ip.forwarding=1
|
||||
echo "match out on egress inet from ${USB_IFACE}:network to any nat-to (egress:0)" | pfctl -f -
|
||||
pfctl -f /etc/pf.conf
|
||||
echo "sharing connecting from upstream interface to usb interface ${USB_IFACE} ..."
|
||||
else
|
||||
echo "can't find usb interface with ip ${USB_IFACE}"
|
||||
exit 1
|
||||
fi
|
@ -101,6 +101,10 @@ def main():
|
||||
main:
|
||||
lang: {lang}
|
||||
ui:
|
||||
font:
|
||||
name: 'DejaVuSansMono'
|
||||
size_offset: 0
|
||||
size: 0
|
||||
fps: 0.3
|
||||
display:
|
||||
enabled: false
|
||||
@ -110,9 +114,8 @@ def main():
|
||||
type: {display}
|
||||
web:
|
||||
enabled: true
|
||||
address: "0.0.0.0"
|
||||
address: '::'
|
||||
port: 8080
|
||||
|
||||
faces:
|
||||
look_r: '( ⚆_⚆)'
|
||||
look_l: '(☉_☉ )'
|
||||
|
82
setup.py
82
setup.py
@ -1,29 +1,31 @@
|
||||
#!/usr/bin/env python3
|
||||
# -*- coding: utf-8 -*-
|
||||
from setuptools import setup, find_packages
|
||||
from distutils.util import strtobool
|
||||
import os
|
||||
from setuptools.command.install import install
|
||||
import glob
|
||||
import shutil
|
||||
import logging
|
||||
import os
|
||||
import re
|
||||
import shutil
|
||||
import warnings
|
||||
|
||||
log = logging.getLogger(__name__)
|
||||
|
||||
def install_file(source_filename, dest_filename):
|
||||
# do not overwrite network configuration if it exists already
|
||||
# https://github.com/evilsocket/pwnagotchi/issues/483
|
||||
if dest_filename.startswith('/etc/network/interfaces.d/') and os.path.exists(dest_filename):
|
||||
print("%s exists, skipping ..." % dest_filename)
|
||||
log.info(f"{dest_filename} exists, skipping ...")
|
||||
return
|
||||
|
||||
print("installing %s to %s ..." % (source_filename, dest_filename))
|
||||
try:
|
||||
dest_folder = os.path.dirname(dest_filename)
|
||||
if not os.path.isdir(dest_folder):
|
||||
os.makedirs(dest_folder)
|
||||
log.info(f"installing {source_filename} to {dest_filename} ...")
|
||||
dest_folder = os.path.dirname(dest_filename)
|
||||
if not os.path.isdir(dest_folder):
|
||||
os.makedirs(dest_folder)
|
||||
|
||||
shutil.copyfile(source_filename, dest_filename)
|
||||
except Exception as e:
|
||||
print("error installing %s: %s" % (source_filename, e))
|
||||
shutil.copyfile(source_filename, dest_filename)
|
||||
if dest_filename.startswith("/usr/bin/"):
|
||||
os.chmod(dest_filename, 0o755)
|
||||
|
||||
|
||||
def install_system_files():
|
||||
@ -35,31 +37,54 @@ def install_system_files():
|
||||
dest_filename = source_filename.replace(data_path, '')
|
||||
install_file(source_filename, dest_filename)
|
||||
|
||||
|
||||
def restart_services():
|
||||
# reload systemd units
|
||||
os.system("systemctl daemon-reload")
|
||||
|
||||
|
||||
def installer():
|
||||
install_system_files()
|
||||
# for people updating https://github.com/evilsocket/pwnagotchi/pull/551/files
|
||||
os.system("systemctl enable fstrim.timer")
|
||||
|
||||
def version(version_file):
|
||||
with open(version_file, 'rt') as vf:
|
||||
version_file_content = vf.read()
|
||||
|
||||
version_match = re.search(r"__version__\s*=\s*[\"\']([^\"\']+)", version_file_content)
|
||||
if version_match:
|
||||
return version_match.groups()[0]
|
||||
class CustomInstall(install):
|
||||
def run(self):
|
||||
super().run()
|
||||
if os.geteuid() != 0:
|
||||
warnings.warn(
|
||||
"Not running as root, can't install pwnagotchi system files!"
|
||||
)
|
||||
return
|
||||
install_system_files()
|
||||
restart_services()
|
||||
|
||||
|
||||
def version(version_file):
|
||||
#with open(version_file, 'rt') as vf:
|
||||
#version_file_content = vf.read()
|
||||
|
||||
#version_match = re.search(r"__version__\s*=\s*[\"\']([^\"\']+)", version_file_content)
|
||||
#if version_match:
|
||||
#return version_match.groups()[0]
|
||||
|
||||
if "PWN_VERSION" in os.environ:
|
||||
return os.environ["PWN_VERSION"]
|
||||
else:
|
||||
with open(version_file, 'rt') as vf:
|
||||
version_file_content = vf.read()
|
||||
|
||||
version_match = re.search(r"__version__\s*=\s*[\"\']([^\"\']+)", version_file_content)
|
||||
|
||||
if version_match:
|
||||
return version_match.groups()[0]
|
||||
|
||||
return None
|
||||
|
||||
|
||||
if strtobool(os.environ.get("PWNAGOTCHI_ENABLE_INSTALLER", "1")):
|
||||
installer()
|
||||
|
||||
with open('requirements.txt') as fp:
|
||||
required = [line.strip() for line in fp if line.strip() != ""]
|
||||
required = [
|
||||
line.strip()
|
||||
for line in fp
|
||||
if line.strip() and not line.startswith("--")
|
||||
]
|
||||
|
||||
VERSION_FILE = 'pwnagotchi/_version.py'
|
||||
pwnagotchi_version = version(VERSION_FILE)
|
||||
@ -72,8 +97,11 @@ setup(name='pwnagotchi',
|
||||
url='https://pwnagotchi.ai/',
|
||||
license='GPL',
|
||||
install_requires=required,
|
||||
cmdclass={
|
||||
"install": CustomInstall,
|
||||
},
|
||||
scripts=['bin/pwnagotchi'],
|
||||
package_data={'pwnagotchi': ['defaults.yml', 'pwnagotchi/defaults.yml', 'locale/*/LC_MESSAGES/*.mo']},
|
||||
package_data={'pwnagotchi': ['defaults.toml', 'pwnagotchi/defaults.toml', 'locale/*/LC_MESSAGES/*.mo']},
|
||||
include_package_data=True,
|
||||
packages=find_packages(),
|
||||
classifiers=[
|
||||
|
Loading…
x
Reference in New Issue
Block a user