mirror of
https://github.com/jayofelony/pwnagotchi.git
synced 2025-07-01 18:37:27 -04:00
Compare commits
180 Commits
Author | SHA1 | Date | |
---|---|---|---|
7abf9ff8da | |||
18fb956251 | |||
afb1d11cd8 | |||
627be80e6c | |||
c4c4d6c417 | |||
8dcae13ce9 | |||
1352e99774 | |||
2182d7c29d | |||
703c05a93b | |||
697cc5d88b | |||
4b04f9b7a5 | |||
c7b94a0707 | |||
5116bac2a7 | |||
51625e61f9 | |||
bf9a0a96c1 | |||
bd03f07aa8 | |||
3f13df8f20 | |||
ce0275f2ae | |||
b971f18f75 | |||
ea9d11d018 | |||
8a572f1b70 | |||
0c4f2a5093 | |||
7edd752664 | |||
cc550aa236 | |||
ff033d41d3 | |||
6d0a0d8d5f | |||
b33af167d4 | |||
9053762e71 | |||
26fef7dd99 | |||
5dd17291f7 | |||
bac79f3465 | |||
bb8dfe0244 | |||
1b16975031 | |||
dd2b559ebc | |||
93ba2a7f79 | |||
dd18072002 | |||
1eafbb841f | |||
aa817288ea | |||
43285eb2c0 | |||
4b29476c2f | |||
1a8ff930d9 | |||
8a65afdc8f | |||
6fcd1b0e23 | |||
e0b0f5d800 | |||
2243079bf6 | |||
e10eb31ed1 | |||
1c8114e444 | |||
91638a151f | |||
d4adaabcbd | |||
006cdb0fe3 | |||
29386fb945 | |||
4b4646d604 | |||
3fae6ec312 | |||
09a82aa0b4 | |||
541865a2eb | |||
928de2769d | |||
6987840da2 | |||
58de15ce2d | |||
b5ea3da619 | |||
6c68d4608f | |||
14a727954b | |||
aeada2ee6e | |||
ebb8fef3fc | |||
e531288369 | |||
2015b56c5d | |||
2497475057 | |||
0bdbbc23fd | |||
111787544f | |||
efa5d8b197 | |||
9e5fb49d7c | |||
cbbd7d5a6d | |||
90c5818123 | |||
eb76ef4c54 | |||
59e42daeb5 | |||
5b7014c68e | |||
42cc3136ee | |||
2a243870f7 | |||
8f043ff5ef | |||
255bbdbc08 | |||
6f57b66cf4 | |||
a4c25e9996 | |||
9495d55296 | |||
69e7503d67 | |||
25cb1f2175 | |||
54b7ce5d0d | |||
1f9afd541d | |||
87eae76a58 | |||
6691257036 | |||
2149c5dbdf | |||
77af772b4b | |||
14e4fc6d47 | |||
5be8580a59 | |||
f7a599ab8f | |||
5f907b236a | |||
bc92613700 | |||
501ec9ca2b | |||
e5e0180f3c | |||
ea60808700 | |||
a34db250b5 | |||
d29aca15a9 | |||
8531b89771 | |||
59d510d0e1 | |||
913b1a6e1d | |||
de2cdaa3c9 | |||
f2cf34a8b9 | |||
bbb46128fe | |||
46713b6e73 | |||
aa2b09fb21 | |||
9125e43b20 | |||
7e4d926b14 | |||
6417ef5a78 | |||
46c03063fe | |||
d5384d5a81 | |||
e800c66e57 | |||
e3a404cb39 | |||
6cb6aaeb81 | |||
5761dac073 | |||
3ada0628e1 | |||
9fa772c36a | |||
a92e66137c | |||
a7e98cf166 | |||
9a1a264a9f | |||
8a0d482fe0 | |||
0cc320d31f | |||
d841d2b649 | |||
966126a986 | |||
d67935fa6a | |||
46d867ce7f | |||
2e16069850 | |||
6e3cbbdd39 | |||
a19bd6a181 | |||
6c03d95724 | |||
60a9da9acc | |||
25aba0d73c | |||
ecde496534 | |||
7ebd4dd7a0 | |||
35f1707436 | |||
60ced12ea5 | |||
6082a0c169 | |||
a72c1b1628 | |||
e37a0bdc62 | |||
262f8bf551 | |||
5311dd9ddc | |||
bd9c44b4a3 | |||
3b01aec872 | |||
3e571bff2d | |||
391941e64a | |||
3fff6182ed | |||
73e0a1fce7 | |||
9ba23d77d1 | |||
8be75627e9 | |||
c2a36aa678 | |||
fdf0a087f6 | |||
c6efa5df08 | |||
62327a711e | |||
bb460a9cc6 | |||
93b2322ab5 | |||
53a8af4711 | |||
ed5decffa0 | |||
147cbfaa07 | |||
ced9b4d5ee | |||
472c3165ed | |||
1578d13d98 | |||
0965e7eb3e | |||
b789c14f14 | |||
0855b44d59 | |||
55c6007d32 | |||
a7cf8a3383 | |||
faa48b2752 | |||
d20619340c | |||
43a07fe969 | |||
bcce22c164 | |||
0264b7a7db | |||
fba5dd0341 | |||
dd7760efdf | |||
f7cd0fb1fc | |||
0585fe75fe | |||
a7634a2b4a | |||
ca4feb895e | |||
418dbf21e3 |
1
.github/FUNDING.yml
vendored
1
.github/FUNDING.yml
vendored
@ -1,3 +1,4 @@
|
|||||||
# These are supported funding model platforms
|
# These are supported funding model platforms
|
||||||
|
|
||||||
github: jayofelony
|
github: jayofelony
|
||||||
|
custom: https://tikkie.me/pay/dubcto94hnskg539kar0
|
236
.github/workflows/publish.yml
vendored
236
.github/workflows/publish.yml
vendored
@ -8,205 +8,65 @@ on:
|
|||||||
required: true
|
required: true
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
|
build:
|
||||||
publish:
|
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
|
|
||||||
steps:
|
steps:
|
||||||
|
- uses: actions/checkout@v4
|
||||||
|
|
||||||
- name: Remove unnecessary directories
|
- name: Extract version from file
|
||||||
run: |
|
id: get_version
|
||||||
sudo rm -rf /usr/share/dotnet
|
run: |
|
||||||
sudo rm -rf /opt/ghc
|
VERSION=$(cut -d "'" -f2 < pwnagotchi/_version.py)
|
||||||
sudo rm -rf /usr/local/share/boost
|
echo "VERSION=$VERSION" >> $GITHUB_ENV
|
||||||
sudo rm -rf "$AGENT_TOOLSDIRECTORY"
|
|
||||||
|
|
||||||
- name: Check disk space
|
- name: Get latest tag
|
||||||
run: df -BG
|
uses: actions-ecosystem/action-get-latest-tag@v1
|
||||||
|
id: get-latest-tag
|
||||||
|
|
||||||
- name: Checkout code
|
- name: Set LAST_VERSION as an environment variable
|
||||||
uses: actions/checkout@v4
|
run: echo "LAST_VERSION=${{ steps.get-latest-tag.outputs.tag }}" >> $GITHUB_ENV
|
||||||
|
|
||||||
- name: Validate tag
|
- name: Generate release notes
|
||||||
id: tag-setter
|
id: generate_release_notes
|
||||||
run: |
|
run: |
|
||||||
TAG=${{ github.event.inputs.version }}
|
COMMITS=$(git log --merges --pretty=format:"* %s" $LAST_VERSION--$VERSION | sed 's/$/\\n/g')
|
||||||
if [[ $TAG =~ ^v[0-9]+\.[0-9]+\.[0-9]+$ ]]; then
|
CONTRIBUTORS=$(git shortlog -sn $LAST_VERSION--$VERSION | awk '{print "* @" $2}' | sed 's/$/\\n/g')
|
||||||
echo "Tag $TAG is valid."
|
RELEASE_BODY="**Full Changelog**: https://github.com/jayofelony/pwnagotchi/compare/$LAST_VERSION...$VERSION"
|
||||||
echo "TAG=$TAG" >> $GITHUB_OUTPUT
|
echo "RELEASE_BODY=$RELEASE_BODY" >> $GITHUB_ENV
|
||||||
else
|
|
||||||
echo "Tag $TAG is not a valid semantic version. Aborting."
|
|
||||||
exit 1
|
|
||||||
fi
|
|
||||||
|
|
||||||
- name: Set up Python
|
- name: Install qemu dependencies
|
||||||
uses: actions/setup-python@v5
|
run: sudo apt update && sudo apt install qemu-user-static qemu-utils xz-utils -y
|
||||||
with:
|
|
||||||
python-version: 3.9
|
|
||||||
|
|
||||||
- name: Install dependencies
|
- name: Build img file
|
||||||
run: |
|
run: ls -la .; pwd; make all
|
||||||
sudo apt-get update && sudo apt-get install -y libdbus-1-dev curl unzip gettext qemu-utils qemu qemu-user-static binfmt-support
|
|
||||||
pip install -r requirements.txt
|
|
||||||
|
|
||||||
- name: Update QEMU
|
- name: Transfer 32bit.img to docker and give permissions
|
||||||
run: |
|
run: sudo chown runner:docker "pwnagotchi-32bit.img"
|
||||||
sudo update-binfmts --enable qemu-aarch64
|
|
||||||
echo $(ls /usr/bin/qemu-aarch64-static)
|
|
||||||
|
|
||||||
- name: Restart binfmt-support
|
- name: Transfer 64bit.img to docker and give permissions
|
||||||
run: sudo service binfmt-support restart
|
run: sudo chown runner:docker "pwnagotchi-64bit.img"
|
||||||
|
|
||||||
- name: Mount binfmt_misc
|
- name: PiShrink
|
||||||
run: |
|
run: |
|
||||||
if ! grep -qs '/proc/sys/fs/binfmt_misc ' /proc/mounts; then
|
wget https://raw.githubusercontent.com/Drewsif/PiShrink/master/pishrink.sh
|
||||||
echo "Mounting binfmt_misc"
|
chmod +x pishrink.sh
|
||||||
sudo mount binfmt_misc -t binfmt_misc /proc/sys/fs/binfmt_misc
|
sudo mv pishrink.sh /usr/local/bin
|
||||||
fi
|
find /home/runner/work/ -type f -name "*.img" -exec sudo pishrink.sh -aZ {} \;
|
||||||
|
|
||||||
- name: Restart binfmt-support
|
- name: Change name of 32.img.xz to add version
|
||||||
run: sudo service binfmt-support restart
|
run: mv "pwnagotchi-32bit.img.xz" "pwnagotchi-$VERSION-32bit.img.xz"
|
||||||
|
|
||||||
- name: Update Languages
|
|
||||||
run: make update_langs
|
|
||||||
|
|
||||||
- name: Compile Languages
|
|
||||||
run: make compile_langs
|
|
||||||
|
|
||||||
- name: Check disk space
|
- name: Change name of 64.img.xz to add version
|
||||||
run: df -BG
|
run: mv "pwnagotchi-64bit.img.xz" "pwnagotchi-$VERSION-64bit.img.xz"
|
||||||
|
|
||||||
- name: Check qemu-user-static package
|
- name: Release
|
||||||
run: |
|
uses: softprops/action-gh-release@v2
|
||||||
echo "Checking qemu-user-static package..."
|
with:
|
||||||
dpkg -s qemu-user-static && echo "qemu-user-static is installed." || echo "qemu-user-static is NOT installed."
|
prerelease: true
|
||||||
|
tag_name: v${{ env.VERSION }}
|
||||||
- name: Check binfmt-support service
|
name: Pwnagotchi v${{ env.VERSION }}
|
||||||
run: |
|
files: |
|
||||||
echo "Checking binfmt-support service..."
|
pwnagotchi-${{ env.VERSION }}-32bit.img.xz
|
||||||
service binfmt-support status && echo "binfmt-support service is running." || echo "binfmt-support service is NOT running."
|
pwnagotchi-${{ env.VERSION }}-64bit.img.xz
|
||||||
|
body: ${{ env.RELEASE_BODY }}
|
||||||
- name: Check binfmt_misc filesystem
|
|
||||||
run: |
|
|
||||||
echo "Checking binfmt_misc filesystem..."
|
|
||||||
mount | grep binfmt_misc && echo "binfmt_misc is mounted." || echo "binfmt_misc is NOT mounted."
|
|
||||||
echo $(ls /proc/sys/fs/binfmt_misc | grep qemu-aarch64)
|
|
||||||
|
|
||||||
- name: Run Makefile
|
|
||||||
run: make
|
|
||||||
env:
|
|
||||||
PWN_VERSION: ${{ steps.tag-setter.outputs.TAG }}
|
|
||||||
|
|
||||||
- name: PiShrink
|
|
||||||
run: |
|
|
||||||
wget https://raw.githubusercontent.com/Drewsif/PiShrink/master/pishrink.sh
|
|
||||||
chmod +x pishrink.sh
|
|
||||||
sudo mv pishrink.sh /usr/local/bin
|
|
||||||
find /home/runner/work/ -type f -name "*.img" -exec sudo pishrink.sh {} \;
|
|
||||||
|
|
||||||
- name: Compress .img files
|
|
||||||
run: |
|
|
||||||
find /home/runner/work/ -type f -name "*.img" -exec xz --no-warn {} \;
|
|
||||||
|
|
||||||
- name: Create tag
|
|
||||||
uses: actions/github-script@v7
|
|
||||||
with:
|
|
||||||
script: |
|
|
||||||
const version = "${{ steps.tag-setter.outputs.TAG }}"
|
|
||||||
console.log(`Creating tag ${version}`)
|
|
||||||
await github.rest.git.createRef({
|
|
||||||
owner: context.repo.owner,
|
|
||||||
repo: context.repo.repo,
|
|
||||||
ref: `refs/tags/${version}`,
|
|
||||||
sha: context.sha
|
|
||||||
})
|
|
||||||
|
|
||||||
- name: Create Release
|
|
||||||
id: create_release
|
|
||||||
uses: actions/github-script@v7
|
|
||||||
with:
|
|
||||||
script: |
|
|
||||||
const tag = "${{ steps.tag-setter.outputs.TAG }}"
|
|
||||||
console.log(`Creating release with tag: ${tag}`)
|
|
||||||
const release = await github.rest.repos.createRelease({
|
|
||||||
owner: context.repo.owner,
|
|
||||||
repo: context.repo.repo,
|
|
||||||
tag_name: tag,
|
|
||||||
name: tag,
|
|
||||||
draft: false,
|
|
||||||
prerelease: true,
|
|
||||||
generate_release_notes: true
|
|
||||||
})
|
|
||||||
console.log(`Created release with id: ${release.data.id}`)
|
|
||||||
return release.data.id
|
|
||||||
|
|
||||||
- name: Upload Release Asset
|
|
||||||
id: upload-release-asset
|
|
||||||
uses: actions/github-script@v7
|
|
||||||
env:
|
|
||||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
|
||||||
with:
|
|
||||||
script: |
|
|
||||||
const fs = require('fs');
|
|
||||||
const path = require('path');
|
|
||||||
const release_id = "${{ steps.create_release.outputs.result }}";
|
|
||||||
const asset_content_type = 'application/octet-stream';
|
|
||||||
const distDir = '/home/runner/work/';
|
|
||||||
|
|
||||||
const uploadFile = async (filePath) => {
|
|
||||||
if (fs.lstatSync(filePath).isDirectory()) {
|
|
||||||
const files = fs.readdirSync(filePath);
|
|
||||||
for (const file of files) {
|
|
||||||
await uploadFile(path.join(filePath, file));
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
// Check if the file has a .xz extension
|
|
||||||
if (path.extname(filePath) === '.xz') {
|
|
||||||
console.log(`Uploading ${filePath}...`);
|
|
||||||
|
|
||||||
const asset_name = path.basename(filePath);
|
|
||||||
const asset_size = fs.statSync(filePath).size;
|
|
||||||
const asset = fs.createReadStream(filePath);
|
|
||||||
|
|
||||||
const response = await github.rest.repos.uploadReleaseAsset({
|
|
||||||
owner: context.repo.owner,
|
|
||||||
repo: context.repo.repo,
|
|
||||||
release_id: release_id,
|
|
||||||
name: asset_name,
|
|
||||||
data: asset,
|
|
||||||
headers: {
|
|
||||||
'content-type': asset_content_type,
|
|
||||||
'content-length': asset_size
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
console.log(`Uploaded ${filePath}: ${response.data.browser_download_url}`);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
await uploadFile(distDir);
|
|
||||||
|
|
||||||
- name: Update Release
|
|
||||||
uses: actions/github-script@v7
|
|
||||||
with:
|
|
||||||
script: |
|
|
||||||
const release_id = "${{ steps.create_release.outputs.result }}"
|
|
||||||
console.log(`Updating release with id: ${release_id}`)
|
|
||||||
github.rest.repos.updateRelease({
|
|
||||||
owner: context.repo.owner,
|
|
||||||
repo: context.repo.repo,
|
|
||||||
release_id: release_id,
|
|
||||||
tag_name: "${{ steps.tag-setter.outputs.TAG }}",
|
|
||||||
name: "${{ steps.tag-setter.outputs.TAG }}",
|
|
||||||
draft: false,
|
|
||||||
prerelease: false
|
|
||||||
})
|
|
||||||
|
|
||||||
- name: Save environment variable
|
|
||||||
run: echo "${{ steps.tag-setter.outputs.TAG }}" > env_var.txt
|
|
||||||
|
|
||||||
- name: Upload artifact
|
|
||||||
uses: actions/upload-artifact@v4
|
|
||||||
with:
|
|
||||||
name: env-var
|
|
||||||
path: env_var.txt
|
|
39
Makefile
39
Makefile
@ -25,7 +25,8 @@ ifneq (,$(UNSHARE))
|
|||||||
UNSHARE := $(UNSHARE) --uts
|
UNSHARE := $(UNSHARE) --uts
|
||||||
endif
|
endif
|
||||||
|
|
||||||
all: clean image clean
|
# sudo apt-get install qemu-user-static qemu-utils
|
||||||
|
all: clean packer image
|
||||||
|
|
||||||
update_langs:
|
update_langs:
|
||||||
@for lang in pwnagotchi/locale/*/; do\
|
@for lang in pwnagotchi/locale/*/; do\
|
||||||
@ -39,30 +40,22 @@ compile_langs:
|
|||||||
./scripts/language.sh compile $$(basename $$lang); \
|
./scripts/language.sh compile $$(basename $$lang); \
|
||||||
done
|
done
|
||||||
|
|
||||||
PACKER := ~/pwnagotchi/packer
|
packer: clean
|
||||||
PACKER_URL := https://releases.hashicorp.com/packer/$(PACKER_VERSION)/packer_$(PACKER_VERSION)_linux_$(GOARCH).zip
|
curl https://releases.hashicorp.com/packer/$(PACKER_VERSION)/packer_$(PACKER_VERSION)_linux_amd64.zip -o /tmp/packer.zip
|
||||||
$(PACKER):
|
unzip /tmp/packer.zip -d /tmp
|
||||||
mkdir -p $(@D)
|
sudo mv /tmp/packer /usr/bin/packer
|
||||||
curl -L "$(PACKER_URL)" -o $(PACKER).zip
|
|
||||||
unzip $(PACKER).zip -d $(@D)
|
|
||||||
rm $(PACKER).zip
|
|
||||||
chmod +x $@
|
|
||||||
|
|
||||||
SDIST := dist/pwnagotchi-$(PWN_VERSION).tar.gz
|
image: clean packer
|
||||||
$(SDIST): setup.py pwnagotchi
|
export LC_ALL=en_GB.UTF-8
|
||||||
python3 setup.py sdist
|
cd builder && sudo /usr/bin/packer init combined.json.pkr.hcl && sudo $(UNSHARE) /usr/bin/packer build -var "pwn_hostname=$(PWN_HOSTNAME)" -var "pwn_version=$(PWN_VERSION)" combined.json.pkr.hcl
|
||||||
|
|
||||||
# Building the image requires packer, but don't rebuild the image just because packer updated.
|
bullseye: clean packer
|
||||||
pwnagotchi: | $(PACKER)
|
export LC_ALL=en_GB.UTF-8
|
||||||
|
cd builder && sudo /usr/bin/packer init raspberrypi32.json.pkr.hcl && sudo $(UNSHARE) /usr/bin/packer build -var "pwn_hostname=$(PWN_HOSTNAME)" -var "pwn_version=$(PWN_VERSION)" raspberrypi32.json.pkr.hcl
|
||||||
|
|
||||||
# If the packer or ansible files are updated, rebuild the image.
|
bookworm: clean packer
|
||||||
pwnagotchi: $(SDIST) builder/pwnagotchi.json.pkr.hcl builder/raspberrypi64.yml $(shell find builder/data -type f)
|
export LC_ALL=en_GB.UTF-8
|
||||||
|
cd builder && sudo /usr/bin/packer init raspberrypi64.json.pkr.hcl && sudo $(UNSHARE) /usr/bin/packer build -var "pwn_hostname=$(PWN_HOSTNAME)" -var "pwn_version=$(PWN_VERSION)" raspberrypi64.json.pkr.hcl
|
||||||
cd builder && $(PACKER) init pwnagotchi.json.pkr.hcl && sudo $(UNSHARE) $(PACKER) build -var "pwn_hostname=$(PWN_HOSTNAME)" -var "pwn_version=$(PWN_VERSION)" pwnagotchi.json.pkr.hcl
|
|
||||||
|
|
||||||
.PHONY: image
|
|
||||||
image: pwnagotchi
|
|
||||||
|
|
||||||
clean:
|
clean:
|
||||||
- rm -rf dist pwnagotchi.egg-info
|
- rm -rf /tmp/packer*
|
||||||
- rm -f $(PACKER)
|
|
||||||
|
@ -11,7 +11,7 @@ Select ‘Credentials’ from the left menu, click ‘Create Credentials’, sel
|
|||||||
|
|
||||||
Now, the product name and consent screen need to be set -> click ‘Configure consent screen’ and follow the instructions. Once finished:
|
Now, the product name and consent screen need to be set -> click ‘Configure consent screen’ and follow the instructions. Once finished:
|
||||||
|
|
||||||
Select ‘Application type’ to be Web application.
|
Select ‘Application type’ to be Desktop application.
|
||||||
|
|
||||||
Enter an appropriate name.
|
Enter an appropriate name.
|
||||||
|
|
||||||
@ -19,7 +19,7 @@ Input http://localhost/ for ‘Authorized redirect URIs’.
|
|||||||
|
|
||||||
Select the correct oauth scope:
|
Select the correct oauth scope:
|
||||||
|
|
||||||
- drive.file
|
- drive
|
||||||
- drive.install
|
- drive.install
|
||||||
|
|
||||||
Click ‘Create’.
|
Click ‘Create’.
|
||||||
|
31
README.md
31
README.md
@ -1,30 +1,7 @@
|
|||||||
# Pwnagotchi-Torch
|
# Pwnagotchi
|
||||||
<a href="https://github.com/jayofelony/pwnagotchi-bookworm/releases/latest"><img alt="Release" src="https://img.shields.io/github/release/jayofelony/pwnagotchi-bookworm.svg"></a><br/>
|
This is the main source for all forks:
|
||||||
**This fork of [Pwnagotchi](https://www.pwnagotchi.ai) is only for 64-bit Raspberry Pi's. Such as the 02W, 3(b+) and 4(b) and the new Raspberry Pi 5!!.**
|
- RPiZeroW (32bit)
|
||||||
|
- RPiZero2W, RPi3, RPi4, RPi5 (64bit)
|
||||||
The RPi5 can only be used headless currently. (without display.)
|
|
||||||
|
|
||||||
|
|
||||||
If you are using an older 32-bit version Raspberry Pi, ZeroWH, use this [fork](https://github.com/jayofelony/pwnagotchi-torch/releases/tag/v2.6.4) and make sure you download the `armhf` version.
|
|
||||||
|
|
||||||
---
|
|
||||||
Download the latest image file [here](https://github.com/jayofelony/pwnagotchi-bookworm/releases/tag/v2.8.2), and let it auto-update from here on out.
|
|
||||||
|
|
||||||
**Use RPi imager to flash, please don't flash a new user as this will mess with logs created.**
|
|
||||||
- Select `Use Custom Image`
|
|
||||||
- Browse for the downloaded image file
|
|
||||||
- Select No under `Use OS Customization`
|
|
||||||
|
|
||||||
SSH credentials are `pi/raspberry`.
|
|
||||||
|
|
||||||
# Donations:
|
|
||||||
I would like to thank
|
|
||||||
- [findingmoist](https://github.com/findingmoist)
|
|
||||||
- [kr4k0n](https://github.com/kr4k0n)
|
|
||||||
|
|
||||||
for donating!
|
|
||||||
|
|
||||||
[Pwnagotchi-Torch](https://www.patreon.com/pwnagotchi_torch)
|
|
||||||
|
|
||||||
[GH Sponsor](https://github.com/sponsors/jayofelony)
|
[GH Sponsor](https://github.com/sponsors/jayofelony)
|
||||||
|
|
||||||
|
1
apt_packages.txt
Normal file
1
apt_packages.txt
Normal file
@ -0,0 +1 @@
|
|||||||
|
bluez-tools
|
139
bin/pwnagotchi
139
bin/pwnagotchi
@ -7,6 +7,7 @@ import sys
|
|||||||
import toml
|
import toml
|
||||||
import requests
|
import requests
|
||||||
import os
|
import os
|
||||||
|
import re
|
||||||
|
|
||||||
import pwnagotchi
|
import pwnagotchi
|
||||||
from pwnagotchi import utils
|
from pwnagotchi import utils
|
||||||
@ -50,7 +51,6 @@ def pwnagotchi_cli():
|
|||||||
|
|
||||||
agent.mode = 'auto'
|
agent.mode = 'auto'
|
||||||
agent.start()
|
agent.start()
|
||||||
config = agent.config()
|
|
||||||
|
|
||||||
while True:
|
while True:
|
||||||
try:
|
try:
|
||||||
@ -60,7 +60,7 @@ def pwnagotchi_cli():
|
|||||||
channels = agent.get_access_points_by_channel()
|
channels = agent.get_access_points_by_channel()
|
||||||
# for each channel
|
# for each channel
|
||||||
for ch, aps in channels:
|
for ch, aps in channels:
|
||||||
time.sleep(0.2) # This is to make sure it doesn't error (https://github.com/seemoo-lab/nexmon/issues/596)
|
time.sleep(0.2)
|
||||||
agent.set_channel(ch)
|
agent.set_channel(ch)
|
||||||
|
|
||||||
if not agent.is_stale() and agent.any_activity():
|
if not agent.is_stale() and agent.any_activity():
|
||||||
@ -68,9 +68,6 @@ def pwnagotchi_cli():
|
|||||||
|
|
||||||
# for each ap on this channel
|
# for each ap on this channel
|
||||||
for ap in aps:
|
for ap in aps:
|
||||||
if ap['mac'][:13].lower in config['main']['whitelist'] or ap['hostname'] in config['main']['whitelist']:
|
|
||||||
logging.info(f"Found your MAC address {ap['mac']} - {config['main']['whitelist']}")
|
|
||||||
continue
|
|
||||||
# send an association frame in order to get for a PMKID
|
# send an association frame in order to get for a PMKID
|
||||||
agent.associate(ap)
|
agent.associate(ap)
|
||||||
# deauth all client stations in order to get a full handshake
|
# deauth all client stations in order to get a full handshake
|
||||||
@ -92,10 +89,8 @@ def pwnagotchi_cli():
|
|||||||
|
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
if str(e).find("wifi.interface not set") > 0:
|
if str(e).find("wifi.interface not set") > 0:
|
||||||
logging.exception(
|
logging.exception("main loop exception due to unavailable wifi device, likely programmatically disabled (%s)", e)
|
||||||
"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")
|
||||||
logging.info(
|
|
||||||
"sleeping 60 seconds then advancing to next epoch to allow for cleanup code to trigger")
|
|
||||||
time.sleep(60)
|
time.sleep(60)
|
||||||
agent.next_epoch()
|
agent.next_epoch()
|
||||||
else:
|
else:
|
||||||
@ -137,6 +132,8 @@ def pwnagotchi_cli():
|
|||||||
help="Print the configuration.")
|
help="Print the configuration.")
|
||||||
|
|
||||||
# Jayofelony added these
|
# Jayofelony added these
|
||||||
|
parser.add_argument('--wizard', dest="wizard", action="store_true", default=False,
|
||||||
|
help="Interactive installation of your personal configuration.")
|
||||||
parser.add_argument('--check-update', dest="check_update", action="store_true", default=False,
|
parser.add_argument('--check-update', dest="check_update", action="store_true", default=False,
|
||||||
help="Check for updates on Pwnagotchi. And tells current version.")
|
help="Check for updates on Pwnagotchi. And tells current version.")
|
||||||
parser.add_argument('--donate', dest="donate", action="store_true", default=False,
|
parser.add_argument('--donate', dest="donate", action="store_true", default=False,
|
||||||
@ -161,30 +158,137 @@ def pwnagotchi_cli():
|
|||||||
print(pwnagotchi.__version__)
|
print(pwnagotchi.__version__)
|
||||||
sys.exit(0)
|
sys.exit(0)
|
||||||
|
|
||||||
|
if args.wizard:
|
||||||
|
def is_valid_hostname(hostname):
|
||||||
|
if len(hostname) > 255:
|
||||||
|
return False
|
||||||
|
if hostname[-1] == ".":
|
||||||
|
hostname = hostname[:-1] # strip exactly one dot from the right, if present
|
||||||
|
allowed = re.compile("(?!-)[A-Z\d-]{1,63}(?<!-)$", re.IGNORECASE)
|
||||||
|
return all(allowed.match(x) for x in hostname.split("."))
|
||||||
|
|
||||||
|
pwn_restore = input("Do you want to restore the previous configuration?\n\n"
|
||||||
|
"[Y/N]: ")
|
||||||
|
if pwn_restore in ('y', 'yes'):
|
||||||
|
os.system("cp -f /etc/pwnagotchi/config.toml.bak /etc/pwnagotchi/config.toml")
|
||||||
|
print("Your previous configuration is restored, and I will restart in 5 seconds.")
|
||||||
|
time.sleep(5)
|
||||||
|
os.system("service pwnagotchi restart")
|
||||||
|
else:
|
||||||
|
pwn_check = input("This will create a new configuration file and overwrite your current backup, are you sure?\n\n"
|
||||||
|
"[Y/N]: ")
|
||||||
|
if pwn_check.lower() in ('y', 'yes'):
|
||||||
|
os.system("mv -f /etc/pwnagotchi/config.toml /etc/pwnagotchi/config.toml.bak")
|
||||||
|
with open("/etc/pwnagotchi/config.toml", "a+") as f:
|
||||||
|
f.write("# Do not edit this file if you do not know what you are doing!!!\n\n")
|
||||||
|
# Set pwnagotchi name
|
||||||
|
print("Welcome to the interactive installation of your personal Pwnagotchi configuration!\n"
|
||||||
|
"My name is Jayofelony, how may I call you?\n\n")
|
||||||
|
pwn_name = input("Pwnagotchi name (no spaces): ")
|
||||||
|
if pwn_name == "":
|
||||||
|
pwn_name = "Pwnagotchi"
|
||||||
|
print("I shall go by Pwnagotchi from now on!")
|
||||||
|
pwn_name = f"main.name = \"{pwn_name}\"\n"
|
||||||
|
f.write(pwn_name)
|
||||||
|
else:
|
||||||
|
if is_valid_hostname(pwn_name):
|
||||||
|
print(f"I shall go by {pwn_name} from now on!")
|
||||||
|
pwn_name = f"main.name = \"{pwn_name}\"\n"
|
||||||
|
f.write(pwn_name)
|
||||||
|
else:
|
||||||
|
print("You have chosen an invalid name. Please start over.")
|
||||||
|
exit()
|
||||||
|
pwn_whitelist = input("How many networks do you want to whitelist? "
|
||||||
|
"We will also ask a MAC for each network?\n"
|
||||||
|
"Each SSID and BSSID count as 1 network. \n\n"
|
||||||
|
"Be sure to use digits as your answer.\n\n"
|
||||||
|
"Amount of networks: ")
|
||||||
|
if int(pwn_whitelist) > 0:
|
||||||
|
f.write("main.whitelist = [\n")
|
||||||
|
for x in range(int(pwn_whitelist)):
|
||||||
|
ssid = input("SSID (Name): ")
|
||||||
|
bssid = input("BSSID (MAC): ")
|
||||||
|
f.write(f"\t\"{ssid}\",\n")
|
||||||
|
f.write(f"\t\"{bssid}\",\n")
|
||||||
|
f.write("]\n")
|
||||||
|
# set bluetooth tether
|
||||||
|
pwn_bluetooth = input("Do you want to enable BT-Tether?\n\n"
|
||||||
|
"[Y/N] ")
|
||||||
|
if pwn_bluetooth.lower() in ('y', 'yes'):
|
||||||
|
f.write("main.plugins.bt-tether.enabled = true\n\n")
|
||||||
|
pwn_bluetooth_device = input("What device do you use? Android or iOS?\n\n"
|
||||||
|
"Device: ")
|
||||||
|
if pwn_bluetooth_device.lower() == "android":
|
||||||
|
f.write("main.plugins.bt-tether.devices.android-phone.enabled = true\n")
|
||||||
|
pwn_bluetooth_mac = input("What is the bluetooth MAC of your device?\n\n"
|
||||||
|
"MAC: ")
|
||||||
|
if pwn_bluetooth_mac != "":
|
||||||
|
f.write(f"main.plugins.bt-tether.devices.android-phone.mac = \"{pwn_bluetooth_mac}\"\n")
|
||||||
|
elif pwn_bluetooth_device.lower() == "ios":
|
||||||
|
f.write("main.plugins.bt-tether.devices.ios-phone.enabled = true\n")
|
||||||
|
pwn_bluetooth_mac = input("What is the bluetooth MAC of your device?\n\n"
|
||||||
|
"MAC: ")
|
||||||
|
if pwn_bluetooth_mac != "":
|
||||||
|
f.write(f"main.plugins.bt-tether.devices.ios-phone.mac = \"{pwn_bluetooth_mac}\"\n")
|
||||||
|
# set up display settings
|
||||||
|
pwn_display_enabled = input("Do you want to enable a display?\n\n"
|
||||||
|
"[Y/N]: ")
|
||||||
|
if pwn_display_enabled.lower() in ('y', 'yes'):
|
||||||
|
f.write("ui.display.enabled = true\n")
|
||||||
|
pwn_display_type = input("What display do you use?\n\n"
|
||||||
|
"Be sure to check for the correct display type @ \n"
|
||||||
|
"https://github.com/jayofelony/pwnagotchi/blob/master/pwnagotchi/utils.py#L240-L431\n\n"
|
||||||
|
"Display type: ")
|
||||||
|
if pwn_display_type != "":
|
||||||
|
f.write(f"ui.display.type = \"{pwn_display_type}\"\n")
|
||||||
|
pwn_display_invert = input("Do you want to invert the display colors?\n"
|
||||||
|
"N = Black background\n"
|
||||||
|
"Y = White background\n\n"
|
||||||
|
"[Y/N]: ")
|
||||||
|
if pwn_display_invert.lower() in ('y', 'yes'):
|
||||||
|
f.write("ui.invert = true\n")
|
||||||
|
f.close()
|
||||||
|
if pwn_bluetooth.lower() in ('y', 'yes'):
|
||||||
|
if pwn_bluetooth_device.lower == "android":
|
||||||
|
print("To visit the webui when connected with your phone, visit: http://192.168.44.44:8080\n"
|
||||||
|
"Your configuration is done, and I will restart in 5 seconds.")
|
||||||
|
elif pwn_bluetooth_device.lower == "ios":
|
||||||
|
print("To visit the webui when connected with your phone, visit: http://172.20.10.6:8080\n"
|
||||||
|
"Your configuration is done, and I will restart in 5 seconds.")
|
||||||
|
else:
|
||||||
|
print("Your configuration is done, and I will restart in 5 seconds.")
|
||||||
|
time.sleep(5)
|
||||||
|
os.system("service pwnagotchi restart")
|
||||||
|
else:
|
||||||
|
print("Ok, doing nothing.")
|
||||||
|
sys.exit(0)
|
||||||
|
|
||||||
if args.donate:
|
if args.donate:
|
||||||
print("Donations can made @ https://github.com/sponsors/jayofelony \n\nBut only if you really want to!")
|
print("Donations can made @ \n "
|
||||||
|
"https://www.patreon.com/pwnagotchi_torch \n "
|
||||||
|
"https://github.com/sponsors/jayofelony \n\n"
|
||||||
|
"But only if you really want to!")
|
||||||
sys.exit(0)
|
sys.exit(0)
|
||||||
|
|
||||||
if args.check_update:
|
if args.check_update:
|
||||||
resp = requests.get("https://api.github.com/repos/jayofelony/pwnagotchi-bookworm/releases/latest")
|
resp = requests.get("https://api.github.com/repos/jayofelony/pwnagotchi/releases/latest")
|
||||||
latest = resp.json()
|
latest = resp.json()
|
||||||
latest_ver = latest['tag_name'].replace('v', '')
|
latest_ver = latest['tag_name'].replace('v', '')
|
||||||
|
|
||||||
local = version_to_tuple(pwnagotchi.__version__)
|
local = version_to_tuple(pwnagotchi.__version__)
|
||||||
remote = version_to_tuple(latest_ver)
|
remote = version_to_tuple(latest_ver)
|
||||||
if remote > local:
|
if remote > local:
|
||||||
user_input = input("There is a new version available! Update from v%s to v%s?\n[y(es)/n(o)]"
|
user_input = input("There is a new version available! Update from v%s to v%s?\n[Y/N] "
|
||||||
% (pwnagotchi.__version__, latest_ver))
|
% (pwnagotchi.__version__, latest_ver))
|
||||||
# input validation
|
# input validation
|
||||||
if user_input.lower() in ('y', 'yes'):
|
if user_input.lower() in ('y', 'yes'):
|
||||||
if os.path.exists('/root/.auto-update'):
|
if os.path.exists('/root/.auto-update'):
|
||||||
os.system("rm /root/.auto-update && systemctl restart pwnagotchi")
|
os.system("rm /root/.auto-update && systemctl restart pwnagotchi")
|
||||||
os.system("systemctl restart pwnagotchi")
|
else:
|
||||||
|
logging.error("You should make sure auto-update is enabled!")
|
||||||
print("Okay, give me a couple minutes. Just watch pwnlog while you wait.")
|
print("Okay, give me a couple minutes. Just watch pwnlog while you wait.")
|
||||||
elif user_input.lower() in ('n', 'no'):
|
elif user_input.lower() in ('n', 'no'): # using this elif for readability
|
||||||
print("Okay, guess not!")
|
print("Okay, guess not!")
|
||||||
else:
|
|
||||||
print("Invalid input.")
|
|
||||||
else:
|
else:
|
||||||
print("You are currently on the latest release, v%s." % pwnagotchi.__version__)
|
print("You are currently on the latest release, v%s." % pwnagotchi.__version__)
|
||||||
sys.exit(0)
|
sys.exit(0)
|
||||||
@ -230,6 +334,5 @@ def pwnagotchi_cli():
|
|||||||
else:
|
else:
|
||||||
do_auto_mode(agent)
|
do_auto_mode(agent)
|
||||||
|
|
||||||
|
|
||||||
if __name__ == '__main__':
|
if __name__ == '__main__':
|
||||||
pwnagotchi_cli()
|
pwnagotchi_cli()
|
173
builder/combined.json.pkr.hcl
Normal file
173
builder/combined.json.pkr.hcl
Normal file
@ -0,0 +1,173 @@
|
|||||||
|
packer {
|
||||||
|
required_plugins {
|
||||||
|
arm = {
|
||||||
|
version = "1.0.0"
|
||||||
|
source = "github.com/cdecoux/builder-arm"
|
||||||
|
}
|
||||||
|
ansible = {
|
||||||
|
source = "github.com/hashicorp/ansible"
|
||||||
|
version = ">= 1.1.1"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
variable "pwn_hostname" {
|
||||||
|
type = string
|
||||||
|
}
|
||||||
|
|
||||||
|
variable "pwn_version" {
|
||||||
|
type = string
|
||||||
|
}
|
||||||
|
|
||||||
|
source "arm" "rpi64-pwnagotchi" {
|
||||||
|
file_checksum_url = "https://downloads.raspberrypi.com/raspios_lite_arm64/images/raspios_lite_arm64-2024-03-15/2024-03-15-raspios-bookworm-arm64-lite.img.xz.sha256"
|
||||||
|
file_urls = ["https://downloads.raspberrypi.com/raspios_lite_arm64/images/raspios_lite_arm64-2024-03-15/2024-03-15-raspios-bookworm-arm64-lite.img.xz"]
|
||||||
|
file_checksum_type = "sha256"
|
||||||
|
file_target_extension = "xz"
|
||||||
|
file_unarchive_cmd = ["unxz", "$ARCHIVE_PATH"]
|
||||||
|
image_path = "../pwnagotchi-64bit.img"
|
||||||
|
qemu_binary_source_path = "/usr/libexec/qemu-binfmt/aarch64-binfmt-P"
|
||||||
|
qemu_binary_destination_path = "/usr/libexec/qemu-binfmt/aarch64-binfmt-P"
|
||||||
|
image_build_method = "resize"
|
||||||
|
image_size = "9G"
|
||||||
|
image_type = "dos"
|
||||||
|
image_partitions {
|
||||||
|
name = "boot"
|
||||||
|
type = "c"
|
||||||
|
start_sector = "8192"
|
||||||
|
filesystem = "fat"
|
||||||
|
size = "256M"
|
||||||
|
mountpoint = "/boot/firmware"
|
||||||
|
}
|
||||||
|
image_partitions {
|
||||||
|
name = "root"
|
||||||
|
type = "83"
|
||||||
|
start_sector = "532480"
|
||||||
|
filesystem = "ext4"
|
||||||
|
size = "0"
|
||||||
|
mountpoint = "/"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
source "arm" "rpi32-pwnagotchi" {
|
||||||
|
file_checksum_url = "https://downloads.raspberrypi.com/raspios_oldstable_lite_armhf/images/raspios_oldstable_lite_armhf-2024-03-12/2024-03-12-raspios-bullseye-armhf-lite.img.xz.sha256"
|
||||||
|
file_urls = ["https://downloads.raspberrypi.com/raspios_oldstable_lite_armhf/images/raspios_oldstable_lite_armhf-2024-03-12/2024-03-12-raspios-bullseye-armhf-lite.img.xz"]
|
||||||
|
file_checksum_type = "sha256"
|
||||||
|
file_target_extension = "xz"
|
||||||
|
file_unarchive_cmd = ["unxz", "$ARCHIVE_PATH"]
|
||||||
|
image_path = "../pwnagotchi-32bit.img"
|
||||||
|
qemu_binary_source_path = "/usr/libexec/qemu-binfmt/arm-binfmt-P"
|
||||||
|
qemu_binary_destination_path = "/usr/libexec/qemu-binfmt/arm-binfmt-P"
|
||||||
|
image_build_method = "resize"
|
||||||
|
image_size = "9G"
|
||||||
|
image_type = "dos"
|
||||||
|
image_partitions {
|
||||||
|
name = "boot"
|
||||||
|
type = "c"
|
||||||
|
start_sector = "8192"
|
||||||
|
filesystem = "fat"
|
||||||
|
size = "256M"
|
||||||
|
mountpoint = "/boot"
|
||||||
|
}
|
||||||
|
image_partitions {
|
||||||
|
name = "root"
|
||||||
|
type = "83"
|
||||||
|
start_sector = "532480"
|
||||||
|
filesystem = "ext4"
|
||||||
|
size = "0"
|
||||||
|
mountpoint = "/"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
# a build block invokes sources and runs provisioning steps on them. The
|
||||||
|
# documentation for build blocks can be found here:
|
||||||
|
# https://www.packer.io/docs/from-1.5/blocks/build
|
||||||
|
build {
|
||||||
|
name = "Raspberry Pi 64 Pwnagotchi"
|
||||||
|
sources = ["source.arm.rpi64-pwnagotchi"]
|
||||||
|
|
||||||
|
provisioner "file" {
|
||||||
|
destination = "/usr/bin/"
|
||||||
|
sources = [
|
||||||
|
"data/64bit/usr/bin/bettercap-launcher",
|
||||||
|
"data/64bit/usr/bin/hdmioff",
|
||||||
|
"data/64bit/usr/bin/hdmion",
|
||||||
|
"data/64bit/usr/bin/monstart",
|
||||||
|
"data/64bit/usr/bin/monstop",
|
||||||
|
"data/64bit/usr/bin/pwnagotchi-launcher",
|
||||||
|
"data/64bit/usr/bin/pwnlib",
|
||||||
|
]
|
||||||
|
}
|
||||||
|
provisioner "shell" {
|
||||||
|
inline = ["chmod +x /usr/bin/*"]
|
||||||
|
}
|
||||||
|
|
||||||
|
provisioner "file" {
|
||||||
|
destination = "/etc/systemd/system/"
|
||||||
|
sources = [
|
||||||
|
"data/64bit/etc/systemd/system/bettercap.service",
|
||||||
|
"data/64bit/etc/systemd/system/pwnagotchi.service",
|
||||||
|
"data/64bit/etc/systemd/system/pwngrid-peer.service",
|
||||||
|
]
|
||||||
|
}
|
||||||
|
provisioner "file" {
|
||||||
|
destination = "/etc/update-motd.d/01-motd"
|
||||||
|
source = "data/64bit/etc/update-motd.d/01-motd"
|
||||||
|
}
|
||||||
|
provisioner "shell" {
|
||||||
|
inline = ["chmod +x /etc/update-motd.d/*"]
|
||||||
|
}
|
||||||
|
provisioner "shell" {
|
||||||
|
inline = ["apt-get -y --allow-releaseinfo-change update", "apt-get -y dist-upgrade", "apt-get install -y --no-install-recommends ansible"]
|
||||||
|
}
|
||||||
|
provisioner "ansible-local" {
|
||||||
|
command = "ANSIBLE_FORCE_COLOR=1 PYTHONUNBUFFERED=1 PWN_VERSION=${var.pwn_version} PWN_HOSTNAME=${var.pwn_hostname} ansible-playbook"
|
||||||
|
extra_arguments = ["--extra-vars \"ansible_python_interpreter=/usr/bin/python3\""]
|
||||||
|
playbook_file = "raspberrypi64.yml"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
build {
|
||||||
|
name = "Raspberry Pi 32 Pwnagotchi"
|
||||||
|
sources = ["source.arm.rpi32-pwnagotchi"]
|
||||||
|
provisioner "file" {
|
||||||
|
destination = "/usr/bin/"
|
||||||
|
sources = [
|
||||||
|
"data/32bit/usr/bin/bettercap-launcher",
|
||||||
|
"data/32bit/usr/bin/hdmioff",
|
||||||
|
"data/32bit/usr/bin/hdmion",
|
||||||
|
"data/32bit/usr/bin/monstart",
|
||||||
|
"data/32bit/usr/bin/monstop",
|
||||||
|
"data/32bit/usr/bin/pwnagotchi-launcher",
|
||||||
|
"data/32bit/usr/bin/pwnlib",
|
||||||
|
]
|
||||||
|
}
|
||||||
|
provisioner "shell" {
|
||||||
|
inline = ["chmod +x /usr/bin/*"]
|
||||||
|
}
|
||||||
|
|
||||||
|
provisioner "file" {
|
||||||
|
destination = "/etc/systemd/system/"
|
||||||
|
sources = [
|
||||||
|
"data/32bit/etc/systemd/system/bettercap.service",
|
||||||
|
"data/32bit/etc/systemd/system/pwnagotchi.service",
|
||||||
|
"data/32bit/etc/systemd/system/pwngrid-peer.service",
|
||||||
|
]
|
||||||
|
}
|
||||||
|
provisioner "file" {
|
||||||
|
destination = "/etc/update-motd.d/01-motd"
|
||||||
|
source = "data/32bit/etc/update-motd.d/01-motd"
|
||||||
|
}
|
||||||
|
provisioner "shell" {
|
||||||
|
inline = ["chmod +x /etc/update-motd.d/*"]
|
||||||
|
}
|
||||||
|
provisioner "shell" {
|
||||||
|
inline = ["apt-get -y --allow-releaseinfo-change update", "apt-get -y dist-upgrade", "apt-get install -y --no-install-recommends ansible"]
|
||||||
|
}
|
||||||
|
provisioner "ansible-local" {
|
||||||
|
command = "ANSIBLE_FORCE_COLOR=1 PYTHONUNBUFFERED=1 PWN_VERSION=${var.pwn_version} PWN_HOSTNAME=${var.pwn_hostname} ansible-playbook"
|
||||||
|
extra_arguments = ["--extra-vars \"ansible_python_interpreter=/usr/bin/python3\""]
|
||||||
|
playbook_dir = "extras/"
|
||||||
|
playbook_file = "raspberrypi32.yml"
|
||||||
|
}
|
||||||
|
}
|
67
builder/data/32bit/boot/config.txt
Normal file
67
builder/data/32bit/boot/config.txt
Normal file
@ -0,0 +1,67 @@
|
|||||||
|
# For more options and information see
|
||||||
|
# http://rptl.io/configtxt
|
||||||
|
# Some settings may impact device functionality. See link above for details
|
||||||
|
|
||||||
|
# Uncomment some or all of these to enable the optional hardware interfaces
|
||||||
|
#dtparam=i2c_arm=on
|
||||||
|
#dtparam=i2s=on
|
||||||
|
#dtparam=spi=on
|
||||||
|
|
||||||
|
# Enable audio (loads snd_bcm2835)
|
||||||
|
dtparam=audio=on
|
||||||
|
|
||||||
|
# Additional overlays and parameters are documented
|
||||||
|
# /boot/overlays/README
|
||||||
|
|
||||||
|
# Automatically load overlays for detected cameras
|
||||||
|
camera_auto_detect=1
|
||||||
|
|
||||||
|
# Automatically load overlays for detected DSI displays
|
||||||
|
display_auto_detect=1
|
||||||
|
|
||||||
|
# Automatically load initramfs files, if found
|
||||||
|
auto_initramfs=1
|
||||||
|
|
||||||
|
# Enable DRM VC4 V3D driver
|
||||||
|
dtoverlay=vc4-kms-v3d
|
||||||
|
max_framebuffers=2
|
||||||
|
|
||||||
|
# Don't have the firmware create an initial video= setting in cmdline.txt.
|
||||||
|
# Use the kernel's default instead.
|
||||||
|
disable_fw_kms_setup=1
|
||||||
|
|
||||||
|
# Run in 64-bit mode
|
||||||
|
arm_64bit=0
|
||||||
|
|
||||||
|
# Disable compensation for displays with overscan
|
||||||
|
disable_overscan=1
|
||||||
|
|
||||||
|
# Run as fast as firmware / board allows
|
||||||
|
arm_boost=1
|
||||||
|
|
||||||
|
[cm4]
|
||||||
|
# Enable host mode on the 2711 built-in XHCI USB controller.
|
||||||
|
# This line should be removed if the legacy DWC2 controller is required
|
||||||
|
# (e.g. for USB device mode) or if USB support is not required.
|
||||||
|
otg_mode=1
|
||||||
|
|
||||||
|
[all]
|
||||||
|
dtoverlay=dwc2
|
||||||
|
dtparam=i2c1=on
|
||||||
|
dtparam=i2c_arm=on
|
||||||
|
dtparam=spi=on
|
||||||
|
gpu_mem=1
|
||||||
|
dtoverlay=dwc2
|
||||||
|
#dtoverlay=disable-wifi
|
||||||
|
|
||||||
|
[pi0]
|
||||||
|
dtoverlay=spi1-3cs
|
||||||
|
#dtoverlay=disable-wifi
|
||||||
|
|
||||||
|
[pi3]
|
||||||
|
dtoverlay=spi1-3cs
|
||||||
|
#dtoverlay=disable-wifi
|
||||||
|
|
||||||
|
[pi4]
|
||||||
|
dtoverlay=spi1-3cs
|
||||||
|
#dtoverlay=disable-wifi
|
62
builder/data/32bit/etc/dhcpcd.conf
Normal file
62
builder/data/32bit/etc/dhcpcd.conf
Normal file
@ -0,0 +1,62 @@
|
|||||||
|
# A sample configuration for dhcpcd.
|
||||||
|
# See dhcpcd.conf(5) for details.
|
||||||
|
|
||||||
|
# Allow users of this group to interact with dhcpcd via the control socket.
|
||||||
|
#controlgroup wheel
|
||||||
|
|
||||||
|
# Inform the DHCP server of our hostname for DDNS.
|
||||||
|
hostname
|
||||||
|
|
||||||
|
# Use the hardware address of the interface for the Client ID.
|
||||||
|
clientid
|
||||||
|
# or
|
||||||
|
# Use the same DUID + IAID as set in DHCPv6 for DHCPv4 ClientID as per RFC4361.
|
||||||
|
# Some non-RFC compliant DHCP servers do not reply with this set.
|
||||||
|
# In this case, comment out duid and enable clientid above.
|
||||||
|
#duid
|
||||||
|
|
||||||
|
# Persist interface configuration when dhcpcd exits.
|
||||||
|
persistent
|
||||||
|
|
||||||
|
# Rapid commit support.
|
||||||
|
# Safe to enable by default because it requires the equivalent option set
|
||||||
|
# on the server to actually work.
|
||||||
|
option rapid_commit
|
||||||
|
|
||||||
|
# A list of options to request from the DHCP server.
|
||||||
|
option domain_name_servers, domain_name, domain_search, host_name
|
||||||
|
option classless_static_routes
|
||||||
|
# Respect the network MTU. This is applied to DHCP routes.
|
||||||
|
option interface_mtu
|
||||||
|
|
||||||
|
# Most distributions have NTP support.
|
||||||
|
#option ntp_servers
|
||||||
|
|
||||||
|
# A ServerID is required by RFC2131.
|
||||||
|
require dhcp_server_identifier
|
||||||
|
|
||||||
|
# Generate SLAAC address using the Hardware Address of the interface
|
||||||
|
#slaac hwaddr
|
||||||
|
# OR generate Stable Private IPv6 Addresses based from the DUID
|
||||||
|
slaac private
|
||||||
|
|
||||||
|
# !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
|
||||||
|
# !! DO NOT EDIT THESE LINES BELOW PLEASE !!
|
||||||
|
# !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
|
||||||
|
|
||||||
|
# static IP configuration:
|
||||||
|
denyinterfaces wlan0
|
||||||
|
|
||||||
|
interface eth0
|
||||||
|
static domain_name_servers=8.8.8.8 1.1.1.1
|
||||||
|
metric 201
|
||||||
|
|
||||||
|
interface usb0
|
||||||
|
static ip_address=10.0.0.2/24
|
||||||
|
static routers=10.0.0.1
|
||||||
|
static domain_name_servers=10.0.0.1 8.8.8.8 1.1.1.1
|
||||||
|
metric 202
|
||||||
|
|
||||||
|
interface bnep0
|
||||||
|
static domain_name_servers=8.8.8.8 1.1.1.1
|
||||||
|
metric 203
|
6
builder/data/32bit/etc/modules-load.d/modules.conf
Normal file
6
builder/data/32bit/etc/modules-load.d/modules.conf
Normal file
@ -0,0 +1,6 @@
|
|||||||
|
# /etc/modules: kernel modules to load at boot time.
|
||||||
|
#
|
||||||
|
# This file contains the names of kernel modules that should be loaded
|
||||||
|
# at boot time, one per line. Lines beginning with "#" are ignored.
|
||||||
|
# Parameters can be specified after the module name.
|
||||||
|
i2c-dev
|
40
builder/data/32bit/etc/profile
Normal file
40
builder/data/32bit/etc/profile
Normal file
@ -0,0 +1,40 @@
|
|||||||
|
# /etc/profile: system-wide .profile file for the Bourne shell (sh(1))
|
||||||
|
# and Bourne compatible shells (bash(1), ksh(1), ash(1), ...).
|
||||||
|
|
||||||
|
if [ "$(id -u)" -eq 0 ]; then
|
||||||
|
PATH="/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin"
|
||||||
|
else
|
||||||
|
PATH="/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin:/usr/local/games:/usr/games"
|
||||||
|
fi
|
||||||
|
export PATH
|
||||||
|
|
||||||
|
if [ "${PS1-}" ]; then
|
||||||
|
if [ "${BASH-}" ] && [ "$BASH" != "/bin/sh" ]; then
|
||||||
|
# The file bash.bashrc already sets the default PS1.
|
||||||
|
# PS1='\h:\w\$ '
|
||||||
|
if [ -f /etc/bash.bashrc ]; then
|
||||||
|
. /etc/bash.bashrc
|
||||||
|
fi
|
||||||
|
else
|
||||||
|
if [ "$(id -u)" -eq 0 ]; then
|
||||||
|
PS1='# '
|
||||||
|
else
|
||||||
|
PS1='$ '
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [ -d /etc/profile.d ]; then
|
||||||
|
for i in /etc/profile.d/*.sh; do
|
||||||
|
if [ -r $i ]; then
|
||||||
|
. $i
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
unset i
|
||||||
|
fi
|
||||||
|
alias custom='cd /usr/local/share/pwnagotchi/custom-plugins/'
|
||||||
|
alias config='sudo nano /etc/pwnagotchi/config.toml'
|
||||||
|
alias pwnlog='tail -f -n300 /etc/pwnagotchi/log/pwn*.log | sed --unbuffered "s/,[[:digit:]]\\{3\\}\\]//g" | cut -d " " -f 2-'
|
||||||
|
alias pwnver='python3 -c "import pwnagotchi as p; print(p.__version__)"'
|
||||||
|
alias pwnkill='sudo killall -USR1 pwnagotchi'
|
||||||
|
|
16
builder/data/32bit/etc/rc.local
Normal file
16
builder/data/32bit/etc/rc.local
Normal file
@ -0,0 +1,16 @@
|
|||||||
|
#!/bin/sh -e
|
||||||
|
#
|
||||||
|
# rc.local
|
||||||
|
#
|
||||||
|
# This script is executed at the end of each multiuser runlevel.
|
||||||
|
# Make sure that the script will "exit 0" on success or any other
|
||||||
|
# value on error.
|
||||||
|
#
|
||||||
|
# In order to enable or disable this script just change the execution
|
||||||
|
# bits.
|
||||||
|
#
|
||||||
|
# By default this script does nothing.
|
||||||
|
|
||||||
|
timedatectl set-ntp true
|
||||||
|
|
||||||
|
exit 0
|
20
builder/data/32bit/etc/systemd/system/bluetooth.service
Normal file
20
builder/data/32bit/etc/systemd/system/bluetooth.service
Normal file
@ -0,0 +1,20 @@
|
|||||||
|
[Unit]
|
||||||
|
Description=Bluetooth service
|
||||||
|
Documentation=man:bluetoothd(8)
|
||||||
|
ConditionPathIsDirectory=/sys/class/bluetooth
|
||||||
|
|
||||||
|
[Service]
|
||||||
|
Type=dbus
|
||||||
|
BusName=org.bluez
|
||||||
|
ExecStart=/usr/libexec/bluetooth/bluetoothd --noplugin=sap,a2dp
|
||||||
|
NotifyAccess=main
|
||||||
|
#WatchdogSec=10
|
||||||
|
#Restart=on-failure
|
||||||
|
CapabilityBoundingSet=CAP_NET_ADMIN CAP_NET_BIND_SERVICE
|
||||||
|
LimitNPROC=1
|
||||||
|
ProtectHome=true
|
||||||
|
ProtectSystem=full
|
||||||
|
|
||||||
|
[Install]
|
||||||
|
WantedBy=bluetooth.target
|
||||||
|
Alias=dbus-org.bluez.service
|
@ -0,0 +1,6 @@
|
|||||||
|
[Unit]
|
||||||
|
After=hciuart.service bluetooth.service
|
||||||
|
Before=
|
||||||
|
|
||||||
|
[Service]
|
||||||
|
ExecStartPre=/bin/sleep 5
|
19
builder/data/32bit/etc/systemd/system/pwnagotchi.service
Normal file
19
builder/data/32bit/etc/systemd/system/pwnagotchi.service
Normal file
@ -0,0 +1,19 @@
|
|||||||
|
[Unit]
|
||||||
|
Description=pwnagotchi Deep Reinforcement Learning instrumenting bettercap for WiFI pwning.
|
||||||
|
Documentation=https://pwnagotchi.ai
|
||||||
|
Wants=network.target
|
||||||
|
After=pwngrid-peer.service
|
||||||
|
|
||||||
|
[Service]
|
||||||
|
Type=simple
|
||||||
|
WorkingDirectory=~
|
||||||
|
ExecStart=/usr/bin/pwnagotchi-launcher
|
||||||
|
Restart=always
|
||||||
|
RestartSec=30
|
||||||
|
TasksMax=infinity
|
||||||
|
LimitNPROC=infinity
|
||||||
|
StandardOutput=null
|
||||||
|
StandardError=null
|
||||||
|
|
||||||
|
[Install]
|
||||||
|
WantedBy=multi-user.target
|
@ -8,7 +8,7 @@ After=bettercap.service
|
|||||||
Environment=LD_PRELOAD=/usr/local/lib/libpcap.so.1
|
Environment=LD_PRELOAD=/usr/local/lib/libpcap.so.1
|
||||||
Environment=LD_LIBRARY_PATH=/usr/local/lib
|
Environment=LD_LIBRARY_PATH=/usr/local/lib
|
||||||
Type=simple
|
Type=simple
|
||||||
ExecStart=/usr/local/bin/pwngrid -keys /etc/pwnagotchi -peers /root/peers -address 127.0.0.1:8666 -client-token /root/.api-enrollment.json -wait -log /home/pi/logs/pwngrid-peer.log -iface wlan0mon
|
ExecStart=/usr/local/bin/pwngrid -keys /etc/pwnagotchi -peers /root/peers -address 127.0.0.1:8666 -client-token /root/.api-enrollment.json -wait -log /etc/pwnagotchi/log/pwngrid-peer.log -iface wlan0mon
|
||||||
Restart=always
|
Restart=always
|
||||||
RestartSec=30
|
RestartSec=30
|
||||||
|
|
33
builder/data/32bit/etc/update-motd.d/01-motd
Executable file
33
builder/data/32bit/etc/update-motd.d/01-motd
Executable file
@ -0,0 +1,33 @@
|
|||||||
|
#!/bin/sh
|
||||||
|
_hostname=$(hostname)
|
||||||
|
_version=$(cut -d"'" -f2 < /usr/local/lib/python3.9/dist-packages/pwnagotchi/_version.py)
|
||||||
|
echo
|
||||||
|
echo "(◕‿‿◕) $_hostname"
|
||||||
|
echo
|
||||||
|
echo " Hi! I'm a pwnagotchi $_version, please take good care of me!"
|
||||||
|
echo " Here are some basic things you need to know to raise me properly!"
|
||||||
|
echo
|
||||||
|
echo " If you want to change my configuration, use /etc/pwnagotchi/config.toml"
|
||||||
|
echo " All plugin config files are located in /etc/pwnagotchi/conf.d/"
|
||||||
|
echo " Read the readme if you want to use gdrivesync plugin!!"
|
||||||
|
echo
|
||||||
|
echo " All the configuration options can be found on /etc/pwnagotchi/default.toml,"
|
||||||
|
echo " but don't change this file because I will recreate it every time I'm restarted!"
|
||||||
|
echo
|
||||||
|
echo " I use oPwnGrid as my main API, you can check stats at https://opwngrid.xyz"
|
||||||
|
echo
|
||||||
|
echo " I'm managed by systemd. Here are some basic commands."
|
||||||
|
echo
|
||||||
|
echo " If you want to know what I'm doing, you can check my logs with the command"
|
||||||
|
echo " - pwnlog"
|
||||||
|
echo " - sudo pwnagotchi --version, to check the current version"
|
||||||
|
echo " - sudo pwnagotchi --donate, to see how you can donate to this project"
|
||||||
|
echo " - sudo pwnagotchi --check-update, to see if there is a new version available"
|
||||||
|
echo
|
||||||
|
echo " If you want to know if I'm running, you can use"
|
||||||
|
echo " sudo systemctl status pwnagotchi"
|
||||||
|
echo
|
||||||
|
echo " You can restart me using"
|
||||||
|
echo " pwnkill"
|
||||||
|
echo
|
||||||
|
echo " You learn more about me at https://pwnagotchi.ai/"
|
28
builder/data/32bit/usr/bin/bettercap-launcher
Executable file
28
builder/data/32bit/usr/bin/bettercap-launcher
Executable file
@ -0,0 +1,28 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
source /usr/bin/pwnlib
|
||||||
|
|
||||||
|
# we need to decrypt something
|
||||||
|
if is_crypted_mode; then
|
||||||
|
while ! is_decrypted; do
|
||||||
|
echo "Waiting for decryption..."
|
||||||
|
sleep 1
|
||||||
|
done
|
||||||
|
fi
|
||||||
|
|
||||||
|
# check if wifi driver is bugged
|
||||||
|
if ! check_brcm; then
|
||||||
|
if ! reload_brcm; then
|
||||||
|
echo "Could not reload wifi driver. Reboot"
|
||||||
|
reboot
|
||||||
|
fi
|
||||||
|
sleep 10
|
||||||
|
fi
|
||||||
|
|
||||||
|
# start wlan0mon
|
||||||
|
start_monitor_interface
|
||||||
|
|
||||||
|
if is_auto_mode_no_delete; then
|
||||||
|
/usr/local/bin/bettercap -no-colors -caplet pwnagotchi-auto -iface wlan0mon
|
||||||
|
else
|
||||||
|
/usr/local/bin/bettercap -no-colors -caplet pwnagotchi-manual -iface wlan0mon
|
||||||
|
fi
|
149
builder/data/32bit/usr/bin/decryption-webserver
Executable file
149
builder/data/32bit/usr/bin/decryption-webserver
Executable file
@ -0,0 +1,149 @@
|
|||||||
|
#!/usr/bin/env python3
|
||||||
|
|
||||||
|
from http.server import HTTPServer, BaseHTTPRequestHandler
|
||||||
|
from urllib.parse import parse_qsl
|
||||||
|
|
||||||
|
|
||||||
|
_HTML_FORM_TEMPLATE = """
|
||||||
|
<!DOCTYPE html>
|
||||||
|
<html>
|
||||||
|
<head>
|
||||||
|
<title>Decryption</title>
|
||||||
|
<style>
|
||||||
|
body {{ text-align: center; padding: 150px; }}
|
||||||
|
h1 {{ font-size: 50px; }}
|
||||||
|
body {{ font: 20px Helvetica, sans-serif; color: #333; }}
|
||||||
|
article {{ display: block; text-align: center; width: 650px; margin: 0 auto;}}
|
||||||
|
input {{
|
||||||
|
padding: 12px 20px;
|
||||||
|
margin: 8px 0;
|
||||||
|
box-sizing: border-box;
|
||||||
|
border: 1px solid #ccc;
|
||||||
|
}}
|
||||||
|
input[type=password] {{
|
||||||
|
width: 75%;
|
||||||
|
font-size: 24px;
|
||||||
|
}}
|
||||||
|
input[type=submit] {{
|
||||||
|
cursor: pointer;
|
||||||
|
width: 75%;
|
||||||
|
}}
|
||||||
|
input[type=submit]:hover {{
|
||||||
|
background-color: #d9d9d9;
|
||||||
|
}}
|
||||||
|
</style>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<article>
|
||||||
|
<h1>Decryption</h1>
|
||||||
|
<p>Some of your files are encrypted.</p>
|
||||||
|
<p>Please provide the decryption password.</p>
|
||||||
|
<div>
|
||||||
|
<form action="/set-password" method="POST">
|
||||||
|
{password_fields}
|
||||||
|
<input type="submit" value="Submit">
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
</article>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
|
"""
|
||||||
|
|
||||||
|
POST_RESPONSE = """
|
||||||
|
<!DOCTYPE html>
|
||||||
|
<html>
|
||||||
|
<head>
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||||
|
<style>
|
||||||
|
/* Center the loader */
|
||||||
|
#loader {
|
||||||
|
position: absolute;
|
||||||
|
left: 50%;
|
||||||
|
top: 50%;
|
||||||
|
z-index: 1;
|
||||||
|
width: 150px;
|
||||||
|
height: 150px;
|
||||||
|
margin: -75px 0 0 -75px;
|
||||||
|
border: 16px solid #f3f3f3;
|
||||||
|
border-radius: 50%;
|
||||||
|
border-top: 16px solid #3498db;
|
||||||
|
width: 120px;
|
||||||
|
height: 120px;
|
||||||
|
-webkit-animation: spin 2s linear infinite;
|
||||||
|
animation: spin 2s linear infinite;
|
||||||
|
}
|
||||||
|
|
||||||
|
@-webkit-keyframes spin {
|
||||||
|
0% { -webkit-transform: rotate(0deg); }
|
||||||
|
100% { -webkit-transform: rotate(360deg); }
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes spin {
|
||||||
|
0% { transform: rotate(0deg); }
|
||||||
|
100% { transform: rotate(360deg); }
|
||||||
|
}
|
||||||
|
|
||||||
|
#myDiv {
|
||||||
|
display: none;
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
<script type="text/javascript">
|
||||||
|
|
||||||
|
function checkPwnagotchi() {
|
||||||
|
var target = 'http://' + document.location.hostname + ':8080/';
|
||||||
|
var xhr = new XMLHttpRequest();
|
||||||
|
xhr.open('GET', target);
|
||||||
|
xhr.onreadystatechange = function () {
|
||||||
|
if (xhr.readyState == 4) {
|
||||||
|
if (xhr.status == 200 || xhr.status == 401) {
|
||||||
|
window.location.replace(target);
|
||||||
|
}else{
|
||||||
|
setTimeout(checkPwnagotchi, 1000);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
xhr.send();
|
||||||
|
}
|
||||||
|
|
||||||
|
setTimeout(checkPwnagotchi, 1000);
|
||||||
|
|
||||||
|
</script>
|
||||||
|
</head>
|
||||||
|
<body style="margin:0;">
|
||||||
|
|
||||||
|
<div id="loader"></div>
|
||||||
|
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
|
"""
|
||||||
|
|
||||||
|
HTML_FORM = None
|
||||||
|
|
||||||
|
|
||||||
|
class SimpleHTTPRequestHandler(BaseHTTPRequestHandler):
|
||||||
|
|
||||||
|
def do_GET(self):
|
||||||
|
self.send_response(200)
|
||||||
|
self.end_headers()
|
||||||
|
self.wfile.write(HTML_FORM.encode())
|
||||||
|
|
||||||
|
def do_POST(self):
|
||||||
|
content_length = int(self.headers['Content-Length'])
|
||||||
|
body = self.rfile.read(content_length)
|
||||||
|
for mapping, password in parse_qsl(body.decode('UTF-8')):
|
||||||
|
with open('/tmp/.pwnagotchi-secret-{}'.format(mapping), 'wt') as pwfile:
|
||||||
|
pwfile.write(password)
|
||||||
|
self.send_response(200)
|
||||||
|
self.end_headers()
|
||||||
|
self.wfile.write(POST_RESPONSE.encode())
|
||||||
|
|
||||||
|
|
||||||
|
with open('/root/.pwnagotchi-crypted') as crypted_file:
|
||||||
|
mappings = [line.split()[0] for line in crypted_file.readlines()]
|
||||||
|
fields = ''.join(['<label for="{m}">Passphrase for {m}:</label>\n<input type="password" id="{m}" name="{m}" value=""><br>'.format(m=m)
|
||||||
|
for m in mappings])
|
||||||
|
HTML_FORM = _HTML_FORM_TEMPLATE.format(password_fields=fields)
|
||||||
|
|
||||||
|
httpd = HTTPServer(('0.0.0.0', 80), SimpleHTTPRequestHandler)
|
||||||
|
httpd.serve_forever()
|
@ -11,6 +11,7 @@ fi
|
|||||||
|
|
||||||
if is_auto_mode; then
|
if is_auto_mode; then
|
||||||
/usr/local/bin/pwnagotchi
|
/usr/local/bin/pwnagotchi
|
||||||
|
systemctl restart bettercap
|
||||||
else
|
else
|
||||||
/usr/local/bin/pwnagotchi --manual
|
/usr/local/bin/pwnagotchi --manual
|
||||||
fi
|
fi
|
184
builder/data/32bit/usr/bin/pwnlib
Executable file
184
builder/data/32bit/usr/bin/pwnlib
Executable file
@ -0,0 +1,184 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
|
||||||
|
# check if brcm is stuck
|
||||||
|
check_brcm() {
|
||||||
|
if [[ "$(journalctl -n10 -k --since -5m | grep -c 'brcmf_cfg80211_nexmon_set_channel.*Set Channel failed')" -ge 5 ]]; then
|
||||||
|
return 1
|
||||||
|
fi
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
|
# reload mod
|
||||||
|
reload_brcm() {
|
||||||
|
if ! modprobe -r brcmfmac; then
|
||||||
|
return 1
|
||||||
|
fi
|
||||||
|
sleep 1
|
||||||
|
if ! modprobe brcmfmac; then
|
||||||
|
return 1
|
||||||
|
fi
|
||||||
|
sleep 2
|
||||||
|
iw dev wlan0 set power_save off
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
|
# starts mon0
|
||||||
|
start_monitor_interface() {
|
||||||
|
rfkill unblock all
|
||||||
|
ifconfig wlan0 up
|
||||||
|
iw dev wlan0 set power_save off
|
||||||
|
iw phy "$(iw phy | head -1 | cut -d" " -f2)" interface add wlan0mon type monitor
|
||||||
|
rfkill unblock all
|
||||||
|
ifconfig wlan0 down
|
||||||
|
ifconfig wlan0mon up
|
||||||
|
iw dev wlan0mon set power_save off
|
||||||
|
}
|
||||||
|
|
||||||
|
# stops mon0
|
||||||
|
stop_monitor_interface() {
|
||||||
|
ifconfig wlan0mon down && iw dev wlan0mon del
|
||||||
|
reload_brcm
|
||||||
|
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
|
||||||
|
fi
|
||||||
|
return 1
|
||||||
|
}
|
||||||
|
|
||||||
|
# returns 0 if conditions for AUTO mode are met
|
||||||
|
is_auto_mode() {
|
||||||
|
# check override file first
|
||||||
|
if [ -f /root/.pwnagotchi-manual ]; then
|
||||||
|
# remove the override file if found
|
||||||
|
rm -rf /root/.pwnagotchi-manual
|
||||||
|
return 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
# check override file first
|
||||||
|
if [ -f /root/.pwnagotchi-auto ]; then
|
||||||
|
# remove the override file if found
|
||||||
|
rm -rf /root/.pwnagotchi-auto
|
||||||
|
return 0
|
||||||
|
fi
|
||||||
|
|
||||||
|
# if usb0 is up, we're in MANU
|
||||||
|
if is_interface_up usb0; then
|
||||||
|
return 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
# if eth0 is up (for other boards), we're in MANU
|
||||||
|
if is_interface_up eth0; then
|
||||||
|
return 0
|
||||||
|
fi
|
||||||
|
|
||||||
|
# no override, but none of the interfaces is up -> AUTO
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
|
# returns 0 if conditions for AUTO mode are met
|
||||||
|
is_auto_mode_no_delete() {
|
||||||
|
# check override file first
|
||||||
|
if [ -f /root/.pwnagotchi-manual ]; then
|
||||||
|
return 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
# check override file first
|
||||||
|
if [ -f /root/.pwnagotchi-auto ]; then
|
||||||
|
return 0
|
||||||
|
fi
|
||||||
|
|
||||||
|
# if usb0 is up, we're in MANU
|
||||||
|
if is_interface_up usb0; then
|
||||||
|
return 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
# if eth0 is up (for other boards), we're in MANU
|
||||||
|
if is_interface_up eth0; then
|
||||||
|
return 0
|
||||||
|
fi
|
||||||
|
|
||||||
|
# no override, but none of the interfaces is up -> AUTO
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
|
# check if we need to decrypt something
|
||||||
|
is_crypted_mode() {
|
||||||
|
if [ -f /root/.pwnagotchi-crypted ]; then
|
||||||
|
return 0
|
||||||
|
fi
|
||||||
|
return 1
|
||||||
|
}
|
||||||
|
|
||||||
|
# decryption loop
|
||||||
|
is_decrypted() {
|
||||||
|
while read -r mapping container mount; do
|
||||||
|
# mapping = name the device or file will be mapped to
|
||||||
|
# container = the luks encrypted device or file
|
||||||
|
# mount = the mountpoint
|
||||||
|
|
||||||
|
# fail if not mounted
|
||||||
|
if ! mountpoint -q "$mount" >/dev/null 2>&1; then
|
||||||
|
if [ -f /tmp/.pwnagotchi-secret-"$mapping" ]; then
|
||||||
|
</tmp/.pwnagotchi-secret-"$mapping" read -r SECRET
|
||||||
|
if ! test -b /dev/disk/by-id/dm-uuid-*"$(cryptsetup luksUUID "$container" | tr -d -)"*; then
|
||||||
|
if echo -n "$SECRET" | cryptsetup luksOpen -d- "$container" "$mapping" >/dev/null 2>&1; then
|
||||||
|
echo "Container decrypted!"
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
|
||||||
|
if mount /dev/mapper/"$mapping" "$mount" >/dev/null 2>&1; then
|
||||||
|
echo "Mounted /dev/mapper/$mapping to $mount"
|
||||||
|
continue
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
|
||||||
|
if ! ip -4 addr show wlan0 | grep inet >/dev/null 2>&1; then
|
||||||
|
>/dev/null 2>&1 ip addr add 192.168.0.10/24 dev wlan0
|
||||||
|
fi
|
||||||
|
|
||||||
|
if ! pgrep -f decryption-webserver >/dev/null 2>&1; then
|
||||||
|
>/dev/null 2>&1 decryption-webserver &
|
||||||
|
fi
|
||||||
|
|
||||||
|
if ! pgrep wpa_supplicant >/dev/null 2>&1; then
|
||||||
|
>/tmp/wpa_supplicant.conf cat <<EOF
|
||||||
|
ctrl_interface=DIR=/var/run/wpa_supplicant GROUP=netdev
|
||||||
|
update_config=1
|
||||||
|
ap_scan=2
|
||||||
|
|
||||||
|
network={
|
||||||
|
ssid="DECRYPT-ME"
|
||||||
|
mode=2
|
||||||
|
key_mgmt=WPA-PSK
|
||||||
|
psk="pwnagotchi"
|
||||||
|
frequency=2437
|
||||||
|
}
|
||||||
|
EOF
|
||||||
|
>/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
|
||||||
|
>/dev/null 2>&1 dnsmasq -k -p 53 -h -O "6,192.168.0.10" -A "/#/192.168.0.10" -i wlan0 -K -F 192.168.0.50,192.168.0.60,255.255.255.0,24h &
|
||||||
|
fi
|
||||||
|
|
||||||
|
return 1
|
||||||
|
fi
|
||||||
|
done </root/.pwnagotchi-crypted
|
||||||
|
|
||||||
|
# overwrite passwords
|
||||||
|
python3 -c 'print("A"*4096)' | tee /tmp/.pwnagotchi-secret-* >/dev/null
|
||||||
|
# delete
|
||||||
|
rm /tmp/.pwnagotchi-secret-*
|
||||||
|
sync # flush
|
||||||
|
|
||||||
|
pkill wpa_supplicant
|
||||||
|
pkill dnsmasq
|
||||||
|
pid="$(pgrep -f "decryption-webserver")"
|
||||||
|
[[ -n "$pid" ]] && kill "$pid"
|
||||||
|
|
||||||
|
return 0
|
||||||
|
}
|
70
builder/data/64bit/boot/firmware/config.txt
Normal file
70
builder/data/64bit/boot/firmware/config.txt
Normal file
@ -0,0 +1,70 @@
|
|||||||
|
# For more options and information see
|
||||||
|
# http://rptl.io/configtxt
|
||||||
|
# Some settings may impact device functionality. See link above for details
|
||||||
|
|
||||||
|
# Uncomment some or all of these to enable the optional hardware interfaces
|
||||||
|
#dtparam=i2c_arm=on
|
||||||
|
#dtparam=i2s=on
|
||||||
|
#dtparam=spi=on
|
||||||
|
|
||||||
|
# Enable audio (loads snd_bcm2835)
|
||||||
|
dtparam=audio=on
|
||||||
|
|
||||||
|
# Additional overlays and parameters are documented
|
||||||
|
# /boot/firmware/overlays/README
|
||||||
|
|
||||||
|
# Automatically load overlays for detected cameras
|
||||||
|
camera_auto_detect=1
|
||||||
|
|
||||||
|
# Automatically load overlays for detected DSI displays
|
||||||
|
display_auto_detect=1
|
||||||
|
|
||||||
|
# Automatically load initramfs files, if found
|
||||||
|
auto_initramfs=1
|
||||||
|
|
||||||
|
# Enable DRM VC4 V3D driver
|
||||||
|
dtoverlay=vc4-kms-v3d
|
||||||
|
max_framebuffers=2
|
||||||
|
|
||||||
|
# Don't have the firmware create an initial video= setting in cmdline.txt.
|
||||||
|
# Use the kernel's default instead.
|
||||||
|
disable_fw_kms_setup=1
|
||||||
|
|
||||||
|
# Run in 64-bit mode
|
||||||
|
arm_64bit=1
|
||||||
|
|
||||||
|
# Disable compensation for displays with overscan
|
||||||
|
disable_overscan=1
|
||||||
|
|
||||||
|
# Run as fast as firmware / board allows
|
||||||
|
arm_boost=1
|
||||||
|
|
||||||
|
[cm4]
|
||||||
|
# Enable host mode on the 2711 built-in XHCI USB controller.
|
||||||
|
# This line should be removed if the legacy DWC2 controller is required
|
||||||
|
# (e.g. for USB device mode) or if USB support is not required.
|
||||||
|
otg_mode=1
|
||||||
|
|
||||||
|
[all]
|
||||||
|
dtparam=i2c1=on
|
||||||
|
dtparam=i2c_arm=on
|
||||||
|
dtparam=spi=on
|
||||||
|
gpu_mem=1
|
||||||
|
dtoverlay=dwc2
|
||||||
|
#dtoverlay=disable-wifi
|
||||||
|
|
||||||
|
[pi0]
|
||||||
|
dtoverlay=spi0-0cs
|
||||||
|
#dtoverlay=disable-wifi
|
||||||
|
|
||||||
|
[pi3]
|
||||||
|
dtoverlay=spi0-0cs
|
||||||
|
#dtoverlay=disable-wifi
|
||||||
|
|
||||||
|
[pi4]
|
||||||
|
dtoverlay=spi0-0cs
|
||||||
|
#dtoverlay=disable-wifi
|
||||||
|
|
||||||
|
[pi5]
|
||||||
|
dtoverlay=spi0-0cs
|
||||||
|
#dtoverlay=disable-wifi
|
@ -0,0 +1,36 @@
|
|||||||
|
_show_complete()
|
||||||
|
{
|
||||||
|
local cur opts node_names all_options opt_line
|
||||||
|
all_options="
|
||||||
|
pwnagotchi -h --help -C --config -U --user-config --manual --skip-session --clear --debug --version --print-config --wizard --check-update --donate {plugins,google}
|
||||||
|
pwnagotchi plugins -h --help {list,install,enable,disable,uninstall,update,upgrade}
|
||||||
|
pwnagotchi plugins list -i --installed -h --help
|
||||||
|
pwnagotchi plugins install -h --help
|
||||||
|
pwnagotchi plugins uninstall -h --help
|
||||||
|
pwnagotchi plugins enable -h --help
|
||||||
|
pwnagotchi plugins disable -h --help
|
||||||
|
pwnagotchi plugins update -h --help
|
||||||
|
pwnagotchi plugins upgrade -h --help
|
||||||
|
pwnagotchi google -h --help {login,refresh}
|
||||||
|
pwnagotchi google login -h --help
|
||||||
|
pwnagotchi google refresh -h --help
|
||||||
|
"
|
||||||
|
COMPREPLY=()
|
||||||
|
cur="${COMP_WORDS[COMP_CWORD]}"
|
||||||
|
# shellcheck disable=SC2124
|
||||||
|
cmd="${COMP_WORDS[@]:0:${#COMP_WORDS[@]}-1}"
|
||||||
|
opt_line="$(grep -m1 "$cmd" <<<"$all_options")"
|
||||||
|
if [[ ${cur} == -* ]] ; then
|
||||||
|
opts="$(echo "$opt_line" | tr ' ' '\n' | awk '/^ *-/{gsub("[^a-zA-Z0-9-]","",$1);print $1}')"
|
||||||
|
# shellcheck disable=SC2207
|
||||||
|
COMPREPLY=( $(compgen -W "${opts}" -- "${cur}") )
|
||||||
|
return 0
|
||||||
|
fi
|
||||||
|
|
||||||
|
# shellcheck disable=SC2086
|
||||||
|
opts="$(echo $opt_line | grep -Po '{\K[^}]+' | tr ',' '\n')"
|
||||||
|
# shellcheck disable=SC2207
|
||||||
|
COMPREPLY=( $(compgen -W "${opts}" -- "${cur}") )
|
||||||
|
}
|
||||||
|
|
||||||
|
complete -F _show_complete pwnagotchi
|
26
builder/data/64bit/etc/dphys-swapfile
Normal file
26
builder/data/64bit/etc/dphys-swapfile
Normal file
@ -0,0 +1,26 @@
|
|||||||
|
# /etc/dphys-swapfile - user settings for dphys-swapfile package
|
||||||
|
# author Neil Franklin, last modification 2010.05.05
|
||||||
|
# copyright ETH Zuerich Physics Departement
|
||||||
|
# use under either modified/non-advertising BSD or GPL license
|
||||||
|
|
||||||
|
# this file is sourced with . so full normal sh syntax applies
|
||||||
|
|
||||||
|
# the default settings are added as commented out CONF_*=* lines
|
||||||
|
|
||||||
|
|
||||||
|
# where we want the swapfile to be, this is the default
|
||||||
|
#CONF_SWAPFILE=/var/swap
|
||||||
|
|
||||||
|
# set size to absolute value, leaving empty (default) then uses computed value
|
||||||
|
# you most likely don't want this, unless you have an special disk situation
|
||||||
|
CONF_SWAPSIZE=2048
|
||||||
|
|
||||||
|
# set size to computed value, this times RAM size, dynamically adapts,
|
||||||
|
# guarantees that there is enough swap without wasting disk space on excess
|
||||||
|
#CONF_SWAPFACTOR=2
|
||||||
|
|
||||||
|
# restrict size (computed and absolute!) to maximally this limit
|
||||||
|
# can be set to empty for no limit, but beware of filled partitions!
|
||||||
|
# this is/was a (outdated?) 32bit kernel limit (in MBytes), do not overrun it
|
||||||
|
# but is also sensible on 64bit to prevent filling /var or even / partition
|
||||||
|
#CONF_MAXSWAP=2048
|
6
builder/data/64bit/etc/modules-load.d/modules.conf
Normal file
6
builder/data/64bit/etc/modules-load.d/modules.conf
Normal file
@ -0,0 +1,6 @@
|
|||||||
|
# /etc/modules: kernel modules to load at boot time.
|
||||||
|
#
|
||||||
|
# This file contains the names of kernel modules that should be loaded
|
||||||
|
# at boot time, one per line. Lines beginning with "#" are ignored.
|
||||||
|
# Parameters can be specified after the module name.
|
||||||
|
i2c-dev
|
40
builder/data/64bit/etc/profile
Normal file
40
builder/data/64bit/etc/profile
Normal file
@ -0,0 +1,40 @@
|
|||||||
|
# /etc/profile: system-wide .profile file for the Bourne shell (sh(1))
|
||||||
|
# and Bourne compatible shells (bash(1), ksh(1), ash(1), ...).
|
||||||
|
|
||||||
|
if [ "$(id -u)" -eq 0 ]; then
|
||||||
|
PATH="/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin"
|
||||||
|
else
|
||||||
|
PATH="/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin:/usr/local/games:/usr/games"
|
||||||
|
fi
|
||||||
|
export PATH
|
||||||
|
|
||||||
|
if [ "${PS1-}" ]; then
|
||||||
|
if [ "${BASH-}" ] && [ "$BASH" != "/bin/sh" ]; then
|
||||||
|
# The file bash.bashrc already sets the default PS1.
|
||||||
|
# PS1='\h:\w\$ '
|
||||||
|
if [ -f /etc/bash.bashrc ]; then
|
||||||
|
. /etc/bash.bashrc
|
||||||
|
fi
|
||||||
|
else
|
||||||
|
if [ "$(id -u)" -eq 0 ]; then
|
||||||
|
PS1='# '
|
||||||
|
else
|
||||||
|
PS1='$ '
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [ -d /etc/profile.d ]; then
|
||||||
|
for i in /etc/profile.d/*.sh; do
|
||||||
|
if [ -r $i ]; then
|
||||||
|
. $i
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
unset i
|
||||||
|
fi
|
||||||
|
alias custom='cd /usr/local/share/pwnagotchi/custom-plugins/'
|
||||||
|
alias config='sudo nano /etc/pwnagotchi/config.toml'
|
||||||
|
alias pwnlog='tail -f -n300 /etc/pwnagotchi/log/pwn*.log | sed --unbuffered "s/,[[:digit:]]\\{3\\}\\]//g" | cut -d " " -f 2-'
|
||||||
|
alias pwnver='python3 -c "import pwnagotchi as p; print(p.__version__)"'
|
||||||
|
alias pwnkill='sudo killall -USR1 pwnagotchi'
|
||||||
|
|
16
builder/data/64bit/etc/rc.local
Normal file
16
builder/data/64bit/etc/rc.local
Normal file
@ -0,0 +1,16 @@
|
|||||||
|
#!/bin/sh -e
|
||||||
|
#
|
||||||
|
# rc.local
|
||||||
|
#
|
||||||
|
# This script is executed at the end of each multiuser runlevel.
|
||||||
|
# Make sure that the script will "exit 0" on success or any other
|
||||||
|
# value on error.
|
||||||
|
#
|
||||||
|
# In order to enable or disable this script just change the execution
|
||||||
|
# bits.
|
||||||
|
#
|
||||||
|
# By default this script does nothing.
|
||||||
|
|
||||||
|
timedatectl set-ntp true
|
||||||
|
|
||||||
|
exit 0
|
13
builder/data/64bit/etc/systemd/system/bettercap.service
Normal file
13
builder/data/64bit/etc/systemd/system/bettercap.service
Normal file
@ -0,0 +1,13 @@
|
|||||||
|
[Unit]
|
||||||
|
Description=bettercap api.rest service.
|
||||||
|
Documentation=https://bettercap.org
|
||||||
|
Wants=network.target
|
||||||
|
|
||||||
|
[Service]
|
||||||
|
Type=simple
|
||||||
|
ExecStart=/usr/bin/bettercap-launcher
|
||||||
|
Restart=always
|
||||||
|
RestartSec=30
|
||||||
|
|
||||||
|
[Install]
|
||||||
|
WantedBy=multi-user.target
|
20
builder/data/64bit/etc/systemd/system/bluetooth.service
Normal file
20
builder/data/64bit/etc/systemd/system/bluetooth.service
Normal file
@ -0,0 +1,20 @@
|
|||||||
|
[Unit]
|
||||||
|
Description=Bluetooth service
|
||||||
|
Documentation=man:bluetoothd(8)
|
||||||
|
ConditionPathIsDirectory=/sys/class/bluetooth
|
||||||
|
|
||||||
|
[Service]
|
||||||
|
Type=dbus
|
||||||
|
BusName=org.bluez
|
||||||
|
ExecStart=/usr/libexec/bluetooth/bluetoothd --noplugin=sap,a2dp
|
||||||
|
NotifyAccess=main
|
||||||
|
#WatchdogSec=10
|
||||||
|
#Restart=on-failure
|
||||||
|
CapabilityBoundingSet=CAP_NET_ADMIN CAP_NET_BIND_SERVICE
|
||||||
|
LimitNPROC=1
|
||||||
|
ProtectHome=true
|
||||||
|
ProtectSystem=full
|
||||||
|
|
||||||
|
[Install]
|
||||||
|
WantedBy=bluetooth.target
|
||||||
|
Alias=dbus-org.bluez.service
|
16
builder/data/64bit/etc/systemd/system/pwngrid-peer.service
Normal file
16
builder/data/64bit/etc/systemd/system/pwngrid-peer.service
Normal file
@ -0,0 +1,16 @@
|
|||||||
|
[Unit]
|
||||||
|
Description=pwngrid peer service.
|
||||||
|
Documentation=https://pwnagotchi.ai
|
||||||
|
Wants=network.target
|
||||||
|
After=bettercap.service
|
||||||
|
|
||||||
|
[Service]
|
||||||
|
Environment=LD_PRELOAD=/usr/local/lib/libpcap.so.1
|
||||||
|
Environment=LD_LIBRARY_PATH=/usr/local/lib
|
||||||
|
Type=simple
|
||||||
|
ExecStart=/usr/local/bin/pwngrid -keys /etc/pwnagotchi -peers /root/peers -address 127.0.0.1:8666 -client-token /root/.api-enrollment.json -wait -log /etc/pwnagotchi/log/pwngrid-peer.log -iface wlan0mon
|
||||||
|
Restart=always
|
||||||
|
RestartSec=30
|
||||||
|
|
||||||
|
[Install]
|
||||||
|
WantedBy=multi-user.target
|
@ -20,6 +20,7 @@ echo " I'm managed by systemd. Here are some basic commands."
|
|||||||
echo
|
echo
|
||||||
echo " If you want to know what I'm doing, you can check my logs with the command"
|
echo " If you want to know what I'm doing, you can check my logs with the command"
|
||||||
echo " - pwnlog"
|
echo " - pwnlog"
|
||||||
|
echo " - sudo pwnagotchi --wizard, to help set up a config.toml"
|
||||||
echo " - sudo pwnagotchi --version, to check the current version"
|
echo " - sudo pwnagotchi --version, to check the current version"
|
||||||
echo " - sudo pwnagotchi --donate, to see how you can donate to this project"
|
echo " - sudo pwnagotchi --donate, to see how you can donate to this project"
|
||||||
echo " - sudo pwnagotchi --check-update, to see if there is a new version available"
|
echo " - sudo pwnagotchi --check-update, to see if there is a new version available"
|
15
builder/data/64bit/root/settings.yaml
Normal file
15
builder/data/64bit/root/settings.yaml
Normal file
@ -0,0 +1,15 @@
|
|||||||
|
client_config_backend: file
|
||||||
|
client_config_file: /root/client_secrets.json
|
||||||
|
client_config:
|
||||||
|
client_id: <YOUR CLIENT ID>
|
||||||
|
client_secret: <YOUR CLIENT SECRET>
|
||||||
|
|
||||||
|
save_credentials: True
|
||||||
|
save_credentials_backend: file
|
||||||
|
save_credentials_file: /root/credentials.json
|
||||||
|
|
||||||
|
get_refresh_token: True
|
||||||
|
|
||||||
|
oauth_scope:
|
||||||
|
- https://www.googleapis.com/auth/drive
|
||||||
|
- https://www.googleapis.com/auth/drive.install
|
2
builder/data/64bit/usr/bin/hdmioff
Executable file
2
builder/data/64bit/usr/bin/hdmioff
Executable file
@ -0,0 +1,2 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
sudo /usr/bin/tvservice -o
|
2
builder/data/64bit/usr/bin/hdmion
Executable file
2
builder/data/64bit/usr/bin/hdmion
Executable file
@ -0,0 +1,2 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
sudo /usr/bin/tvservice -p
|
3
builder/data/64bit/usr/bin/monstart
Executable file
3
builder/data/64bit/usr/bin/monstart
Executable file
@ -0,0 +1,3 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
source /usr/bin/pwnlib
|
||||||
|
start_monitor_interface
|
3
builder/data/64bit/usr/bin/monstop
Executable file
3
builder/data/64bit/usr/bin/monstop
Executable file
@ -0,0 +1,3 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
source /usr/bin/pwnlib
|
||||||
|
stop_monitor_interface
|
17
builder/data/64bit/usr/bin/pwnagotchi-launcher
Executable file
17
builder/data/64bit/usr/bin/pwnagotchi-launcher
Executable file
@ -0,0 +1,17 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
source /usr/bin/pwnlib
|
||||||
|
|
||||||
|
# we need to decrypt something
|
||||||
|
if is_crypted_mode; then
|
||||||
|
while ! is_decrypted; do
|
||||||
|
echo "Waiting for decryption..."
|
||||||
|
sleep 1
|
||||||
|
done
|
||||||
|
fi
|
||||||
|
|
||||||
|
if is_auto_mode; then
|
||||||
|
/usr/local/bin/pwnagotchi
|
||||||
|
systemctl restart bettercap
|
||||||
|
else
|
||||||
|
/usr/local/bin/pwnagotchi --manual
|
||||||
|
fi
|
@ -1,18 +1,5 @@
|
|||||||
#!/usr/bin/env bash
|
#!/usr/bin/env bash
|
||||||
|
|
||||||
# well ... it blinks the led
|
|
||||||
blink_led() {
|
|
||||||
# shellcheck disable=SC2034
|
|
||||||
for i in $(seq 1 "$1"); do
|
|
||||||
echo 0 >/sys/class/leds/led0/brightness
|
|
||||||
sleep 0.3
|
|
||||||
echo 1 >/sys/class/leds/led0/brightness
|
|
||||||
sleep 0.3
|
|
||||||
done
|
|
||||||
echo 0 >/sys/class/leds/led0/brightness
|
|
||||||
sleep 0.3
|
|
||||||
}
|
|
||||||
|
|
||||||
# reload mod
|
# reload mod
|
||||||
reload_brcm() {
|
reload_brcm() {
|
||||||
if ! modprobe -r brcmfmac; then
|
if ! modprobe -r brcmfmac; then
|
40
builder/extras/nexmon.yml
Normal file
40
builder/extras/nexmon.yml
Normal file
@ -0,0 +1,40 @@
|
|||||||
|
# Install nexmon to fix wireless scanning (takes 2.5G of space)
|
||||||
|
- name: clone nexmon repository
|
||||||
|
git:
|
||||||
|
repo: https://github.com/DrSchottky/nexmon.git
|
||||||
|
dest: /usr/local/src/nexmon
|
||||||
|
|
||||||
|
- name: make firmware
|
||||||
|
shell: "source ./setup_env.sh && make"
|
||||||
|
args:
|
||||||
|
executable: /bin/bash
|
||||||
|
chdir: /usr/local/src/nexmon/
|
||||||
|
|
||||||
|
- name: "make firmware patch ({{ item.name }})"
|
||||||
|
shell: "source ./setup_env.sh && cd /usr/local/src/nexmon/patches/{{ item.patch }}/nexmon/ && make"
|
||||||
|
args:
|
||||||
|
executable: /bin/bash
|
||||||
|
chdir: /usr/local/src/nexmon/
|
||||||
|
environment:
|
||||||
|
QEMU_UNAME: "{{ item.kernel }}"
|
||||||
|
ARCHFLAGS: "{{ item.arch_flags }}"
|
||||||
|
|
||||||
|
- name: "install new firmware ({{ item.name }})"
|
||||||
|
copy:
|
||||||
|
src: "/usr/local/src/nexmon/patches/{{ item.patch }}/nexmon/{{ item.firmware }}"
|
||||||
|
dest: "/usr/lib/firmware/brcm/{{ item.firmware }}"
|
||||||
|
follow: true
|
||||||
|
environment:
|
||||||
|
QEMU_UNAME: "{{ item.kernel }}"
|
||||||
|
ARCHFLAGS: "{{ item.arch_flags }}"
|
||||||
|
|
||||||
|
- name: backup original driver
|
||||||
|
command: "mv /usr/lib/modules/{{ item.kernel }}/kernel/drivers/net/wireless/broadcom/brcm80211/brcmfmac/brcmfmac.ko.xz /usr/lib/modules/{{ item.kernel }}/kernel/drivers/net/wireless/broadcom/brcm80211/brcmfmac/brcmfmac.ko.xz.orig"
|
||||||
|
|
||||||
|
- name: copy modified driver
|
||||||
|
copy:
|
||||||
|
src: "/usr/local/src/nexmon/patches/driver/brcmfmac_6.1.y-nexmon/brcmfmac.ko"
|
||||||
|
dest: "/usr/lib/modules/{{ item.kernel }}/kernel/drivers/net/wireless/broadcom/brcm80211/brcmfmac/brcmfmac.ko"
|
||||||
|
|
||||||
|
- name : load brcmfmac drivers
|
||||||
|
command: "/sbin/depmod -a {{ item.kernel }}"
|
94
builder/raspberrypi32.json.pkr.hcl
Normal file
94
builder/raspberrypi32.json.pkr.hcl
Normal file
@ -0,0 +1,94 @@
|
|||||||
|
packer {
|
||||||
|
required_plugins {
|
||||||
|
arm = {
|
||||||
|
version = "1.0.0"
|
||||||
|
source = "github.com/cdecoux/builder-arm"
|
||||||
|
}
|
||||||
|
ansible = {
|
||||||
|
source = "github.com/hashicorp/ansible"
|
||||||
|
version = ">= 1.1.1"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
variable "pwn_hostname" {
|
||||||
|
type = string
|
||||||
|
}
|
||||||
|
|
||||||
|
variable "pwn_version" {
|
||||||
|
type = string
|
||||||
|
}
|
||||||
|
|
||||||
|
source "arm" "rpi32-pwnagotchi" {
|
||||||
|
file_checksum_url = "https://downloads.raspberrypi.com/raspios_oldstable_lite_armhf/images/raspios_oldstable_lite_armhf-2024-03-12/2024-03-12-raspios-bullseye-armhf-lite.img.xz.sha256"
|
||||||
|
file_urls = ["https://downloads.raspberrypi.com/raspios_oldstable_lite_armhf/images/raspios_oldstable_lite_armhf-2024-03-12/2024-03-12-raspios-bullseye-armhf-lite.img.xz"]
|
||||||
|
file_checksum_type = "sha256"
|
||||||
|
file_target_extension = "xz"
|
||||||
|
file_unarchive_cmd = ["unxz", "$ARCHIVE_PATH"]
|
||||||
|
image_path = "../../pwnagotchi-32bit.img"
|
||||||
|
qemu_binary_source_path = "/usr/libexec/qemu-binfmt/arm-binfmt-P"
|
||||||
|
qemu_binary_destination_path = "/usr/libexec/qemu-binfmt/arm-binfmt-P"
|
||||||
|
image_build_method = "resize"
|
||||||
|
image_size = "9G"
|
||||||
|
image_type = "dos"
|
||||||
|
image_partitions {
|
||||||
|
name = "boot"
|
||||||
|
type = "c"
|
||||||
|
start_sector = "8192"
|
||||||
|
filesystem = "fat"
|
||||||
|
size = "256M"
|
||||||
|
mountpoint = "/boot"
|
||||||
|
}
|
||||||
|
image_partitions {
|
||||||
|
name = "root"
|
||||||
|
type = "83"
|
||||||
|
start_sector = "532480"
|
||||||
|
filesystem = "ext4"
|
||||||
|
size = "0"
|
||||||
|
mountpoint = "/"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
build {
|
||||||
|
name = "Raspberry Pi 32 Pwnagotchi"
|
||||||
|
sources = ["source.arm.rpi32-pwnagotchi"]
|
||||||
|
provisioner "file" {
|
||||||
|
destination = "/usr/bin/"
|
||||||
|
sources = [
|
||||||
|
"data/32bit/usr/bin/bettercap-launcher",
|
||||||
|
"data/32bit/usr/bin/hdmioff",
|
||||||
|
"data/32bit/usr/bin/hdmion",
|
||||||
|
"data/32bit/usr/bin/monstart",
|
||||||
|
"data/32bit/usr/bin/monstop",
|
||||||
|
"data/32bit/usr/bin/pwnagotchi-launcher",
|
||||||
|
"data/32bit/usr/bin/pwnlib",
|
||||||
|
]
|
||||||
|
}
|
||||||
|
provisioner "shell" {
|
||||||
|
inline = ["chmod +x /usr/bin/*"]
|
||||||
|
}
|
||||||
|
|
||||||
|
provisioner "file" {
|
||||||
|
destination = "/etc/systemd/system/"
|
||||||
|
sources = [
|
||||||
|
"data/32bit/etc/systemd/system/bettercap.service",
|
||||||
|
"data/32bit/etc/systemd/system/pwnagotchi.service",
|
||||||
|
"data/32bit/etc/systemd/system/pwngrid-peer.service",
|
||||||
|
]
|
||||||
|
}
|
||||||
|
provisioner "file" {
|
||||||
|
destination = "/etc/update-motd.d/01-motd"
|
||||||
|
source = "data/32bit/etc/update-motd.d/01-motd"
|
||||||
|
}
|
||||||
|
provisioner "shell" {
|
||||||
|
inline = ["chmod +x /etc/update-motd.d/*"]
|
||||||
|
}
|
||||||
|
provisioner "shell" {
|
||||||
|
inline = ["apt-get -y --allow-releaseinfo-change update", "apt-get -y dist-upgrade", "apt-get install -y --no-install-recommends ansible"]
|
||||||
|
}
|
||||||
|
provisioner "ansible-local" {
|
||||||
|
command = "ANSIBLE_FORCE_COLOR=1 PYTHONUNBUFFERED=1 PWN_VERSION=${var.pwn_version} PWN_HOSTNAME=${var.pwn_hostname} ansible-playbook"
|
||||||
|
extra_arguments = ["--extra-vars \"ansible_python_interpreter=/usr/bin/python3\""]
|
||||||
|
playbook_dir = "extras/"
|
||||||
|
playbook_file = "raspberrypi32.yml"
|
||||||
|
}
|
||||||
|
}
|
562
builder/raspberrypi32.yml
Normal file
562
builder/raspberrypi32.yml
Normal file
@ -0,0 +1,562 @@
|
|||||||
|
---
|
||||||
|
- hosts:
|
||||||
|
- 127.0.0.1
|
||||||
|
gather_facts: true
|
||||||
|
become: true
|
||||||
|
vars:
|
||||||
|
boards:
|
||||||
|
- {
|
||||||
|
kernel: "6.1.21+",
|
||||||
|
name: "PiZeroW",
|
||||||
|
firmware: "brcmfmac43430-sdio.bin",
|
||||||
|
patch: "bcm43430a1/7_45_41_46",
|
||||||
|
cpu: arm1176,
|
||||||
|
arch_flags: "-arch armv6l"
|
||||||
|
}
|
||||||
|
- {
|
||||||
|
kernel: "6.1.21-v7+",
|
||||||
|
name: "PiZero2W",
|
||||||
|
firmware: "brcmfmac43436-sdio.bin",
|
||||||
|
patch: "bcm43436b0/9_88_4_65",
|
||||||
|
cpu: any, #cortex-a53
|
||||||
|
arch_flags: "-arch armv7l"
|
||||||
|
}
|
||||||
|
- {
|
||||||
|
kernel: "6.1.21-v7l+",
|
||||||
|
name: "Pi4b_32",
|
||||||
|
firmware: "brcmfmac43455-sdio.bin",
|
||||||
|
patch: "bcm43455c0/7_45_206",
|
||||||
|
cpu: any, #cortex-a72
|
||||||
|
arch_flags: "-arch armv7l"
|
||||||
|
}
|
||||||
|
kernel:
|
||||||
|
min: "6.1"
|
||||||
|
full: "6.1.21+"
|
||||||
|
full_2w: "6.1.21-v7+"
|
||||||
|
full_4b: "6.1.21-v7l+"
|
||||||
|
arch: "v6l"
|
||||||
|
pwnagotchi:
|
||||||
|
hostname: "{{ lookup('env', 'PWN_HOSTNAME') | default('pwnagotchi', true) }}"
|
||||||
|
version: "{{ lookup('env', 'PWN_VERSION') | default('pwnagotchi-torch', true) }}"
|
||||||
|
custom_plugin_dir: "/usr/local/share/pwnagotchi/custom-plugins"
|
||||||
|
services:
|
||||||
|
enable:
|
||||||
|
- bettercap.service
|
||||||
|
- bluetooth.service
|
||||||
|
- dphys-swapfile.service
|
||||||
|
- fstrim.timer
|
||||||
|
- pwnagotchi.service
|
||||||
|
- pwngrid-peer.service
|
||||||
|
disable:
|
||||||
|
- apt-daily-upgrade.service
|
||||||
|
- apt-daily-upgrade.timer
|
||||||
|
- apt-daily.service
|
||||||
|
- apt-daily.timer
|
||||||
|
- ifup@wlan0.service
|
||||||
|
- triggerhappy.service
|
||||||
|
- wpa_supplicant.service
|
||||||
|
packages:
|
||||||
|
caplets:
|
||||||
|
source: "https://github.com/jayofelony/caplets.git"
|
||||||
|
bettercap:
|
||||||
|
source: "https://github.com/jayofelony/bettercap.git"
|
||||||
|
url: "https://github.com/jayofelony/bettercap/releases/download/2.32.2/bettercap-2.32.2-armhf.zip"
|
||||||
|
ui: "https://github.com/bettercap/ui/releases/download/v1.3.0/ui.zip"
|
||||||
|
opwngrid:
|
||||||
|
source: "https://github.com/jayofelony/pwngrid.git"
|
||||||
|
url: "https://github.com/jayofelony/pwngrid/releases/download/v1.10.7/pwngrid-1.10.7-armhf.zip"
|
||||||
|
torch:
|
||||||
|
wheel: "torch-2.1.0a0+gitunknown-cp39-cp39-linux_armv6l.whl"
|
||||||
|
url: "https://github.com/Sniffleupagus/Torch4Pizero/releases/download/v1.0.0/torch-2.1.0a0+gitunknown-cp39-cp39-linux_armv6l.whl"
|
||||||
|
torchvision:
|
||||||
|
wheel: "torchvision-0.16.0a0-cp39-cp39-linux_armv6l.whl"
|
||||||
|
url: "https://github.com/Sniffleupagus/Torch4Pizero/releases/download/v1.0.0/torchvision-0.16.0a0-cp39-cp39-linux_armv6l.whl"
|
||||||
|
apt:
|
||||||
|
downgrade:
|
||||||
|
- libpcap-dev_1.9.1-4_armhf.deb
|
||||||
|
- libpcap0.8-dbg_1.9.1-4_armhf.deb
|
||||||
|
- libpcap0.8-dev_1.9.1-4_armhf.deb
|
||||||
|
- libpcap0.8_1.9.1-4_armhf.deb
|
||||||
|
hold:
|
||||||
|
- firmware-atheros
|
||||||
|
- firmware-brcm80211
|
||||||
|
- firmware-libertas
|
||||||
|
- firmware-misc-nonfree
|
||||||
|
- firmware-realtek
|
||||||
|
- libpcap-dev
|
||||||
|
- libpcap0.8
|
||||||
|
- libpcap0.8-dev
|
||||||
|
- libpcap0.8-dbg
|
||||||
|
remove:
|
||||||
|
- avahi-daemon
|
||||||
|
- nfs-common
|
||||||
|
- triggerhappy
|
||||||
|
- wpasupplicant
|
||||||
|
install:
|
||||||
|
- autoconf
|
||||||
|
- bc
|
||||||
|
- bison
|
||||||
|
- bluez
|
||||||
|
- bluez-tools
|
||||||
|
- build-essential
|
||||||
|
- curl
|
||||||
|
- dkms
|
||||||
|
- dphys-swapfile
|
||||||
|
- espeak-ng
|
||||||
|
- evtest
|
||||||
|
- fbi
|
||||||
|
- flex
|
||||||
|
- fonts-dejavu
|
||||||
|
- fonts-dejavu-core
|
||||||
|
- fonts-dejavu-extra
|
||||||
|
- fonts-freefont-ttf
|
||||||
|
- g++
|
||||||
|
- gawk
|
||||||
|
- gcc-arm-none-eabi
|
||||||
|
- git
|
||||||
|
- libatlas-base-dev
|
||||||
|
- libavcodec58
|
||||||
|
- libavformat58
|
||||||
|
- libblas-dev
|
||||||
|
- libbluetooth-dev
|
||||||
|
- libbz2-dev
|
||||||
|
- libc-ares-dev
|
||||||
|
- libc6-dev
|
||||||
|
- libcpuinfo-dev
|
||||||
|
- libdbus-1-dev
|
||||||
|
- libdbus-glib-1-dev
|
||||||
|
- libeigen3-dev
|
||||||
|
- libelf-dev
|
||||||
|
- libffi-dev
|
||||||
|
- libfl-dev
|
||||||
|
- libfuse-dev
|
||||||
|
- libgdbm-dev
|
||||||
|
- libgl1-mesa-glx
|
||||||
|
- libgmp3-dev
|
||||||
|
- libgstreamer1.0-0
|
||||||
|
- libhdf5-dev
|
||||||
|
- liblapack-dev
|
||||||
|
- libncursesw5-dev
|
||||||
|
- libnetfilter-queue-dev
|
||||||
|
- libopenblas-dev
|
||||||
|
- libopenjp2-7
|
||||||
|
- libopenmpi-dev
|
||||||
|
- libopenmpi3
|
||||||
|
- libpcap-dev
|
||||||
|
- libprotobuf-dev
|
||||||
|
- libraspberrypi-bin
|
||||||
|
- libraspberrypi-dev
|
||||||
|
- libraspberrypi-doc
|
||||||
|
- libraspberrypi0
|
||||||
|
- libsleef-dev
|
||||||
|
- libsqlite3-dev
|
||||||
|
- libssl-dev
|
||||||
|
- libswscale5
|
||||||
|
- libtiff5
|
||||||
|
- libtool
|
||||||
|
- libts-bin
|
||||||
|
- libusb-1.0-0-dev
|
||||||
|
- lsof
|
||||||
|
- make
|
||||||
|
- ntp
|
||||||
|
- python3-dbus
|
||||||
|
- python3-flask
|
||||||
|
- python3-flask-cors
|
||||||
|
- python3-flaskext.wtf
|
||||||
|
- python3-pil
|
||||||
|
- python3-pip
|
||||||
|
- python3-protobuf
|
||||||
|
- python3-smbus
|
||||||
|
- qpdf
|
||||||
|
- raspberrypi-kernel-headers
|
||||||
|
- rsync
|
||||||
|
- screen
|
||||||
|
- tcpdump
|
||||||
|
- texinfo
|
||||||
|
- time
|
||||||
|
- tk-dev
|
||||||
|
- unzip
|
||||||
|
- vim
|
||||||
|
- wget
|
||||||
|
- wl
|
||||||
|
- xxd
|
||||||
|
- zlib1g-dev
|
||||||
|
|
||||||
|
tasks:
|
||||||
|
- name: Create pi user
|
||||||
|
copy:
|
||||||
|
dest: /boot/userconf
|
||||||
|
content: |
|
||||||
|
pi:$6$3jNr0GA9KIyt4hmM$efeVIopdMQ8DGgEPCWWlbx3mJJNAYci1lEXGdlky0xPyjqwKNbwTL5SrCcpb4144C4IvzWjn7Iv.QjqmU7iyT/
|
||||||
|
|
||||||
|
- name: change hostname
|
||||||
|
lineinfile:
|
||||||
|
dest: /etc/hostname
|
||||||
|
regexp: '^raspberrypi'
|
||||||
|
line: "{{pwnagotchi.hostname}}"
|
||||||
|
state: present
|
||||||
|
when: lookup('file', '/etc/hostname') == "raspberrypi"
|
||||||
|
register: hostname
|
||||||
|
|
||||||
|
- name: add hostname to /etc/hosts
|
||||||
|
lineinfile:
|
||||||
|
dest: /etc/hosts
|
||||||
|
regexp: '^127\.0\.1\.1[ \t]+raspberrypi'
|
||||||
|
line: "127.0.1.1\t{{pwnagotchi.hostname}}"
|
||||||
|
state: present
|
||||||
|
when: hostname.changed
|
||||||
|
|
||||||
|
- name: Create custom plugin directory
|
||||||
|
file:
|
||||||
|
path: '{{ pwnagotchi.custom_plugin_dir }}'
|
||||||
|
state: directory
|
||||||
|
|
||||||
|
- name: remove current rc.local
|
||||||
|
file:
|
||||||
|
path: /etc/rc.local
|
||||||
|
state: absent
|
||||||
|
|
||||||
|
- name: update apt package cache
|
||||||
|
apt:
|
||||||
|
update_cache: yes
|
||||||
|
|
||||||
|
- name: install packages
|
||||||
|
apt:
|
||||||
|
name: "{{ packages.apt.install }}"
|
||||||
|
state: present
|
||||||
|
|
||||||
|
- name: update pip3, setuptools, wheel
|
||||||
|
shell: "python3 -m pip install --upgrade pip setuptools wheel"
|
||||||
|
args:
|
||||||
|
executable: /bin/bash
|
||||||
|
chdir: /usr/local/src
|
||||||
|
|
||||||
|
###########################################
|
||||||
|
#
|
||||||
|
# libpcap v1.9 - build from source
|
||||||
|
#
|
||||||
|
###########################################
|
||||||
|
|
||||||
|
# check for presence, then it can re-run in later parts if needed
|
||||||
|
# use the "make" built in
|
||||||
|
|
||||||
|
# install libpcap before bettercap and pwngrid, so they use it
|
||||||
|
- name: clone libpcap v1.9 from github
|
||||||
|
git:
|
||||||
|
repo: 'https://github.com/the-tcpdump-group/libpcap.git'
|
||||||
|
dest: /usr/local/src/libpcap
|
||||||
|
version: libpcap-1.9
|
||||||
|
|
||||||
|
- name: build and install libpcap into /usr/local/lib
|
||||||
|
shell: "./configure && make && make install"
|
||||||
|
args:
|
||||||
|
executable: /bin/bash
|
||||||
|
chdir: /usr/local/src/libpcap
|
||||||
|
|
||||||
|
- name: remove libpcap build folder
|
||||||
|
file:
|
||||||
|
state: absent
|
||||||
|
path: /usr/local/src/libpcap
|
||||||
|
|
||||||
|
- name: create symlink /usr/local/lib/libpcap.so.1.9.1
|
||||||
|
file:
|
||||||
|
src: /usr/local/lib/libpcap.so.1.9.1
|
||||||
|
dest: /usr/local/lib/libpcap.so.0.8
|
||||||
|
state: link
|
||||||
|
|
||||||
|
###############################################################
|
||||||
|
# Install nexmon to fix wireless scanning (takes 2.5G of space)
|
||||||
|
###############################################################
|
||||||
|
|
||||||
|
# Install nexmon for all boards
|
||||||
|
- name: build and install nexmon as needed
|
||||||
|
include_tasks: nexmon.yml
|
||||||
|
loop: "{{ boards }}"
|
||||||
|
|
||||||
|
# some pizero2w have the pizeroW wifi chip
|
||||||
|
# could this be a link instead of a copy? and force, only if not a link?
|
||||||
|
- name: copy 43430-sdio as 43436s-sdio for the special 43430/1 /2
|
||||||
|
copy:
|
||||||
|
src: /usr/lib/firmware/brcm/brcmfmac43430-sdio.bin
|
||||||
|
dest: /usr/lib/firmware/brcm/brcmfmac43436s-sdio.bin
|
||||||
|
follow: true
|
||||||
|
|
||||||
|
# delete blob files that make nexmon sad
|
||||||
|
- name: Delete the firmware blob files to avoid some nexmon crashing
|
||||||
|
file:
|
||||||
|
state: absent
|
||||||
|
path: '{{ item }}'
|
||||||
|
loop:
|
||||||
|
- /usr/lib/firmware/brcm/brcmfmac43430-sdio.clm_blob
|
||||||
|
- /usr/lib/firmware/brcm/brcmfmac43430-sdio.raspberrypi,model-zero-w.clm_blob
|
||||||
|
- /usr/lib/firmware/brcm/brcmfmac43430b0-sdio.raspberrypi,model-zero-2-w.clm_blob
|
||||||
|
- /usr/lib/firmware/brcm/brcmfmac43436-sdio.raspberrypi,model-zero-2-w.clm_blob
|
||||||
|
- /usr/lib/firmware/brcm/brcmfmac43430-sdio.raspberrypi,3-model-b.clm_blob
|
||||||
|
|
||||||
|
# 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: clone pwnagotchi repository
|
||||||
|
git:
|
||||||
|
repo: https://github.com/jayofelony/pwnagotchi.git
|
||||||
|
dest: /usr/local/src/pwnagotchi
|
||||||
|
register: pwnagotchigit
|
||||||
|
|
||||||
|
# is this even necessary? Can't we just link from /home/pi/pwnagotchi to /usr/local/{bin,lib,etc}
|
||||||
|
# then just git update in the home dir and encourage hacking?
|
||||||
|
# make owned by pi.pi, and custom plugins.
|
||||||
|
- 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: download torch whl
|
||||||
|
get_url:
|
||||||
|
url: "{{ packages.torch.url }}"
|
||||||
|
dest: /usr/local/src/
|
||||||
|
|
||||||
|
- name: download torchvision whl
|
||||||
|
get_url:
|
||||||
|
url: "{{ packages.torchvision.url }}"
|
||||||
|
dest: /usr/local/src/
|
||||||
|
|
||||||
|
- name: install 32-bit pwnagotchi wheel and dependencies with 32-bit torch wheels
|
||||||
|
pip:
|
||||||
|
name:
|
||||||
|
- "{{ lookup('fileglob', '/usr/local/src/pwnagotchi/dist/pwnagotchi*.whl') }}"
|
||||||
|
- "{{ packages.torch.url }}"
|
||||||
|
- "{{ packages.torchvision.url }}"
|
||||||
|
extra_args: "--no-cache-dir"
|
||||||
|
environment:
|
||||||
|
QEMU_CPU: arm1176
|
||||||
|
QEMU_UNAME: "{{ kernel.full }}"
|
||||||
|
when: (pwnagotchigit.changed) or (pip_packages['pwnagotchi'] is undefined) or (pip_packages['pwnagotchi'] != pwnagotchi_version)
|
||||||
|
|
||||||
|
- name: create /usr/local/share/pwnagotchi/ folder
|
||||||
|
file:
|
||||||
|
path: /usr/local/share/pwnagotchi/
|
||||||
|
state: directory
|
||||||
|
|
||||||
|
- name: remove pwnagotchi folder
|
||||||
|
file:
|
||||||
|
state: absent
|
||||||
|
path: /usr/local/src/pwnagotchi
|
||||||
|
|
||||||
|
- name: remove torch whl
|
||||||
|
file:
|
||||||
|
state: absent
|
||||||
|
path: "{{ lookup('fileglob', '/usr/local/src/torch*.whl') }}"
|
||||||
|
|
||||||
|
##########################################
|
||||||
|
#
|
||||||
|
# pwngrid, bettercap
|
||||||
|
#
|
||||||
|
##########################################
|
||||||
|
|
||||||
|
- name: Install go-1.21
|
||||||
|
unarchive:
|
||||||
|
src: https://go.dev/dl/go1.21.6.linux-armv6l.tar.gz
|
||||||
|
dest: /usr/local
|
||||||
|
remote_src: yes
|
||||||
|
register: golang
|
||||||
|
|
||||||
|
- name: Update .bashrc for go-1.21
|
||||||
|
blockinfile:
|
||||||
|
dest: /home/pi/.bashrc
|
||||||
|
state: present
|
||||||
|
block: |
|
||||||
|
export GOPATH=$HOME/go
|
||||||
|
export PATH=/usr/local/go/bin:$PATH:$GOPATH/bin
|
||||||
|
when: golang.changed
|
||||||
|
|
||||||
|
- name: download pwngrid
|
||||||
|
unarchive:
|
||||||
|
remote_src: yes
|
||||||
|
src: "{{ packages.opwngrid.url }}"
|
||||||
|
dest: /usr/local/bin/
|
||||||
|
mode: 0755
|
||||||
|
|
||||||
|
- name: download and install bettercap
|
||||||
|
unarchive:
|
||||||
|
src: "{{ packages.bettercap.url }}"
|
||||||
|
dest: /usr/local/bin
|
||||||
|
remote_src: yes
|
||||||
|
exclude:
|
||||||
|
- README.md
|
||||||
|
- LICENSE.md
|
||||||
|
mode: 0755
|
||||||
|
|
||||||
|
- name: clone bettercap caplets
|
||||||
|
git:
|
||||||
|
repo: "{{ packages.caplets.source }}"
|
||||||
|
dest: /tmp/caplets
|
||||||
|
register: capletsgit
|
||||||
|
|
||||||
|
- name: install bettercap caplets
|
||||||
|
make:
|
||||||
|
chdir: /tmp/caplets
|
||||||
|
target: install
|
||||||
|
when: capletsgit.changed
|
||||||
|
|
||||||
|
- name: download and install bettercap ui
|
||||||
|
unarchive:
|
||||||
|
src: "{{ packages.bettercap.ui }}"
|
||||||
|
dest: /usr/local/share/bettercap/
|
||||||
|
remote_src: yes
|
||||||
|
mode: 0755
|
||||||
|
|
||||||
|
# to always have the bettercap webui available (because why not?)
|
||||||
|
- name: copy pwnagotchi-manual over pwnagotchi-auto caplet
|
||||||
|
ansible.builtin.copy:
|
||||||
|
src: /usr/local/share/bettercap/caplets/pwnagotchi-manual.cap
|
||||||
|
dest: /usr/local/share/bettercap/caplets/pwnagotchi-auto.cap
|
||||||
|
force: true
|
||||||
|
ignore_errors: true
|
||||||
|
|
||||||
|
- name: create /etc/pwnagotchi folder
|
||||||
|
file:
|
||||||
|
path: /etc/pwnagotchi
|
||||||
|
state: directory
|
||||||
|
|
||||||
|
- name: create log folder
|
||||||
|
file:
|
||||||
|
path: /home/pi/logs
|
||||||
|
state: directory
|
||||||
|
|
||||||
|
- name: check if user configuration exists
|
||||||
|
stat:
|
||||||
|
path: /etc/pwnagotchi/config.toml
|
||||||
|
register: user_config
|
||||||
|
|
||||||
|
- name: create /etc/pwnagotchi/config.toml
|
||||||
|
copy:
|
||||||
|
dest: /etc/pwnagotchi/config.toml
|
||||||
|
content: |
|
||||||
|
# Add your configuration overrides on this file any configuration changes done to default.toml will be lost!
|
||||||
|
# Example:
|
||||||
|
# ui.display.enabled = true
|
||||||
|
# ui.display.type = "waveshare_4"
|
||||||
|
when: not user_config.stat.exists
|
||||||
|
|
||||||
|
- name: Delete motd 10-uname
|
||||||
|
file:
|
||||||
|
state: absent
|
||||||
|
path: /etc/update-motd.d/10-uname
|
||||||
|
|
||||||
|
- name: enable ssh on boot
|
||||||
|
file:
|
||||||
|
path: /boot/ssh
|
||||||
|
state: touch
|
||||||
|
|
||||||
|
- name: change root partition
|
||||||
|
replace:
|
||||||
|
dest: /boot/cmdline.txt
|
||||||
|
backup: no
|
||||||
|
regexp: "root=PARTUUID=[a-zA-Z0-9\\-]+"
|
||||||
|
replace: "root=/dev/mmcblk0p2"
|
||||||
|
|
||||||
|
- name: configure /boot/cmdline.txt
|
||||||
|
lineinfile:
|
||||||
|
path: /boot/cmdline.txt
|
||||||
|
backrefs: True
|
||||||
|
state: present
|
||||||
|
backup: no
|
||||||
|
regexp: '(.*)$'
|
||||||
|
line: '\1 modules-load=dwc2,g_ether'
|
||||||
|
|
||||||
|
- name: add firmware packages to hold
|
||||||
|
dpkg_selections:
|
||||||
|
name: "{{ item }}"
|
||||||
|
selection: hold
|
||||||
|
with_items: "{{ packages.apt.hold }}"
|
||||||
|
|
||||||
|
- name: disable unnecessary services
|
||||||
|
systemd:
|
||||||
|
name: "{{ item }}"
|
||||||
|
state: stopped
|
||||||
|
enabled: no
|
||||||
|
with_items: "{{ services.disable }}"
|
||||||
|
|
||||||
|
- name: enable services
|
||||||
|
systemd:
|
||||||
|
name: "{{ item }}"
|
||||||
|
enabled: true
|
||||||
|
state: stopped
|
||||||
|
with_items: "{{ services.enable }}"
|
||||||
|
|
||||||
|
#- name: remove golang build libraries
|
||||||
|
# file:
|
||||||
|
# state: absent
|
||||||
|
# path: /root/go
|
||||||
|
|
||||||
|
#- name: remove golang
|
||||||
|
# file:
|
||||||
|
# state: absent
|
||||||
|
# path: /usr/local/go
|
||||||
|
|
||||||
|
- name: make /root readable, becauase that's where all the files are
|
||||||
|
file:
|
||||||
|
path: /root
|
||||||
|
mode: '755'
|
||||||
|
|
||||||
|
- name: fix permissions on /home/pi
|
||||||
|
file:
|
||||||
|
path: /home/pi
|
||||||
|
owner: pi
|
||||||
|
group: pi
|
||||||
|
recurse: true
|
||||||
|
|
||||||
|
- name: remove unnecessary apt packages
|
||||||
|
apt:
|
||||||
|
name: "{{ packages.apt.remove }}"
|
||||||
|
state: absent
|
||||||
|
purge: yes
|
||||||
|
|
||||||
|
- name: remove dependencies that are no longer required
|
||||||
|
apt:
|
||||||
|
autoremove: yes
|
||||||
|
|
||||||
|
- name: clean apt cache
|
||||||
|
apt:
|
||||||
|
autoclean: true
|
||||||
|
|
||||||
|
- name: remove golang build libraries
|
||||||
|
file:
|
||||||
|
state: absent
|
||||||
|
path: /root/go
|
||||||
|
|
||||||
|
- name: remove pre-collected packages zip
|
||||||
|
file:
|
||||||
|
path: /root/go_pkgs.tgz
|
||||||
|
state: absent
|
||||||
|
|
||||||
|
- name: remove golang
|
||||||
|
file:
|
||||||
|
state: absent
|
||||||
|
path: /usr/local/go
|
||||||
|
|
||||||
|
- name: remove /root/.cache (pip cache)
|
||||||
|
file:
|
||||||
|
state: absent
|
||||||
|
path: /root/.cache
|
||||||
|
|
||||||
|
- name: remove ssh keys
|
||||||
|
file:
|
||||||
|
state: absent
|
||||||
|
path: "{{ item }}"
|
||||||
|
with_fileglob:
|
||||||
|
- "/etc/ssh/ssh_host*_key*"
|
||||||
|
|
||||||
|
- name: regenerate ssh keys
|
||||||
|
shell: "dpkg-reconfigure openssh-server"
|
||||||
|
args:
|
||||||
|
executable: /bin/bash
|
||||||
|
|
||||||
|
handlers:
|
||||||
|
- name: reload systemd services
|
||||||
|
systemd:
|
||||||
|
daemon_reload: yes
|
@ -6,7 +6,7 @@ packer {
|
|||||||
}
|
}
|
||||||
ansible = {
|
ansible = {
|
||||||
source = "github.com/hashicorp/ansible"
|
source = "github.com/hashicorp/ansible"
|
||||||
version = "~> 1"
|
version = ">= 1.1.1"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -20,12 +20,12 @@ variable "pwn_version" {
|
|||||||
}
|
}
|
||||||
|
|
||||||
source "arm" "rpi64-pwnagotchi" {
|
source "arm" "rpi64-pwnagotchi" {
|
||||||
file_checksum_url = "https://downloads.raspberrypi.org/raspios_lite_arm64/images/raspios_lite_arm64-2023-12-11/2023-12-11-raspios-bookworm-arm64-lite.img.xz.sha256"
|
file_checksum_url = "https://downloads.raspberrypi.com/raspios_lite_arm64/images/raspios_lite_arm64-2024-03-15/2024-03-15-raspios-bookworm-arm64-lite.img.xz.sha256"
|
||||||
file_urls = ["https://downloads.raspberrypi.org/raspios_lite_arm64/images/raspios_lite_arm64-2023-12-11/2023-12-11-raspios-bookworm-arm64-lite.img.xz"]
|
file_urls = ["https://downloads.raspberrypi.com/raspios_lite_arm64/images/raspios_lite_arm64-2024-03-15/2024-03-15-raspios-bookworm-arm64-lite.img.xz"]
|
||||||
file_checksum_type = "sha256"
|
file_checksum_type = "sha256"
|
||||||
file_target_extension = "xz"
|
file_target_extension = "xz"
|
||||||
file_unarchive_cmd = ["unxz", "$ARCHIVE_PATH"]
|
file_unarchive_cmd = ["unxz", "$ARCHIVE_PATH"]
|
||||||
image_path = "../../../pwnagotchi-rpi-bookworm-${var.pwn_version}-arm64.img"
|
image_path = "../../../pwnagotchi-64bit.img"
|
||||||
qemu_binary_source_path = "/usr/libexec/qemu-binfmt/aarch64-binfmt-P"
|
qemu_binary_source_path = "/usr/libexec/qemu-binfmt/aarch64-binfmt-P"
|
||||||
qemu_binary_destination_path = "/usr/libexec/qemu-binfmt/aarch64-binfmt-P"
|
qemu_binary_destination_path = "/usr/libexec/qemu-binfmt/aarch64-binfmt-P"
|
||||||
image_build_method = "resize"
|
image_build_method = "resize"
|
||||||
@ -61,13 +61,13 @@ build {
|
|||||||
provisioner "file" {
|
provisioner "file" {
|
||||||
destination = "/usr/bin/"
|
destination = "/usr/bin/"
|
||||||
sources = [
|
sources = [
|
||||||
"data/usr/bin/bettercap-launcher",
|
"data/64bit/usr/bin/bettercap-launcher",
|
||||||
"data/usr/bin/hdmioff",
|
"data/64bit/usr/bin/hdmioff",
|
||||||
"data/usr/bin/hdmion",
|
"data/64bit/usr/bin/hdmion",
|
||||||
"data/usr/bin/monstart",
|
"data/64bit/usr/bin/monstart",
|
||||||
"data/usr/bin/monstop",
|
"data/64bit/usr/bin/monstop",
|
||||||
"data/usr/bin/pwnagotchi-launcher",
|
"data/64bit/usr/bin/pwnagotchi-launcher",
|
||||||
"data/usr/bin/pwnlib",
|
"data/64bit/usr/bin/pwnlib",
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
provisioner "shell" {
|
provisioner "shell" {
|
||||||
@ -77,14 +77,14 @@ build {
|
|||||||
provisioner "file" {
|
provisioner "file" {
|
||||||
destination = "/etc/systemd/system/"
|
destination = "/etc/systemd/system/"
|
||||||
sources = [
|
sources = [
|
||||||
"data/etc/systemd/system/bettercap.service",
|
"data/64bit/etc/systemd/system/bettercap.service",
|
||||||
"data/etc/systemd/system/pwnagotchi.service",
|
"data/64bit/etc/systemd/system/pwnagotchi.service",
|
||||||
"data/etc/systemd/system/pwngrid-peer.service",
|
"data/64bit/etc/systemd/system/pwngrid-peer.service",
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
provisioner "file" {
|
provisioner "file" {
|
||||||
destination = "/etc/update-motd.d/01-motd"
|
destination = "/etc/update-motd.d/01-motd"
|
||||||
source = "data/etc/update-motd.d/01-motd"
|
source = "data/64bit/etc/update-motd.d/01-motd"
|
||||||
}
|
}
|
||||||
provisioner "shell" {
|
provisioner "shell" {
|
||||||
inline = ["chmod +x /etc/update-motd.d/*"]
|
inline = ["chmod +x /etc/update-motd.d/*"]
|
@ -5,22 +5,12 @@
|
|||||||
become: true
|
become: true
|
||||||
vars:
|
vars:
|
||||||
kernel:
|
kernel:
|
||||||
min: "6.1"
|
min: "6.6"
|
||||||
full: "6.1.0-rpi8-rpi-v8"
|
full: "6.6.20+rpt-rpi-v8"
|
||||||
full_pi5: "6.1.0-rpi8-rpi-2712"
|
full_pi5: "6.6.20+rpt-rpi-2712"
|
||||||
pwnagotchi:
|
pwnagotchi:
|
||||||
hostname: "{{ lookup('env', 'PWN_HOSTNAME') | default('pwnagotchi', true) }}"
|
hostname: "{{ lookup('env', 'PWN_HOSTNAME') | default('pwnagotchi', true) }}"
|
||||||
version: "{{ lookup('env', 'PWN_VERSION') | default('pwnagotchi-torch', true) }}"
|
version: "{{ lookup('env', 'PWN_VERSION') | default('pwnagotchi', true) }}"
|
||||||
system:
|
|
||||||
boot_options:
|
|
||||||
- "dtoverlay=dwc2"
|
|
||||||
- "dtoverlay=spi1-3cs"
|
|
||||||
- "dtparam=i2c1=on"
|
|
||||||
- "dtparam=i2c_arm=on"
|
|
||||||
- "dtparam=spi=on"
|
|
||||||
- "gpu_mem=16"
|
|
||||||
modules:
|
|
||||||
- "i2c-dev"
|
|
||||||
services:
|
services:
|
||||||
enable:
|
enable:
|
||||||
- bettercap.service
|
- bettercap.service
|
||||||
@ -74,6 +64,7 @@
|
|||||||
- bc
|
- bc
|
||||||
- bison
|
- bison
|
||||||
- bluez
|
- bluez
|
||||||
|
- bluez-tools
|
||||||
- build-essential
|
- build-essential
|
||||||
- curl
|
- curl
|
||||||
- dkms
|
- dkms
|
||||||
@ -137,6 +128,7 @@
|
|||||||
- libusb-1.0-0-dev
|
- libusb-1.0-0-dev
|
||||||
- lsof
|
- lsof
|
||||||
- make
|
- make
|
||||||
|
- ntp
|
||||||
- python3-dbus
|
- python3-dbus
|
||||||
- python3-flask
|
- python3-flask
|
||||||
- python3-flask-cors
|
- python3-flask-cors
|
||||||
@ -180,6 +172,12 @@
|
|||||||
update_cache: yes
|
update_cache: yes
|
||||||
install_recommends: false
|
install_recommends: false
|
||||||
|
|
||||||
|
- name: update pip3, setuptools, wheel
|
||||||
|
shell: "python3 -m pip install --upgrade pip setuptools wheel --break-system-packages"
|
||||||
|
args:
|
||||||
|
executable: /bin/bash
|
||||||
|
chdir: /usr/local/src
|
||||||
|
|
||||||
# Now we set up /boot/firmware
|
# Now we set up /boot/firmware
|
||||||
- name: Create pi user
|
- name: Create pi user
|
||||||
copy:
|
copy:
|
||||||
@ -192,12 +190,10 @@
|
|||||||
path: /boot/firmware/ssh
|
path: /boot/firmware/ssh
|
||||||
state: touch
|
state: touch
|
||||||
|
|
||||||
- name: adjust /boot/firmware/config.txt
|
- name: remove current rc.local
|
||||||
lineinfile:
|
file:
|
||||||
dest: /boot/firmware/config.txt
|
path: /etc/rc.local
|
||||||
insertafter: EOF
|
state: absent
|
||||||
line: '{{ item }}'
|
|
||||||
with_items: "{{ system.boot_options }}"
|
|
||||||
|
|
||||||
- name: change root partition
|
- name: change root partition
|
||||||
replace:
|
replace:
|
||||||
@ -459,7 +455,7 @@
|
|||||||
|
|
||||||
- name: clone pwnagotchi repository
|
- name: clone pwnagotchi repository
|
||||||
git:
|
git:
|
||||||
repo: https://github.com/jayofelony/pwnagotchi-bookworm.git
|
repo: https://github.com/jayofelony/pwnagotchi.git
|
||||||
dest: /usr/local/src/pwnagotchi
|
dest: /usr/local/src/pwnagotchi
|
||||||
|
|
||||||
- name: build pwnagotchi wheel
|
- name: build pwnagotchi wheel
|
||||||
@ -486,7 +482,7 @@
|
|||||||
|
|
||||||
- name: Update .bashrc for go-1.21
|
- name: Update .bashrc for go-1.21
|
||||||
blockinfile:
|
blockinfile:
|
||||||
dest: /home/pi/.bashrc
|
dest: /etc/profile
|
||||||
state: present
|
state: present
|
||||||
block: |
|
block: |
|
||||||
export GOPATH=$HOME/go
|
export GOPATH=$HOME/go
|
||||||
@ -567,11 +563,6 @@
|
|||||||
path: /etc/pwnagotchi
|
path: /etc/pwnagotchi
|
||||||
state: directory
|
state: directory
|
||||||
|
|
||||||
- name: create log folder
|
|
||||||
file:
|
|
||||||
path: /home/pi/logs
|
|
||||||
state: directory
|
|
||||||
|
|
||||||
- name: check if user configuration exists
|
- name: check if user configuration exists
|
||||||
stat:
|
stat:
|
||||||
path: /etc/pwnagotchi/config.toml
|
path: /etc/pwnagotchi/config.toml
|
||||||
@ -584,7 +575,7 @@
|
|||||||
# Add your configuration overrides on this file any configuration changes done to default.toml will be lost!
|
# Add your configuration overrides on this file any configuration changes done to default.toml will be lost!
|
||||||
# Example:
|
# Example:
|
||||||
# ui.display.enabled = true
|
# ui.display.enabled = true
|
||||||
# ui.display.type = "waveshare_2"
|
# ui.display.type = "waveshare_4"
|
||||||
when: not user_config.stat.exists
|
when: not user_config.stat.exists
|
||||||
|
|
||||||
- name: Delete motd
|
- name: Delete motd
|
||||||
@ -597,24 +588,6 @@
|
|||||||
state: absent
|
state: absent
|
||||||
path: /etc/update-motd.d/10-uname
|
path: /etc/update-motd.d/10-uname
|
||||||
|
|
||||||
- name: Add pwnlog alias
|
|
||||||
lineinfile:
|
|
||||||
dest: /home/pi/.bashrc
|
|
||||||
line: "\nalias pwnlog='tail -f -n300 /home/pi/logs/pwn*.log | sed --unbuffered \"s/,[[:digit:]]\\{3\\}\\]//g\" | cut -d \" \" -f 2-'"
|
|
||||||
insertafter: EOF
|
|
||||||
|
|
||||||
- name: Add pwnver alias
|
|
||||||
lineinfile:
|
|
||||||
dest: /home/pi/.bashrc
|
|
||||||
line: "\nalias pwnver='python3 -c \"import pwnagotchi as p; print(p.__version__)\"'"
|
|
||||||
insertafter: EOF
|
|
||||||
|
|
||||||
- name: Add pwnkill alias to restart pwnagotchi with a signal
|
|
||||||
lineinfile:
|
|
||||||
dest: /home/pi/.bashrc
|
|
||||||
line: "\nalias pwnkill='sudo killall -USR1 pwnagotchi'"
|
|
||||||
insertafter: EOF
|
|
||||||
|
|
||||||
- name: add firmware packages to hold
|
- name: add firmware packages to hold
|
||||||
dpkg_selections:
|
dpkg_selections:
|
||||||
name: "{{ item }}"
|
name: "{{ item }}"
|
||||||
|
@ -1 +1 @@
|
|||||||
__version__ = '2.8.4'
|
__version__ = '2.8.8'
|
||||||
|
@ -254,7 +254,7 @@ class Agent(Client, Automata, AsyncAdvertiser, AsyncTrainer):
|
|||||||
txt = '%d (%d)' % (len(self._handshakes), tot)
|
txt = '%d (%d)' % (len(self._handshakes), tot)
|
||||||
|
|
||||||
if self._last_pwnd is not None:
|
if self._last_pwnd is not None:
|
||||||
txt += ' [%s]' % self._last_pwnd[:11] # So it doesn't overlap with fix_brcmfmac_plugin
|
txt += ' [%s]' % self._last_pwnd
|
||||||
|
|
||||||
self._view.set('shakes', txt)
|
self._view.set('shakes', txt)
|
||||||
|
|
||||||
|
@ -23,6 +23,12 @@ def load(config, agent, epoch, from_disk=True):
|
|||||||
from stable_baselines3 import A2C
|
from stable_baselines3 import A2C
|
||||||
logging.debug("[AI] A2C imported in %.2fs" % (time.time() - start))
|
logging.debug("[AI] A2C imported in %.2fs" % (time.time() - start))
|
||||||
|
|
||||||
|
# remove invalid ai.parameters leftover from tensor_flow, if present
|
||||||
|
for key in [ 'alpha', 'epsilon', 'lr_schedule' ]:
|
||||||
|
if key in config['params']:
|
||||||
|
logging.info("Removing legacy ai parameter %s" % key);
|
||||||
|
del config['params'][key]
|
||||||
|
|
||||||
start = time.time()
|
start = time.time()
|
||||||
from stable_baselines3.a2c import MlpPolicy
|
from stable_baselines3.a2c import MlpPolicy
|
||||||
logging.debug("[AI] MlpPolicy imported in %.2fs" % (time.time() - start))
|
logging.debug("[AI] MlpPolicy imported in %.2fs" % (time.time() - start))
|
||||||
|
@ -100,7 +100,6 @@ class Client(object):
|
|||||||
await asyncio.sleep(sleep_time)
|
await asyncio.sleep(sleep_time)
|
||||||
continue
|
continue
|
||||||
except OSError:
|
except OSError:
|
||||||
sleep_time = min_sleep + max_sleep * random.random()
|
|
||||||
logging.warning('connection to the bettercap endpoint failed...')
|
logging.warning('connection to the bettercap endpoint failed...')
|
||||||
pwnagotchi.restart("AUTO")
|
pwnagotchi.restart("AUTO")
|
||||||
|
|
||||||
|
@ -18,10 +18,6 @@ main.custom_plugin_repos = [
|
|||||||
|
|
||||||
main.custom_plugins = "/usr/local/share/pwnagotchi/custom-plugins/"
|
main.custom_plugins = "/usr/local/share/pwnagotchi/custom-plugins/"
|
||||||
|
|
||||||
main.plugins.aircrackonly.enabled = true
|
|
||||||
|
|
||||||
main.plugins.hashie.enabled = true
|
|
||||||
|
|
||||||
main.plugins.auto-update.enabled = true
|
main.plugins.auto-update.enabled = true
|
||||||
main.plugins.auto-update.install = true
|
main.plugins.auto-update.install = true
|
||||||
main.plugins.auto-update.interval = 1
|
main.plugins.auto-update.interval = 1
|
||||||
@ -36,7 +32,7 @@ main.plugins.bt-tether.devices.android-phone.netmask = 24
|
|||||||
main.plugins.bt-tether.devices.android-phone.interval = 1
|
main.plugins.bt-tether.devices.android-phone.interval = 1
|
||||||
main.plugins.bt-tether.devices.android-phone.scantime = 10
|
main.plugins.bt-tether.devices.android-phone.scantime = 10
|
||||||
main.plugins.bt-tether.devices.android-phone.max_tries = 10
|
main.plugins.bt-tether.devices.android-phone.max_tries = 10
|
||||||
main.plugins.bt-tether.devices.android-phone.share_internet = false
|
main.plugins.bt-tether.devices.android-phone.share_internet = true
|
||||||
main.plugins.bt-tether.devices.android-phone.priority = 1
|
main.plugins.bt-tether.devices.android-phone.priority = 1
|
||||||
|
|
||||||
main.plugins.bt-tether.devices.ios-phone.enabled = false
|
main.plugins.bt-tether.devices.ios-phone.enabled = false
|
||||||
@ -47,7 +43,7 @@ main.plugins.bt-tether.devices.ios-phone.netmask = 24
|
|||||||
main.plugins.bt-tether.devices.ios-phone.interval = 5
|
main.plugins.bt-tether.devices.ios-phone.interval = 5
|
||||||
main.plugins.bt-tether.devices.ios-phone.scantime = 20
|
main.plugins.bt-tether.devices.ios-phone.scantime = 20
|
||||||
main.plugins.bt-tether.devices.ios-phone.max_tries = 0
|
main.plugins.bt-tether.devices.ios-phone.max_tries = 0
|
||||||
main.plugins.bt-tether.devices.ios-phone.share_internet = false
|
main.plugins.bt-tether.devices.ios-phone.share_internet = true
|
||||||
main.plugins.bt-tether.devices.ios-phone.priority = 999
|
main.plugins.bt-tether.devices.ios-phone.priority = 999
|
||||||
|
|
||||||
main.plugins.fix_services.enabled = true
|
main.plugins.fix_services.enabled = true
|
||||||
@ -116,9 +112,7 @@ main.mon_stop_cmd = "/usr/bin/monstop"
|
|||||||
main.mon_max_blind_epochs = 50
|
main.mon_max_blind_epochs = 50
|
||||||
main.no_restart = false
|
main.no_restart = false
|
||||||
|
|
||||||
main.filter = ""
|
main.log.path = "/etc/pwnagotchi/log/pwnagotchi.log"
|
||||||
|
|
||||||
main.log.path = "/home/pi/logs/pwnagotchi.log"
|
|
||||||
main.log.rotation.enabled = true
|
main.log.rotation.enabled = true
|
||||||
main.log.rotation.size = "10M"
|
main.log.rotation.size = "10M"
|
||||||
|
|
||||||
@ -158,6 +152,8 @@ personality.throttle_d = 0.9
|
|||||||
|
|
||||||
personality.clear_on_exit = true # clear display when shutting down cleanly
|
personality.clear_on_exit = true # clear display when shutting down cleanly
|
||||||
|
|
||||||
|
ui.invert = false # false = black background, true = white background
|
||||||
|
|
||||||
ui.fps = 0.0
|
ui.fps = 0.0
|
||||||
ui.font.name = "DejaVuSansMono" # for japanese: fonts-japanese-gothic
|
ui.font.name = "DejaVuSansMono" # for japanese: fonts-japanese-gothic
|
||||||
ui.font.size_offset = 0 # will be added to the font size
|
ui.font.size_offset = 0 # will be added to the font size
|
||||||
@ -187,6 +183,9 @@ ui.faces.debug = "(#__#)"
|
|||||||
ui.faces.upload = "(1__0)"
|
ui.faces.upload = "(1__0)"
|
||||||
ui.faces.upload1 = "(1__1)"
|
ui.faces.upload1 = "(1__1)"
|
||||||
ui.faces.upload2 = "(0__1)"
|
ui.faces.upload2 = "(0__1)"
|
||||||
|
ui.faces.png = false
|
||||||
|
ui.faces.position_x = 0
|
||||||
|
ui.faces.position_y = 34
|
||||||
|
|
||||||
ui.web.enabled = true
|
ui.web.enabled = true
|
||||||
ui.web.address = "::" # listening on both ipv4 and ipv6 - switch to 0.0.0.0 to listen on just ipv4
|
ui.web.address = "::" # listening on both ipv4 and ipv6 - switch to 0.0.0.0 to listen on just ipv4
|
||||||
@ -218,7 +217,7 @@ bettercap.silence = [
|
|||||||
|
|
||||||
fs.memory.enabled = true
|
fs.memory.enabled = true
|
||||||
fs.memory.mounts.log.enabled = true
|
fs.memory.mounts.log.enabled = true
|
||||||
fs.memory.mounts.log.mount = "/home/pi/logs"
|
fs.memory.mounts.log.mount = "/etc/pwnagotchi/log/"
|
||||||
fs.memory.mounts.log.size = "50M"
|
fs.memory.mounts.log.size = "50M"
|
||||||
fs.memory.mounts.log.sync = 60
|
fs.memory.mounts.log.sync = 60
|
||||||
fs.memory.mounts.log.zram = true
|
fs.memory.mounts.log.zram = true
|
||||||
|
@ -89,7 +89,7 @@ def update_data(last_session):
|
|||||||
'uname': subprocess.getoutput("uname -a"),
|
'uname': subprocess.getoutput("uname -a"),
|
||||||
'brain': brain,
|
'brain': brain,
|
||||||
'version': pwnagotchi.__version__,
|
'version': pwnagotchi.__version__,
|
||||||
'build': "Pwnagotchi-Torch by Jayofelony",
|
'build': "Pwnagotchi by Jayofelony",
|
||||||
'plugins': enabled,
|
'plugins': enabled,
|
||||||
'language': language,
|
'language': language,
|
||||||
'bettercap': subprocess.getoutput("bettercap -version").split(".\n\n")[1],
|
'bettercap': subprocess.getoutput("bettercap -version").split(".\n\n")[1],
|
||||||
|
0
pwnagotchi/locale/__init__.py
Normal file
0
pwnagotchi/locale/__init__.py
Normal file
Binary file not shown.
@ -36,18 +36,18 @@ msgid "The neural network is ready."
|
|||||||
msgstr "La red neuronal está lista."
|
msgstr "La red neuronal está lista."
|
||||||
|
|
||||||
msgid "Generating keys, do not turn off ..."
|
msgid "Generating keys, do not turn off ..."
|
||||||
msgstr ""
|
msgstr "Generando llaves, no me apagues ..."
|
||||||
|
|
||||||
#, python-brace-format
|
#, python-brace-format
|
||||||
msgid "Hey, channel {channel} is free! Your AP will say thanks."
|
msgid "Hey, channel {channel} is free! Your AP will say thanks."
|
||||||
msgstr "¡Oye, el canal {channel} está libre! Tu AP lo agradecerá."
|
msgstr "¡Oye, el canal {channel} está libre! Tu AP lo agradecerá."
|
||||||
|
|
||||||
msgid "Reading last session logs ..."
|
msgid "Reading last session logs ..."
|
||||||
msgstr ""
|
msgstr "Leyendo los logs de la última sesión ..."
|
||||||
|
|
||||||
#, python-brace-format
|
#, python-brace-format
|
||||||
msgid "Read {lines_so_far} log lines so far ..."
|
msgid "Read {lines_so_far} log lines so far ..."
|
||||||
msgstr ""
|
msgstr "Leyendo {lines_so_far} líneas de log hasta ahora ..."
|
||||||
|
|
||||||
msgid "I'm bored ..."
|
msgid "I'm bored ..."
|
||||||
msgstr "Estoy aburrido ..."
|
msgstr "Estoy aburrido ..."
|
||||||
@ -74,7 +74,7 @@ msgid "Leave me alone ..."
|
|||||||
msgstr "Me siento tan solo ..."
|
msgstr "Me siento tan solo ..."
|
||||||
|
|
||||||
msgid "I'm mad at you!"
|
msgid "I'm mad at you!"
|
||||||
msgstr ""
|
msgstr "Toy re enojado con vos!"
|
||||||
|
|
||||||
msgid "I'm living the life!"
|
msgid "I'm living the life!"
|
||||||
msgstr "¡Estoy viviendo la vida!"
|
msgstr "¡Estoy viviendo la vida!"
|
||||||
@ -97,11 +97,11 @@ msgstr "¡Hola {name}! Encantado de conocerte."
|
|||||||
|
|
||||||
#, python-brace-format
|
#, python-brace-format
|
||||||
msgid "Yo {name}! Sup?"
|
msgid "Yo {name}! Sup?"
|
||||||
msgstr ""
|
msgstr "Que onda {name}!?"
|
||||||
|
|
||||||
#, python-brace-format
|
#, python-brace-format
|
||||||
msgid "Hey {name} how are you doing?"
|
msgid "Hey {name} how are you doing?"
|
||||||
msgstr ""
|
msgstr "Eh!, ¿Que haces {name}?"
|
||||||
|
|
||||||
#, python-brace-format
|
#, python-brace-format
|
||||||
msgid "Unit {name} is nearby!"
|
msgid "Unit {name} is nearby!"
|
||||||
@ -127,10 +127,10 @@ msgid "Missed!"
|
|||||||
msgstr "¡Perdido!"
|
msgstr "¡Perdido!"
|
||||||
|
|
||||||
msgid "Good friends are a blessing!"
|
msgid "Good friends are a blessing!"
|
||||||
msgstr ""
|
msgstr "Lxs buenxs amigxs son una masa!"
|
||||||
|
|
||||||
msgid "I love my friends!"
|
msgid "I love my friends!"
|
||||||
msgstr ""
|
msgstr "¡Amo a mis amigxs!"
|
||||||
|
|
||||||
msgid "Nobody wants to play with me ..."
|
msgid "Nobody wants to play with me ..."
|
||||||
msgstr "Nadie quiere jugar conmigo ..."
|
msgstr "Nadie quiere jugar conmigo ..."
|
||||||
@ -143,7 +143,7 @@ msgstr "¡¿Dónde está todo el mundo?!"
|
|||||||
|
|
||||||
#, python-brace-format
|
#, python-brace-format
|
||||||
msgid "Napping for {secs}s ..."
|
msgid "Napping for {secs}s ..."
|
||||||
msgstr "Descansando durante {secs}s ..."
|
msgstr "Descansando por {secs}s ..."
|
||||||
|
|
||||||
msgid "Zzzzz"
|
msgid "Zzzzz"
|
||||||
msgstr "Zzzzz"
|
msgstr "Zzzzz"
|
||||||
@ -203,7 +203,7 @@ msgstr "Oops, algo salió mal ... Reiniciando ..."
|
|||||||
|
|
||||||
#, python-brace-format
|
#, python-brace-format
|
||||||
msgid "Uploading data to {to} ..."
|
msgid "Uploading data to {to} ..."
|
||||||
msgstr ""
|
msgstr "Subiendo data a {to} ..."
|
||||||
|
|
||||||
#, python-brace-format
|
#, python-brace-format
|
||||||
msgid "Downloading from {name} ..."
|
msgid "Downloading from {name} ..."
|
||||||
|
Binary file not shown.
@ -248,7 +248,7 @@ msgid "minutes"
|
|||||||
msgstr "minuten"
|
msgstr "minuten"
|
||||||
|
|
||||||
msgid "seconds"
|
msgid "seconds"
|
||||||
msgstr "seconds"
|
msgstr "sekondes"
|
||||||
|
|
||||||
msgid "hour"
|
msgid "hour"
|
||||||
msgstr "oere"
|
msgstr "oere"
|
||||||
|
@ -8,7 +8,7 @@ msgid ""
|
|||||||
msgstr ""
|
msgstr ""
|
||||||
"Project-Id-Version: PACKAGE VERSION\n"
|
"Project-Id-Version: PACKAGE VERSION\n"
|
||||||
"Report-Msgid-Bugs-To: \n"
|
"Report-Msgid-Bugs-To: \n"
|
||||||
"POT-Creation-Date: 2024-01-25 23:40+0100\n"
|
"POT-Creation-Date: 2024-02-16 15:26-0300\n"
|
||||||
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
|
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
|
||||||
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
|
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
|
||||||
"Language-Team: LANGUAGE <LL@li.org>\n"
|
"Language-Team: LANGUAGE <LL@li.org>\n"
|
||||||
|
@ -199,6 +199,9 @@ def list_plugins(args, config, pattern='*'):
|
|||||||
available_not_installed = set(available.keys()) - set(installed.keys())
|
available_not_installed = set(available.keys()) - set(installed.keys())
|
||||||
|
|
||||||
max_len_list = available_and_installed if args.installed else available_not_installed
|
max_len_list = available_and_installed if args.installed else available_not_installed
|
||||||
|
if not max_len_list:
|
||||||
|
print('Maybe try: sudo pwnagotchi plugins update')
|
||||||
|
return 1
|
||||||
max_len = max(map(len, max_len_list))
|
max_len = max(map(len, max_len_list))
|
||||||
header = line.format(name='Plugin', width=max_len, version='Version', enabled='Active', status='Status')
|
header = line.format(name='Plugin', width=max_len, version='Version', enabled='Active', status='Status')
|
||||||
line_length = max(max_len, len('Plugin')) + len(header) - len('Plugin') - 12 # lol
|
line_length = max(max_len, len('Plugin')) + len(header) - len('Plugin') - 12 # lol
|
||||||
@ -239,7 +242,7 @@ def list_plugins(args, config, pattern='*'):
|
|||||||
print('-' * line_length)
|
print('-' * line_length)
|
||||||
|
|
||||||
if not found:
|
if not found:
|
||||||
logging.info('Maybe try: pwnagotchi plugins update')
|
print('Maybe try: sudo pwnagotchi plugins update')
|
||||||
return 1
|
return 1
|
||||||
return 0
|
return 0
|
||||||
|
|
||||||
|
@ -1,73 +0,0 @@
|
|||||||
import pwnagotchi.plugins as plugins
|
|
||||||
|
|
||||||
import logging
|
|
||||||
import subprocess
|
|
||||||
import string
|
|
||||||
import os
|
|
||||||
|
|
||||||
'''
|
|
||||||
Aircrack-ng needed, to install:
|
|
||||||
> apt-get install aircrack-ng
|
|
||||||
'''
|
|
||||||
|
|
||||||
|
|
||||||
class AircrackOnly(plugins.Plugin):
|
|
||||||
__author__ = 'pwnagotchi [at] rossmarks [dot] uk'
|
|
||||||
__version__ = '1.0.1'
|
|
||||||
__license__ = 'GPL3'
|
|
||||||
__description__ = 'confirm pcap contains handshake/PMKID or delete it'
|
|
||||||
|
|
||||||
def __init__(self):
|
|
||||||
self.text_to_set = ""
|
|
||||||
self.options = dict()
|
|
||||||
|
|
||||||
def on_ready(self):
|
|
||||||
return
|
|
||||||
|
|
||||||
def on_loaded(self):
|
|
||||||
logging.info("aircrackonly plugin loaded")
|
|
||||||
|
|
||||||
if 'face' not in self.options:
|
|
||||||
self.options['face'] = '(>.<)'
|
|
||||||
|
|
||||||
check = subprocess.run(
|
|
||||||
'/usr/bin/dpkg -l aircrack-ng | grep aircrack-ng | awk \'{print $2, $3}\'', shell=True,
|
|
||||||
stdout=subprocess.PIPE)
|
|
||||||
check = check.stdout.decode('utf-8').strip()
|
|
||||||
if check != "aircrack-ng <none>":
|
|
||||||
logging.info("aircrackonly: Found " + check)
|
|
||||||
else:
|
|
||||||
logging.warning("aircrack-ng is not installed!")
|
|
||||||
|
|
||||||
def on_handshake(self, agent, filename, access_point, client_station):
|
|
||||||
display = agent.view()
|
|
||||||
to_delete = 0
|
|
||||||
handshake_found = 0
|
|
||||||
|
|
||||||
result = subprocess.run(('/usr/bin/aircrack-ng ' + filename + ' | grep "1 handshake" | awk \'{print $2}\''),
|
|
||||||
shell=True, stdout=subprocess.PIPE)
|
|
||||||
result = result.stdout.decode('utf-8').translate({ord(c): None for c in string.whitespace})
|
|
||||||
if result:
|
|
||||||
handshake_found = 1
|
|
||||||
logging.info("[AircrackOnly] contains handshake")
|
|
||||||
|
|
||||||
if handshake_found == 0:
|
|
||||||
result = subprocess.run(('/usr/bin/aircrack-ng ' + filename + ' | grep "PMKID" | awk \'{print $2}\''),
|
|
||||||
shell=True, stdout=subprocess.PIPE)
|
|
||||||
result = result.stdout.decode('utf-8').translate({ord(c): None for c in string.whitespace})
|
|
||||||
if result:
|
|
||||||
logging.info("[AircrackOnly] contains PMKID")
|
|
||||||
else:
|
|
||||||
to_delete = 1
|
|
||||||
|
|
||||||
if to_delete == 1:
|
|
||||||
os.remove(filename)
|
|
||||||
self.text_to_set = "Removed an uncrackable pcap"
|
|
||||||
logging.warning("Removed uncrackable pcap " + filename)
|
|
||||||
display.update(force=True)
|
|
||||||
|
|
||||||
def on_ui_update(self, ui):
|
|
||||||
if self.text_to_set:
|
|
||||||
ui.set('face', self.options['face'])
|
|
||||||
ui.set('status', self.text_to_set)
|
|
||||||
self.text_to_set = ""
|
|
@ -28,7 +28,8 @@ def check(version, repo, native=True):
|
|||||||
resp = requests.get("https://api.github.com/repos/%s/releases/latest" % repo)
|
resp = requests.get("https://api.github.com/repos/%s/releases/latest" % repo)
|
||||||
latest = resp.json()
|
latest = resp.json()
|
||||||
info['available'] = latest_ver = latest['tag_name'].replace('v', '')
|
info['available'] = latest_ver = latest['tag_name'].replace('v', '')
|
||||||
is_arm64 = info['arch'].startswith('aarch')
|
is_armhf = info['arch'].startswith('arm')
|
||||||
|
is_aarch = info['arch'].startswith('aarch')
|
||||||
|
|
||||||
local = version_to_tuple(info['current'])
|
local = version_to_tuple(info['current'])
|
||||||
remote = version_to_tuple(latest_ver)
|
remote = version_to_tuple(latest_ver)
|
||||||
@ -36,12 +37,20 @@ def check(version, repo, native=True):
|
|||||||
if not native:
|
if not native:
|
||||||
info['url'] = "https://github.com/%s/archive/%s.zip" % (repo, latest['tag_name'])
|
info['url'] = "https://github.com/%s/archive/%s.zip" % (repo, latest['tag_name'])
|
||||||
else:
|
else:
|
||||||
if is_arm64:
|
if is_armhf:
|
||||||
# check if this release is compatible with aarch64
|
# check if this release is compatible with armhf
|
||||||
for asset in latest['assets']:
|
for asset in latest['assets']:
|
||||||
download_url = asset['browser_download_url']
|
download_url = asset['browser_download_url']
|
||||||
if (download_url.endswith('.zip') and
|
if (download_url.endswith('.zip') and
|
||||||
(info['arch'] in download_url or (is_arm64 and 'aarch64' in download_url))):
|
(info['arch'] in download_url or (is_armhf and 'armhf' in download_url))):
|
||||||
|
info['url'] = download_url
|
||||||
|
break
|
||||||
|
elif is_aarch:
|
||||||
|
# check if this release is compatible with arm64/aarch64
|
||||||
|
for asset in latest['assets']:
|
||||||
|
download_url = asset['browser_download_url']
|
||||||
|
if (download_url.endswith('.zip') and
|
||||||
|
(info['arch'] in download_url or (is_aarch and 'aarch' in download_url))):
|
||||||
info['url'] = download_url
|
info['url'] = download_url
|
||||||
break
|
break
|
||||||
|
|
||||||
@ -189,7 +198,7 @@ class AutoUpdate(plugins.Plugin):
|
|||||||
to_check = [
|
to_check = [
|
||||||
('jayofelony/bettercap', parse_version('bettercap -version'), True, 'bettercap'),
|
('jayofelony/bettercap', parse_version('bettercap -version'), True, 'bettercap'),
|
||||||
('jayofelony/pwngrid', parse_version('pwngrid -version'), True, 'pwngrid-peer'),
|
('jayofelony/pwngrid', parse_version('pwngrid -version'), True, 'pwngrid-peer'),
|
||||||
('jayofelony/pwnagotchi-bookworm', pwnagotchi.__version__, False, 'pwnagotchi')
|
('jayofelony/pwnagotchi', pwnagotchi.__version__, False, 'pwnagotchi')
|
||||||
]
|
]
|
||||||
|
|
||||||
for repo, local_version, is_native, svc_name in to_check:
|
for repo, local_version, is_native, svc_name in to_check:
|
||||||
|
@ -4,6 +4,8 @@ import subprocess
|
|||||||
import time
|
import time
|
||||||
import random
|
import random
|
||||||
from io import TextIOWrapper
|
from io import TextIOWrapper
|
||||||
|
import os
|
||||||
|
import platform
|
||||||
|
|
||||||
import pwnagotchi
|
import pwnagotchi
|
||||||
from pwnagotchi import plugins
|
from pwnagotchi import plugins
|
||||||
@ -11,6 +13,10 @@ from pwnagotchi import plugins
|
|||||||
import pwnagotchi.ui.faces as faces
|
import pwnagotchi.ui.faces as faces
|
||||||
from pwnagotchi.bettercap import Client
|
from pwnagotchi.bettercap import Client
|
||||||
|
|
||||||
|
from pwnagotchi.ui.components import Text
|
||||||
|
from pwnagotchi.ui.view import BLACK
|
||||||
|
import pwnagotchi.ui.fonts as fonts
|
||||||
|
|
||||||
|
|
||||||
class FixServices(plugins.Plugin):
|
class FixServices(plugins.Plugin):
|
||||||
__author__ = 'jayofelony'
|
__author__ = 'jayofelony'
|
||||||
@ -21,19 +27,18 @@ class FixServices(plugins.Plugin):
|
|||||||
__help__ = """
|
__help__ = """
|
||||||
Reload brcmfmac module when blindbug is detected, instead of rebooting. Adapted from WATCHDOG.
|
Reload brcmfmac module when blindbug is detected, instead of rebooting. Adapted from WATCHDOG.
|
||||||
"""
|
"""
|
||||||
__defaults__ = {
|
|
||||||
'enabled': True,
|
|
||||||
}
|
|
||||||
|
|
||||||
def __init__(self):
|
def __init__(self):
|
||||||
self.options = dict()
|
self.options = dict()
|
||||||
self.pattern1 = re.compile(r'wifi error while hopping to channel')
|
self.pattern = re.compile(r'brcmf_cfg80211_nexmon_set_channel.*?Set Channel failed')
|
||||||
self.pattern2 = re.compile(r'Firmware has halted or crashed')
|
self.pattern2 = re.compile(r'wifi error while hopping to channel')
|
||||||
self.pattern3 = re.compile(r'error 400: could not find interface wlan0mon')
|
self.pattern3 = re.compile(r'Firmware has halted or crashed')
|
||||||
|
self.pattern4 = re.compile(r'error 400: could not find interface wlan0mon')
|
||||||
|
self.pattern5 = re.compile(r'fatal error: concurrent map iteration and map write')
|
||||||
|
self.pattern6 = re.compile(r'panic: runtime error')
|
||||||
self.isReloadingMon = False
|
self.isReloadingMon = False
|
||||||
self.connection = None
|
self.connection = None
|
||||||
self.LASTTRY = 0
|
self.LASTTRY = 0
|
||||||
self._count = 0
|
|
||||||
|
|
||||||
def on_loaded(self):
|
def on_loaded(self):
|
||||||
"""
|
"""
|
||||||
@ -42,14 +47,27 @@ class FixServices(plugins.Plugin):
|
|||||||
logging.info("[Fix_Services] plugin loaded.")
|
logging.info("[Fix_Services] plugin loaded.")
|
||||||
|
|
||||||
def on_ready(self, agent):
|
def on_ready(self, agent):
|
||||||
last_lines = self.get_last_lines('journalctl', ['-n10', '-k'], 10)
|
last_lines = ''.join(list(TextIOWrapper(subprocess.Popen(['journalctl', '-n10', '-k'],
|
||||||
|
stdout=subprocess.PIPE).stdout))[-10:])
|
||||||
try:
|
try:
|
||||||
cmd_output = subprocess.check_output("ip link show wlan0mon", shell=True)
|
cmd_output = subprocess.check_output("ip link show wlan0mon", shell=True)
|
||||||
logging.debug("[Fix_Services ip link show wlan0mon]: %s" % repr(cmd_output))
|
logging.debug("[Fix_Services ip link show wlan0mon]: %s" % repr(cmd_output))
|
||||||
if ",UP," in str(cmd_output):
|
if ",UP," in str(cmd_output):
|
||||||
logging.info("wlan0mon is up.")
|
logging.debug("wlan0mon is up.")
|
||||||
|
|
||||||
logging.info("[Fix_Services] Logs look good!")
|
if len(self.pattern.findall(last_lines)) >= 3:
|
||||||
|
if hasattr(agent, 'view'):
|
||||||
|
display = agent.view()
|
||||||
|
display.set('status', 'Blind-Bug detected. Restarting.')
|
||||||
|
display.update(force=True)
|
||||||
|
logging.debug('[Fix_Services] Blind-Bug detected. Restarting.')
|
||||||
|
try:
|
||||||
|
self._tryTurningItOffAndOnAgain(agent)
|
||||||
|
except Exception as err:
|
||||||
|
logging.warning("[Fix_Services turnOffAndOn] %s" % repr(err))
|
||||||
|
|
||||||
|
else:
|
||||||
|
logging.debug("[Fix_Services] Logs look good!")
|
||||||
|
|
||||||
except Exception as err:
|
except Exception as err:
|
||||||
logging.error("[Fix_Services ip link show wlan0mon]: %s" % repr(err))
|
logging.error("[Fix_Services ip link show wlan0mon]: %s" % repr(err))
|
||||||
@ -63,12 +81,12 @@ class FixServices(plugins.Plugin):
|
|||||||
# apparently this only gets messages from bettercap going to syslog, not from syslog
|
# apparently this only gets messages from bettercap going to syslog, not from syslog
|
||||||
def on_bcap_sys_log(self, agent, event):
|
def on_bcap_sys_log(self, agent, event):
|
||||||
if re.search('wifi error while hopping to channel', event['data']['Message']):
|
if re.search('wifi error while hopping to channel', event['data']['Message']):
|
||||||
logging.info("[Fix_Services]SYSLOG MATCH: %s" % event['data']['Message'])
|
logging.debug("[Fix_Services]SYSLOG MATCH: %s" % event['data']['Message'])
|
||||||
logging.info("[Fix_Services]**** restarting wifi.recon")
|
logging.debug("[Fix_Services]**** restarting wifi.recon")
|
||||||
try:
|
try:
|
||||||
result = agent.run("wifi.recon off; wifi.recon on")
|
result = agent.run("wifi.recon off; wifi.recon on")
|
||||||
if result["success"]:
|
if result["success"]:
|
||||||
logging.info("[Fix_Services] wifi.recon flip: success!")
|
logging.debug("[Fix_Services] wifi.recon flip: success!")
|
||||||
if hasattr(agent, 'view'):
|
if hasattr(agent, 'view'):
|
||||||
display = agent.view()
|
display = agent.view()
|
||||||
if display:
|
if display:
|
||||||
@ -82,23 +100,14 @@ class FixServices(plugins.Plugin):
|
|||||||
logging.error("[Fix_Services]SYSLOG wifi.recon flip fail: %s" % err)
|
logging.error("[Fix_Services]SYSLOG wifi.recon flip fail: %s" % err)
|
||||||
self._tryTurningItOffAndOnAgain(agent)
|
self._tryTurningItOffAndOnAgain(agent)
|
||||||
|
|
||||||
def get_last_lines(self, command, args, n):
|
|
||||||
try:
|
|
||||||
process = subprocess.Popen([command] + args, stdout=subprocess.PIPE)
|
|
||||||
output = TextIOWrapper(process.stdout)
|
|
||||||
lines = output.readlines()
|
|
||||||
last_n_lines = ''.join(lines[-n:])
|
|
||||||
return last_n_lines
|
|
||||||
except Exception as e:
|
|
||||||
print(f"Error occurred: {e}")
|
|
||||||
return None
|
|
||||||
|
|
||||||
def on_epoch(self, agent, epoch, epoch_data):
|
def on_epoch(self, agent, epoch, epoch_data):
|
||||||
|
last_lines = ''.join(list(TextIOWrapper(subprocess.Popen(['journalctl', '-n10', '-k'],
|
||||||
last_lines = self.get_last_lines('journalctl', ['-n10', '-k'], 10)
|
stdout=subprocess.PIPE).stdout))[-10:])
|
||||||
other_last_lines = self.get_last_lines('journalctl', ['-n10'], 10)
|
other_last_lines = ''.join(list(TextIOWrapper(subprocess.Popen(['journalctl', '-n10'],
|
||||||
other_other_last_lines = self.get_last_lines('tail', ['-n10', '/home/pi/logs/pwnagotchi.log'], 10)
|
stdout=subprocess.PIPE).stdout))[-10:])
|
||||||
|
other_other_last_lines = ''.join(
|
||||||
|
list(TextIOWrapper(subprocess.Popen(['tail', '-n10', '/var/log/pwnagotchi.log'],
|
||||||
|
stdout=subprocess.PIPE).stdout))[-10:])
|
||||||
# don't check if we ran a reset recently
|
# don't check if we ran a reset recently
|
||||||
logging.debug("[Fix_Services]**** epoch")
|
logging.debug("[Fix_Services]**** epoch")
|
||||||
if time.time() - self.LASTTRY > 180:
|
if time.time() - self.LASTTRY > 180:
|
||||||
@ -108,32 +117,47 @@ class FixServices(plugins.Plugin):
|
|||||||
logging.debug("[Fix_Services]**** checking")
|
logging.debug("[Fix_Services]**** checking")
|
||||||
|
|
||||||
# Look for pattern 1
|
# Look for pattern 1
|
||||||
if len(self.pattern1.findall(other_last_lines)) >= 5:
|
if platform.machine().startswith('arm'):
|
||||||
logging.debug("[Fix_Services]**** Should trigger a reload of the wlan0mon device:\n%s" % last_lines)
|
if len(self.pattern.findall(last_lines)) >= 3:
|
||||||
if hasattr(agent, 'view'):
|
logging.debug("[Fix_Services]**** Should trigger a reload of the wlan0mon device:\n%s" % last_lines)
|
||||||
display = agent.view()
|
if hasattr(agent, 'view'):
|
||||||
display.set('status', 'Wifi channel stuck. Restarting recon.')
|
display = agent.view()
|
||||||
display.update(force=True)
|
display.set('status', 'Blind-Bug detected. Restarting.')
|
||||||
logging.info('[Fix_Services] Wifi channel stuck. Restarting recon.')
|
display.update(force=True)
|
||||||
|
logging.debug('[Fix_Services] Blind-Bug detected. Restarting.')
|
||||||
try:
|
try:
|
||||||
result = agent.run("wifi.recon off; wifi.recon on")
|
self._tryTurningItOffAndOnAgain(agent)
|
||||||
if result["success"]:
|
except Exception as err:
|
||||||
logging.info("[Fix_Services] wifi.recon flip: success!")
|
logging.warning("[Fix_Services] TTOAOA: %s" % repr(err))
|
||||||
if display:
|
|
||||||
display.update(force=True, new_data={"status": "Wifi recon flipped!",
|
|
||||||
"face": faces.COOL})
|
|
||||||
else:
|
|
||||||
print("Wifi recon flipped\nthat was easy!")
|
|
||||||
else:
|
|
||||||
logging.warning("[Fix_Services] wifi.recon flip: FAILED: %s" % repr(result))
|
|
||||||
|
|
||||||
except Exception as err:
|
|
||||||
logging.error("[Fix_Services wifi.recon flip] %s" % repr(err))
|
|
||||||
|
|
||||||
# Look for pattern 2
|
# Look for pattern 2
|
||||||
elif len(self.pattern2.findall(other_last_lines)) >= 1:
|
elif len(self.pattern2.findall(other_last_lines)) >= 5:
|
||||||
logging.info("[Fix_Services] Firmware has halted or crashed. Restarting wlan0mon.")
|
if platform.machine().startswith('arm'):
|
||||||
|
logging.debug("[Fix_Services]**** Should trigger a reload of the wlan0mon device:\n%s" % last_lines)
|
||||||
|
if hasattr(agent, 'view'):
|
||||||
|
display = agent.view()
|
||||||
|
display.set('status', 'Wifi channel stuck. Restarting recon.')
|
||||||
|
display.update(force=True)
|
||||||
|
logging.debug('[Fix_Services] Wifi channel stuck. Restarting recon.')
|
||||||
|
|
||||||
|
try:
|
||||||
|
result = agent.run("wifi.recon off; wifi.recon on")
|
||||||
|
if result["success"]:
|
||||||
|
logging.debug("[Fix_Services] wifi.recon flip: success!")
|
||||||
|
if display:
|
||||||
|
display.update(force=True, new_data={"status": "Wifi recon flipped!",
|
||||||
|
"face": faces.COOL})
|
||||||
|
else:
|
||||||
|
print("Wifi recon flipped\nthat was easy!")
|
||||||
|
else:
|
||||||
|
logging.warning("[Fix_Services] wifi.recon flip: FAILED: %s" % repr(result))
|
||||||
|
|
||||||
|
except Exception as err:
|
||||||
|
logging.error("[Fix_Services wifi.recon flip] %s" % repr(err))
|
||||||
|
|
||||||
|
# Look for pattern 3
|
||||||
|
elif len(self.pattern3.findall(other_last_lines)) >= 1:
|
||||||
|
logging.debug("[Fix_Services] Firmware has halted or crashed. Restarting wlan0mon.")
|
||||||
if hasattr(agent, 'view'):
|
if hasattr(agent, 'view'):
|
||||||
display = agent.view()
|
display = agent.view()
|
||||||
display.set('status', 'Firmware has halted or crashed. Restarting wlan0mon.')
|
display.set('status', 'Firmware has halted or crashed. Restarting wlan0mon.')
|
||||||
@ -145,9 +169,9 @@ class FixServices(plugins.Plugin):
|
|||||||
except Exception as err:
|
except Exception as err:
|
||||||
logging.error("[Fix_Services monstart]: %s" % repr(err))
|
logging.error("[Fix_Services monstart]: %s" % repr(err))
|
||||||
|
|
||||||
# Look for pattern 3
|
# Look for pattern 4
|
||||||
elif len(self.pattern3.findall(other_other_last_lines)) >= 3:
|
elif len(self.pattern4.findall(other_other_last_lines)) >= 3:
|
||||||
logging.info("[Fix_Services] wlan0 is down!")
|
logging.debug("[Fix_Services] wlan0 is down!")
|
||||||
if hasattr(agent, 'view'):
|
if hasattr(agent, 'view'):
|
||||||
display = agent.view()
|
display = agent.view()
|
||||||
display.set('status', 'Restarting wlan0 now!')
|
display.set('status', 'Restarting wlan0 now!')
|
||||||
@ -159,6 +183,25 @@ class FixServices(plugins.Plugin):
|
|||||||
except Exception as err:
|
except Exception as err:
|
||||||
logging.error("[Fix_Services monstart]: %s" % repr(err))
|
logging.error("[Fix_Services monstart]: %s" % repr(err))
|
||||||
|
|
||||||
|
# Look for pattern 5
|
||||||
|
elif len(self.pattern5.findall(other_other_last_lines)) >= 1:
|
||||||
|
logging.debug("[Fix_Services] Bettercap has crashed!")
|
||||||
|
if hasattr(agent, 'view'):
|
||||||
|
display = agent.view()
|
||||||
|
display.set('status', 'Restarting pwnagotchi!')
|
||||||
|
display.update(force=True)
|
||||||
|
os.system("systemctl restart bettercap")
|
||||||
|
pwnagotchi.restart("AUTO")
|
||||||
|
|
||||||
|
# Look for pattern 6
|
||||||
|
elif len(self.pattern6.findall(other_other_last_lines)) >= 1:
|
||||||
|
logging.debug("[Fix_Services] Bettercap has crashed!")
|
||||||
|
if hasattr(agent, 'view'):
|
||||||
|
display = agent.view()
|
||||||
|
display.set('status', 'Restarting pwnagotchi!')
|
||||||
|
display.update(force=True)
|
||||||
|
os.system("systemctl restart bettercap")
|
||||||
|
pwnagotchi.restart("AUTO")
|
||||||
else:
|
else:
|
||||||
print("logs look good")
|
print("logs look good")
|
||||||
|
|
||||||
@ -171,7 +214,7 @@ class FixServices(plugins.Plugin):
|
|||||||
elif level == "debug":
|
elif level == "debug":
|
||||||
logging.debug(message)
|
logging.debug(message)
|
||||||
else:
|
else:
|
||||||
logging.info(message)
|
logging.debug(message)
|
||||||
|
|
||||||
if ui:
|
if ui:
|
||||||
ui.update(force=force, new_data=displayData)
|
ui.update(force=force, new_data=displayData)
|
||||||
@ -186,7 +229,7 @@ class FixServices(plugins.Plugin):
|
|||||||
# avoid overlapping restarts, but allow it if it's been a while
|
# avoid overlapping restarts, but allow it if it's been a while
|
||||||
# (in case the last attempt failed before resetting "isReloadingMon")
|
# (in case the last attempt failed before resetting "isReloadingMon")
|
||||||
if self.isReloadingMon and (time.time() - self.LASTTRY) < 180:
|
if self.isReloadingMon and (time.time() - self.LASTTRY) < 180:
|
||||||
logging.info("[Fix_Services] Duplicate attempt ignored")
|
logging.debug("[Fix_Services] Duplicate attempt ignored")
|
||||||
else:
|
else:
|
||||||
self.isReloadingMon = True
|
self.isReloadingMon = True
|
||||||
self.LASTTRY = time.time()
|
self.LASTTRY = time.time()
|
||||||
@ -213,7 +256,7 @@ class FixServices(plugins.Plugin):
|
|||||||
cmd_output = subprocess.check_output("ip link show wlan0mon", shell=True)
|
cmd_output = subprocess.check_output("ip link show wlan0mon", shell=True)
|
||||||
logging.debug("[Fix_Services ip link show wlan0mon]: %s" % repr(cmd_output))
|
logging.debug("[Fix_Services ip link show wlan0mon]: %s" % repr(cmd_output))
|
||||||
if ",UP," in str(cmd_output):
|
if ",UP," in str(cmd_output):
|
||||||
logging.info("wlan0mon is up. Skip reset?")
|
logging.debug("wlan0mon is up. Skip reset?")
|
||||||
# not reliable, so don't skip just yet
|
# not reliable, so don't skip just yet
|
||||||
# print("wlan0mon is up. Skipping reset.")
|
# print("wlan0mon is up. Skipping reset.")
|
||||||
# self.isReloadingMon = False
|
# self.isReloadingMon = False
|
||||||
@ -234,7 +277,7 @@ class FixServices(plugins.Plugin):
|
|||||||
except Exception as err:
|
except Exception as err:
|
||||||
logging.error("[Fix_Services wifi.recon off] error %s" % (repr(err)))
|
logging.error("[Fix_Services wifi.recon off] error %s" % (repr(err)))
|
||||||
|
|
||||||
logging.info("[Fix_Services] recon paused. Now trying wlan0mon reload")
|
logging.debug("[Fix_Services] recon paused. Now trying wlan0mon reload")
|
||||||
|
|
||||||
try:
|
try:
|
||||||
cmd_output = subprocess.check_output("monstop", shell=True)
|
cmd_output = subprocess.check_output("monstop", shell=True)
|
||||||
@ -250,14 +293,13 @@ class FixServices(plugins.Plugin):
|
|||||||
#
|
#
|
||||||
# Future: while "not fixed yet": blah blah blah. if "max_attemts", then reboot like the old days
|
# Future: while "not fixed yet": blah blah blah. if "max_attemts", then reboot like the old days
|
||||||
#
|
#
|
||||||
tries = 0
|
tries = 1
|
||||||
while tries < 3:
|
while tries < 3:
|
||||||
try:
|
try:
|
||||||
# unload the module
|
# unload the module
|
||||||
cmd_output = subprocess.check_output("sudo modprobe -r brcmfmac", shell=True)
|
cmd_output = subprocess.check_output("sudo modprobe -r brcmfmac", shell=True)
|
||||||
self.logPrintView("info", "[Fix_Services] unloaded brcmfmac", display,
|
self.logPrintView("info", "[Fix_Services] unloaded brcmfmac", display,
|
||||||
{"status": "Turning it off #%s" % tries, "face": faces.SMART})
|
{"status": "Turning it off #%s" % tries, "face": faces.SMART})
|
||||||
time.sleep(1 + tries)
|
|
||||||
|
|
||||||
# reload the module
|
# reload the module
|
||||||
try:
|
try:
|
||||||
@ -265,27 +307,25 @@ class FixServices(plugins.Plugin):
|
|||||||
cmd_output = subprocess.check_output("sudo modprobe brcmfmac", shell=True)
|
cmd_output = subprocess.check_output("sudo modprobe brcmfmac", shell=True)
|
||||||
|
|
||||||
self.logPrintView("info", "[Fix_Services] reloaded brcmfmac")
|
self.logPrintView("info", "[Fix_Services] reloaded brcmfmac")
|
||||||
time.sleep(10 + 4 * tries) # give it some time for wlan device to stabilize, or whatever
|
|
||||||
|
|
||||||
# success! now make the mon0
|
# success! now make the mon0
|
||||||
try:
|
try:
|
||||||
cmd_output = subprocess.check_output("monstart", shell=True)
|
cmd_output = subprocess.check_output("monstart", shell=True)
|
||||||
self.logPrintView("info", "[Fix_Services interface add wlan0mon] worked #%s: %s"
|
self.logPrintView("info", "[Fix_Services interface add wlan0mon worked #%s: %s"
|
||||||
% (tries, cmd_output))
|
% (tries, cmd_output))
|
||||||
time.sleep(tries + 5)
|
|
||||||
try:
|
try:
|
||||||
# try accessing mon0 in bettercap
|
# try accessing mon0 in bettercap
|
||||||
result = connection.run("set wifi.interface wlan0mon")
|
result = connection.run("set wifi.interface wlan0mon")
|
||||||
if "success" in result:
|
if "success" in result:
|
||||||
logging.info("[Fix_Services set wifi.interface wlan0mon] worked!")
|
logging.debug("[Fix_Services set wifi.interface wlan0mon worked!")
|
||||||
self._count = self._count + 1
|
|
||||||
time.sleep(1)
|
|
||||||
# stop looping and get back to recon
|
# stop looping and get back to recon
|
||||||
break
|
break
|
||||||
else:
|
else:
|
||||||
logging.debug("[Fix_Services set wifi.interfaceface wlan0mon] failed? %s" % repr(result))
|
logging.debug(
|
||||||
|
"[Fix_Services set wifi.interfaceface wlan0mon] failed? %s" % repr(result))
|
||||||
except Exception as err:
|
except Exception as err:
|
||||||
logging.debug("[Fix_Services set wifi.interface wlan0mon] except: %s" % repr(err))
|
logging.debug(
|
||||||
|
"[Fix_Services set wifi.interface wlan0mon] except: %s" % repr(err))
|
||||||
except Exception as cerr: #
|
except Exception as cerr: #
|
||||||
if not display:
|
if not display:
|
||||||
print("failed loading wlan0mon attempt #%s: %s" % (tries, repr(cerr)))
|
print("failed loading wlan0mon attempt #%s: %s" % (tries, repr(cerr)))
|
||||||
@ -303,11 +343,11 @@ class FixServices(plugins.Plugin):
|
|||||||
|
|
||||||
tries = tries + 1
|
tries = tries + 1
|
||||||
if tries < 3:
|
if tries < 3:
|
||||||
logging.info("[Fix_Services] wlan0mon didn't make it. trying again")
|
logging.debug("[Fix_Services] wlan0mon didn't make it. trying again")
|
||||||
if not display:
|
if not display:
|
||||||
print(" wlan0mon didn't make it. trying again")
|
print(" wlan0mon didn't make it. trying again")
|
||||||
else:
|
else:
|
||||||
logging.info("[Fix_Services] wlan0mon loading failed, no choice but to reboot ..")
|
logging.debug("[Fix_Services] wlan0mon loading failed, no choice but to reboot ..")
|
||||||
pwnagotchi.reboot()
|
pwnagotchi.reboot()
|
||||||
|
|
||||||
# exited the loop, so hopefully it loaded
|
# exited the loop, so hopefully it loaded
|
||||||
@ -317,14 +357,14 @@ class FixServices(plugins.Plugin):
|
|||||||
"face": faces.INTENSE})
|
"face": faces.INTENSE})
|
||||||
else:
|
else:
|
||||||
print("And back on again...")
|
print("And back on again...")
|
||||||
logging.info("[Fix_Services] wlan0mon back up")
|
logging.debug("[Fix_Services] wlan0mon back up")
|
||||||
else:
|
else:
|
||||||
self.LASTTRY = time.time()
|
self.LASTTRY = time.time()
|
||||||
|
|
||||||
time.sleep(8 + tries * 2) # give it a bit before restarting recon in bettercap
|
time.sleep(8 + tries * 2) # give it a bit before restarting recon in bettercap
|
||||||
self.isReloadingMon = False
|
self.isReloadingMon = False
|
||||||
|
|
||||||
logging.info("[Fix_Services] re-enable recon")
|
logging.debug("[Fix_Services] re-enable recon")
|
||||||
try:
|
try:
|
||||||
result = connection.run("wifi.clear; wifi.recon on")
|
result = connection.run("wifi.clear; wifi.recon on")
|
||||||
|
|
||||||
@ -345,13 +385,24 @@ class FixServices(plugins.Plugin):
|
|||||||
logging.error("[Fix_Services wifi.recon on] %s" % repr(err))
|
logging.error("[Fix_Services wifi.recon on] %s" % repr(err))
|
||||||
pwnagotchi.reboot()
|
pwnagotchi.reboot()
|
||||||
|
|
||||||
def on_unload(self, ui):
|
# called to setup the ui elements
|
||||||
|
def on_ui_setup(self, ui):
|
||||||
with ui._lock:
|
with ui._lock:
|
||||||
try:
|
# add custom UI elements
|
||||||
logging.info("[Fix_Services] unloaded")
|
if "position" in self.options:
|
||||||
except Exception as err:
|
pos = self.options['position'].split(',')
|
||||||
logging.error("[Fix_Services] unload err %s " % repr(err))
|
pos = [int(x.strip()) for x in pos]
|
||||||
pass
|
else:
|
||||||
|
pos = (ui.width() / 2 + 35, ui.height() - 11)
|
||||||
|
|
||||||
|
logging.debug("Got here")
|
||||||
|
|
||||||
|
# called when the ui is updated
|
||||||
|
def on_ui_update(self, ui):
|
||||||
|
return
|
||||||
|
|
||||||
|
def on_unload(self, ui):
|
||||||
|
return
|
||||||
|
|
||||||
|
|
||||||
# run from command line to brute force a reload
|
# run from command line to brute force a reload
|
||||||
|
@ -98,7 +98,7 @@ class GPS(plugins.Plugin):
|
|||||||
lat_pos = (127, 74)
|
lat_pos = (127, 74)
|
||||||
lon_pos = (122, 84)
|
lon_pos = (122, 84)
|
||||||
alt_pos = (127, 94)
|
alt_pos = (127, 94)
|
||||||
elif ui.is_waveshare27inch():
|
elif ui.is_waveshare2in7():
|
||||||
lat_pos = (6, 120)
|
lat_pos = (6, 120)
|
||||||
lon_pos = (1, 135)
|
lon_pos = (1, 135)
|
||||||
alt_pos = (6, 150)
|
alt_pos = (6, 150)
|
||||||
|
@ -5,7 +5,6 @@ import glob
|
|||||||
import re
|
import re
|
||||||
|
|
||||||
import pwnagotchi.grid as grid
|
import pwnagotchi.grid as grid
|
||||||
import pwnagotchi.plugins
|
|
||||||
import pwnagotchi.plugins as plugins
|
import pwnagotchi.plugins as plugins
|
||||||
from pwnagotchi.utils import StatusFile, WifiInfo, extract_from_pcap
|
from pwnagotchi.utils import StatusFile, WifiInfo, extract_from_pcap
|
||||||
from threading import Lock
|
from threading import Lock
|
||||||
@ -87,7 +86,7 @@ class Grid(plugins.Plugin):
|
|||||||
agent.view().on_unread_messages(self.unread_messages, self.total_messages)
|
agent.view().on_unread_messages(self.unread_messages, self.total_messages)
|
||||||
|
|
||||||
def check_handshakes(self, agent):
|
def check_handshakes(self, agent):
|
||||||
logging.debug("checking pcaps")
|
logging.debug("checking pcap's")
|
||||||
config = agent.config()
|
config = agent.config()
|
||||||
|
|
||||||
pcap_files = glob.glob(os.path.join(agent.config()['bettercap']['handshakes'], "*.pcap"))
|
pcap_files = glob.glob(os.path.join(agent.config()['bettercap']['handshakes'], "*.pcap"))
|
||||||
|
@ -1,179 +0,0 @@
|
|||||||
import logging
|
|
||||||
import subprocess
|
|
||||||
import os
|
|
||||||
import json
|
|
||||||
import pwnagotchi.plugins as plugins
|
|
||||||
from threading import Lock
|
|
||||||
|
|
||||||
'''
|
|
||||||
hcxpcapngtool needed, to install:
|
|
||||||
> git clone https://github.com/ZerBea/hcxtools.git
|
|
||||||
> cd hcxtools
|
|
||||||
> apt-get install libcurl4-openssl-dev libssl-dev zlib1g-dev
|
|
||||||
> make
|
|
||||||
> sudo make install
|
|
||||||
'''
|
|
||||||
|
|
||||||
|
|
||||||
class Hashie(plugins.Plugin):
|
|
||||||
__author__ = 'Jayofelony'
|
|
||||||
__version__ = '1.0.4'
|
|
||||||
__license__ = 'GPL3'
|
|
||||||
__description__ = '''
|
|
||||||
Attempt to automatically convert pcaps to a crackable format.
|
|
||||||
If successful, the files containing the hashes will be saved
|
|
||||||
in the same folder as the handshakes.
|
|
||||||
The files are saved in their respective Hashcat format:
|
|
||||||
- EAPOL hashes are saved as *.22000
|
|
||||||
- PMKID hashes are saved as *.16800
|
|
||||||
All PCAP files without enough information to create a hash are
|
|
||||||
stored in a file that can be read by the webgpsmap plugin.
|
|
||||||
|
|
||||||
Why use it?:
|
|
||||||
- Automatically convert handshakes to crackable formats!
|
|
||||||
We dont all upload our hashes online ;)
|
|
||||||
- Repair PMKID handshakes that hcxpcapngtool misses
|
|
||||||
- If running at time of handshake capture, on_handshake can
|
|
||||||
be used to improve the chance of the repair succeeding
|
|
||||||
- Be a completionist! Not enough packets captured to crack a network?
|
|
||||||
This generates an output file for the webgpsmap plugin, use the
|
|
||||||
location data to revisit networks you need more packets for!
|
|
||||||
|
|
||||||
Additional information:
|
|
||||||
- Currently requires hcxpcapngtool compiled and installed
|
|
||||||
- Attempts to repair PMKID hashes when hcxpcapngtool cant find the SSID
|
|
||||||
- hcxpcapngtool sometimes has trouble extracting the SSID, so we
|
|
||||||
use the raw 16800 output and attempt to retrieve the SSID via tcpdump
|
|
||||||
- When access_point data is available (on_handshake), we leverage
|
|
||||||
the reported AP name and MAC to complete the hash
|
|
||||||
- The repair is very basic and could certainly be improved!
|
|
||||||
Todo:
|
|
||||||
Make it so users dont need hcxpcapngtool (unless it gets added to the base image)
|
|
||||||
Phase 1: Extract/construct 22000/16800 hashes through tcpdump commands
|
|
||||||
Phase 2: Extract/construct 22000/16800 hashes entirely in python
|
|
||||||
Improve the code, a lot
|
|
||||||
'''
|
|
||||||
|
|
||||||
def __init__(self):
|
|
||||||
self.lock = Lock()
|
|
||||||
self.options = dict()
|
|
||||||
|
|
||||||
def on_loaded(self):
|
|
||||||
logging.info("[Hashie] Plugin loaded")
|
|
||||||
|
|
||||||
def on_unloaded(self):
|
|
||||||
logging.info("[Hashie] Plugin unloaded")
|
|
||||||
|
|
||||||
# called when everything is ready and the main loop is about to start
|
|
||||||
def on_ready(self, agent):
|
|
||||||
config = agent.config()
|
|
||||||
handshake_dir = config['bettercap']['handshakes']
|
|
||||||
|
|
||||||
logging.info('[Hashie] Starting batch conversion of pcap files')
|
|
||||||
with self.lock:
|
|
||||||
self._process_stale_pcaps(handshake_dir)
|
|
||||||
|
|
||||||
def on_handshake(self, agent, filename, access_point, client_station):
|
|
||||||
with self.lock:
|
|
||||||
handshake_status = []
|
|
||||||
fullpathNoExt = filename.split('.')[0]
|
|
||||||
name = filename.split('/')[-1:][0].split('.')[0]
|
|
||||||
|
|
||||||
if os.path.isfile(fullpathNoExt + '.22000'):
|
|
||||||
handshake_status.append('Already have {}.22000 (EAPOL)'.format(name))
|
|
||||||
elif self._writeEAPOL(filename):
|
|
||||||
handshake_status.append('Created {}.22000 (EAPOL) from pcap'.format(name))
|
|
||||||
|
|
||||||
if os.path.isfile(fullpathNoExt + '.16800'):
|
|
||||||
handshake_status.append('Already have {}.16800 (PMKID)'.format(name))
|
|
||||||
elif self._writePMKID(filename):
|
|
||||||
handshake_status.append('Created {}.16800 (PMKID) from pcap'.format(name))
|
|
||||||
|
|
||||||
if handshake_status:
|
|
||||||
logging.info('[Hashie] Good news:\n\t' + '\n\t'.join(handshake_status))
|
|
||||||
|
|
||||||
def _writeEAPOL(self, fullpath):
|
|
||||||
fullpathNoExt = fullpath.split('.')[0]
|
|
||||||
filename = fullpath.split('/')[-1:][0].split('.')[0]
|
|
||||||
subprocess.getoutput('hcxpcapngtool -o {}.22000 {} >/dev/null 2>&1'.format(fullpathNoExt, fullpath))
|
|
||||||
if os.path.isfile(fullpathNoExt + '.22000'):
|
|
||||||
logging.debug('[Hashie] [+] EAPOL Success: {}.22000 created'.format(filename))
|
|
||||||
return True
|
|
||||||
return False
|
|
||||||
|
|
||||||
def _writePMKID(self, fullpath):
|
|
||||||
fullpathNoExt = fullpath.split('.')[0]
|
|
||||||
filename = fullpath.split('/')[-1:][0].split('.')[0]
|
|
||||||
subprocess.getoutput('hcxpcapngtool -o {}.16800 {} >/dev/null 2>&1'.format(fullpathNoExt, fullpath))
|
|
||||||
if os.path.isfile(fullpathNoExt + '.16800'):
|
|
||||||
logging.debug('[Hashie] [+] PMKID Success: {}.16800 created'.format(filename))
|
|
||||||
return True
|
|
||||||
return False
|
|
||||||
|
|
||||||
def _process_stale_pcaps(self, handshake_dir):
|
|
||||||
handshakes_list = [os.path.join(handshake_dir, filename) for filename in os.listdir(handshake_dir) if filename.endswith('.pcap')]
|
|
||||||
failed_jobs = []
|
|
||||||
successful_jobs = []
|
|
||||||
lonely_pcaps = []
|
|
||||||
for num, handshake in enumerate(handshakes_list):
|
|
||||||
fullpathNoExt = handshake.split('.')[0]
|
|
||||||
pcapFileName = handshake.split('/')[-1:][0]
|
|
||||||
if not os.path.isfile(fullpathNoExt + '.22000'): # if no 22000, try
|
|
||||||
if self._writeEAPOL(handshake):
|
|
||||||
successful_jobs.append('22000: ' + pcapFileName)
|
|
||||||
else:
|
|
||||||
failed_jobs.append('22000: ' + pcapFileName)
|
|
||||||
if not os.path.isfile(fullpathNoExt + '.16800'): # if no 16800, try
|
|
||||||
if self._writePMKID(handshake):
|
|
||||||
successful_jobs.append('16800: ' + pcapFileName)
|
|
||||||
else:
|
|
||||||
failed_jobs.append('16800: ' + pcapFileName)
|
|
||||||
if not os.path.isfile(fullpathNoExt + '.22000'): # if no 16800 AND no 22000
|
|
||||||
lonely_pcaps.append(handshake)
|
|
||||||
logging.debug('[hashie] Batch job: added {} to lonely list'.format(pcapFileName))
|
|
||||||
if ((num + 1) % 50 == 0) or (num + 1 == len(handshakes_list)): # report progress every 50, or when done
|
|
||||||
logging.info('[Hashie] Batch job: {}/{} done ({} fails)'.format(num + 1, len(handshakes_list), len(lonely_pcaps)))
|
|
||||||
if successful_jobs:
|
|
||||||
logging.info('[Hashie] Batch job: {} new handshake files created'.format(len(successful_jobs)))
|
|
||||||
if lonely_pcaps:
|
|
||||||
logging.info('[Hashie] Batch job: {} networks without enough packets to create a hash'.format(len(lonely_pcaps)))
|
|
||||||
self._getLocations(lonely_pcaps)
|
|
||||||
|
|
||||||
def _getLocations(self, lonely_pcaps):
|
|
||||||
# export a file for webgpsmap to load
|
|
||||||
with open('/root/.incompletePcaps', 'w') as isIncomplete:
|
|
||||||
count = 0
|
|
||||||
for pcapFile in lonely_pcaps:
|
|
||||||
filename = pcapFile.split('/')[-1:][0] # keep extension
|
|
||||||
fullpathNoExt = pcapFile.split('.')[0]
|
|
||||||
isIncomplete.write(filename + '\n')
|
|
||||||
if os.path.isfile(fullpathNoExt + '.gps.json') or os.path.isfile(fullpathNoExt + '.geo.json'):
|
|
||||||
count += 1
|
|
||||||
if count != 0:
|
|
||||||
logging.info('[Hashie] Used {} GPS/GEO files to find lonely networks, '
|
|
||||||
'go check webgpsmap! ;)'.format(str(count)))
|
|
||||||
else:
|
|
||||||
logging.info('[Hashie] Could not find any GPS/GEO files '
|
|
||||||
'for the lonely networks'.format(str(count)))
|
|
||||||
|
|
||||||
def _getLocationsCSV(self, lonely_pcaps):
|
|
||||||
# in case we need this later, export locations manually to CSV file, needs try/catch format/etc.
|
|
||||||
locations = []
|
|
||||||
for pcapFile in lonely_pcaps:
|
|
||||||
filename = pcapFile.split('/')[-1:][0].split('.')[0]
|
|
||||||
fullpathNoExt = pcapFile.split('.')[0]
|
|
||||||
if os.path.isfile(fullpathNoExt + '.gps.json'):
|
|
||||||
with open(fullpathNoExt + '.gps.json', 'r') as tempFileA:
|
|
||||||
data = json.load(tempFileA)
|
|
||||||
locations.append(filename + ',' + str(data['Latitude']) + ',' + str(data['Longitude']) + ',50')
|
|
||||||
elif os.path.isfile(fullpathNoExt + '.geo.json'):
|
|
||||||
with open(fullpathNoExt + '.geo.json', 'r') as tempFileB:
|
|
||||||
data = json.load(tempFileB)
|
|
||||||
locations.append(
|
|
||||||
filename + ',' + str(data['location']['lat']) + ',' + str(data['location']['lng']) + ',' + str(data['accuracy']))
|
|
||||||
if locations:
|
|
||||||
with open('/root/locations.csv', 'w') as tempFileD:
|
|
||||||
for loc in locations:
|
|
||||||
tempFileD.write(loc + '\n')
|
|
||||||
logging.info('[Hashie] Used {} GPS/GEO files to find lonely networks, '
|
|
||||||
'load /root/locations.csv into a mapping app and go say hi!'.format(len(locations)))
|
|
@ -144,6 +144,9 @@ class MemTemp(plugins.Plugin):
|
|||||||
elif ui.is_waveshare2in7():
|
elif ui.is_waveshare2in7():
|
||||||
h_pos = (192, 138)
|
h_pos = (192, 138)
|
||||||
v_pos = (211, 122)
|
v_pos = (211, 122)
|
||||||
|
elif ui.is_waveshare1in54V2():
|
||||||
|
h_pos = (53, 77)
|
||||||
|
v_pos = (154, 65)
|
||||||
else:
|
else:
|
||||||
h_pos = (155, 76)
|
h_pos = (155, 76)
|
||||||
v_pos = (175, 61)
|
v_pos = (175, 61)
|
||||||
|
@ -142,6 +142,6 @@ class OnlineHashCrack(plugins.Plugin):
|
|||||||
for row in csv.DictReader(cracked_list):
|
for row in csv.DictReader(cracked_list):
|
||||||
if row['password']:
|
if row['password']:
|
||||||
filename = re.sub(r'[^a-zA-Z0-9]', '', row['ESSID']) + '_' + row['BSSID'].replace(':','')
|
filename = re.sub(r'[^a-zA-Z0-9]', '', row['ESSID']) + '_' + row['BSSID'].replace(':','')
|
||||||
if os.path.exists( os.path.join(handshake_dir, filename+'.pcap') ):
|
if os.path.exists( os.path.join(handshake_dir, filename+'.pcap')):
|
||||||
with open(os.path.join(handshake_dir, filename+'.pcap.cracked'), 'w') as f:
|
with open(os.path.join(handshake_dir, filename+'.pcap.cracked'), 'w') as f:
|
||||||
f.write(row['password'])
|
f.write(row['password'])
|
||||||
|
@ -14,7 +14,7 @@ from dateutil.parser import parse
|
|||||||
|
|
||||||
the plugin does the following:
|
the plugin does the following:
|
||||||
- search for *.pcap files in your /handshakes/ dir
|
- search for *.pcap files in your /handshakes/ dir
|
||||||
- for every found .pcap file it looks for a .geo.json or .gps.json or .paw-gps.json file with
|
- for every found .pcap file it looks for a .geo.json or .gps.json or file with
|
||||||
latitude+longitude data inside and shows this position on the map
|
latitude+longitude data inside and shows this position on the map
|
||||||
- if also an .cracked file with a plaintext password inside exist, it reads the content and shows the
|
- if also an .cracked file with a plaintext password inside exist, it reads the content and shows the
|
||||||
position as green instead of red and the password inside the infopox of the position
|
position as green instead of red and the password inside the infopox of the position
|
||||||
@ -87,7 +87,8 @@ class Webgpsmap(plugins.Plugin):
|
|||||||
# returns all positions
|
# returns all positions
|
||||||
try:
|
try:
|
||||||
self.ALREADY_SENT = list()
|
self.ALREADY_SENT = list()
|
||||||
response_data = bytes(json.dumps(self.load_gps_from_dir(self.config['bettercap']['handshakes'])), "utf-8")
|
response_data = bytes(
|
||||||
|
json.dumps(self.load_gps_from_dir(self.config['bettercap']['handshakes'])), "utf-8")
|
||||||
response_status = 200
|
response_status = 200
|
||||||
response_mimetype = "application/json"
|
response_mimetype = "application/json"
|
||||||
response_header_contenttype = 'application/json'
|
response_header_contenttype = 'application/json'
|
||||||
@ -100,7 +101,8 @@ class Webgpsmap(plugins.Plugin):
|
|||||||
self.ALREADY_SENT = list()
|
self.ALREADY_SENT = list()
|
||||||
json_data = json.dumps(self.load_gps_from_dir(self.config['bettercap']['handshakes']))
|
json_data = json.dumps(self.load_gps_from_dir(self.config['bettercap']['handshakes']))
|
||||||
html_data = self.get_html()
|
html_data = self.get_html()
|
||||||
html_data = html_data.replace('var positions = [];', 'var positions = ' + json_data + ';positionsLoaded=true;drawPositions();')
|
html_data = html_data.replace('var positions = [];',
|
||||||
|
'var positions = ' + json_data + ';positionsLoaded=true;drawPositions();')
|
||||||
response_data = bytes(html_data, "utf-8")
|
response_data = bytes(html_data, "utf-8")
|
||||||
response_status = 200
|
response_status = 200
|
||||||
response_mimetype = "application/xhtml+xml"
|
response_mimetype = "application/xhtml+xml"
|
||||||
@ -163,7 +165,8 @@ class Webgpsmap(plugins.Plugin):
|
|||||||
|
|
||||||
all_files = os.listdir(handshake_dir)
|
all_files = os.listdir(handshake_dir)
|
||||||
# print(all_files)
|
# print(all_files)
|
||||||
all_pcap_files = [os.path.join(handshake_dir, filename) for filename in all_files if filename.endswith('.pcap')]
|
all_pcap_files = [os.path.join(handshake_dir, filename) for filename in all_files if
|
||||||
|
filename.endswith('.pcap')]
|
||||||
all_geo_or_gps_files = []
|
all_geo_or_gps_files = []
|
||||||
for filename_pcap in all_pcap_files:
|
for filename_pcap in all_pcap_files:
|
||||||
filename_base = filename_pcap[:-5] # remove ".pcap"
|
filename_base = filename_pcap[:-5] # remove ".pcap"
|
||||||
@ -180,22 +183,18 @@ class Webgpsmap(plugins.Plugin):
|
|||||||
if check_for in all_files:
|
if check_for in all_files:
|
||||||
filename_position = str(os.path.join(handshake_dir, check_for))
|
filename_position = str(os.path.join(handshake_dir, check_for))
|
||||||
|
|
||||||
logging.debug("[webgpsmap] search for .paw-gps.json")
|
|
||||||
check_for = os.path.basename(filename_base) + ".paw-gps.json"
|
|
||||||
if check_for in all_files:
|
|
||||||
filename_position = str(os.path.join(handshake_dir, check_for))
|
|
||||||
|
|
||||||
logging.debug(f"[webgpsmap] end search for position data files and use {filename_position}")
|
logging.debug(f"[webgpsmap] end search for position data files and use {filename_position}")
|
||||||
|
|
||||||
if filename_position is not None:
|
if filename_position is not None:
|
||||||
all_geo_or_gps_files.append(filename_position)
|
all_geo_or_gps_files.append(filename_position)
|
||||||
|
|
||||||
# all_geo_or_gps_files = set(all_geo_or_gps_files) - set(SKIP) # remove skipped networks? No!
|
# all_geo_or_gps_files = set(all_geo_or_gps_files) - set(SKIP) # remove skipped networks? No!
|
||||||
|
|
||||||
if newest_only:
|
if newest_only:
|
||||||
all_geo_or_gps_files = set(all_geo_or_gps_files) - set(self.ALREADY_SENT)
|
all_geo_or_gps_files = set(all_geo_or_gps_files) - set(self.ALREADY_SENT)
|
||||||
|
|
||||||
logging.info(f"[webgpsmap] Found {len(all_geo_or_gps_files)} position-data files from {len(all_pcap_files)} handshakes. Fetching positions ...")
|
logging.info(
|
||||||
|
f"[webgpsmap] Found {len(all_geo_or_gps_files)} position-data files from {len(all_pcap_files)} handshakes. Fetching positions ...")
|
||||||
|
|
||||||
for pos_file in all_geo_or_gps_files:
|
for pos_file in all_geo_or_gps_files:
|
||||||
try:
|
try:
|
||||||
@ -213,9 +212,7 @@ class Webgpsmap(plugins.Plugin):
|
|||||||
pos_type = 'gps'
|
pos_type = 'gps'
|
||||||
elif pos.type() == PositionFile.GEO:
|
elif pos.type() == PositionFile.GEO:
|
||||||
pos_type = 'geo'
|
pos_type = 'geo'
|
||||||
elif pos.type() == PositionFile.PAWGPS:
|
gps_data[ssid + "_" + mac] = {
|
||||||
pos_type = 'paw'
|
|
||||||
gps_data[ssid+"_"+mac] = {
|
|
||||||
'ssid': ssid,
|
'ssid': ssid,
|
||||||
'mac': mac,
|
'mac': mac,
|
||||||
'type': pos_type,
|
'type': pos_type,
|
||||||
@ -224,7 +221,7 @@ class Webgpsmap(plugins.Plugin):
|
|||||||
'acc': pos.accuracy(),
|
'acc': pos.accuracy(),
|
||||||
'ts_first': pos.timestamp_first(),
|
'ts_first': pos.timestamp_first(),
|
||||||
'ts_last': pos.timestamp_last(),
|
'ts_last': pos.timestamp_last(),
|
||||||
}
|
}
|
||||||
|
|
||||||
# get ap password if exist
|
# get ap password if exist
|
||||||
check_for = os.path.basename(pos_file).split(".")[0] + ".pcap.cracked"
|
check_for = os.path.basename(pos_file).split(".")[0] + ".pcap.cracked"
|
||||||
@ -265,7 +262,6 @@ class PositionFile:
|
|||||||
"""
|
"""
|
||||||
GPS = 1
|
GPS = 1
|
||||||
GEO = 2
|
GEO = 2
|
||||||
PAWGPS = 3
|
|
||||||
|
|
||||||
def __init__(self, path):
|
def __init__(self, path):
|
||||||
self._file = path
|
self._file = path
|
||||||
@ -282,7 +278,7 @@ class PositionFile:
|
|||||||
"""
|
"""
|
||||||
Returns the mac from filename
|
Returns the mac from filename
|
||||||
"""
|
"""
|
||||||
parsed_mac = re.search(r'.*_?([a-zA-Z0-9]{12})\.(?:gps|geo|paw-gps)\.json', self._filename)
|
parsed_mac = re.search(r'.*_?([a-zA-Z0-9]{12})\.(?:gps|geo)\.json', self._filename)
|
||||||
if parsed_mac:
|
if parsed_mac:
|
||||||
mac = parsed_mac.groups()[0]
|
mac = parsed_mac.groups()[0]
|
||||||
return mac
|
return mac
|
||||||
@ -292,7 +288,7 @@ class PositionFile:
|
|||||||
"""
|
"""
|
||||||
Returns the ssid from filename
|
Returns the ssid from filename
|
||||||
"""
|
"""
|
||||||
parsed_ssid = re.search(r'(.+)_[a-zA-Z0-9]{12}\.(?:gps|geo|paw-gps)\.json', self._filename)
|
parsed_ssid = re.search(r'(.+)_[a-zA-Z0-9]{12}\.(?:gps|geo)\.json', self._filename)
|
||||||
if parsed_ssid:
|
if parsed_ssid:
|
||||||
return parsed_ssid.groups()[0]
|
return parsed_ssid.groups()[0]
|
||||||
return None
|
return None
|
||||||
@ -354,8 +350,6 @@ class PositionFile:
|
|||||||
return PositionFile.GPS
|
return PositionFile.GPS
|
||||||
if self._file.endswith('.geo.json'):
|
if self._file.endswith('.geo.json'):
|
||||||
return PositionFile.GEO
|
return PositionFile.GEO
|
||||||
if self._file.endswith('.paw-gps.json'):
|
|
||||||
return PositionFile.PAWGPS
|
|
||||||
return None
|
return None
|
||||||
|
|
||||||
def lat(self):
|
def lat(self):
|
||||||
@ -402,9 +396,7 @@ class PositionFile:
|
|||||||
|
|
||||||
def accuracy(self):
|
def accuracy(self):
|
||||||
if self.type() == PositionFile.GPS:
|
if self.type() == PositionFile.GPS:
|
||||||
return 50.0 # a default
|
return 50.0 # a default
|
||||||
if self.type() == PositionFile.PAWGPS:
|
|
||||||
return 50.0 # a default
|
|
||||||
if self.type() == PositionFile.GEO:
|
if self.type() == PositionFile.GEO:
|
||||||
try:
|
try:
|
||||||
return self._json['accuracy']
|
return self._json['accuracy']
|
||||||
|
0
pwnagotchi/ui/__init__.py
Normal file
0
pwnagotchi/ui/__init__.py
Normal file
@ -1,4 +1,4 @@
|
|||||||
from PIL import Image
|
from PIL import Image, ImageOps
|
||||||
from textwrap import TextWrapper
|
from textwrap import TextWrapper
|
||||||
|
|
||||||
|
|
||||||
@ -40,21 +40,37 @@ class FilledRect(Widget):
|
|||||||
|
|
||||||
|
|
||||||
class Text(Widget):
|
class Text(Widget):
|
||||||
def __init__(self, value="", position=(0, 0), font=None, color=0, wrap=False, max_length=0):
|
def __init__(self, value="", position=(0, 0), font=None, color=0, wrap=False, max_length=0, png=False):
|
||||||
super().__init__(position, color)
|
super().__init__(position, color)
|
||||||
self.value = value
|
self.value = value
|
||||||
self.font = font
|
self.font = font
|
||||||
self.wrap = wrap
|
self.wrap = wrap
|
||||||
self.max_length = max_length
|
self.max_length = max_length
|
||||||
self.wrapper = TextWrapper(width=self.max_length, replace_whitespace=False) if wrap else None
|
self.wrapper = TextWrapper(width=self.max_length, replace_whitespace=False) if wrap else None
|
||||||
|
self.png = png
|
||||||
|
|
||||||
def draw(self, canvas, drawer):
|
def draw(self, canvas, drawer):
|
||||||
if self.value is not None:
|
if self.value is not None:
|
||||||
if self.wrap:
|
if not self.png:
|
||||||
text = '\n'.join(self.wrapper.wrap(self.value))
|
if self.wrap:
|
||||||
|
text = '\n'.join(self.wrapper.wrap(self.value))
|
||||||
|
else:
|
||||||
|
text = self.value
|
||||||
|
drawer.text(self.xy, text, font=self.font, fill=self.color)
|
||||||
else:
|
else:
|
||||||
text = self.value
|
self.image = Image.open(self.value)
|
||||||
drawer.text(self.xy, text, font=self.font, fill=self.color)
|
self.image = self.image.convert('RGBA')
|
||||||
|
self.pixels = self.image.load()
|
||||||
|
for y in range(self.image.size[1]):
|
||||||
|
for x in range(self.image.size[0]):
|
||||||
|
if self.pixels[x,y][3] < 255: # check alpha
|
||||||
|
self.pixels[x,y] = (255, 255, 255, 255)
|
||||||
|
if self.color == 255:
|
||||||
|
self._image = ImageOps.colorize(self.image.convert('L'), black = "white", white = "black")
|
||||||
|
else:
|
||||||
|
self._image = self.image
|
||||||
|
self.image = self._image.convert('1')
|
||||||
|
canvas.paste(self.image, self.xy)
|
||||||
|
|
||||||
|
|
||||||
class LabeledValue(Widget):
|
class LabeledValue(Widget):
|
||||||
|
@ -118,6 +118,9 @@ class Display(View):
|
|||||||
def is_waveshare2in66g(self):
|
def is_waveshare2in66g(self):
|
||||||
return self._implementation.name == 'waveshare2in66g'
|
return self._implementation.name == 'waveshare2in66g'
|
||||||
|
|
||||||
|
def is_weact2in9(self):
|
||||||
|
return self._implementation.name == 'weact2in9'
|
||||||
|
|
||||||
def is_waveshare3in0g(self):
|
def is_waveshare3in0g(self):
|
||||||
return self._implementation.name == 'waveshare3in0g'
|
return self._implementation.name == 'waveshare3in0g'
|
||||||
|
|
||||||
@ -151,6 +154,12 @@ class Display(View):
|
|||||||
def is_waveshare5in65f(self):
|
def is_waveshare5in65f(self):
|
||||||
return self._implementation.name == 'waveshare5in65f'
|
return self._implementation.name == 'waveshare5in65f'
|
||||||
|
|
||||||
|
def is_waveshare5in79(self):
|
||||||
|
return self._implementation.name == 'waveshare5in79'
|
||||||
|
|
||||||
|
def is_waveshare5in79b(self):
|
||||||
|
return self._implementation.name == 'waveshare5in79b'
|
||||||
|
|
||||||
def is_waveshare5in83(self):
|
def is_waveshare5in83(self):
|
||||||
return self._implementation.name == 'waveshare5in83'
|
return self._implementation.name == 'waveshare5in83'
|
||||||
|
|
||||||
@ -193,6 +202,9 @@ class Display(View):
|
|||||||
def is_inky(self):
|
def is_inky(self):
|
||||||
return self._implementation.name == 'inky'
|
return self._implementation.name == 'inky'
|
||||||
|
|
||||||
|
def is_dummy_display(self):
|
||||||
|
return self._implementation.name == 'dummydisplay'
|
||||||
|
|
||||||
def is_papirus(self):
|
def is_papirus(self):
|
||||||
return self._implementation.name == 'papirus'
|
return self._implementation.name == 'papirus'
|
||||||
|
|
||||||
@ -208,9 +220,24 @@ class Display(View):
|
|||||||
def is_displayhatmini(self):
|
def is_displayhatmini(self):
|
||||||
return self._implementation.name == 'displayhatmini'
|
return self._implementation.name == 'displayhatmini'
|
||||||
|
|
||||||
|
def is_pirateaudio(self):
|
||||||
|
return self._implementation.name == 'pirateaudio'
|
||||||
|
|
||||||
|
def is_pitft(self):
|
||||||
|
return self._implementation.name == 'pitft'
|
||||||
|
|
||||||
|
def is_tftbonnet(self):
|
||||||
|
return self._implementation.name == 'tftbonnet'
|
||||||
|
|
||||||
|
def is_waveshareoledlcd(self):
|
||||||
|
return self._implementation.name == 'waveshareoledlcd'
|
||||||
|
|
||||||
def is_waveshare35lcd(self):
|
def is_waveshare35lcd(self):
|
||||||
return self._implementation.name == 'waveshare35lcd'
|
return self._implementation.name == 'waveshare35lcd'
|
||||||
|
|
||||||
|
def is_adfruit213v3(self):
|
||||||
|
return self._implementation.name == 'adafruit2in13_v3'
|
||||||
|
|
||||||
def is_waveshare_any(self):
|
def is_waveshare_any(self):
|
||||||
return self.is_waveshare_v1() or self.is_waveshare_v2()
|
return self.is_waveshare_v1() or self.is_waveshare_v2()
|
||||||
|
|
||||||
|
@ -23,6 +23,9 @@ DEBUG = '(#__#)'
|
|||||||
UPLOAD = '(1__0)'
|
UPLOAD = '(1__0)'
|
||||||
UPLOAD1 = '(1__1)'
|
UPLOAD1 = '(1__1)'
|
||||||
UPLOAD2 = '(0__1)'
|
UPLOAD2 = '(0__1)'
|
||||||
|
PNG = False
|
||||||
|
POSITION_X = 0
|
||||||
|
POSITION_Y = 40
|
||||||
|
|
||||||
|
|
||||||
def load_from_config(config):
|
def load_from_config(config):
|
||||||
|
@ -1,4 +1,5 @@
|
|||||||
from pwnagotchi.ui.hw.inky import Inky
|
from pwnagotchi.ui.hw.inky import Inky
|
||||||
|
from pwnagotchi.ui.hw.dummydisplay import DummyDisplay
|
||||||
from pwnagotchi.ui.hw.papirus import Papirus
|
from pwnagotchi.ui.hw.papirus import Papirus
|
||||||
from pwnagotchi.ui.hw.oledhat import OledHat
|
from pwnagotchi.ui.hw.oledhat import OledHat
|
||||||
from pwnagotchi.ui.hw.lcdhat import LcdHat
|
from pwnagotchi.ui.hw.lcdhat import LcdHat
|
||||||
@ -21,6 +22,11 @@ from pwnagotchi.ui.hw.waveshare2in13b_V4 import Waveshare213bV4
|
|||||||
from pwnagotchi.ui.hw.waveshare3in5lcd import Waveshare35lcd
|
from pwnagotchi.ui.hw.waveshare3in5lcd import Waveshare35lcd
|
||||||
from pwnagotchi.ui.hw.spotpear24in import Spotpear24inch
|
from pwnagotchi.ui.hw.spotpear24in import Spotpear24inch
|
||||||
from pwnagotchi.ui.hw.displayhatmini import DisplayHatMini
|
from pwnagotchi.ui.hw.displayhatmini import DisplayHatMini
|
||||||
|
from pwnagotchi.ui.hw.pirateaudio import PirateAudio
|
||||||
|
from pwnagotchi.ui.hw.pitft import Pitft
|
||||||
|
from pwnagotchi.ui.hw.tftbonnet import TftBonnet
|
||||||
|
from pwnagotchi.ui.hw.adafruit2in13 import Adafruit2in13V3
|
||||||
|
from pwnagotchi.ui.hw.waveshareoledlcd import Waveshareoledlcd
|
||||||
from pwnagotchi.ui.hw.waveshare1in02 import Waveshare1in02
|
from pwnagotchi.ui.hw.waveshare1in02 import Waveshare1in02
|
||||||
from pwnagotchi.ui.hw.waveshare1in54 import Waveshare154
|
from pwnagotchi.ui.hw.waveshare1in54 import Waveshare154
|
||||||
from pwnagotchi.ui.hw.waveshare1in54_V2 import Waveshare154V2
|
from pwnagotchi.ui.hw.waveshare1in54_V2 import Waveshare154V2
|
||||||
@ -49,6 +55,8 @@ from pwnagotchi.ui.hw.waveshare4in2bc import Waveshare4in2bc
|
|||||||
from pwnagotchi.ui.hw.waveshare4in26 import Waveshare4in26
|
from pwnagotchi.ui.hw.waveshare4in26 import Waveshare4in26
|
||||||
from pwnagotchi.ui.hw.waveshare4in37g import Waveshare4in37g
|
from pwnagotchi.ui.hw.waveshare4in37g import Waveshare4in37g
|
||||||
from pwnagotchi.ui.hw.waveshare5in65f import Waveshare5in65f
|
from pwnagotchi.ui.hw.waveshare5in65f import Waveshare5in65f
|
||||||
|
from pwnagotchi.ui.hw.waveshare5in79 import Waveshare5in79
|
||||||
|
from pwnagotchi.ui.hw.waveshare5in79b import Waveshare5in79b
|
||||||
from pwnagotchi.ui.hw.waveshare5in83 import Waveshare5in83
|
from pwnagotchi.ui.hw.waveshare5in83 import Waveshare5in83
|
||||||
from pwnagotchi.ui.hw.waveshare5in83_V2 import Waveshare5in83V2
|
from pwnagotchi.ui.hw.waveshare5in83_V2 import Waveshare5in83V2
|
||||||
from pwnagotchi.ui.hw.waveshare5in83b_V2 import Waveshare5in83bV2
|
from pwnagotchi.ui.hw.waveshare5in83b_V2 import Waveshare5in83bV2
|
||||||
@ -69,6 +77,9 @@ def display_for(config):
|
|||||||
if config['ui']['display']['type'] == 'inky':
|
if config['ui']['display']['type'] == 'inky':
|
||||||
return Inky(config)
|
return Inky(config)
|
||||||
|
|
||||||
|
elif config['ui']['display']['type'] == 'dummydisplay':
|
||||||
|
return DummyDisplay(config)
|
||||||
|
|
||||||
elif config['ui']['display']['type'] == 'papirus':
|
elif config['ui']['display']['type'] == 'papirus':
|
||||||
return Papirus(config)
|
return Papirus(config)
|
||||||
|
|
||||||
@ -95,7 +106,19 @@ def display_for(config):
|
|||||||
|
|
||||||
elif config['ui']['display']['type'] == 'displayhatmini':
|
elif config['ui']['display']['type'] == 'displayhatmini':
|
||||||
return DisplayHatMini(config)
|
return DisplayHatMini(config)
|
||||||
|
|
||||||
|
elif config['ui']['display']['type'] == 'pirateaudio':
|
||||||
|
return PirateAudio(config)
|
||||||
|
|
||||||
|
elif config['ui']['display']['type'] == 'pitft':
|
||||||
|
return Pitft(config)
|
||||||
|
|
||||||
|
elif config['ui']['display']['type'] == 'tftbonnet':
|
||||||
|
return TftBonnet(config)
|
||||||
|
|
||||||
|
elif config['ui']['display']['type'] == 'waveshareoledlcd':
|
||||||
|
return Waveshareoledlcd(config)
|
||||||
|
|
||||||
elif config['ui']['display']['type'] == 'waveshare1in02':
|
elif config['ui']['display']['type'] == 'waveshare1in02':
|
||||||
return Waveshare1in02(config)
|
return Waveshare1in02(config)
|
||||||
|
|
||||||
@ -159,6 +182,9 @@ def display_for(config):
|
|||||||
elif config['ui']['display']['type'] == 'waveshare_4':
|
elif config['ui']['display']['type'] == 'waveshare_4':
|
||||||
return WaveshareV4(config)
|
return WaveshareV4(config)
|
||||||
|
|
||||||
|
elif config['ui']['display']['type'] == 'adafruit2in13':
|
||||||
|
return Adafruit2in13(config)
|
||||||
|
|
||||||
elif config['ui']['display']['type'] == 'waveshare2in13bc':
|
elif config['ui']['display']['type'] == 'waveshare2in13bc':
|
||||||
return Waveshare213bc(config)
|
return Waveshare213bc(config)
|
||||||
|
|
||||||
@ -219,6 +245,12 @@ def display_for(config):
|
|||||||
elif config['ui']['display']['type'] == 'waveshare5in65f':
|
elif config['ui']['display']['type'] == 'waveshare5in65f':
|
||||||
return Waveshare5in65f(config)
|
return Waveshare5in65f(config)
|
||||||
|
|
||||||
|
elif config['ui']['display']['type'] == 'waveshare5in79':
|
||||||
|
return Waveshare5in79(config)
|
||||||
|
|
||||||
|
elif config['ui']['display']['type'] == 'waveshare5in79b':
|
||||||
|
return Waveshare5in79b(config)
|
||||||
|
|
||||||
elif config['ui']['display']['type'] == 'waveshare5in83':
|
elif config['ui']['display']['type'] == 'waveshare5in83':
|
||||||
return Waveshare5in83(config)
|
return Waveshare5in83(config)
|
||||||
|
|
||||||
|
45
pwnagotchi/ui/hw/adafruit2in13.py
Normal file
45
pwnagotchi/ui/hw/adafruit2in13.py
Normal file
@ -0,0 +1,45 @@
|
|||||||
|
import logging
|
||||||
|
|
||||||
|
import pwnagotchi.ui.fonts as fonts
|
||||||
|
from pwnagotchi.ui.hw.base import DisplayImpl
|
||||||
|
|
||||||
|
|
||||||
|
class Adafruit2in13V3(DisplayImpl):
|
||||||
|
def __init__(self, config):
|
||||||
|
super(Adafruit2in13V3, self).__init__(config, 'adafruit2in13_v3')
|
||||||
|
|
||||||
|
def layout(self):
|
||||||
|
fonts.setup(10, 9, 10, 35, 25, 9)
|
||||||
|
self._layout['width'] = 250
|
||||||
|
self._layout['height'] = 122
|
||||||
|
self._layout['face'] = (0, 40)
|
||||||
|
self._layout['name'] = (5, 20)
|
||||||
|
self._layout['channel'] = (0, 0)
|
||||||
|
self._layout['aps'] = (28, 0)
|
||||||
|
self._layout['uptime'] = (185, 0)
|
||||||
|
self._layout['line1'] = [0, 14, 250, 14]
|
||||||
|
self._layout['line2'] = [0, 108, 250, 108]
|
||||||
|
self._layout['friend_face'] = (0, 92)
|
||||||
|
self._layout['friend_name'] = (40, 94)
|
||||||
|
self._layout['shakes'] = (0, 109)
|
||||||
|
self._layout['mode'] = (225, 109)
|
||||||
|
self._layout['status'] = {
|
||||||
|
'pos': (125, 20),
|
||||||
|
'font': fonts.status_font(fonts.Medium),
|
||||||
|
'max': 20
|
||||||
|
}
|
||||||
|
return self._layout
|
||||||
|
|
||||||
|
def initialize(self):
|
||||||
|
logging.info("initializing adafruit 2in13 V3 display")
|
||||||
|
from pwnagotchi.ui.hw.libs.adafruit.v2in13_v3.epd2in13_v3 import EPD
|
||||||
|
self._display = EPD()
|
||||||
|
self._display.init()
|
||||||
|
self._display.Clear(0xFF)
|
||||||
|
|
||||||
|
def render(self, canvas):
|
||||||
|
buf = self._display.getbuffer(canvas)
|
||||||
|
self._display.displayPartial(buf)
|
||||||
|
|
||||||
|
def clear(self):
|
||||||
|
self._display.Clear(0xFF)
|
@ -34,10 +34,10 @@ class DisplayHatMini(DisplayImpl):
|
|||||||
def initialize(self):
|
def initialize(self):
|
||||||
logging.info("initializing Display Hat Mini")
|
logging.info("initializing Display Hat Mini")
|
||||||
from pwnagotchi.ui.hw.libs.pimoroni.displayhatmini.ST7789 import ST7789
|
from pwnagotchi.ui.hw.libs.pimoroni.displayhatmini.ST7789 import ST7789
|
||||||
self._display = ST7789(0,1,9,13)
|
self._display = ST7789(0, 1, 9, 13)
|
||||||
|
|
||||||
def render(self, canvas):
|
def render(self, canvas):
|
||||||
self._display.display(canvas)
|
self._display.display(canvas)
|
||||||
|
|
||||||
def clear(self):
|
def clear(self):
|
||||||
self._display.clear()
|
self._display.clear()
|
||||||
|
43
pwnagotchi/ui/hw/dummydisplay.py
Normal file
43
pwnagotchi/ui/hw/dummydisplay.py
Normal file
@ -0,0 +1,43 @@
|
|||||||
|
import logging
|
||||||
|
|
||||||
|
import pwnagotchi.ui.fonts as fonts
|
||||||
|
from pwnagotchi.ui.hw.base import DisplayImpl
|
||||||
|
|
||||||
|
|
||||||
|
class DummyDisplay(DisplayImpl):
|
||||||
|
def __init__(self, config):
|
||||||
|
super(DummyDisplay, self).__init__(config, 'DummyDisplay')
|
||||||
|
|
||||||
|
def layout(self):
|
||||||
|
width = 480 if 'width' not in self.config else self.config['width']
|
||||||
|
height = 720 if 'height' not in self.config else self.config['height']
|
||||||
|
fonts.setup(int(height/30), int(height/40), int(height/30), int(height/6), int(height/30), int(height/35))
|
||||||
|
self._layout['width'] = width
|
||||||
|
self._layout['height'] = height
|
||||||
|
self._layout['face'] = (0, int(width/12))
|
||||||
|
self._layout['name'] = (5, int(width/25))
|
||||||
|
self._layout['channel'] = (0, 0)
|
||||||
|
self._layout['aps'] = (int(width/8), 0)
|
||||||
|
self._layout['uptime'] = (width-int(width/12), 0)
|
||||||
|
self._layout['line1'] = [0, int(height/32), width, int(height/32)]
|
||||||
|
self._layout['line2'] = [0, height-int(height/25)-1, width, height-int(height/25)-1]
|
||||||
|
self._layout['friend_face'] = (0, int(height/10))
|
||||||
|
self._layout['friend_name'] = (int(width/12), int(height/10))
|
||||||
|
self._layout['shakes'] = (0, height-int(height/25))
|
||||||
|
self._layout['mode'] = (width-int(width/8), height - int (height/25))
|
||||||
|
lw, lh = fonts.Small.getsize("W")
|
||||||
|
self._layout['status'] = {
|
||||||
|
'pos': (int(width/48), int(height/3)),
|
||||||
|
'font': fonts.status_font(fonts.Small),
|
||||||
|
'max': int(width / lw)
|
||||||
|
}
|
||||||
|
return self._layout
|
||||||
|
|
||||||
|
def initialize(self):
|
||||||
|
return
|
||||||
|
|
||||||
|
def render(self, canvas):
|
||||||
|
return
|
||||||
|
|
||||||
|
def clear(self):
|
||||||
|
return
|
132
pwnagotchi/ui/hw/libs/adafruit/epdconfig.py
Normal file
132
pwnagotchi/ui/hw/libs/adafruit/epdconfig.py
Normal file
@ -0,0 +1,132 @@
|
|||||||
|
# /*****************************************************************************
|
||||||
|
# * | File : epdconfig.py
|
||||||
|
# * | Author : Waveshare team
|
||||||
|
# * | Function : Hardware underlying interface
|
||||||
|
# * | Info :
|
||||||
|
# *----------------
|
||||||
|
# * | This version: V1.2
|
||||||
|
# * | Date : 2022-10-29
|
||||||
|
# * | Info :
|
||||||
|
# ******************************************************************************
|
||||||
|
# Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
|
# of this software and associated documnetation files (the "Software"), to deal
|
||||||
|
# in the Software without restriction, including without limitation the rights
|
||||||
|
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
|
# copies of the Software, and to permit persons to whom the Software is
|
||||||
|
# furished to do so, subject to the following conditions:
|
||||||
|
#
|
||||||
|
# The above copyright notice and this permission notice shall be included in
|
||||||
|
# all copies or substantial portions of the Software.
|
||||||
|
#
|
||||||
|
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
# FITNESS OR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
# LIABILITY WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
|
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||||
|
# THE SOFTWARE.
|
||||||
|
#
|
||||||
|
|
||||||
|
import os
|
||||||
|
import logging
|
||||||
|
import sys
|
||||||
|
import time
|
||||||
|
import subprocess
|
||||||
|
|
||||||
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
|
class RaspberryPi:
|
||||||
|
# Pin definition
|
||||||
|
RST_PIN = 27
|
||||||
|
DC_PIN = 22
|
||||||
|
CS_PIN = 8
|
||||||
|
BUSY_PIN = 17
|
||||||
|
PWR_PIN = 18
|
||||||
|
|
||||||
|
def __init__(self):
|
||||||
|
import spidev
|
||||||
|
import gpiozero
|
||||||
|
|
||||||
|
self.SPI = spidev.SpiDev()
|
||||||
|
self.GPIO_RST_PIN = gpiozero.LED(self.RST_PIN)
|
||||||
|
self.GPIO_DC_PIN = gpiozero.LED(self.DC_PIN)
|
||||||
|
self.GPIO_CS_PIN = gpiozero.LED(self.CS_PIN)
|
||||||
|
self.GPIO_PWR_PIN = gpiozero.LED(self.PWR_PIN)
|
||||||
|
self.GPIO_BUSY_PIN = gpiozero.Button(self.BUSY_PIN, pull_up=False)
|
||||||
|
|
||||||
|
def digital_write(self, pin, value):
|
||||||
|
if pin == self.RST_PIN:
|
||||||
|
if value:
|
||||||
|
self.GPIO_RST_PIN.on()
|
||||||
|
else:
|
||||||
|
self.GPIO_RST_PIN.off()
|
||||||
|
elif pin == self.DC_PIN:
|
||||||
|
if value:
|
||||||
|
self.GPIO_DC_PIN.on()
|
||||||
|
else:
|
||||||
|
self.GPIO_DC_PIN.off()
|
||||||
|
elif pin == self.CS_PIN:
|
||||||
|
if value:
|
||||||
|
self.GPIO_CS_PIN.on()
|
||||||
|
else:
|
||||||
|
self.GPIO_CS_PIN.off()
|
||||||
|
elif pin == self.PWR_PIN:
|
||||||
|
if value:
|
||||||
|
self.GPIO_PWR_PIN.on()
|
||||||
|
else:
|
||||||
|
self.GPIO_PWR_PIN.off()
|
||||||
|
|
||||||
|
def digital_read(self, pin):
|
||||||
|
if pin == self.BUSY_PIN:
|
||||||
|
return self.GPIO_BUSY_PIN.value
|
||||||
|
elif pin == self.RST_PIN:
|
||||||
|
return self.RST_PIN.value
|
||||||
|
elif pin == self.DC_PIN:
|
||||||
|
return self.DC_PIN.value
|
||||||
|
elif pin == self.CS_PIN:
|
||||||
|
return self.CS_PIN.value
|
||||||
|
elif pin == self.PWR_PIN:
|
||||||
|
return self.PWR_PIN.value
|
||||||
|
|
||||||
|
def delay_ms(self, delaytime):
|
||||||
|
time.sleep(delaytime / 1000.0)
|
||||||
|
|
||||||
|
def spi_writebyte(self, data):
|
||||||
|
self.SPI.writebytes(data)
|
||||||
|
|
||||||
|
def spi_writebyte2(self, data):
|
||||||
|
self.SPI.writebytes2(data)
|
||||||
|
|
||||||
|
def module_init(self):
|
||||||
|
self.GPIO_PWR_PIN.on()
|
||||||
|
|
||||||
|
# SPI device, bus = 0, device = 0
|
||||||
|
self.SPI.open(0, 0)
|
||||||
|
self.SPI.max_speed_hz = 4000000
|
||||||
|
self.SPI.mode = 0b00
|
||||||
|
return 0
|
||||||
|
|
||||||
|
def module_exit(self, cleanup=False):
|
||||||
|
logger.debug("spi end")
|
||||||
|
self.SPI.close()
|
||||||
|
|
||||||
|
self.GPIO_RST_PIN.off()
|
||||||
|
self.GPIO_DC_PIN.off()
|
||||||
|
self.GPIO_PWR_PIN.off()
|
||||||
|
logger.debug("close 5V, Module enters 0 power consumption ...")
|
||||||
|
|
||||||
|
if cleanup:
|
||||||
|
self.GPIO_RST_PIN.close()
|
||||||
|
self.GPIO_DC_PIN.close()
|
||||||
|
self.GPIO_CS_PIN.close()
|
||||||
|
self.GPIO_PWR_PIN.close()
|
||||||
|
self.GPIO_BUSY_PIN.close()
|
||||||
|
|
||||||
|
|
||||||
|
implementation = RaspberryPi()
|
||||||
|
|
||||||
|
for func in [x for x in dir(implementation) if not x.startswith('_')]:
|
||||||
|
setattr(sys.modules[__name__], func, getattr(implementation, func))
|
||||||
|
|
||||||
|
### END OF FILE ###
|
362
pwnagotchi/ui/hw/libs/adafruit/pitft/ILI9341.py
Normal file
362
pwnagotchi/ui/hw/libs/adafruit/pitft/ILI9341.py
Normal file
@ -0,0 +1,362 @@
|
|||||||
|
# Copyright (c) 2014 Adafruit Industries
|
||||||
|
# Author: Tony DiCola
|
||||||
|
#
|
||||||
|
# Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
|
# of this software and associated documentation files (the "Software"), to deal
|
||||||
|
# in the Software without restriction, including without limitation the rights
|
||||||
|
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
|
# copies of the Software, and to permit persons to whom the Software is
|
||||||
|
# furnished to do so, subject to the following conditions:
|
||||||
|
#
|
||||||
|
# The above copyright notice and this permission notice shall be included in
|
||||||
|
# all copies or substantial portions of the Software.
|
||||||
|
#
|
||||||
|
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
|
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||||
|
# THE SOFTWARE.
|
||||||
|
|
||||||
|
# Modified for Pwnagotchi by RasTacsko
|
||||||
|
# Based on ST7899 driver for pimoroni displayhatmini by Do-Ki
|
||||||
|
|
||||||
|
import numbers
|
||||||
|
import time
|
||||||
|
import numpy as np
|
||||||
|
|
||||||
|
from PIL import Image
|
||||||
|
from PIL import ImageDraw
|
||||||
|
|
||||||
|
import spidev
|
||||||
|
import RPi.GPIO as GPIO
|
||||||
|
|
||||||
|
__version__ = '0.0.1'
|
||||||
|
|
||||||
|
# Constants for interacting with display registers.
|
||||||
|
ILI9341_TFTWIDTH = 320
|
||||||
|
ILI9341_TFTHEIGHT = 240
|
||||||
|
|
||||||
|
ILI9341_NOP = 0x00
|
||||||
|
ILI9341_SWRESET = 0x01
|
||||||
|
ILI9341_RDDID = 0x04
|
||||||
|
ILI9341_RDDST = 0x09
|
||||||
|
|
||||||
|
ILI9341_SLPIN = 0x10
|
||||||
|
ILI9341_SLPOUT = 0x11
|
||||||
|
ILI9341_PTLON = 0x12
|
||||||
|
ILI9341_NORON = 0x13
|
||||||
|
|
||||||
|
ILI9341_RDMODE = 0x0A
|
||||||
|
ILI9341_RDMADCTL = 0x0B
|
||||||
|
ILI9341_RDPIXFMT = 0x0C
|
||||||
|
ILI9341_RDIMGFMT = 0x0A
|
||||||
|
ILI9341_RDSELFDIAG = 0x0F
|
||||||
|
|
||||||
|
ILI9341_INVOFF = 0x20
|
||||||
|
ILI9341_INVON = 0x21
|
||||||
|
ILI9341_GAMMASET = 0x26
|
||||||
|
ILI9341_DISPOFF = 0x28
|
||||||
|
ILI9341_DISPON = 0x29
|
||||||
|
|
||||||
|
ILI9341_CASET = 0x2A
|
||||||
|
ILI9341_PASET = 0x2B
|
||||||
|
ILI9341_RAMWR = 0x2C
|
||||||
|
ILI9341_RAMRD = 0x2E
|
||||||
|
|
||||||
|
ILI9341_PTLAR = 0x30
|
||||||
|
ILI9341_MADCTL = 0x36
|
||||||
|
ILI9341_PIXFMT = 0x3A
|
||||||
|
|
||||||
|
ILI9341_FRMCTR1 = 0xB1
|
||||||
|
ILI9341_FRMCTR2 = 0xB2
|
||||||
|
ILI9341_FRMCTR3 = 0xB3
|
||||||
|
ILI9341_INVCTR = 0xB4
|
||||||
|
ILI9341_DFUNCTR = 0xB6
|
||||||
|
|
||||||
|
ILI9341_PWCTR1 = 0xC0
|
||||||
|
ILI9341_PWCTR2 = 0xC1
|
||||||
|
ILI9341_PWCTR3 = 0xC2
|
||||||
|
ILI9341_PWCTR4 = 0xC3
|
||||||
|
ILI9341_PWCTR5 = 0xC4
|
||||||
|
ILI9341_VMCTR1 = 0xC5
|
||||||
|
ILI9341_VMCTR2 = 0xC7
|
||||||
|
|
||||||
|
ILI9341_RDID1 = 0xDA
|
||||||
|
ILI9341_RDID2 = 0xDB
|
||||||
|
ILI9341_RDID3 = 0xDC
|
||||||
|
ILI9341_RDID4 = 0xDD
|
||||||
|
|
||||||
|
ILI9341_GMCTRP1 = 0xE0
|
||||||
|
ILI9341_GMCTRN1 = 0xE1
|
||||||
|
|
||||||
|
ILI9341_PWCTR6 = 0xFC
|
||||||
|
|
||||||
|
class ILI9341(object):
|
||||||
|
"""Representation of an ILI9341 TFT LCD."""
|
||||||
|
|
||||||
|
def __init__(self, port, cs, dc, backlight, rst=None,
|
||||||
|
width=ILI9341_TFTWIDTH, height=ILI9341_TFTHEIGHT,
|
||||||
|
rotation=270, invert=False, spi_speed_hz=64000000,
|
||||||
|
offset_left=0, offset_top=0):
|
||||||
|
"""Create an instance of the display using SPI communication.
|
||||||
|
Must provide the GPIO pin number for the D/C pin and the SPI driver.
|
||||||
|
Can optionally provide the GPIO pin number for the reset pin as the rst parameter.
|
||||||
|
:param port: SPI port number -> 0
|
||||||
|
:param cs: SPI chip-select number (0 or 1 for BCM) -> 1
|
||||||
|
:param backlight: Pin for controlling backlight -> 18
|
||||||
|
:param rst: Reset pin for ILI9341 -> 24?
|
||||||
|
:param width: Width of display connected to ILI9341 -> 240
|
||||||
|
:param height: Height of display connected to ILI9341 -> 320
|
||||||
|
:param rotation: Rotation of display connected to ILI9341
|
||||||
|
:param invert: Invert display
|
||||||
|
:param spi_speed_hz: SPI speed (in Hz)
|
||||||
|
"""
|
||||||
|
|
||||||
|
if rotation not in [0, 90, 180, 270]:
|
||||||
|
raise ValueError("Invalid rotation {}".format(rotation))
|
||||||
|
|
||||||
|
GPIO.setwarnings(False)
|
||||||
|
GPIO.setmode(GPIO.BCM)
|
||||||
|
|
||||||
|
self._spi = spidev.SpiDev(port, cs)
|
||||||
|
self._spi.mode = 0
|
||||||
|
self._spi.lsbfirst = False
|
||||||
|
self._spi.max_speed_hz = spi_speed_hz
|
||||||
|
|
||||||
|
self._dc = dc
|
||||||
|
self._rst = rst
|
||||||
|
self._width = width
|
||||||
|
self._height = height
|
||||||
|
self._rotation = rotation
|
||||||
|
self._invert = invert
|
||||||
|
|
||||||
|
self._offset_left = offset_left
|
||||||
|
self._offset_top = offset_top
|
||||||
|
|
||||||
|
# Set DC as output.
|
||||||
|
GPIO.setup(dc, GPIO.OUT)
|
||||||
|
|
||||||
|
# Setup backlight as output (if provided).
|
||||||
|
self._backlight = backlight
|
||||||
|
if backlight is not None:
|
||||||
|
GPIO.setup(backlight, GPIO.OUT)
|
||||||
|
GPIO.output(backlight, GPIO.LOW)
|
||||||
|
time.sleep(0.05)
|
||||||
|
GPIO.output(backlight, GPIO.HIGH)
|
||||||
|
|
||||||
|
# Setup reset as output (if provided).
|
||||||
|
if rst is not None:
|
||||||
|
GPIO.setup(self._rst, GPIO.OUT)
|
||||||
|
self.reset()
|
||||||
|
|
||||||
|
# Create an image buffer.
|
||||||
|
self.buffer = Image.new('RGB', (width, height))
|
||||||
|
|
||||||
|
self._init()
|
||||||
|
|
||||||
|
def send(self, data, is_data=True, chunk_size=4096):
|
||||||
|
"""Write a byte or array of bytes to the display. Is_data parameter
|
||||||
|
controls if byte should be interpreted as display data (True) or command
|
||||||
|
data (False). Chunk_size is an optional size of bytes to write in a
|
||||||
|
single SPI transaction, with a default of 4096.
|
||||||
|
"""
|
||||||
|
# Set DC low for command, high for data.
|
||||||
|
GPIO.output(self._dc, is_data)
|
||||||
|
# Convert scalar argument to list so either can be passed as parameter.
|
||||||
|
if isinstance(data, numbers.Number):
|
||||||
|
data = [data & 0xFF]
|
||||||
|
# Write data a chunk at a time.
|
||||||
|
for start in range(0, len(data), chunk_size):
|
||||||
|
end = min(start+chunk_size, len(data))
|
||||||
|
self._spi.xfer(data[start:end])
|
||||||
|
|
||||||
|
def set_backlight(self, value):
|
||||||
|
"""Set the backlight on/off."""
|
||||||
|
if self._backlight is not None:
|
||||||
|
GPIO.output(self._backlight, value)
|
||||||
|
|
||||||
|
@property
|
||||||
|
def width(self):
|
||||||
|
return self._width if self._rotation == 0 or self._rotation == 180 else self._height
|
||||||
|
|
||||||
|
@property
|
||||||
|
def height(self):
|
||||||
|
return self._height if self._rotation == 0 or self._rotation == 180 else self._width
|
||||||
|
|
||||||
|
def command(self, data):
|
||||||
|
"""Write a byte or array of bytes to the display as command data."""
|
||||||
|
self.send(data, False)
|
||||||
|
|
||||||
|
def data(self, data):
|
||||||
|
"""Write a byte or array of bytes to the display as display data."""
|
||||||
|
self.send(data, True)
|
||||||
|
|
||||||
|
def reset(self):
|
||||||
|
"""Reset the display, if reset pin is connected."""
|
||||||
|
if self._rst is not None:
|
||||||
|
GPIO.output(self._rst, 1)
|
||||||
|
time.sleep(0.005)
|
||||||
|
GPIO.output(self._rst, 0)
|
||||||
|
time.sleep(0.02)
|
||||||
|
GPIO.output(self._rst, 1)
|
||||||
|
time.sleep(0.150)
|
||||||
|
|
||||||
|
def _init(self):
|
||||||
|
# Initialize the display. Broken out as a separate function so it can
|
||||||
|
# be overridden by other displays in the future.
|
||||||
|
self.command(0xEF)
|
||||||
|
self.data(0x03)
|
||||||
|
self.data(0x80)
|
||||||
|
self.data(0x02)
|
||||||
|
self.command(0xCF)
|
||||||
|
self.data(0x00)
|
||||||
|
self.data(0XC1)
|
||||||
|
self.data(0X30)
|
||||||
|
self.command(0xED)
|
||||||
|
self.data(0x64)
|
||||||
|
self.data(0x03)
|
||||||
|
self.data(0X12)
|
||||||
|
self.data(0X81)
|
||||||
|
self.command(0xE8)
|
||||||
|
self.data(0x85)
|
||||||
|
self.data(0x00)
|
||||||
|
self.data(0x78)
|
||||||
|
self.command(0xCB)
|
||||||
|
self.data(0x39)
|
||||||
|
self.data(0x2C)
|
||||||
|
self.data(0x00)
|
||||||
|
self.data(0x34)
|
||||||
|
self.data(0x02)
|
||||||
|
self.command(0xF7)
|
||||||
|
self.data(0x20)
|
||||||
|
self.command(0xEA)
|
||||||
|
self.data(0x00)
|
||||||
|
self.data(0x00)
|
||||||
|
self.command(ILI9341_PWCTR1) # Power control
|
||||||
|
self.data(0x23) # VRH[5:0]
|
||||||
|
self.command(ILI9341_PWCTR2) # Power control
|
||||||
|
self.data(0x10) # SAP[2:0];BT[3:0]
|
||||||
|
self.command(ILI9341_VMCTR1) # VCM control
|
||||||
|
self.data(0x3e)
|
||||||
|
self.data(0x28)
|
||||||
|
self.command(ILI9341_VMCTR2) # VCM control2
|
||||||
|
self.data(0x86) # --
|
||||||
|
self.command(ILI9341_MADCTL) # Memory Access Control
|
||||||
|
self.data(0x48)
|
||||||
|
self.command(ILI9341_PIXFMT)
|
||||||
|
self.data(0x55)
|
||||||
|
self.command(ILI9341_FRMCTR1)
|
||||||
|
self.data(0x00)
|
||||||
|
self.data(0x18)
|
||||||
|
self.command(ILI9341_DFUNCTR) # Display Function Control
|
||||||
|
self.data(0x08)
|
||||||
|
self.data(0x82)
|
||||||
|
self.data(0x27)
|
||||||
|
self.command(0xF2) # 3Gamma Function Disable
|
||||||
|
self.data(0x00)
|
||||||
|
self.command(ILI9341_GAMMASET) # Gamma curve selected
|
||||||
|
self.data(0x01)
|
||||||
|
self.command(ILI9341_GMCTRP1) # Set Gamma
|
||||||
|
self.data(0x0F)
|
||||||
|
self.data(0x31)
|
||||||
|
self.data(0x2B)
|
||||||
|
self.data(0x0C)
|
||||||
|
self.data(0x0E)
|
||||||
|
self.data(0x08)
|
||||||
|
self.data(0x4E)
|
||||||
|
self.data(0xF1)
|
||||||
|
self.data(0x37)
|
||||||
|
self.data(0x07)
|
||||||
|
self.data(0x10)
|
||||||
|
self.data(0x03)
|
||||||
|
self.data(0x0E)
|
||||||
|
self.data(0x09)
|
||||||
|
self.data(0x00)
|
||||||
|
self.command(ILI9341_GMCTRN1) # Set Gamma
|
||||||
|
self.data(0x00)
|
||||||
|
self.data(0x0E)
|
||||||
|
self.data(0x14)
|
||||||
|
self.data(0x03)
|
||||||
|
self.data(0x11)
|
||||||
|
self.data(0x07)
|
||||||
|
self.data(0x31)
|
||||||
|
self.data(0xC1)
|
||||||
|
self.data(0x48)
|
||||||
|
self.data(0x08)
|
||||||
|
self.data(0x0F)
|
||||||
|
self.data(0x0C)
|
||||||
|
self.data(0x31)
|
||||||
|
self.data(0x36)
|
||||||
|
self.data(0x0F)
|
||||||
|
if self._invert:
|
||||||
|
self.command(ILI9341_INVON) # Invert display
|
||||||
|
else:
|
||||||
|
self.command(ILI9341_INVOFF) # Don't invert display
|
||||||
|
self.command(ILI9341_SLPOUT) # Exit Sleep
|
||||||
|
time.sleep(0.120)
|
||||||
|
self.command(ILI9341_DISPON) # Display on
|
||||||
|
|
||||||
|
def begin(self):
|
||||||
|
"""Set up the display deprecated.
|
||||||
|
Included in __init__. """
|
||||||
|
pass
|
||||||
|
|
||||||
|
def set_window(self, x0=0, y0=0, x1=None, y1=None):
|
||||||
|
"""Set the pixel address window for proceeding drawing commands. x0 and
|
||||||
|
x1 should define the minimum and maximum x pixel bounds. y0 and y1
|
||||||
|
should define the minimum and maximum y pixel bound. If no parameters
|
||||||
|
are specified the default will be to update the entire display from 0,0
|
||||||
|
to 239,319.
|
||||||
|
"""
|
||||||
|
if x1 is None:
|
||||||
|
x1 = self.width-1
|
||||||
|
if y1 is None:
|
||||||
|
y1 = self.height-1
|
||||||
|
|
||||||
|
self.command(ILI9341_CASET) # Column addr set
|
||||||
|
self.data(x0 >> 8)
|
||||||
|
self.data(x0 & 0xFF) # XSTART
|
||||||
|
self.data(x1 >> 8)
|
||||||
|
self.data(x1 & 0xFF) # XEND
|
||||||
|
self.command(ILI9341_PASET) # Row addr set
|
||||||
|
self.data(y0 >> 8)
|
||||||
|
self.data(y0 & 0xFF) # YSTART
|
||||||
|
self.data(y1 >> 8)
|
||||||
|
self.data(y1 & 0xFF) # YEND
|
||||||
|
self.command(ILI9341_RAMWR) # write to RAM
|
||||||
|
|
||||||
|
def display(self, image):
|
||||||
|
"""Write the provided image to the hardware.
|
||||||
|
:param image: Should be RGB format and the same dimensions as the display hardware.
|
||||||
|
"""
|
||||||
|
# Set address bounds to entire display.
|
||||||
|
self.set_window()
|
||||||
|
|
||||||
|
# Convert image to 16bit RGB565 format and
|
||||||
|
# flatten into bytes.
|
||||||
|
pixelbytes = self.image_to_data(image, self._rotation)
|
||||||
|
|
||||||
|
# Write data to hardware.
|
||||||
|
for i in range(0, len(pixelbytes), 4096):
|
||||||
|
self.data(pixelbytes[i:i + 4096])
|
||||||
|
|
||||||
|
def image_to_data(self, image, rotation=0):
|
||||||
|
if not isinstance(image, np.ndarray):
|
||||||
|
image = np.array(image.convert('RGB'))
|
||||||
|
|
||||||
|
# Rotate the image
|
||||||
|
pb = np.rot90(image, rotation // 90).astype('uint16')
|
||||||
|
|
||||||
|
|
||||||
|
# Mask and shift the 888 RGB into 565 RGB
|
||||||
|
red = (pb[..., [0]] & 0xf8) << 8
|
||||||
|
green = (pb[..., [1]] & 0xfc) << 3
|
||||||
|
blue = (pb[..., [2]] & 0xf8) >> 3
|
||||||
|
|
||||||
|
# Stick 'em together
|
||||||
|
result = red | green | blue
|
||||||
|
|
||||||
|
# Output the raw bytes
|
||||||
|
return result.byteswap().tobytes()
|
360
pwnagotchi/ui/hw/libs/adafruit/tftbonnet/ST7789.py
Normal file
360
pwnagotchi/ui/hw/libs/adafruit/tftbonnet/ST7789.py
Normal file
@ -0,0 +1,360 @@
|
|||||||
|
# Copyright (c) 2014 Adafruit Industries
|
||||||
|
# Author: Tony DiCola
|
||||||
|
#
|
||||||
|
# Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
|
# of this software and associated documentation files (the "Software"), to deal
|
||||||
|
# in the Software without restriction, including without limitation the rights
|
||||||
|
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
|
# copies of the Software, and to permit persons to whom the Software is
|
||||||
|
# furnished to do so, subject to the following conditions:
|
||||||
|
#
|
||||||
|
# The above copyright notice and this permission notice shall be included in
|
||||||
|
# all copies or substantial portions of the Software.
|
||||||
|
#
|
||||||
|
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
|
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||||
|
# THE SOFTWARE.
|
||||||
|
import numbers
|
||||||
|
import time
|
||||||
|
import numpy as np
|
||||||
|
|
||||||
|
import spidev
|
||||||
|
import RPi.GPIO as GPIO
|
||||||
|
|
||||||
|
|
||||||
|
__version__ = '0.0.4'
|
||||||
|
|
||||||
|
BG_SPI_CS_BACK = 0
|
||||||
|
BG_SPI_CS_FRONT = 1
|
||||||
|
|
||||||
|
SPI_CLOCK_HZ = 16000000
|
||||||
|
|
||||||
|
ST7789_NOP = 0x00
|
||||||
|
ST7789_SWRESET = 0x01
|
||||||
|
ST7789_RDDID = 0x04
|
||||||
|
ST7789_RDDST = 0x09
|
||||||
|
|
||||||
|
ST7789_SLPIN = 0x10
|
||||||
|
ST7789_SLPOUT = 0x11
|
||||||
|
ST7789_PTLON = 0x12
|
||||||
|
ST7789_NORON = 0x13
|
||||||
|
|
||||||
|
ST7789_INVOFF = 0x20
|
||||||
|
ST7789_INVON = 0x21
|
||||||
|
ST7789_DISPOFF = 0x28
|
||||||
|
ST7789_DISPON = 0x29
|
||||||
|
|
||||||
|
ST7789_CASET = 0x2A
|
||||||
|
ST7789_RASET = 0x2B
|
||||||
|
ST7789_RAMWR = 0x2C
|
||||||
|
ST7789_RAMRD = 0x2E
|
||||||
|
|
||||||
|
ST7789_PTLAR = 0x30
|
||||||
|
ST7789_MADCTL = 0x36
|
||||||
|
ST7789_COLMOD = 0x3A
|
||||||
|
|
||||||
|
ST7789_FRMCTR1 = 0xB1
|
||||||
|
ST7789_FRMCTR2 = 0xB2
|
||||||
|
ST7789_FRMCTR3 = 0xB3
|
||||||
|
ST7789_INVCTR = 0xB4
|
||||||
|
ST7789_DISSET5 = 0xB6
|
||||||
|
|
||||||
|
ST7789_GCTRL = 0xB7
|
||||||
|
ST7789_GTADJ = 0xB8
|
||||||
|
ST7789_VCOMS = 0xBB
|
||||||
|
|
||||||
|
ST7789_LCMCTRL = 0xC0
|
||||||
|
ST7789_IDSET = 0xC1
|
||||||
|
ST7789_VDVVRHEN = 0xC2
|
||||||
|
ST7789_VRHS = 0xC3
|
||||||
|
ST7789_VDVS = 0xC4
|
||||||
|
ST7789_VMCTR1 = 0xC5
|
||||||
|
ST7789_FRCTRL2 = 0xC6
|
||||||
|
ST7789_CABCCTRL = 0xC7
|
||||||
|
|
||||||
|
ST7789_RDID1 = 0xDA
|
||||||
|
ST7789_RDID2 = 0xDB
|
||||||
|
ST7789_RDID3 = 0xDC
|
||||||
|
ST7789_RDID4 = 0xDD
|
||||||
|
|
||||||
|
ST7789_GMCTRP1 = 0xE0
|
||||||
|
ST7789_GMCTRN1 = 0xE1
|
||||||
|
|
||||||
|
ST7789_PWCTR6 = 0xFC
|
||||||
|
|
||||||
|
|
||||||
|
class ST7789(object):
|
||||||
|
"""Representation of an ST7789 TFT LCD."""
|
||||||
|
|
||||||
|
def __init__(self, port, cs, dc, backlight, rst=None, width=240,
|
||||||
|
height=240, rotation=90, invert=True, spi_speed_hz=60 * 1000 * 1000,
|
||||||
|
offset_left=0,
|
||||||
|
offset_top=0):
|
||||||
|
"""Create an instance of the display using SPI communication.
|
||||||
|
|
||||||
|
Must provide the GPIO pin number for the D/C pin and the SPI driver.
|
||||||
|
|
||||||
|
Can optionally provide the GPIO pin number for the reset pin as the rst parameter.
|
||||||
|
|
||||||
|
:param port: SPI port number
|
||||||
|
:param cs: SPI chip-select number (0 or 1 for BCM
|
||||||
|
:param backlight: Pin for controlling backlight
|
||||||
|
:param rst: Reset pin for ST7789
|
||||||
|
:param width: Width of display connected to ST7789
|
||||||
|
:param height: Height of display connected to ST7789
|
||||||
|
:param rotation: Rotation of display connected to ST7789
|
||||||
|
:param invert: Invert display
|
||||||
|
:param spi_speed_hz: SPI speed (in Hz)
|
||||||
|
|
||||||
|
"""
|
||||||
|
if rotation not in [0, 90, 180, 270]:
|
||||||
|
raise ValueError("Invalid rotation {}".format(rotation))
|
||||||
|
|
||||||
|
if width != height and rotation in [90, 270]:
|
||||||
|
raise ValueError("Invalid rotation {} for {}x{} resolution".format(rotation, width, height))
|
||||||
|
|
||||||
|
GPIO.setwarnings(False)
|
||||||
|
GPIO.setmode(GPIO.BCM)
|
||||||
|
|
||||||
|
self._spi = spidev.SpiDev(port, cs)
|
||||||
|
self._spi.mode = 0
|
||||||
|
self._spi.lsbfirst = False
|
||||||
|
self._spi.max_speed_hz = spi_speed_hz
|
||||||
|
|
||||||
|
self._dc = dc
|
||||||
|
self._rst = rst
|
||||||
|
self._width = width
|
||||||
|
self._height = height
|
||||||
|
self._rotation = rotation
|
||||||
|
self._invert = invert
|
||||||
|
|
||||||
|
self._offset_left = offset_left
|
||||||
|
self._offset_top = offset_top
|
||||||
|
|
||||||
|
# Set DC as output.
|
||||||
|
GPIO.setup(dc, GPIO.OUT)
|
||||||
|
|
||||||
|
# Setup backlight as output (if provided).
|
||||||
|
self._backlight = backlight
|
||||||
|
if backlight is not None:
|
||||||
|
GPIO.setup(backlight, GPIO.OUT)
|
||||||
|
GPIO.output(backlight, GPIO.LOW)
|
||||||
|
time.sleep(0.1)
|
||||||
|
GPIO.output(backlight, GPIO.HIGH)
|
||||||
|
|
||||||
|
# Setup reset as output (if provided).
|
||||||
|
if rst is not None:
|
||||||
|
GPIO.setup(self._rst, GPIO.OUT)
|
||||||
|
self.reset()
|
||||||
|
self._init()
|
||||||
|
|
||||||
|
def send(self, data, is_data=True, chunk_size=4096):
|
||||||
|
"""Write a byte or array of bytes to the display. Is_data parameter
|
||||||
|
controls if byte should be interpreted as display data (True) or command
|
||||||
|
data (False). Chunk_size is an optional size of bytes to write in a
|
||||||
|
single SPI transaction, with a default of 4096.
|
||||||
|
"""
|
||||||
|
# Set DC low for command, high for data.
|
||||||
|
GPIO.output(self._dc, is_data)
|
||||||
|
# Convert scalar argument to list so either can be passed as parameter.
|
||||||
|
if isinstance(data, numbers.Number):
|
||||||
|
data = [data & 0xFF]
|
||||||
|
# Write data a chunk at a time.
|
||||||
|
for start in range(0, len(data), chunk_size):
|
||||||
|
end = min(start + chunk_size, len(data))
|
||||||
|
self._spi.xfer(data[start:end])
|
||||||
|
|
||||||
|
def set_backlight(self, value):
|
||||||
|
"""Set the backlight on/off."""
|
||||||
|
if self._backlight is not None:
|
||||||
|
GPIO.output(self._backlight, value)
|
||||||
|
|
||||||
|
@property
|
||||||
|
def width(self):
|
||||||
|
return self._width if self._rotation == 0 or self._rotation == 180 else self._height
|
||||||
|
|
||||||
|
@property
|
||||||
|
def height(self):
|
||||||
|
return self._height if self._rotation == 0 or self._rotation == 180 else self._width
|
||||||
|
|
||||||
|
def command(self, data):
|
||||||
|
"""Write a byte or array of bytes to the display as command data."""
|
||||||
|
self.send(data, False)
|
||||||
|
|
||||||
|
def data(self, data):
|
||||||
|
"""Write a byte or array of bytes to the display as display data."""
|
||||||
|
self.send(data, True)
|
||||||
|
|
||||||
|
def reset(self):
|
||||||
|
"""Reset the display, if reset pin is connected."""
|
||||||
|
if self._rst is not None:
|
||||||
|
GPIO.output(self._rst, 1)
|
||||||
|
time.sleep(0.500)
|
||||||
|
GPIO.output(self._rst, 0)
|
||||||
|
time.sleep(0.500)
|
||||||
|
GPIO.output(self._rst, 1)
|
||||||
|
time.sleep(0.500)
|
||||||
|
|
||||||
|
def _init(self):
|
||||||
|
# Initialize the display.
|
||||||
|
|
||||||
|
self.command(ST7789_SWRESET) # Software reset
|
||||||
|
time.sleep(0.150) # delay 150 ms
|
||||||
|
|
||||||
|
self.command(ST7789_MADCTL)
|
||||||
|
self.data(0x70)
|
||||||
|
|
||||||
|
self.command(ST7789_FRMCTR2) # Frame rate ctrl - idle mode
|
||||||
|
self.data(0x0C)
|
||||||
|
self.data(0x0C)
|
||||||
|
self.data(0x00)
|
||||||
|
self.data(0x33)
|
||||||
|
self.data(0x33)
|
||||||
|
|
||||||
|
self.command(ST7789_COLMOD)
|
||||||
|
self.data(0x05)
|
||||||
|
|
||||||
|
self.command(ST7789_GCTRL)
|
||||||
|
self.data(0x14)
|
||||||
|
|
||||||
|
self.command(ST7789_VCOMS)
|
||||||
|
self.data(0x37)
|
||||||
|
|
||||||
|
self.command(ST7789_LCMCTRL) # Power control
|
||||||
|
self.data(0x2C)
|
||||||
|
|
||||||
|
self.command(ST7789_VDVVRHEN) # Power control
|
||||||
|
self.data(0x01)
|
||||||
|
|
||||||
|
self.command(ST7789_VRHS) # Power control
|
||||||
|
self.data(0x12)
|
||||||
|
|
||||||
|
self.command(ST7789_VDVS) # Power control
|
||||||
|
self.data(0x20)
|
||||||
|
|
||||||
|
self.command(0xD0)
|
||||||
|
self.data(0xA4)
|
||||||
|
self.data(0xA1)
|
||||||
|
|
||||||
|
self.command(ST7789_FRCTRL2)
|
||||||
|
self.data(0x0F)
|
||||||
|
|
||||||
|
self.command(ST7789_GMCTRP1) # Set Gamma
|
||||||
|
self.data(0xD0)
|
||||||
|
self.data(0x04)
|
||||||
|
self.data(0x0D)
|
||||||
|
self.data(0x11)
|
||||||
|
self.data(0x13)
|
||||||
|
self.data(0x2B)
|
||||||
|
self.data(0x3F)
|
||||||
|
self.data(0x54)
|
||||||
|
self.data(0x4C)
|
||||||
|
self.data(0x18)
|
||||||
|
self.data(0x0D)
|
||||||
|
self.data(0x0B)
|
||||||
|
self.data(0x1F)
|
||||||
|
self.data(0x23)
|
||||||
|
|
||||||
|
self.command(ST7789_GMCTRN1) # Set Gamma
|
||||||
|
self.data(0xD0)
|
||||||
|
self.data(0x04)
|
||||||
|
self.data(0x0C)
|
||||||
|
self.data(0x11)
|
||||||
|
self.data(0x13)
|
||||||
|
self.data(0x2C)
|
||||||
|
self.data(0x3F)
|
||||||
|
self.data(0x44)
|
||||||
|
self.data(0x51)
|
||||||
|
self.data(0x2F)
|
||||||
|
self.data(0x1F)
|
||||||
|
self.data(0x1F)
|
||||||
|
self.data(0x20)
|
||||||
|
self.data(0x23)
|
||||||
|
|
||||||
|
if self._invert:
|
||||||
|
self.command(ST7789_INVON) # Invert display
|
||||||
|
else:
|
||||||
|
self.command(ST7789_INVOFF) # Don't invert display
|
||||||
|
|
||||||
|
self.command(ST7789_SLPOUT)
|
||||||
|
|
||||||
|
self.command(ST7789_DISPON) # Display on
|
||||||
|
time.sleep(0.100) # 100 ms
|
||||||
|
|
||||||
|
def begin(self):
|
||||||
|
"""Set up the display
|
||||||
|
|
||||||
|
Deprecated. Included in __init__.
|
||||||
|
|
||||||
|
"""
|
||||||
|
pass
|
||||||
|
|
||||||
|
def set_window(self, x0=0, y0=0, x1=None, y1=None):
|
||||||
|
"""Set the pixel address window for proceeding drawing commands. x0 and
|
||||||
|
x1 should define the minimum and maximum x pixel bounds. y0 and y1
|
||||||
|
should define the minimum and maximum y pixel bound. If no parameters
|
||||||
|
are specified the default will be to update the entire display from 0,0
|
||||||
|
to width-1,height-1.
|
||||||
|
"""
|
||||||
|
if x1 is None:
|
||||||
|
x1 = self._width - 1
|
||||||
|
|
||||||
|
if y1 is None:
|
||||||
|
y1 = self._height - 1
|
||||||
|
|
||||||
|
y0 += self._offset_top
|
||||||
|
y1 += self._offset_top
|
||||||
|
|
||||||
|
x0 += self._offset_left
|
||||||
|
x1 += self._offset_left
|
||||||
|
|
||||||
|
self.command(ST7789_CASET) # Column addr set
|
||||||
|
self.data(x0 >> 8)
|
||||||
|
self.data(x0 & 0xFF) # XSTART
|
||||||
|
self.data(x1 >> 8)
|
||||||
|
self.data(x1 & 0xFF) # XEND
|
||||||
|
self.command(ST7789_RASET) # Row addr set
|
||||||
|
self.data(y0 >> 8)
|
||||||
|
self.data(y0 & 0xFF) # YSTART
|
||||||
|
self.data(y1 >> 8)
|
||||||
|
self.data(y1 & 0xFF) # YEND
|
||||||
|
self.command(ST7789_RAMWR) # write to RAM
|
||||||
|
|
||||||
|
def display(self, image):
|
||||||
|
"""Write the provided image to the hardware.
|
||||||
|
|
||||||
|
:param image: Should be RGB format and the same dimensions as the display hardware.
|
||||||
|
|
||||||
|
"""
|
||||||
|
# Set address bounds to entire display.
|
||||||
|
self.set_window()
|
||||||
|
|
||||||
|
# Convert image to 16bit RGB565 format and
|
||||||
|
# flatten into bytes.
|
||||||
|
pixelbytes = self.image_to_data(image, self._rotation)
|
||||||
|
|
||||||
|
# Write data to hardware.
|
||||||
|
for i in range(0, len(pixelbytes), 4096):
|
||||||
|
self.data(pixelbytes[i:i + 4096])
|
||||||
|
|
||||||
|
def image_to_data(self, image, rotation=0):
|
||||||
|
if not isinstance(image, np.ndarray):
|
||||||
|
image = np.array(image.convert('RGB'))
|
||||||
|
|
||||||
|
# Rotate the image
|
||||||
|
pb = np.rot90(image, rotation // 90).astype('uint16')
|
||||||
|
|
||||||
|
# Mask and shift the 888 RGB into 565 RGB
|
||||||
|
red = (pb[..., [0]] & 0xf8) << 8
|
||||||
|
green = (pb[..., [1]] & 0xfc) << 3
|
||||||
|
blue = (pb[..., [2]] & 0xf8) >> 3
|
||||||
|
|
||||||
|
# Stick 'em together
|
||||||
|
result = red | green | blue
|
||||||
|
|
||||||
|
# Output the raw bytes
|
||||||
|
return result.byteswap().tobytes()
|
@ -4,8 +4,8 @@
|
|||||||
# * | Function : Electronic paper driver
|
# * | Function : Electronic paper driver
|
||||||
# * | Info :
|
# * | Info :
|
||||||
# *----------------
|
# *----------------
|
||||||
# * | This version: V1.1
|
# * | This version: V1.2
|
||||||
# * | Date : 2021-10-30
|
# * | Date : 2022-08-9
|
||||||
# # | Info : python demo
|
# # | Info : python demo
|
||||||
# -----------------------------------------------------------------------------
|
# -----------------------------------------------------------------------------
|
||||||
# Permission is hereby granted, free of charge, to any person obtaining a copy
|
# Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
@ -29,15 +29,15 @@
|
|||||||
|
|
||||||
|
|
||||||
import logging
|
import logging
|
||||||
from . import epdconfig
|
from .. import epdconfig
|
||||||
import numpy as np
|
|
||||||
|
|
||||||
# Display resolution
|
# Display resolution
|
||||||
EPD_WIDTH = 122
|
EPD_WIDTH = 122
|
||||||
EPD_HEIGHT = 250
|
EPD_HEIGHT = 250
|
||||||
|
|
||||||
logger = logging.getLogger(__name__)
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
class EPD:
|
class EPD:
|
||||||
def __init__(self):
|
def __init__(self):
|
||||||
self.reset_pin = epdconfig.RST_PIN
|
self.reset_pin = epdconfig.RST_PIN
|
||||||
@ -46,68 +46,70 @@ class EPD:
|
|||||||
self.cs_pin = epdconfig.CS_PIN
|
self.cs_pin = epdconfig.CS_PIN
|
||||||
self.width = EPD_WIDTH
|
self.width = EPD_WIDTH
|
||||||
self.height = EPD_HEIGHT
|
self.height = EPD_HEIGHT
|
||||||
|
|
||||||
lut_partial_update= [
|
lut_partial_update = [
|
||||||
0x0,0x40,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,
|
0x0, 0x40, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
|
||||||
0x80,0x80,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,
|
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, 0x80, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
|
||||||
0x0,0x0,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,
|
0x14, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
|
||||||
0x1,0x0,0x0,0x0,0x0,0x0,0x0,
|
0x1, 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,
|
0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
|
||||||
0x0,0x0,0x0,0x0,0x0,0x0,0x0,
|
0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
|
||||||
0x0,0x0,0x0,0x0,0x0,0x0,0x0,
|
0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
|
||||||
0x0,0x0,0x0,0x0,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, 0x22, 0x22, 0x22, 0x22, 0x22, 0x0, 0x0, 0x0,
|
||||||
0x22,0x17,0x41,0x00,0x32,0x36,
|
0x22, 0x17, 0x41, 0x00, 0x32, 0x36,
|
||||||
]
|
]
|
||||||
|
|
||||||
lut_full_update = [
|
lut_full_update = [
|
||||||
0x80,0x4A,0x40,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,
|
0x40, 0x4A, 0x80, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
|
||||||
0x80,0x4A,0x40,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,
|
0x40, 0x4A, 0x80, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
|
||||||
0x0,0x0,0x0,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, 0x0, 0x0, 0x0, 0x0,
|
||||||
0xF,0x0,0x0,0xF,0x0,0x0,0x2,
|
0xF, 0x0, 0x0, 0xF, 0x0, 0x0, 0x2,
|
||||||
0xF,0x0,0x0,0x0,0x0,0x0,0x0,
|
0xF, 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,
|
0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
|
||||||
0x0,0x0,0x0,0x0,0x0,0x0,0x0,
|
0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
|
||||||
0x0,0x0,0x0,0x0,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, 0x22, 0x22, 0x22, 0x22, 0x22, 0x0, 0x0, 0x0,
|
||||||
0x22,0x17,0x41,0x0,0x32,0x36,
|
0x22, 0x17, 0x41, 0x0, 0x32, 0x36,
|
||||||
]
|
]
|
||||||
|
|
||||||
'''
|
'''
|
||||||
function :Hardware reset
|
function :Hardware reset
|
||||||
parameter:
|
parameter:
|
||||||
'''
|
'''
|
||||||
|
|
||||||
def reset(self):
|
def reset(self):
|
||||||
epdconfig.digital_write(self.reset_pin, 1)
|
epdconfig.digital_write(self.reset_pin, 1)
|
||||||
epdconfig.delay_ms(20)
|
epdconfig.delay_ms(20)
|
||||||
epdconfig.digital_write(self.reset_pin, 0)
|
epdconfig.digital_write(self.reset_pin, 0)
|
||||||
epdconfig.delay_ms(2)
|
epdconfig.delay_ms(2)
|
||||||
epdconfig.digital_write(self.reset_pin, 1)
|
epdconfig.digital_write(self.reset_pin, 1)
|
||||||
epdconfig.delay_ms(20)
|
epdconfig.delay_ms(20)
|
||||||
|
|
||||||
'''
|
'''
|
||||||
function :send command
|
function :send command
|
||||||
parameter:
|
parameter:
|
||||||
command : Command register
|
command : Command register
|
||||||
'''
|
'''
|
||||||
|
|
||||||
def send_command(self, command):
|
def send_command(self, command):
|
||||||
epdconfig.digital_write(self.dc_pin, 0)
|
epdconfig.digital_write(self.dc_pin, 0)
|
||||||
epdconfig.digital_write(self.cs_pin, 0)
|
epdconfig.digital_write(self.cs_pin, 0)
|
||||||
@ -119,71 +121,84 @@ class EPD:
|
|||||||
parameter:
|
parameter:
|
||||||
data : Write data
|
data : Write data
|
||||||
'''
|
'''
|
||||||
|
|
||||||
def send_data(self, data):
|
def send_data(self, data):
|
||||||
epdconfig.digital_write(self.dc_pin, 1)
|
epdconfig.digital_write(self.dc_pin, 1)
|
||||||
epdconfig.digital_write(self.cs_pin, 0)
|
epdconfig.digital_write(self.cs_pin, 0)
|
||||||
epdconfig.spi_writebyte([data])
|
epdconfig.spi_writebyte([data])
|
||||||
epdconfig.digital_write(self.cs_pin, 1)
|
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
|
function :Wait until the busy_pin goes LOW
|
||||||
parameter:
|
parameter:
|
||||||
'''
|
'''
|
||||||
|
|
||||||
def ReadBusy(self):
|
def ReadBusy(self):
|
||||||
logger.debug("e-Paper busy")
|
logger.debug("e-Paper busy")
|
||||||
while(epdconfig.digital_read(self.busy_pin) == 1): # 0: idle, 1: busy
|
while (epdconfig.digital_read(self.busy_pin) == 1): # 0: idle, 1: busy
|
||||||
epdconfig.delay_ms(10)
|
epdconfig.delay_ms(10)
|
||||||
logger.debug("e-Paper busy release")
|
logger.debug("e-Paper busy release")
|
||||||
|
|
||||||
'''
|
'''
|
||||||
function : Turn On Display
|
function : Turn On Display
|
||||||
parameter:
|
parameter:
|
||||||
'''
|
'''
|
||||||
|
|
||||||
def TurnOnDisplay(self):
|
def TurnOnDisplay(self):
|
||||||
self.send_command(0x22) # Display Update Control
|
self.send_command(0x22) # Display Update Control
|
||||||
self.send_data(0xC7)
|
self.send_data(0xC7)
|
||||||
self.send_command(0x20) # Activate Display Update Sequence
|
self.send_command(0x20) # Activate Display Update Sequence
|
||||||
self.ReadBusy()
|
self.ReadBusy()
|
||||||
|
|
||||||
'''
|
'''
|
||||||
function : Turn On Display Part
|
function : Turn On Display Part
|
||||||
parameter:
|
parameter:
|
||||||
'''
|
'''
|
||||||
|
|
||||||
def TurnOnDisplayPart(self):
|
def TurnOnDisplayPart(self):
|
||||||
self.send_command(0x22) # Display Update Control
|
self.send_command(0x22) # Display Update Control
|
||||||
self.send_data(0x0f) # fast:0x0c, quality:0x0f, 0xcf
|
self.send_data(0x0f) # fast:0x0c, quality:0x0f, 0xcf
|
||||||
self.send_command(0x20) # Activate Display Update Sequence
|
self.send_command(0x20) # Activate Display Update Sequence
|
||||||
self.ReadBusy()
|
self.ReadBusy()
|
||||||
|
|
||||||
'''
|
'''
|
||||||
function : Set lut
|
function : Set lut
|
||||||
parameter:
|
parameter:
|
||||||
lut : lut data
|
lut : lut data
|
||||||
'''
|
'''
|
||||||
|
|
||||||
def Lut(self, lut):
|
def Lut(self, lut):
|
||||||
self.send_command(0x32)
|
self.send_command(0x32)
|
||||||
for i in range(0, 153):
|
for i in range(0, 153):
|
||||||
self.send_data(lut[i])
|
self.send_data(lut[i])
|
||||||
self.ReadBusy()
|
self.ReadBusy()
|
||||||
|
|
||||||
'''
|
'''
|
||||||
function : Send lut data and configuration
|
function : Send lut data and configuration
|
||||||
parameter:
|
parameter:
|
||||||
lut : lut data
|
lut : lut data
|
||||||
'''
|
'''
|
||||||
|
|
||||||
def SetLut(self, lut):
|
def SetLut(self, lut):
|
||||||
self.Lut(lut)
|
self.Lut(lut)
|
||||||
self.send_command(0x3f)
|
self.send_command(0x3f)
|
||||||
self.send_data(lut[153])
|
self.send_data(lut[153])
|
||||||
self.send_command(0x03) # gate voltage
|
self.send_command(0x03) # gate voltage
|
||||||
self.send_data(lut[154])
|
self.send_data(lut[154])
|
||||||
self.send_command(0x04) # source voltage
|
self.send_command(0x04) # source voltage
|
||||||
self.send_data(lut[155]) # VSH
|
self.send_data(lut[155]) # VSH
|
||||||
self.send_data(lut[156]) # VSH2
|
self.send_data(lut[156]) # VSH2
|
||||||
self.send_data(lut[157]) # VSL
|
self.send_data(lut[157]) # VSL
|
||||||
self.send_command(0x2c) # VCOM
|
self.send_command(0x2c) # VCOM
|
||||||
self.send_data(lut[158])
|
self.send_data(lut[158])
|
||||||
|
|
||||||
'''
|
'''
|
||||||
function : Setting the display window
|
function : Setting the display window
|
||||||
parameter:
|
parameter:
|
||||||
@ -192,13 +207,14 @@ class EPD:
|
|||||||
xend : End position of X-axis
|
xend : End position of X-axis
|
||||||
yend : End position of Y-axis
|
yend : End position of Y-axis
|
||||||
'''
|
'''
|
||||||
|
|
||||||
def SetWindow(self, x_start, y_start, x_end, y_end):
|
def SetWindow(self, x_start, y_start, x_end, y_end):
|
||||||
self.send_command(0x44) # SET_RAM_X_ADDRESS_START_END_POSITION
|
self.send_command(0x44) # SET_RAM_X_ADDRESS_START_END_POSITION
|
||||||
# x point must be the multiple of 8 or the last 3 bits will be ignored
|
# 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_start >> 3) & 0xFF)
|
||||||
self.send_data((x_end>>3) & 0xFF)
|
self.send_data((x_end >> 3) & 0xFF)
|
||||||
|
|
||||||
self.send_command(0x45) # SET_RAM_Y_ADDRESS_START_END_POSITION
|
self.send_command(0x45) # SET_RAM_Y_ADDRESS_START_END_POSITION
|
||||||
self.send_data(y_start & 0xFF)
|
self.send_data(y_start & 0xFF)
|
||||||
self.send_data((y_start >> 8) & 0xFF)
|
self.send_data((y_start >> 8) & 0xFF)
|
||||||
self.send_data(y_end & 0xFF)
|
self.send_data(y_end & 0xFF)
|
||||||
@ -210,52 +226,54 @@ class EPD:
|
|||||||
x : X-axis starting position
|
x : X-axis starting position
|
||||||
y : Y-axis starting position
|
y : Y-axis starting position
|
||||||
'''
|
'''
|
||||||
|
|
||||||
def SetCursor(self, x, y):
|
def SetCursor(self, x, y):
|
||||||
self.send_command(0x4E) # SET_RAM_X_ADDRESS_COUNTER
|
self.send_command(0x4E) # SET_RAM_X_ADDRESS_COUNTER
|
||||||
# x point must be the multiple of 8 or the last 3 bits will be ignored
|
# x point must be the multiple of 8 or the last 3 bits will be ignored
|
||||||
self.send_data(x & 0xFF)
|
self.send_data(x & 0xFF)
|
||||||
|
|
||||||
self.send_command(0x4F) # SET_RAM_Y_ADDRESS_COUNTER
|
self.send_command(0x4F) # SET_RAM_Y_ADDRESS_COUNTER
|
||||||
self.send_data(y & 0xFF)
|
self.send_data(y & 0xFF)
|
||||||
self.send_data((y >> 8) & 0xFF)
|
self.send_data((y >> 8) & 0xFF)
|
||||||
|
|
||||||
'''
|
'''
|
||||||
function : Initialize the e-Paper register
|
function : Initialize the e-Paper register
|
||||||
parameter:
|
parameter:
|
||||||
'''
|
'''
|
||||||
|
|
||||||
def init(self):
|
def init(self):
|
||||||
if (epdconfig.module_init() != 0):
|
if (epdconfig.module_init() != 0):
|
||||||
return -1
|
return -1
|
||||||
# EPD hardware init start
|
# EPD hardware init start
|
||||||
self.reset()
|
self.reset()
|
||||||
|
|
||||||
self.ReadBusy()
|
|
||||||
self.send_command(0x12) #SWRESET
|
|
||||||
self.ReadBusy()
|
|
||||||
|
|
||||||
self.send_command(0x01) #Driver output control
|
self.ReadBusy()
|
||||||
|
self.send_command(0x12) # SWRESET
|
||||||
|
self.ReadBusy()
|
||||||
|
|
||||||
|
self.send_command(0x01) # Driver output control
|
||||||
self.send_data(0xf9)
|
self.send_data(0xf9)
|
||||||
self.send_data(0x00)
|
self.send_data(0x00)
|
||||||
self.send_data(0x00)
|
self.send_data(0x00)
|
||||||
|
|
||||||
self.send_command(0x11) #data entry mode
|
self.send_command(0x11) # data entry mode
|
||||||
self.send_data(0x03)
|
self.send_data(0x03)
|
||||||
|
|
||||||
self.SetWindow(0, 0, self.width-1, self.height-1)
|
self.SetWindow(0, 0, self.width - 1, self.height - 1)
|
||||||
self.SetCursor(0, 0)
|
self.SetCursor(0, 0)
|
||||||
|
|
||||||
self.send_command(0x3c)
|
self.send_command(0x3c)
|
||||||
self.send_data(0x05)
|
self.send_data(0x05)
|
||||||
|
|
||||||
self.send_command(0x21) # Display update control
|
self.send_command(0x21) # Display update control
|
||||||
self.send_data(0x00)
|
self.send_data(0x00)
|
||||||
self.send_data(0x80)
|
self.send_data(0x80)
|
||||||
|
|
||||||
self.send_command(0x18)
|
self.send_command(0x18)
|
||||||
self.send_data(0x80)
|
self.send_data(0x80)
|
||||||
|
|
||||||
self.ReadBusy()
|
self.ReadBusy()
|
||||||
|
|
||||||
self.SetLut(self.lut_full_update)
|
self.SetLut(self.lut_full_update)
|
||||||
return 0
|
return 0
|
||||||
|
|
||||||
@ -264,54 +282,52 @@ class EPD:
|
|||||||
parameter:
|
parameter:
|
||||||
image : Image data
|
image : Image data
|
||||||
'''
|
'''
|
||||||
|
|
||||||
def getbuffer(self, image):
|
def getbuffer(self, image):
|
||||||
img = image
|
img = image
|
||||||
imwidth, imheight = img.size
|
imwidth, imheight = img.size
|
||||||
if(imwidth == self.width and imheight == self.height):
|
if (imwidth == self.width and imheight == self.height):
|
||||||
img = img.convert('1')
|
img = img.convert('1')
|
||||||
elif(imwidth == self.height and imheight == self.width):
|
elif (imwidth == self.height and imheight == self.width):
|
||||||
# image has correct dimensions, but needs to be rotated
|
# image has correct dimensions, but needs to be rotated
|
||||||
img = img.rotate(90, expand=True).convert('1')
|
img = img.rotate(90, expand=True).convert('1')
|
||||||
else:
|
else:
|
||||||
logger.warning("Wrong image dimensions: must be " + str(self.width) + "x" + str(self.height))
|
logger.warning("Wrong image dimensions: must be " + str(self.width) + "x" + str(self.height))
|
||||||
# return a blank buffer
|
# return a blank buffer
|
||||||
return [0x00] * (int(self.width/8) * self.height)
|
return [0x00] * (int(self.width / 8) * self.height)
|
||||||
|
|
||||||
buf = bytearray(img.tobytes('raw'))
|
buf = bytearray(img.tobytes('raw'))
|
||||||
return buf
|
return buf
|
||||||
|
|
||||||
'''
|
'''
|
||||||
function : Sends the image buffer in RAM to e-Paper and displays
|
function : Sends the image buffer in RAM to e-Paper and displays
|
||||||
parameter:
|
parameter:
|
||||||
image : Image data
|
image : Image data
|
||||||
'''
|
'''
|
||||||
|
|
||||||
def display(self, image):
|
def display(self, image):
|
||||||
if self.width%8 == 0:
|
if self.width % 8 == 0:
|
||||||
linewidth = int(self.width/8)
|
linewidth = int(self.width / 8)
|
||||||
else:
|
else:
|
||||||
linewidth = int(self.width/8) + 1
|
linewidth = int(self.width / 8) + 1
|
||||||
|
|
||||||
self.send_command(0x24)
|
self.send_command(0x24)
|
||||||
for j in range(0, self.height):
|
for j in range(0, self.height):
|
||||||
for i in range(0, linewidth):
|
for i in range(0, linewidth):
|
||||||
self.send_data(image[i + j * linewidth])
|
self.send_data(image[i + j * linewidth])
|
||||||
self.TurnOnDisplay()
|
self.TurnOnDisplay()
|
||||||
|
|
||||||
'''
|
'''
|
||||||
function : Sends the image buffer in RAM to e-Paper and partial refresh
|
function : Sends the image buffer in RAM to e-Paper and partial refresh
|
||||||
parameter:
|
parameter:
|
||||||
image : Image data
|
image : Image data
|
||||||
'''
|
'''
|
||||||
def displayPartial(self, image):
|
|
||||||
if self.width%8 == 0:
|
|
||||||
linewidth = int(self.width/8)
|
|
||||||
else:
|
|
||||||
linewidth = int(self.width/8) + 1
|
|
||||||
|
|
||||||
|
def displayPartial(self, image):
|
||||||
epdconfig.digital_write(self.reset_pin, 0)
|
epdconfig.digital_write(self.reset_pin, 0)
|
||||||
epdconfig.delay_ms(1)
|
epdconfig.delay_ms(1)
|
||||||
epdconfig.digital_write(self.reset_pin, 1)
|
epdconfig.digital_write(self.reset_pin, 1)
|
||||||
|
|
||||||
self.SetLut(self.lut_partial_update)
|
self.SetLut(self.lut_partial_update)
|
||||||
self.send_command(0x37)
|
self.send_command(0x37)
|
||||||
self.send_data(0x00)
|
self.send_data(0x00)
|
||||||
@ -322,24 +338,25 @@ class EPD:
|
|||||||
self.send_data(0x40)
|
self.send_data(0x40)
|
||||||
self.send_data(0x00)
|
self.send_data(0x00)
|
||||||
self.send_data(0x00)
|
self.send_data(0x00)
|
||||||
self.send_data(0x00)
|
self.send_data(0x00)
|
||||||
self.send_data(0x00)
|
self.send_data(0x00)
|
||||||
|
|
||||||
self.send_command(0x3C) #BorderWavefrom
|
self.send_command(0x3C) # BorderWavefrom
|
||||||
self.send_data(0x80)
|
self.send_data(0x80)
|
||||||
|
|
||||||
self.send_command(0x22)
|
self.send_command(0x22)
|
||||||
self.send_data(0xC0)
|
self.send_data(0xC0)
|
||||||
self.send_command(0x20)
|
self.send_command(0x20)
|
||||||
self.ReadBusy()
|
self.ReadBusy()
|
||||||
|
|
||||||
self.SetWindow(0, 0, self.width - 1, self.height - 1)
|
self.SetWindow(0, 0, self.width - 1, self.height - 1)
|
||||||
self.SetCursor(0, 0)
|
self.SetCursor(0, 0)
|
||||||
|
|
||||||
self.send_command(0x24) # WRITE_RAM
|
self.send_command(0x24) # WRITE_RAM
|
||||||
for j in range(0, self.height):
|
# for j in range(0, self.height):
|
||||||
for i in range(0, linewidth):
|
# for i in range(0, linewidth):
|
||||||
self.send_data(image[i + j * linewidth])
|
# self.send_data(image[i + j * linewidth])
|
||||||
|
self.send_data2(image)
|
||||||
self.TurnOnDisplayPart()
|
self.TurnOnDisplayPart()
|
||||||
|
|
||||||
'''
|
'''
|
||||||
@ -347,51 +364,41 @@ class EPD:
|
|||||||
parameter:
|
parameter:
|
||||||
image : Image data
|
image : Image data
|
||||||
'''
|
'''
|
||||||
def displayPartBaseImage(self, image):
|
|
||||||
if self.width%8 == 0:
|
|
||||||
linewidth = int(self.width/8)
|
|
||||||
else:
|
|
||||||
linewidth = int(self.width/8) + 1
|
|
||||||
|
|
||||||
|
def displayPartBaseImage(self, image):
|
||||||
self.send_command(0x24)
|
self.send_command(0x24)
|
||||||
for j in range(0, self.height):
|
self.send_data2(image)
|
||||||
for i in range(0, linewidth):
|
|
||||||
self.send_data(image[i + j * linewidth])
|
|
||||||
|
|
||||||
self.send_command(0x26)
|
self.send_command(0x26)
|
||||||
for j in range(0, self.height):
|
self.send_data2(image)
|
||||||
for i in range(0, linewidth):
|
|
||||||
self.send_data(image[i + j * linewidth])
|
|
||||||
self.TurnOnDisplay()
|
self.TurnOnDisplay()
|
||||||
|
|
||||||
'''
|
'''
|
||||||
function : Clear screen
|
function : Clear screen
|
||||||
parameter:
|
parameter:
|
||||||
'''
|
'''
|
||||||
def Clear(self, color):
|
|
||||||
if self.width%8 == 0:
|
def Clear(self, color=0xFF):
|
||||||
linewidth = int(self.width/8)
|
if self.width % 8 == 0:
|
||||||
|
linewidth = int(self.width / 8)
|
||||||
else:
|
else:
|
||||||
linewidth = int(self.width/8) + 1
|
linewidth = int(self.width / 8) + 1
|
||||||
# logger.debug(linewidth)
|
# logger.debug(linewidth)
|
||||||
|
|
||||||
self.send_command(0x24)
|
self.send_command(0x24)
|
||||||
for j in range(0, self.height):
|
self.send_data2([color] * int(self.height * linewidth))
|
||||||
for i in range(0, linewidth):
|
|
||||||
self.send_data(color)
|
|
||||||
|
|
||||||
self.TurnOnDisplay()
|
self.TurnOnDisplay()
|
||||||
|
|
||||||
'''
|
'''
|
||||||
function : Enter sleep mode
|
function : Enter sleep mode
|
||||||
parameter:
|
parameter:
|
||||||
'''
|
'''
|
||||||
|
|
||||||
def sleep(self):
|
def sleep(self):
|
||||||
self.send_command(0x10) #enter deep sleep
|
self.send_command(0x10) # enter deep sleep
|
||||||
self.send_data(0x01)
|
self.send_data(0x01)
|
||||||
|
|
||||||
epdconfig.delay_ms(2000)
|
epdconfig.delay_ms(2000)
|
||||||
epdconfig.module_exit()
|
epdconfig.module_exit()
|
||||||
|
|
||||||
### END OF FILE ###
|
### END OF FILE ###
|
||||||
|
|
Binary file not shown.
Binary file not shown.
Binary file not shown.
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user