mirror of
https://github.com/jayofelony/pwnagotchi.git
synced 2025-07-01 18:37:27 -04:00
Compare commits
81 Commits
Author | SHA1 | Date | |
---|---|---|---|
92cd5d3fdb | |||
99caa7a973 | |||
a94e7eef02 | |||
1cefae55d1 | |||
c5ee1df855 | |||
d142840307 | |||
e558146e28 | |||
11a3330153 | |||
53b2dd8628 | |||
6381f9443b | |||
fa7e87b974 | |||
cca2ff2da4 | |||
78415b3137 | |||
34284aa1bc | |||
56ebb60662 | |||
84f6624844 | |||
3bcbb0ce9a | |||
e01e457992 | |||
1780859889 | |||
fdd98bb37a | |||
86991304a5 | |||
4f62759d6d | |||
7b7ba02aad | |||
7040be2d30 | |||
d0617ccfaf | |||
a0b5078b64 | |||
54c1ffd63c | |||
7530709d0c | |||
a9a6fd424b | |||
8c97301992 | |||
91eaa22188 | |||
31a4af4c21 | |||
dceeaff1fb | |||
c0241dc8df | |||
8f405f4ab2 | |||
bd79e71563 | |||
7f662585aa | |||
d23ff8d47a | |||
04435229fe | |||
42e236bafe | |||
5081b72695 | |||
652740f050 | |||
1b6b12bdf5 | |||
c610335a34 | |||
6c39ed97dd | |||
967f1663c6 | |||
ffe1e90ccd | |||
f2d2bcbfdf | |||
bfc0795fb8 | |||
1a55afd74a | |||
1594e7c129 | |||
a244e70a1c | |||
d35d5d6c3c | |||
7aacf9fb44 | |||
dde6fa4c2a | |||
7dfb2f2ff6 | |||
cf46ab3da9 | |||
6824e541a4 | |||
1c4e8fa750 | |||
a50ec4a65c | |||
5414fdf21d | |||
c9883bb4f9 | |||
045f5853bc | |||
6dfcaf05d5 | |||
e2f8119d2c | |||
d675b3d0dd | |||
30850b6530 | |||
1e9c9bae42 | |||
64241515ad | |||
c9f232dae2 | |||
4f97bcaa83 | |||
7d2a03c79f | |||
f89ba85f73 | |||
57f03f4359 | |||
9bc266f9ff | |||
b0db0285bc | |||
cca3e77d50 | |||
4fe603bf5e | |||
9a9ee70a78 | |||
7fc8838f76 | |||
94b2ff7047 |
21
.idea/deployment.xml
generated
21
.idea/deployment.xml
generated
@ -1,4 +1,23 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project version="4">
|
||||
<component name="PublishConfigData" filePermissions="493" folderPermissions="493" remoteFilesAllowedToDisappearOnAutoupload="false" />
|
||||
<component name="PublishConfigData" serverName="pwnagotchi" filePermissions="493" folderPermissions="493" remoteFilesAllowedToDisappearOnAutoupload="false" confirmBeforeUploading="false">
|
||||
<option name="confirmBeforeUploading" value="false" />
|
||||
<serverData>
|
||||
<paths name="pwnagotchi">
|
||||
<serverdata>
|
||||
<mappings>
|
||||
<mapping deploy="/usr/local/lib/python3.11/dist-packages/pwnagotchi" local="$PROJECT_DIR$/pwnagotchi" web="/" />
|
||||
<mapping deploy="/usr/local/bin" local="$PROJECT_DIR$/bin" />
|
||||
<mapping local="" />
|
||||
</mappings>
|
||||
<excludedPaths>
|
||||
<excludedPath local="true" path="$PROJECT_DIR$/venv" />
|
||||
<excludedPath local="true" path="$PROJECT_DIR$/pwnagotchi.egg-info" />
|
||||
<excludedPath local="true" path="$PROJECT_DIR$/dist" />
|
||||
<excludedPath local="true" path="$PROJECT_DIR$/builder/packer-builder-arm" />
|
||||
</excludedPaths>
|
||||
</serverdata>
|
||||
</paths>
|
||||
</serverData>
|
||||
</component>
|
||||
</project>
|
@ -7,9 +7,12 @@ It seems the Pi 5 is unable to run in monitor mode, will keep you updated on thi
|
||||
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 latest image file [here](https://github.com/jayofelony/pwnagotchi-bookworm/releases/tag/v2.7.3), and let it auto-update from here on out.
|
||||
Download the latest image file [here](https://github.com/jayofelony/pwnagotchi-bookworm/releases/tag/v2.7.9), 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`.
|
||||
|
||||
|
@ -50,6 +50,7 @@ def pwnagotchi_cli():
|
||||
|
||||
agent.mode = 'auto'
|
||||
agent.start()
|
||||
config = agent.config()
|
||||
|
||||
while True:
|
||||
try:
|
||||
@ -59,6 +60,7 @@ def pwnagotchi_cli():
|
||||
channels = agent.get_access_points_by_channel()
|
||||
# for each channel
|
||||
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)
|
||||
agent.set_channel(ch)
|
||||
|
||||
if not agent.is_stale() and agent.any_activity():
|
||||
@ -66,6 +68,9 @@ def pwnagotchi_cli():
|
||||
|
||||
# for each ap on this channel
|
||||
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
|
||||
agent.associate(ap)
|
||||
# deauth all client stations in order to get a full handshake
|
||||
@ -157,7 +162,7 @@ def pwnagotchi_cli():
|
||||
sys.exit(0)
|
||||
|
||||
if args.donate:
|
||||
print("Donations can made @ https://www.patreon.com/pwnagotchi_torch \n\nBut only if you really want to!")
|
||||
print("Donations can made @ https://github.com/sponsors/jayofelony \n\nBut only if you really want to!")
|
||||
sys.exit(0)
|
||||
|
||||
if args.check_update:
|
||||
|
@ -1,8 +1,8 @@
|
||||
#!/bin/sh
|
||||
_hostname=$(hostname)
|
||||
_version=$(cut -d"'" -f2 < /usr/local/lib/python3.9/dist-packages/pwnagotchi/_version.py)
|
||||
_version=$(cut -d"'" -f2 < /usr/local/lib/python3.11/dist-packages/pwnagotchi/_version.py)
|
||||
echo
|
||||
echo "(◕‿‿◕) $_hostname"
|
||||
echo "(☉_☉ ) $_hostname"
|
||||
echo
|
||||
echo " Hi! I'm a pwnagotchi $_version, please take good care of me!"
|
||||
echo " Here are some basic things you need to know to raise me properly!"
|
||||
@ -30,4 +30,4 @@ echo
|
||||
echo " You can restart me using"
|
||||
echo " pwnkill"
|
||||
echo
|
||||
echo " You learn more about me at https://pwnagotchi.ai/"
|
||||
echo " You can learn more about me at https://pwnagotchi.org/"
|
||||
|
@ -9,15 +9,6 @@ if is_crypted_mode; then
|
||||
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 mon0
|
||||
start_monitor_interface
|
||||
|
||||
|
@ -13,14 +13,6 @@ blink_led() {
|
||||
sleep 0.3
|
||||
}
|
||||
|
||||
# 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
|
||||
|
@ -74,6 +74,10 @@ build {
|
||||
inline = ["chmod +x /usr/bin/*"]
|
||||
}
|
||||
|
||||
provisioner "shell" {
|
||||
inline = ["dpkg --add-architecture armhf"]
|
||||
}
|
||||
|
||||
provisioner "file" {
|
||||
destination = "/etc/systemd/system/"
|
||||
sources = [
|
||||
@ -91,7 +95,11 @@ build {
|
||||
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"]
|
||||
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"
|
||||
|
@ -6,7 +6,8 @@
|
||||
vars:
|
||||
kernel:
|
||||
min: "6.1"
|
||||
full: "6.1.0-rpi7-rpi-v8"
|
||||
full: "6.1.0-rpi8-rpi-v8"
|
||||
full_pi5: "6.1.0-rpi8-rpi-2712"
|
||||
pwnagotchi:
|
||||
hostname: "{{ lookup('env', 'PWN_HOSTNAME') | default('pwnagotchi', true) }}"
|
||||
version: "{{ lookup('env', 'PWN_VERSION') | default('pwnagotchi-torch', true) }}"
|
||||
@ -51,13 +52,20 @@
|
||||
- libpcap0.8-dev_1.9.1-4_arm64.deb
|
||||
- libpcap0.8_1.9.1-4_arm64.deb
|
||||
hold:
|
||||
- firmware-atheros
|
||||
- firmware-brcm80211
|
||||
- firmware-libertas
|
||||
- firmware-misc-nonfree
|
||||
- firmware-realtek
|
||||
- libpcap-dev
|
||||
- libpcap0.8
|
||||
- libpcap0.8-dev
|
||||
- libpcap0.8-dbg
|
||||
- libpcap0.8-dev
|
||||
remove:
|
||||
- avahi-daemon
|
||||
- dhpys-swapfile
|
||||
- libcurl-ocaml-dev
|
||||
- libssl-ocaml-dev
|
||||
- nfs-common
|
||||
- triggerhappy
|
||||
- wpasupplicant
|
||||
@ -71,6 +79,11 @@
|
||||
- curl
|
||||
- dkms
|
||||
- fbi
|
||||
- firmware-atheros
|
||||
- firmware-brcm80211
|
||||
- firmware-libertas
|
||||
- firmware-misc-nonfree
|
||||
- firmware-realtek
|
||||
- flex
|
||||
- fonts-dejavu
|
||||
- fonts-dejavu-core
|
||||
@ -80,6 +93,7 @@
|
||||
- gawk
|
||||
- gcc-arm-none-eabi
|
||||
- git
|
||||
- hcxtools
|
||||
- libatlas-base-dev
|
||||
- libavcodec59
|
||||
- libavformat59
|
||||
@ -88,7 +102,9 @@
|
||||
- libbz2-dev
|
||||
- libc-ares-dev
|
||||
- libc6-dev
|
||||
- libc6:armhf
|
||||
- libcap-dev
|
||||
- libcurl-ocaml-dev
|
||||
- libdbus-1-dev
|
||||
- libdbus-glib-1-dev
|
||||
- libeigen3-dev
|
||||
@ -101,7 +117,10 @@
|
||||
- libgmp3-dev
|
||||
- libgstreamer1.0-0
|
||||
- libhdf5-dev
|
||||
- libisl23:armhf
|
||||
- liblapack-dev
|
||||
- libmpc3:armhf
|
||||
- libmpfr6:armhf
|
||||
- libncursesw5-dev
|
||||
- libnetfilter-queue-dev
|
||||
- libopenblas-dev
|
||||
@ -115,34 +134,31 @@
|
||||
- libraspberrypi0
|
||||
- libsqlite3-dev
|
||||
- libssl-dev
|
||||
- libssl-ocaml-dev
|
||||
- libstdc++6:armhf
|
||||
- libswscale5
|
||||
- libtiff6
|
||||
- libtool
|
||||
- libusb-1.0-0-dev
|
||||
- lsof
|
||||
- make
|
||||
- python3-yaml
|
||||
- python3-dbus
|
||||
- python3-flask
|
||||
- python3-flask-cors
|
||||
- python3-flaskext.wtf
|
||||
- python3-gast
|
||||
- python3-pil
|
||||
- python3-pip
|
||||
- python3-pycryptodome
|
||||
- python3-requests
|
||||
- python3-scapy
|
||||
- python3-setuptools
|
||||
- python3-smbus
|
||||
- python3-smbus2
|
||||
- python3-spidev
|
||||
- python3-tweepy
|
||||
- python3-werkzeug
|
||||
- firmware-atheros
|
||||
- firmware-brcm80211
|
||||
- firmware-libertas
|
||||
- firmware-misc-nonfree
|
||||
- firmware-realtek
|
||||
- python3-pip
|
||||
- python3-setuptools
|
||||
- python3-smbus
|
||||
- python3-yaml
|
||||
- qpdf
|
||||
- raspberrypi-kernel-headers
|
||||
- rsync
|
||||
@ -158,12 +174,9 @@
|
||||
- xxd
|
||||
- zlib1g-dev
|
||||
- zram-tools
|
||||
environment:
|
||||
ARCHFLAGS: "-arch aarch64"
|
||||
QEMU_UNAME: "{{ kernel.full }}"
|
||||
|
||||
tasks:
|
||||
# First we install and remove unnecessary packages
|
||||
# First we install packages
|
||||
- name: install packages
|
||||
apt:
|
||||
name: "{{ packages.apt.install }}"
|
||||
@ -171,13 +184,6 @@
|
||||
update_cache: yes
|
||||
install_recommends: false
|
||||
|
||||
- name: remove unnecessary apt packages
|
||||
apt:
|
||||
name: "{{ packages.apt.remove }}"
|
||||
state: absent
|
||||
purge: yes
|
||||
register: removed
|
||||
|
||||
# Now we set up /boot/firmware
|
||||
- name: Create pi user
|
||||
copy:
|
||||
@ -271,35 +277,129 @@
|
||||
dest: /usr/local/lib/libpcap.so.0.8
|
||||
state: link
|
||||
|
||||
# install latest hcxtools
|
||||
|
||||
- name: clone hcxtools
|
||||
git:
|
||||
repo: https://github.com/ZerBea/hcxtools.git
|
||||
dest: /usr/local/src/hcxtools
|
||||
|
||||
- name: install hcxtools
|
||||
shell: "make && make install"
|
||||
args:
|
||||
executable: /bin/bash
|
||||
chdir: /usr/local/src/hcxtools
|
||||
|
||||
- name: remove hcxtools directory
|
||||
file:
|
||||
state: absent
|
||||
path: /usr/local/src/hcxtools
|
||||
|
||||
# Install nexmon to fix wireless scanning (takes 2.5G of space)
|
||||
- name: symlink 1
|
||||
file:
|
||||
src: "/usr/lib/arm-linux-gnueabihf/libisl.so.23.2.0"
|
||||
dest: "/usr/lib/arm-linux-gnueabihf/libisl.so.10"
|
||||
state: link
|
||||
|
||||
- name: symlink 2
|
||||
file:
|
||||
src: "/usr/lib/arm-linux-gnueabihf/libmpfr.so.6.2.0"
|
||||
dest: "/usr/lib/arm-linux-gnueabihf/libmpfr.so.4"
|
||||
state: link
|
||||
|
||||
- name: clone nexmon repository
|
||||
git:
|
||||
repo: https://github.com/DrSchottky/nexmon.git
|
||||
repo: https://github.com/seemoo-lab/nexmon.git
|
||||
dest: /usr/local/src/nexmon
|
||||
|
||||
- name: make firmware
|
||||
# FIRST WE BUILD DRIVER FOR RPi5
|
||||
|
||||
- name: make firmware, RPi5
|
||||
shell: "source ./setup_env.sh && make"
|
||||
args:
|
||||
executable: /bin/bash
|
||||
chdir: /usr/local/src/nexmon/
|
||||
environment:
|
||||
QEMU_UNAME: "{{ kernel.full_pi5 }}"
|
||||
ARCHFLAGS: "-arch aarch64"
|
||||
|
||||
- name: make firmware patch (bcm43455c0)
|
||||
- name: make firmware patch (bcm43455c0), RPi5
|
||||
shell: "source ./setup_env.sh && cd /usr/local/src/nexmon/patches/bcm43455c0/7_45_206/nexmon/ && make"
|
||||
args:
|
||||
executable: /bin/bash
|
||||
chdir: /usr/local/src/nexmon/
|
||||
environment:
|
||||
QEMU_UNAME: "{{ kernel.full_pi5 }}"
|
||||
ARCHFLAGS: "-arch aarch64"
|
||||
|
||||
- name: install new firmware (bcm43455c0)
|
||||
- name: copy modified driver, RPi5
|
||||
copy:
|
||||
src: "/usr/local/src/nexmon/patches/driver/brcmfmac_{{ kernel.min }}.y-nexmon/brcmfmac.ko"
|
||||
dest: "/usr/lib/modules/{{ kernel.full_pi5 }}/kernel/drivers/net/wireless/broadcom/brcm80211/brcmfmac/brcmfmac.ko"
|
||||
environment:
|
||||
QEMU_UNAME: "{{ kernel.full_pi5 }}"
|
||||
ARCHFLAGS: "-arch aarch64"
|
||||
|
||||
- name: Delete the modified driver, RPi5
|
||||
file:
|
||||
state: absent
|
||||
path: '/usr/local/src/nexmon/patches/driver/brcmfmac_{{ kernel.min }}.y-nexmon/brcmfmac.ko'
|
||||
|
||||
- name: backup original driver, RPi5
|
||||
command: "mv /usr/lib/modules/{{ kernel.full_pi5 }}/kernel/drivers/net/wireless/broadcom/brcm80211/brcmfmac/brcmfmac.ko.xz /usr/lib/modules/{{ kernel.full_pi5 }}/kernel/drivers/net/wireless/broadcom/brcm80211/brcmfmac/brcmfmac.ko.xz.orig"
|
||||
|
||||
- name: load brcmfmac drivers
|
||||
command: "/sbin/depmod {{ kernel.full_pi5 }}"
|
||||
environment:
|
||||
QEMU_UNAME: "{{ kernel.full_pi5 }}"
|
||||
|
||||
- name: Delete nexmon content & directory
|
||||
file:
|
||||
state: absent
|
||||
path: /usr/local/src/nexmon/
|
||||
|
||||
# NOW WE BUILD DRIVERS FOR RPi4, RPizero2w and RPi3
|
||||
|
||||
- name: clone nexmon repository
|
||||
git:
|
||||
repo: https://github.com/seemoo-lab/nexmon.git
|
||||
dest: /usr/local/src/nexmon
|
||||
|
||||
- name: make firmware, RPi4
|
||||
shell: "source ./setup_env.sh && make"
|
||||
args:
|
||||
executable: /bin/bash
|
||||
chdir: /usr/local/src/nexmon/
|
||||
environment:
|
||||
QEMU_UNAME: "{{ kernel.full }}"
|
||||
ARCHFLAGS: "-arch aarch64"
|
||||
|
||||
- name: make firmware patch (bcm43455c0), RPi4
|
||||
shell: "source ./setup_env.sh && cd /usr/local/src/nexmon/patches/bcm43455c0/7_45_206/nexmon/ && make"
|
||||
args:
|
||||
executable: /bin/bash
|
||||
chdir: /usr/local/src/nexmon/
|
||||
environment:
|
||||
QEMU_UNAME: "{{ kernel.full }}"
|
||||
ARCHFLAGS: "-arch aarch64"
|
||||
|
||||
- name: install new firmware (bcm43455c0), RPi4 RPi5
|
||||
copy:
|
||||
src: /usr/local/src/nexmon/patches/bcm43455c0/7_45_206/nexmon/brcmfmac43455-sdio.bin
|
||||
dest: /usr/lib/firmware/brcm/brcmfmac43455-sdio.bin
|
||||
follow: true
|
||||
|
||||
# NOW WE BUILD DRIVERS FOR RPiZero2W, RPi 3
|
||||
|
||||
- name: make firmware patch (bcm43436b0)
|
||||
shell: "source ./setup_env.sh && cd /usr/local/src/nexmon/patches/bcm43436b0/9_88_4_65/nexmon/ && make"
|
||||
args:
|
||||
executable: /bin/bash
|
||||
chdir: /usr/local/src/nexmon/
|
||||
environment:
|
||||
QEMU_UNAME: "{{ kernel.full }}"
|
||||
ARCHFLAGS: "-arch aarch64"
|
||||
|
||||
- name: install new firmware (bcm43436b0)
|
||||
copy:
|
||||
@ -312,6 +412,17 @@
|
||||
args:
|
||||
executable: /bin/bash
|
||||
chdir: /usr/local/src/nexmon/
|
||||
environment:
|
||||
QEMU_UNAME: "{{ kernel.full }}"
|
||||
ARCHFLAGS: "-arch aarch64"
|
||||
|
||||
- name: copy modified driver, RPi4
|
||||
copy:
|
||||
src: "/usr/local/src/nexmon/patches/driver/brcmfmac_{{ kernel.min }}.y-nexmon/brcmfmac.ko"
|
||||
dest: "/usr/lib/modules/{{ kernel.full }}/kernel/drivers/net/wireless/broadcom/brcm80211/brcmfmac/brcmfmac.ko"
|
||||
environment:
|
||||
QEMU_UNAME: "{{ kernel.full }}"
|
||||
ARCHFLAGS: "-arch aarch64"
|
||||
|
||||
- name: install new firmware (bcm43430a1)
|
||||
copy:
|
||||
@ -342,13 +453,10 @@
|
||||
- name: backup original driver
|
||||
command: "mv /usr/lib/modules/{{ kernel.full }}/kernel/drivers/net/wireless/broadcom/brcm80211/brcmfmac/brcmfmac.ko.xz /usr/lib/modules/{{ kernel.full }}/kernel/drivers/net/wireless/broadcom/brcm80211/brcmfmac/brcmfmac.ko.xz.orig"
|
||||
|
||||
- name: copy modified driver
|
||||
copy:
|
||||
src: "/usr/local/src/nexmon/patches/driver/brcmfmac_{{ kernel.min }}.y-nexmon/brcmfmac.ko"
|
||||
dest: "/usr/lib/modules/{{ kernel.full }}/kernel/drivers/net/wireless/broadcom/brcm80211/brcmfmac/brcmfmac.ko"
|
||||
|
||||
- name : load brcmfmac drivers
|
||||
command: "/sbin/depmod -a"
|
||||
- name: load brcmfmac drivers
|
||||
command: "/sbin/depmod {{ kernel.full }}"
|
||||
environment:
|
||||
QEMU_UNAME: "{{ kernel.full }}"
|
||||
|
||||
# To shrink the final image, remove the nexmon directory (takes 2.5G of space) post build and installation
|
||||
- name: Delete nexmon content & directory
|
||||
@ -557,11 +665,6 @@
|
||||
group: pi
|
||||
recurse: true
|
||||
|
||||
- name: clean apt cache
|
||||
apt:
|
||||
autoclean: true
|
||||
when: removed.changed
|
||||
|
||||
- name: remove pre-collected packages zip
|
||||
file:
|
||||
path: /root/go_pkgs.tgz
|
||||
@ -582,11 +685,6 @@
|
||||
state: absent
|
||||
path: /root/.cache/pip
|
||||
|
||||
- name: remove dependencies that are no longer required
|
||||
apt:
|
||||
autoremove: yes
|
||||
when: removed.changed
|
||||
|
||||
- name: remove ssh keys
|
||||
file:
|
||||
state: absent
|
||||
@ -599,6 +697,24 @@
|
||||
args:
|
||||
executable: /bin/bash
|
||||
|
||||
# Now we remove packages
|
||||
- name: remove unnecessary apt packages
|
||||
apt:
|
||||
name: "{{ packages.apt.remove }}"
|
||||
state: absent
|
||||
purge: yes
|
||||
register: removed
|
||||
|
||||
- name: remove dependencies that are no longer required
|
||||
apt:
|
||||
autoremove: yes
|
||||
when: removed.changed
|
||||
|
||||
- name: clean apt cache
|
||||
apt:
|
||||
autoclean: true
|
||||
when: removed.changed
|
||||
|
||||
handlers:
|
||||
- name: reload systemd services
|
||||
systemd:
|
||||
|
@ -1 +1 @@
|
||||
__version__ = '2.7.5'
|
||||
__version__ = '2.8.1'
|
||||
|
@ -21,17 +21,17 @@ RECOVERY_DATA_FILE = '/root/.pwnagotchi-recovery'
|
||||
|
||||
class Agent(Client, Automata, AsyncAdvertiser, AsyncTrainer):
|
||||
def __init__(self, view, config, keypair):
|
||||
Client.__init__(self, config['bettercap']['hostname'],
|
||||
config['bettercap']['scheme'],
|
||||
config['bettercap']['port'],
|
||||
config['bettercap']['username'],
|
||||
config['bettercap']['password'])
|
||||
Client.__init__(self,
|
||||
"127.0.0.1" if "hostname" not in config['bettercap'] else config['bettercap']['hostname'],
|
||||
"http" if "scheme" not in config['bettercap'] else config['bettercap']['scheme'],
|
||||
8081 if "port" not in config['bettercap'] else config['bettercap']['port'],
|
||||
"pwnagotchi" if "username" not in config['bettercap'] else config['bettercap']['username'],
|
||||
"pwnagotchi" if "password" not in config['bettercap'] else config['bettercap']['password'])
|
||||
Automata.__init__(self, config, view)
|
||||
AsyncAdvertiser.__init__(self, config, view, keypair)
|
||||
AsyncTrainer.__init__(self, config)
|
||||
|
||||
self._started_at = time.time()
|
||||
self._filter = None if not config['main']['filter'] else re.compile(config['main']['filter'])
|
||||
self._current_channel = 0
|
||||
self._tot_aps = 0
|
||||
self._aps_on_channel = 0
|
||||
@ -164,11 +164,6 @@ class Agent(Client, Automata, AsyncAdvertiser, AsyncTrainer):
|
||||
|
||||
self.wait_for(recon_time, sleeping=False)
|
||||
|
||||
def _filter_included(self, ap):
|
||||
return self._filter is None or \
|
||||
self._filter.match(ap['hostname']) is not None or \
|
||||
self._filter.match(ap['mac']) is not None
|
||||
|
||||
def set_access_points(self, aps):
|
||||
self._access_points = aps
|
||||
plugins.on('wifi_update', self, aps)
|
||||
@ -184,13 +179,10 @@ class Agent(Client, Automata, AsyncAdvertiser, AsyncTrainer):
|
||||
for ap in s['wifi']['aps']:
|
||||
if ap['encryption'] == '' or ap['encryption'] == 'OPEN':
|
||||
continue
|
||||
elif ap['hostname'] in whitelist or ap['mac'][:8].lower() in whitelist:
|
||||
elif ap['hostname'] in whitelist or ap['mac'][:13].lower() in whitelist or ap['mac'].lower() in whitelist:
|
||||
continue
|
||||
elif ap['hostname'] not in whitelist \
|
||||
and ap['mac'].lower() not in whitelist \
|
||||
and ap['mac'][:8].lower() not in whitelist:
|
||||
if self._filter_included(ap):
|
||||
aps.append(ap)
|
||||
else:
|
||||
aps.append(ap)
|
||||
except Exception as e:
|
||||
logging.exception("Error while getting access points (%s)", e)
|
||||
|
||||
@ -277,6 +269,10 @@ class Agent(Client, Automata, AsyncAdvertiser, AsyncTrainer):
|
||||
self._save_recovery_data()
|
||||
pwnagotchi.reboot()
|
||||
|
||||
def _restart(self):
|
||||
self._save_recovery_data()
|
||||
pwnagotchi.restart("AUTO")
|
||||
|
||||
def _save_recovery_data(self):
|
||||
logging.warning("writing recovery data to %s ...", RECOVERY_DATA_FILE)
|
||||
with open(RECOVERY_DATA_FILE, 'w') as fp:
|
||||
@ -429,7 +425,6 @@ class Agent(Client, Automata, AsyncAdvertiser, AsyncTrainer):
|
||||
if self.is_stale():
|
||||
logging.debug("recon is stale, skipping assoc(%s)", ap['mac'])
|
||||
return
|
||||
|
||||
if throttle == -1 and "throttle_a" in self._config['personality']:
|
||||
throttle = self._config['personality']['throttle_a']
|
||||
|
||||
|
@ -138,6 +138,6 @@ class Automata(object):
|
||||
plugins.on('epoch', self, self._epoch.epoch - 1, self._epoch.data())
|
||||
|
||||
if self._epoch.blind_for >= self._config['main']['mon_max_blind_epochs']:
|
||||
logging.critical("%d epochs without visible access points -> rebooting ...", self._epoch.blind_for)
|
||||
self._reboot()
|
||||
logging.critical("%d epochs without visible access points -> restarting ...", self._epoch.blind_for)
|
||||
self._restart()
|
||||
self._epoch.blind_for = 0
|
||||
|
@ -1,5 +1,11 @@
|
||||
main.name = "pwnagotchi"
|
||||
main.lang = "en"
|
||||
main.whitelist = [
|
||||
"EXAMPLE_NETWORK",
|
||||
"ANOTHER_EXAMPLE_NETWORK",
|
||||
"fo:od:ba:be:fo:od",
|
||||
"fo:od:ba"
|
||||
]
|
||||
main.confd = "/etc/pwnagotchi/conf.d/"
|
||||
main.custom_plugin_repos = [
|
||||
"https://github.com/jayofelony/pwnagotchi-torch-plugins/archive/master.zip",
|
||||
@ -12,6 +18,10 @@ main.custom_plugin_repos = [
|
||||
|
||||
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.install = true
|
||||
main.plugins.auto-update.interval = 1
|
||||
@ -55,9 +65,6 @@ main.plugins.gps.device = "/dev/ttyUSB0" # for GPSD: "localhost:2947"
|
||||
|
||||
main.plugins.grid.enabled = true
|
||||
main.plugins.grid.report = true
|
||||
main.plugins.grid.exclude = [
|
||||
"YourHomeNetworkHere"
|
||||
]
|
||||
|
||||
main.plugins.logtail.enabled = false
|
||||
main.plugins.logtail.max-lines = 10000
|
||||
@ -73,10 +80,6 @@ main.plugins.onlinehashcrack.enabled = false
|
||||
main.plugins.onlinehashcrack.email = ""
|
||||
main.plugins.onlinehashcrack.dashboard = ""
|
||||
main.plugins.onlinehashcrack.single_files = false
|
||||
main.plugins.onlinehashcrack.whitelist = []
|
||||
|
||||
main.plugins.paw-gps.enabled = false
|
||||
main.plugins.paw-gps.ip = "192.168.44.1:8080"
|
||||
|
||||
main.plugins.pisugar2.enabled = false
|
||||
main.plugins.pisugar2.shutdown = 5
|
||||
@ -100,26 +103,19 @@ main.plugins.webgpsmap.enabled = false
|
||||
|
||||
main.plugins.wigle.enabled = false
|
||||
main.plugins.wigle.api_key = ""
|
||||
main.plugins.wigle.whitelist = []
|
||||
main.plugins.wigle.donate = true
|
||||
main.plugins.wigle.donate = false
|
||||
|
||||
main.plugins.wpa-sec.enabled = false
|
||||
main.plugins.wpa-sec.api_key = ""
|
||||
main.plugins.wpa-sec.api_url = "https://wpa-sec.stanev.org"
|
||||
main.plugins.wpa-sec.download_results = false
|
||||
main.plugins.wpa-sec.whitelist = []
|
||||
|
||||
main.iface = "wlan0mon"
|
||||
main.mon_start_cmd = "/usr/bin/monstart"
|
||||
main.mon_stop_cmd = "/usr/bin/monstop"
|
||||
main.mon_max_blind_epochs = 50
|
||||
main.no_restart = false
|
||||
main.whitelist = [
|
||||
"EXAMPLE_NETWORK",
|
||||
"ANOTHER_EXAMPLE_NETWORK",
|
||||
"fo:od:ba:be:fo:od",
|
||||
"fo:od:ba"
|
||||
]
|
||||
|
||||
main.filter = ""
|
||||
|
||||
main.log.path = "/home/pi/logs/pwnagotchi.log"
|
||||
@ -202,11 +198,6 @@ ui.display.enabled = false
|
||||
ui.display.rotation = 180
|
||||
ui.display.type = "waveshare_4"
|
||||
|
||||
bettercap.scheme = "http"
|
||||
bettercap.hostname = "localhost"
|
||||
bettercap.port = 8081
|
||||
bettercap.username = "pwnagotchi"
|
||||
bettercap.password = "pwnagotchi"
|
||||
bettercap.handshakes = "/root/handshakes"
|
||||
bettercap.silence = [
|
||||
"ble.device.new",
|
||||
|
Binary file not shown.
@ -1,14 +1,13 @@
|
||||
# SOME DESCRIPTIVE TITLE.
|
||||
# Pwnagotchi display English to Esperanto.
|
||||
# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER
|
||||
# This file is distributed under the same license as the PACKAGE package.
|
||||
# FIRST AUTHOR <EMAIL@ADDRESS>, YEAR.
|
||||
# FIRST AUTHOR MADE THIS IN YEAR 2024.
|
||||
#
|
||||
|
||||
msgid ""
|
||||
msgstr ""
|
||||
"Project-Id-Version: PACKAGE VERSION\n"
|
||||
"Report-Msgid-Bugs-To: \n"
|
||||
"POT-Creation-Date: 2023-11-16 21:10+0100\n"
|
||||
"POT-Creation-Date: 2024-01-25 23:40+0100\n"
|
||||
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
|
||||
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
|
||||
"Language-Team: LANGUAGE <LL@li.org>\n"
|
||||
@ -18,218 +17,219 @@ msgstr ""
|
||||
"Content-Transfer-Encoding: 8bit\n"
|
||||
|
||||
msgid "ZzzzZZzzzzZzzz"
|
||||
msgstr ""
|
||||
msgstr "ZzzzZZzzzzZzzz"
|
||||
|
||||
msgid "Hi, I'm Pwnagotchi! Starting ..."
|
||||
msgstr ""
|
||||
msgstr "Sal, mi estas Pwnagotchi! Komencante…"
|
||||
|
||||
msgid "New day, new hunt, new pwns!"
|
||||
msgstr ""
|
||||
msgstr "Nova tago, nova ĉaso, nova wifi!"
|
||||
|
||||
msgid "Hack the Planet!"
|
||||
msgstr ""
|
||||
msgstr "Eniru la elektronikon!"
|
||||
|
||||
msgid "AI ready."
|
||||
msgstr ""
|
||||
msgstr "Mi pretas."
|
||||
|
||||
msgid "The neural network is ready."
|
||||
msgstr ""
|
||||
msgstr "La elektronika reto estas preta."
|
||||
|
||||
msgid "Generating keys, do not turn off ..."
|
||||
msgstr ""
|
||||
msgstr "Mi generas ŝlosilojn, ne malŝaltu min!"
|
||||
|
||||
#, python-brace-format
|
||||
msgid "Hey, channel {channel} is free! Your AP will say thanks."
|
||||
msgstr ""
|
||||
msgstr "Hej, kanalo {channel} disponeblas! Via alirpunkto dankos vin"
|
||||
|
||||
msgid "Reading last session logs ..."
|
||||
msgstr ""
|
||||
msgstr "Legante protokolojn de antaŭa sesio…"
|
||||
|
||||
#, python-brace-format
|
||||
msgid "Read {lines_so_far} log lines so far ..."
|
||||
msgstr ""
|
||||
msgstr "legi {lines_so_far} liniojn ĝis nun…"
|
||||
|
||||
msgid "I'm bored ..."
|
||||
msgstr ""
|
||||
msgstr "Mi enuas…"
|
||||
|
||||
msgid "Let's go for a walk!"
|
||||
msgstr ""
|
||||
msgstr "Ni iru promeni!"
|
||||
|
||||
msgid "This is the best day of my life!"
|
||||
msgstr ""
|
||||
msgstr "Plej bona tago de mia vivo!"
|
||||
|
||||
msgid "Shitty day :/"
|
||||
msgstr ""
|
||||
msgstr "Terura tago :/"
|
||||
|
||||
msgid "I'm extremely bored ..."
|
||||
msgstr ""
|
||||
msgstr "mi estas tre enuigita…"
|
||||
|
||||
msgid "I'm very sad ..."
|
||||
msgstr ""
|
||||
msgstr "Mi estas tre malĝoja…"
|
||||
|
||||
msgid "I'm sad"
|
||||
msgstr ""
|
||||
msgstr "Mi estas malfeliĉa"
|
||||
|
||||
msgid "Leave me alone ..."
|
||||
msgstr ""
|
||||
msgstr "Lasu min sola…"
|
||||
|
||||
msgid "I'm mad at you!"
|
||||
msgstr ""
|
||||
msgstr "Mi koleras kontraŭ vi!"
|
||||
|
||||
msgid "I'm living the life!"
|
||||
msgstr ""
|
||||
msgstr "Mi ĝuas la vivon!"
|
||||
|
||||
msgid "I pwn therefore I am."
|
||||
msgstr ""
|
||||
msgstr "Mi eniras tial mi estas."
|
||||
|
||||
msgid "So many networks!!!"
|
||||
msgstr ""
|
||||
msgstr "Tiom da ludiloj!"
|
||||
|
||||
msgid "I'm having so much fun!"
|
||||
msgstr ""
|
||||
msgstr "Mi tre amuzas!"
|
||||
|
||||
msgid "My crime is that of curiosity ..."
|
||||
msgstr ""
|
||||
msgstr "Scivolemo estas mia krimo…"
|
||||
|
||||
#, python-brace-format
|
||||
msgid "Hello {name}! Nice to meet you."
|
||||
msgstr ""
|
||||
msgstr "Sal {name}! Mi ĝojas renkonti vin."
|
||||
|
||||
#, python-brace-format
|
||||
msgid "Yo {name}! Sup?"
|
||||
msgstr ""
|
||||
msgstr "Hej {name}! Sal?"
|
||||
|
||||
#, python-brace-format
|
||||
msgid "Hey {name} how are you doing?"
|
||||
msgstr ""
|
||||
msgstr "Sal {name}! Kiel vi fartas?"
|
||||
|
||||
#, python-brace-format
|
||||
msgid "Unit {name} is nearby!"
|
||||
msgstr ""
|
||||
msgstr "Iu estas proksime! Ĝia nomo estas {name}."
|
||||
|
||||
#, python-brace-format
|
||||
msgid "Uhm ... goodbye {name}"
|
||||
msgstr ""
|
||||
msgstr "Adiaŭ {name}"
|
||||
|
||||
#, python-brace-format
|
||||
msgid "{name} is gone ..."
|
||||
msgstr ""
|
||||
msgstr "{name} malaperis…"
|
||||
|
||||
#, python-brace-format
|
||||
msgid "Whoops ... {name} is gone."
|
||||
msgstr ""
|
||||
msgstr "Hups… {name} malaperis…"
|
||||
|
||||
#, python-brace-format
|
||||
msgid "{name} missed!"
|
||||
msgstr ""
|
||||
msgstr "{name} mankis!"
|
||||
|
||||
msgid "Missed!"
|
||||
msgstr ""
|
||||
msgstr "Maltrafis!"
|
||||
|
||||
msgid "Good friends are a blessing!"
|
||||
msgstr ""
|
||||
msgstr "Bonaj amikoj estas beno!"
|
||||
|
||||
msgid "I love my friends!"
|
||||
msgstr ""
|
||||
msgstr "Mi amas miajn amikojn!"
|
||||
|
||||
msgid "Nobody wants to play with me ..."
|
||||
msgstr ""
|
||||
msgstr "Neniu volas ludi kun mi..."
|
||||
|
||||
msgid "I feel so alone ..."
|
||||
msgstr ""
|
||||
msgstr "Mi estas tiel sola..."
|
||||
|
||||
msgid "Where's everybody?!"
|
||||
msgstr ""
|
||||
msgstr "KIE ĈIUJ ESTAS?!"
|
||||
|
||||
#, python-brace-format
|
||||
msgid "Napping for {secs}s ..."
|
||||
msgstr ""
|
||||
msgstr "Dormeto por {sec}j…"
|
||||
|
||||
msgid "Zzzzz"
|
||||
msgstr ""
|
||||
msgstr "Zzzzz"
|
||||
|
||||
#, python-brace-format
|
||||
msgid "ZzzZzzz ({secs}s)"
|
||||
msgstr ""
|
||||
msgstr "ZzzZzzz ({sec}j)"
|
||||
|
||||
msgid "Good night."
|
||||
msgstr ""
|
||||
msgstr "Bonan nokton"
|
||||
|
||||
msgid "Zzz"
|
||||
msgstr ""
|
||||
msgstr "Zzz"
|
||||
|
||||
#, python-brace-format
|
||||
msgid "Waiting for {secs}s ..."
|
||||
msgstr ""
|
||||
msgstr "Atendas {sec}j…"
|
||||
|
||||
#, python-brace-format
|
||||
msgid "Looking around ({secs}s)"
|
||||
msgstr ""
|
||||
msgstr "Ĉirkaŭrigardante ({sec}j)"
|
||||
|
||||
#, python-brace-format
|
||||
msgid "Hey {what} let's be friends!"
|
||||
msgstr ""
|
||||
msgstr "Hej {what}, ni estu amikoj!"
|
||||
|
||||
#, python-brace-format
|
||||
msgid "Associating to {what}"
|
||||
msgstr ""
|
||||
msgstr "asociante al {what}"
|
||||
|
||||
#, python-brace-format
|
||||
msgid "Yo {what}!"
|
||||
msgstr ""
|
||||
msgstr "Hej {what}!"
|
||||
|
||||
#, python-brace-format
|
||||
msgid "Just decided that {mac} needs no WiFi!"
|
||||
msgstr ""
|
||||
msgstr "Ĵus decidis, ke {mac} ne bezonas konekton!"
|
||||
|
||||
#, python-brace-format
|
||||
msgid "Deauthenticating {mac}"
|
||||
msgstr ""
|
||||
msgstr "Malaŭtentigi {mac}"
|
||||
|
||||
#, python-brace-format
|
||||
msgid "Kickbanning {mac}!"
|
||||
msgstr ""
|
||||
msgstr "Forigante {mac}!"
|
||||
|
||||
#, python-brace-format
|
||||
msgid "Cool, we got {num} new handshake{plural}!"
|
||||
msgstr ""
|
||||
msgstr "Mirinda, ni havas {num} novajn manpremojn!"
|
||||
|
||||
#, python-brace-format
|
||||
msgid "You have {count} new message{plural}!"
|
||||
msgstr ""
|
||||
msgstr "Vi nun havas {num} novajn mesaĝojn"
|
||||
|
||||
msgid "Oops, something went wrong ... Rebooting ..."
|
||||
msgstr ""
|
||||
msgstr "Lo okazis... Rekomencante…"
|
||||
|
||||
#, python-brace-format
|
||||
msgid "Uploading data to {to} ..."
|
||||
msgstr ""
|
||||
msgstr "alŝuti datumojn al {to}…"
|
||||
|
||||
#, python-brace-format
|
||||
msgid "Downloading from {name} ..."
|
||||
msgstr ""
|
||||
msgstr "Elŝutu de {name}…"
|
||||
|
||||
#, python-brace-format
|
||||
#, fuzzy, python-brace-format
|
||||
msgid "Kicked {num} stations\n"
|
||||
msgstr ""
|
||||
msgstr "Forigita de {num} stacioj"
|
||||
|
||||
#, fuzzy
|
||||
msgid "Made >999 new friends\n"
|
||||
msgstr ""
|
||||
msgstr "Faris pli ol 999 novajn amikojn!"
|
||||
|
||||
#, python-brace-format
|
||||
#, fuzzy, python-brace-format
|
||||
msgid "Made {num} new friends\n"
|
||||
msgstr ""
|
||||
msgstr "faris (nombro) novajn amikojn"
|
||||
|
||||
#, python-brace-format
|
||||
#, fuzzy, python-brace-format
|
||||
msgid "Got {num} handshakes\n"
|
||||
msgstr ""
|
||||
msgstr "Ricevis {num} novajn manpremojn"
|
||||
|
||||
msgid "Met 1 peer"
|
||||
msgstr ""
|
||||
msgstr "Renkontita unu kolego"
|
||||
|
||||
#, python-brace-format
|
||||
msgid "Met {num} peers"
|
||||
msgstr ""
|
||||
msgstr "renkontitajn {num} kolegojn"
|
||||
|
||||
#, python-brace-format
|
||||
msgid ""
|
||||
@ -237,21 +237,24 @@ msgid ""
|
||||
"{associated} new friends and ate {handshakes} handshakes! #pwnagotchi "
|
||||
"#pwnlog #pwnlife #hacktheplanet #skynet"
|
||||
msgstr ""
|
||||
"Mi pwning dum {duration} kaj piedbatis {deauthed} klientojn!Mi ankaŭ "
|
||||
"renkontis {associated} novajn amikojn kaj manĝis {handshakes} manpremojn!"
|
||||
"#pwnagotchi #pwnlog #pwnlife #hacktheplanet #skynet"
|
||||
|
||||
msgid "hours"
|
||||
msgstr ""
|
||||
msgstr "horror"
|
||||
|
||||
msgid "minutes"
|
||||
msgstr ""
|
||||
msgstr "minutoj"
|
||||
|
||||
msgid "seconds"
|
||||
msgstr ""
|
||||
msgstr "sekundoj"
|
||||
|
||||
msgid "hour"
|
||||
msgstr ""
|
||||
msgstr "horo"
|
||||
|
||||
msgid "minute"
|
||||
msgstr ""
|
||||
msgstr "minuto"
|
||||
|
||||
msgid "second"
|
||||
msgstr ""
|
||||
msgstr "dua"
|
||||
|
Binary file not shown.
@ -16,7 +16,7 @@ msgstr ""
|
||||
"Content-Transfer-Encoding: 8bit\n"
|
||||
|
||||
msgid "ZzzzZZzzzzZzzz"
|
||||
msgstr ""
|
||||
msgstr "ZzzzZZzzzzZzzz"
|
||||
|
||||
msgid "Hi, I'm Pwnagotchi! Starting ..."
|
||||
msgstr "Ciao! Piacere Pwnagotchi! Caricamento ..."
|
||||
@ -25,7 +25,7 @@ msgid "New day, new hunt, new pwns!"
|
||||
msgstr "Nuovo giorno...nuovi handshakes!!!"
|
||||
|
||||
msgid "Hack the Planet!"
|
||||
msgstr ""
|
||||
msgstr "Hack il Pianeta"
|
||||
|
||||
msgid "AI ready."
|
||||
msgstr "IA pronta."
|
||||
@ -34,18 +34,18 @@ msgid "The neural network is ready."
|
||||
msgstr "La rete neurale è pronta."
|
||||
|
||||
msgid "Generating keys, do not turn off ..."
|
||||
msgstr ""
|
||||
msgstr "Generazione di chiavi, non spegnere"
|
||||
|
||||
#, python-brace-format
|
||||
msgid "Hey, channel {channel} is free! Your AP will say thanks."
|
||||
msgstr "Hey, il canale {channel} è libero! Il tuo AP ringrazia."
|
||||
|
||||
msgid "Reading last session logs ..."
|
||||
msgstr ""
|
||||
msgstr "Lettura dei log dell'ultima sessione ..."
|
||||
|
||||
#, python-brace-format
|
||||
msgid "Read {lines_so_far} log lines so far ..."
|
||||
msgstr ""
|
||||
msgstr "Leggi le righe di log {lines_so_far} finora ..."
|
||||
|
||||
msgid "I'm bored ..."
|
||||
msgstr "Che noia ..."
|
||||
@ -54,7 +54,7 @@ msgid "Let's go for a walk!"
|
||||
msgstr "Andiamo a fare una passeggiata!"
|
||||
|
||||
msgid "This is the best day of my life!"
|
||||
msgstr "Questo è il più bel giorno della mia vita!!!!"
|
||||
msgstr "Questo e il miglior giorno della mia vita!!!!"
|
||||
|
||||
msgid "Shitty day :/"
|
||||
msgstr "Giorno di merda :/"
|
||||
@ -72,22 +72,22 @@ msgid "Leave me alone ..."
|
||||
msgstr "Mi sento così solo..."
|
||||
|
||||
msgid "I'm mad at you!"
|
||||
msgstr ""
|
||||
msgstr "sono arabiata con te"
|
||||
|
||||
msgid "I'm living the life!"
|
||||
msgstr "Mi sento vivo!"
|
||||
msgstr "sono viva la vita!"
|
||||
|
||||
msgid "I pwn therefore I am."
|
||||
msgstr "Pwn ergo sum."
|
||||
|
||||
msgid "So many networks!!!"
|
||||
msgstr "Qui è pieno di reti!"
|
||||
msgstr "Qui pieno di reti!"
|
||||
|
||||
msgid "I'm having so much fun!"
|
||||
msgstr "Mi sto divertendo tantissimo!"
|
||||
|
||||
msgid "My crime is that of curiosity ..."
|
||||
msgstr ""
|
||||
msgstr "Il mio crimine ? quello della curiosit?"
|
||||
|
||||
#, python-brace-format
|
||||
msgid "Hello {name}! Nice to meet you."
|
||||
@ -95,15 +95,15 @@ msgstr "Ciao {name}! E' un piacere."
|
||||
|
||||
#, python-brace-format
|
||||
msgid "Yo {name}! Sup?"
|
||||
msgstr ""
|
||||
msgstr "Yo {name} Come va"
|
||||
|
||||
#, python-brace-format
|
||||
msgid "Hey {name} how are you doing?"
|
||||
msgstr ""
|
||||
msgstr "Ehi {name} come stai?"
|
||||
|
||||
#, python-brace-format
|
||||
msgid "Unit {name} is nearby!"
|
||||
msgstr "L'Unità {name} è vicina!"
|
||||
msgstr "L'Unit {name} e vicina!"
|
||||
|
||||
#, python-brace-format
|
||||
msgid "Uhm ... goodbye {name}"
|
||||
@ -111,30 +111,30 @@ msgstr "Uhm ... addio {name}, mi mancherai..."
|
||||
|
||||
#, python-brace-format
|
||||
msgid "{name} is gone ..."
|
||||
msgstr "{name} se n'è andato ..."
|
||||
msgstr "{name} se andato ..."
|
||||
|
||||
#, python-brace-format
|
||||
msgid "Whoops ... {name} is gone."
|
||||
msgstr "Whoops ...{name} se n'è andato."
|
||||
msgstr "Whoops ...{name} se andato."
|
||||
|
||||
#, python-brace-format
|
||||
msgid "{name} missed!"
|
||||
msgstr "{name} è scomparso..."
|
||||
msgstr "{name} scomparso..."
|
||||
|
||||
msgid "Missed!"
|
||||
msgstr "Ehi! Dove sei andato!?"
|
||||
|
||||
msgid "Good friends are a blessing!"
|
||||
msgstr ""
|
||||
msgstr "Buoni amici sono una benedizione"
|
||||
|
||||
msgid "I love my friends!"
|
||||
msgstr ""
|
||||
msgstr "Amo i miei amici"
|
||||
|
||||
msgid "Nobody wants to play with me ..."
|
||||
msgstr "Nessuno vuole giocare con me..."
|
||||
|
||||
msgid "I feel so alone ..."
|
||||
msgstr "Mi sento così solo..."
|
||||
msgstr "Mi sento cos solo..."
|
||||
|
||||
msgid "Where's everybody?!"
|
||||
msgstr "Dove sono tutti?!"
|
||||
@ -144,17 +144,17 @@ msgid "Napping for {secs}s ..."
|
||||
msgstr "Schiaccio un pisolino per {secs}s ..."
|
||||
|
||||
msgid "Zzzzz"
|
||||
msgstr ""
|
||||
msgstr "Zzzzz"
|
||||
|
||||
#, python-brace-format
|
||||
msgid "ZzzZzzz ({secs}s)"
|
||||
msgstr ""
|
||||
msgstr "ZzzZzzz ({secs}s)"
|
||||
|
||||
msgid "Good night."
|
||||
msgstr ""
|
||||
msgstr "Buona notte"
|
||||
|
||||
msgid "Zzz"
|
||||
msgstr ""
|
||||
msgstr "Zzz"
|
||||
|
||||
#, python-brace-format
|
||||
msgid "Waiting for {secs}s ..."
|
||||
@ -182,7 +182,7 @@ msgstr "Ho appena deciso che {mac} non necessita di WiFi!"
|
||||
|
||||
#, python-brace-format
|
||||
msgid "Deauthenticating {mac}"
|
||||
msgstr ""
|
||||
msgstr "Annullamento dell'autenticazione {mac}"
|
||||
|
||||
#, python-brace-format
|
||||
msgid "Kickbanning {mac}!"
|
||||
@ -201,11 +201,11 @@ msgstr "Ops, qualcosa è andato storto ... Riavvio ..."
|
||||
|
||||
#, python-brace-format
|
||||
msgid "Uploading data to {to} ..."
|
||||
msgstr ""
|
||||
msgstr "Caricamento dei dati in {to}"
|
||||
|
||||
#, python-brace-format
|
||||
msgid "Downloading from {name} ..."
|
||||
msgstr ""
|
||||
msgstr "Scaricamento da {name} ..."
|
||||
|
||||
#, python-brace-format
|
||||
msgid "Kicked {num} stations\n"
|
||||
|
Binary file not shown.
@ -14,7 +14,7 @@ msgstr ""
|
||||
"Language-Team: LANGUAGE <LL@li.org>\n"
|
||||
"Language: Portuguese (Brazil)\n"
|
||||
"MIME-Version: 1.0\n"
|
||||
"Content-Type: text/plain; charset=CHARSET\n"
|
||||
"Content-Type: text/plain; charset=utf-8\n"
|
||||
"Content-Transfer-Encoding: 8bit\n"
|
||||
|
||||
msgid "ZzzzZZzzzzZzzz"
|
||||
|
@ -3,12 +3,12 @@
|
||||
# This file is distributed under the same license as the PACKAGE package.
|
||||
# FIRST AUTHOR <EMAIL@ADDRESS>, YEAR.
|
||||
#
|
||||
|
||||
#, fuzzy
|
||||
msgid ""
|
||||
msgstr ""
|
||||
"Project-Id-Version: PACKAGE VERSION\n"
|
||||
"Report-Msgid-Bugs-To: \n"
|
||||
"POT-Creation-Date: 2023-11-17 15:46+0100\n"
|
||||
"POT-Creation-Date: 2024-01-25 23:40+0100\n"
|
||||
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
|
||||
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
|
||||
"Language-Team: LANGUAGE <LL@li.org>\n"
|
||||
|
73
pwnagotchi/plugins/default/aircrackonly.py
Normal file
73
pwnagotchi/plugins/default/aircrackonly.py
Normal file
@ -0,0 +1,73 @@
|
||||
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 = ""
|
@ -130,7 +130,7 @@ def install(display, update):
|
||||
source_path = "%s-%s" % (source_path, update['available'])
|
||||
|
||||
# setup.py is going to install data files for us
|
||||
os.system("cd %s && pip3 install . --no-cache-dir --break-system-packages" % source_path)
|
||||
os.system("cd %s && pip3 install . --break-system-packages" % source_path)
|
||||
return True
|
||||
|
||||
|
||||
|
@ -27,10 +27,9 @@ class FixServices(plugins.Plugin):
|
||||
|
||||
def __init__(self):
|
||||
self.options = dict()
|
||||
self.pattern = re.compile(r'brcmf_cfg80211_nexmon_set_channel.*?Set Channel failed')
|
||||
self.pattern2 = re.compile(r'wifi error while hopping to channel')
|
||||
self.pattern3 = re.compile(r'Firmware has halted or crashed')
|
||||
self.pattern4 = re.compile(r'error 400: could not find interface wlan0mon')
|
||||
self.pattern1 = re.compile(r'wifi error while hopping to channel')
|
||||
self.pattern2 = re.compile(r'Firmware has halted or crashed')
|
||||
self.pattern3 = re.compile(r'error 400: could not find interface wlan0mon')
|
||||
self.isReloadingMon = False
|
||||
self.connection = None
|
||||
self.LASTTRY = 0
|
||||
@ -46,23 +45,11 @@ class FixServices(plugins.Plugin):
|
||||
last_lines = self.get_last_lines('journalctl', ['-n10', '-k'], 10)
|
||||
try:
|
||||
cmd_output = subprocess.check_output("ip link show wlan0mon", shell=True)
|
||||
logging.info("[Fix_Services ip link show wlan0mon]: %s" % repr(cmd_output))
|
||||
logging.debug("[Fix_Services ip link show wlan0mon]: %s" % repr(cmd_output))
|
||||
if ",UP," in str(cmd_output):
|
||||
logging.info("wlan0mon is up.")
|
||||
|
||||
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.info('[Fix_Services] Blind-Bug detected. Restarting.')
|
||||
try:
|
||||
self._tryTurningItOffAndOnAgain(agent)
|
||||
except Exception as err:
|
||||
logging.warning("[Fix_Services turnOffAndOn] %s" % repr(err))
|
||||
|
||||
else:
|
||||
logging.info("[Fix_Services] Logs look good!")
|
||||
logging.info("[Fix_Services] Logs look good!")
|
||||
|
||||
except Exception as err:
|
||||
logging.error("[Fix_Services ip link show wlan0mon]: %s" % repr(err))
|
||||
@ -121,21 +108,8 @@ class FixServices(plugins.Plugin):
|
||||
logging.debug("[Fix_Services]**** checking")
|
||||
|
||||
# Look for pattern 1
|
||||
if len(self.pattern.findall(last_lines)) >= 3:
|
||||
logging.info("[Fix_Services]**** Should trigger a reload of the wlan0mon device:\n%s" % last_lines)
|
||||
if hasattr(agent, 'view'):
|
||||
display = agent.view()
|
||||
display.set('status', 'Blind-Bug detected. Restarting.')
|
||||
display.update(force=True)
|
||||
logging.info('[Fix_Services] Blind-Bug detected. Restarting.')
|
||||
try:
|
||||
self._tryTurningItOffAndOnAgain(agent)
|
||||
except Exception as err:
|
||||
logging.warning("[Fix_Services] TTOAOA: %s" % repr(err))
|
||||
|
||||
# Look for pattern 2
|
||||
elif len(self.pattern2.findall(other_last_lines)) >= 5:
|
||||
logging.info("[Fix_Services]**** Should trigger a reload of the wlan0mon device:\n%s" % last_lines)
|
||||
if len(self.pattern1.findall(other_last_lines)) >= 5:
|
||||
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.')
|
||||
@ -157,8 +131,8 @@ class FixServices(plugins.Plugin):
|
||||
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:
|
||||
# Look for pattern 2
|
||||
elif len(self.pattern2.findall(other_last_lines)) >= 1:
|
||||
logging.info("[Fix_Services] Firmware has halted or crashed. Restarting wlan0mon.")
|
||||
if hasattr(agent, 'view'):
|
||||
display = agent.view()
|
||||
@ -167,12 +141,12 @@ class FixServices(plugins.Plugin):
|
||||
try:
|
||||
# Run the monstart command to restart wlan0mon
|
||||
cmd_output = subprocess.check_output("monstart", shell=True)
|
||||
logging.info("[Fix_Services monstart]: %s" % repr(cmd_output))
|
||||
logging.debug("[Fix_Services monstart]: %s" % repr(cmd_output))
|
||||
except Exception as err:
|
||||
logging.error("[Fix_Services monstart]: %s" % repr(err))
|
||||
|
||||
# Look for pattern 4
|
||||
elif len(self.pattern4.findall(other_other_last_lines)) >= 3:
|
||||
# Look for pattern 3
|
||||
elif len(self.pattern3.findall(other_other_last_lines)) >= 3:
|
||||
logging.info("[Fix_Services] wlan0 is down!")
|
||||
if hasattr(agent, 'view'):
|
||||
display = agent.view()
|
||||
@ -181,7 +155,7 @@ class FixServices(plugins.Plugin):
|
||||
try:
|
||||
# Run the monstart command to restart wlan0mon
|
||||
cmd_output = subprocess.check_output("monstart", shell=True)
|
||||
logging.info("[Fix_Services monstart]: %s" % repr(cmd_output))
|
||||
logging.debug("[Fix_Services monstart]: %s" % repr(cmd_output))
|
||||
except Exception as err:
|
||||
logging.error("[Fix_Services monstart]: %s" % repr(err))
|
||||
|
||||
@ -237,7 +211,7 @@ class FixServices(plugins.Plugin):
|
||||
# is it up?
|
||||
try:
|
||||
cmd_output = subprocess.check_output("ip link show wlan0mon", shell=True)
|
||||
logging.info("[Fix_Services ip link show wlan0mon]: %s" % repr(cmd_output))
|
||||
logging.debug("[Fix_Services ip link show wlan0mon]: %s" % repr(cmd_output))
|
||||
if ",UP," in str(cmd_output):
|
||||
logging.info("wlan0mon is up. Skip reset?")
|
||||
# not reliable, so don't skip just yet
|
||||
@ -309,11 +283,9 @@ class FixServices(plugins.Plugin):
|
||||
# stop looping and get back to recon
|
||||
break
|
||||
else:
|
||||
logging.info(
|
||||
"[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:
|
||||
logging.info(
|
||||
"[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: #
|
||||
if not display:
|
||||
print("failed loading wlan0mon attempt #%s: %s" % (tries, repr(cerr)))
|
||||
@ -362,7 +334,7 @@ class FixServices(plugins.Plugin):
|
||||
"face": faces.HAPPY})
|
||||
else:
|
||||
print("I can see again")
|
||||
logging.info("[Fix_Services] wifi.recon on")
|
||||
logging.debug("[Fix_Services] wifi.recon on")
|
||||
self.LASTTRY = time.time() + 120 # 2-minute pause until next time.
|
||||
else:
|
||||
logging.error("[Fix_Services] wifi.recon did not start up")
|
||||
@ -378,7 +350,7 @@ class FixServices(plugins.Plugin):
|
||||
try:
|
||||
logging.info("[Fix_Services] unloaded")
|
||||
except Exception as err:
|
||||
logging.info("[Fix_Services] unload err %s " % repr(err))
|
||||
logging.error("[Fix_Services] unload err %s " % repr(err))
|
||||
pass
|
||||
|
||||
|
||||
|
@ -20,6 +20,7 @@ class GPS(plugins.Plugin):
|
||||
def __init__(self):
|
||||
self.running = False
|
||||
self.coordinates = None
|
||||
self.options = dict()
|
||||
|
||||
def on_loaded(self):
|
||||
logging.info(f"gps plugin loaded for {self.options['device']}")
|
||||
|
@ -58,8 +58,9 @@ class Grid(plugins.Plugin):
|
||||
self.total_messages = 0
|
||||
self.lock = Lock()
|
||||
|
||||
def is_excluded(self, what):
|
||||
for skip in self.options['exclude']:
|
||||
def is_excluded(self, what, agent):
|
||||
config = agent.config()
|
||||
for skip in config['main']['whitelist']:
|
||||
skip = skip.lower()
|
||||
what = what.lower()
|
||||
if skip in what or skip.replace(':', '') in what:
|
||||
@ -87,6 +88,7 @@ class Grid(plugins.Plugin):
|
||||
|
||||
def check_handshakes(self, agent):
|
||||
logging.debug("checking pcaps")
|
||||
config = agent.config()
|
||||
|
||||
pcap_files = glob.glob(os.path.join(agent.config()['bettercap']['handshakes'], "*.pcap"))
|
||||
num_networks = len(pcap_files)
|
||||
@ -98,19 +100,19 @@ class Grid(plugins.Plugin):
|
||||
if self.options['report']:
|
||||
logging.info("grid: %d new networks to report" % num_new)
|
||||
logging.debug("self.options: %s" % self.options)
|
||||
logging.debug(" exclude: %s" % self.options['exclude'])
|
||||
logging.debug(" exclude: %s" % config['main']['whitelist'])
|
||||
|
||||
for pcap_file in pcap_files:
|
||||
net_id = os.path.basename(pcap_file).replace('.pcap', '')
|
||||
if net_id not in reported:
|
||||
if self.is_excluded(net_id):
|
||||
if self.is_excluded(net_id, agent):
|
||||
logging.debug("skipping %s due to exclusion filter" % pcap_file)
|
||||
self.set_reported(reported, net_id)
|
||||
continue
|
||||
|
||||
essid, bssid = parse_pcap(pcap_file)
|
||||
if bssid:
|
||||
if self.is_excluded(essid) or self.is_excluded(bssid):
|
||||
if self.is_excluded(essid, agent) or self.is_excluded(bssid, agent):
|
||||
logging.debug("not reporting %s due to exclusion filter" % pcap_file)
|
||||
self.set_reported(reported, net_id)
|
||||
else:
|
||||
|
179
pwnagotchi/plugins/default/hashie.py
Normal file
179
pwnagotchi/plugins/default/hashie.py
Normal file
@ -0,0 +1,179 @@
|
||||
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)))
|
@ -24,6 +24,7 @@ class NetPos(plugins.Plugin):
|
||||
self.skip = list()
|
||||
self.ready = False
|
||||
self.lock = threading.Lock()
|
||||
self.options = dict()
|
||||
|
||||
def on_loaded(self):
|
||||
if 'api_key' not in self.options or ('api_key' in self.options and not self.options['api_key']):
|
||||
|
@ -25,6 +25,7 @@ class OnlineHashCrack(plugins.Plugin):
|
||||
self.report = StatusFile('/root/.ohc_uploads', data_format='json')
|
||||
self.skip = list()
|
||||
self.lock = Lock()
|
||||
self.options = dict()
|
||||
|
||||
def on_loaded(self):
|
||||
"""
|
||||
@ -34,13 +35,9 @@ class OnlineHashCrack(plugins.Plugin):
|
||||
logging.error("OHC: Email isn't set. Can't upload to onlinehashcrack.com")
|
||||
return
|
||||
|
||||
if 'whitelist' not in self.options:
|
||||
self.options['whitelist'] = list()
|
||||
|
||||
self.ready = True
|
||||
logging.info("OHC: OnlineHashCrack plugin loaded.")
|
||||
|
||||
|
||||
def _upload_to_ohc(self, path, timeout=30):
|
||||
"""
|
||||
Uploads the file to onlinehashcrack.com
|
||||
@ -78,7 +75,6 @@ class OnlineHashCrack(plugins.Plugin):
|
||||
except OSError as os_e:
|
||||
raise os_e
|
||||
|
||||
|
||||
def on_webhook(self, path, request):
|
||||
import requests
|
||||
from flask import redirect
|
||||
@ -87,7 +83,6 @@ class OnlineHashCrack(plugins.Plugin):
|
||||
r = s.post('https://www.onlinehashcrack.com/dashboard', data={'emailTasks': self.options['email'], 'submit': ''})
|
||||
return redirect(r.url, code=302)
|
||||
|
||||
|
||||
def on_internet_available(self, agent):
|
||||
"""
|
||||
Called in manual mode when there's internet connectivity
|
||||
@ -105,7 +100,7 @@ class OnlineHashCrack(plugins.Plugin):
|
||||
handshake_paths = [os.path.join(handshake_dir, filename) for filename in handshake_filenames if
|
||||
filename.endswith('.pcap')]
|
||||
# pull out whitelisted APs
|
||||
handshake_paths = remove_whitelisted(handshake_paths, self.options['whitelist'])
|
||||
handshake_paths = remove_whitelisted(handshake_paths, config['main']['whitelist'])
|
||||
handshake_new = set(handshake_paths) - set(reported) - set(self.skip)
|
||||
if handshake_new:
|
||||
logging.info("OHC: Internet connectivity detected. Uploading new handshakes to onlinehashcrack.com")
|
||||
|
@ -1,39 +0,0 @@
|
||||
import logging
|
||||
import requests
|
||||
import pwnagotchi.plugins as plugins
|
||||
|
||||
'''
|
||||
You need an bluetooth connection to your android phone which is running PAW server with the GPS "hack" from Systemik and edited by shaynemk
|
||||
GUIDE HERE: https://community.pwnagotchi.ai/t/setting-up-paw-gps-on-android
|
||||
'''
|
||||
|
||||
|
||||
class PawGPS(plugins.Plugin):
|
||||
__author__ = 'leont'
|
||||
__version__ = '1.0.1'
|
||||
__name__ = 'pawgps'
|
||||
__license__ = 'GPL3'
|
||||
__description__ = 'Saves GPS coordinates whenever an handshake is captured. The GPS data is get from PAW on android.'
|
||||
|
||||
def on_loaded(self):
|
||||
logging.info("[paw-gps] plugin loaded")
|
||||
if 'ip' not in self.options or ('ip' in self.options and self.options['ip'] is None) or (len('ip' in self.options and self.options['ip']) is 0):
|
||||
logging.info("[paw-gps] no IP Address defined in the config file, will uses paw server default (192.168.44.1:8080)")
|
||||
|
||||
def on_handshake(self, agent, filename, access_point, client_station):
|
||||
if 'ip' not in self.options or ('ip' in self.options and self.options['ip'] is None or (len('ip' in self.options and self.options['ip']) is 0)):
|
||||
ip = "192.168.44.1:8080"
|
||||
else:
|
||||
ip = self.options['ip']
|
||||
|
||||
try:
|
||||
gps = requests.get('http://' + ip + '/gps.xhtml')
|
||||
try:
|
||||
gps_filename = filename.replace('.pcap', '.paw-gps.json')
|
||||
logging.info("[paw-gps] saving GPS data to %s" % (gps_filename))
|
||||
with open(gps_filename, 'w+t') as f:
|
||||
f.write(gps.text)
|
||||
except Exception as error:
|
||||
logging.error(f"[paw-gps] encountered error while saving gps data: {error}")
|
||||
except Exception as error:
|
||||
logging.error(f"[paw-gps] encountered error while getting gps data: {error}")
|
@ -1,9 +1,10 @@
|
||||
import sys
|
||||
|
||||
import pwnagotchi.plugins as plugins
|
||||
import logging
|
||||
import os
|
||||
import json
|
||||
import re
|
||||
import datetime
|
||||
from flask import Response
|
||||
from functools import lru_cache
|
||||
from dateutil.parser import parse
|
||||
@ -22,6 +23,7 @@ from dateutil.parser import parse
|
||||
|
||||
'''
|
||||
|
||||
|
||||
class Webgpsmap(plugins.Plugin):
|
||||
__author__ = 'https://github.com/xenDE and https://github.com/dadav'
|
||||
__version__ = '1.4.0'
|
||||
@ -103,7 +105,7 @@ class Webgpsmap(plugins.Plugin):
|
||||
response_status = 200
|
||||
response_mimetype = "application/xhtml+xml"
|
||||
response_header_contenttype = 'text/html'
|
||||
response_header_contentdisposition = 'attachment; filename=webgpsmap.html';
|
||||
response_header_contentdisposition = 'attachment; filename=webgpsmap.html'
|
||||
except Exception as error:
|
||||
logging.error(f"[webgpsmap] on_webhook offlinemap: error: {error}")
|
||||
return
|
||||
@ -149,7 +151,6 @@ class Webgpsmap(plugins.Plugin):
|
||||
def _get_pos_from_file(self, path):
|
||||
return PositionFile(path)
|
||||
|
||||
|
||||
def load_gps_from_dir(self, gpsdir, newest_only=False):
|
||||
"""
|
||||
Parses the gps-data from disk
|
||||
@ -160,13 +161,9 @@ class Webgpsmap(plugins.Plugin):
|
||||
|
||||
logging.info(f"[webgpsmap] scanning {handshake_dir}")
|
||||
|
||||
|
||||
all_files = os.listdir(handshake_dir)
|
||||
#print(all_files)
|
||||
all_pcap_files = [os.path.join(handshake_dir, filename)
|
||||
for filename in all_files
|
||||
if filename.endswith('.pcap')
|
||||
]
|
||||
# print(all_files)
|
||||
all_pcap_files = [os.path.join(handshake_dir, filename) for filename in all_files if filename.endswith('.pcap')]
|
||||
all_geo_or_gps_files = []
|
||||
for filename_pcap in all_pcap_files:
|
||||
filename_base = filename_pcap[:-5] # remove ".pcap"
|
||||
@ -300,7 +297,6 @@ class PositionFile:
|
||||
return parsed_ssid.groups()[0]
|
||||
return None
|
||||
|
||||
|
||||
def json(self):
|
||||
"""
|
||||
returns the parsed json
|
||||
|
@ -3,6 +3,7 @@ import logging
|
||||
import json
|
||||
import csv
|
||||
import requests
|
||||
import pwnagotchi
|
||||
|
||||
from io import StringIO
|
||||
from datetime import datetime
|
||||
@ -24,7 +25,11 @@ def _extract_gps_data(path):
|
||||
with open(path, 'r') as json_file:
|
||||
tempJson = json.load(json_file)
|
||||
d = datetime.utcfromtimestamp(int(tempJson["ts"]))
|
||||
return {"Latitude": tempJson["location"]["lat"], "Longitude": tempJson["location"]["lng"], "Altitude": 10, "Updated": d.strftime('%Y-%m-%dT%H:%M:%S.%f')}
|
||||
return {"Latitude": tempJson["location"]["lat"],
|
||||
"Longitude": tempJson["location"]["lng"],
|
||||
"Altitude": 10,
|
||||
"Accuracy": tempJson["accuracy"],
|
||||
"Updated": d.strftime('%Y-%m-%dT%H:%M:%S.%f')}
|
||||
else:
|
||||
with open(path, 'r') as json_file:
|
||||
return json.load(json_file)
|
||||
@ -38,7 +43,7 @@ def _format_auth(data):
|
||||
out = ""
|
||||
for auth in data:
|
||||
out = f"{out}[{auth}]"
|
||||
return out
|
||||
return [f"{auth}" for auth in data]
|
||||
|
||||
|
||||
def _transform_wigle_entry(gps_data, pcap_data, plugin_version):
|
||||
@ -47,10 +52,10 @@ def _transform_wigle_entry(gps_data, pcap_data, plugin_version):
|
||||
"""
|
||||
dummy = StringIO()
|
||||
# write kismet header
|
||||
dummy.write(f"WigleWifi-1.6,appRelease={plugin_version},model=pwnagotchi,release={__pwnagotchi_version__},"
|
||||
f"device={pwnagotchi.name()},display=kismet,board=RaspberryPi,brand=pwnagotchi,star=Sol,body=3,subBody=0\n")
|
||||
dummy.write(
|
||||
"WigleWifi-1.4,appRelease={},model=pwnagotchi,release={},device=pwnagotchi,display=kismet,board=kismet,brand=pwnagotchi\n".format(plugin_version, __pwnagotchi_version__))
|
||||
dummy.write(
|
||||
"MAC,SSID,AuthMode,FirstSeen,Channel,RSSI,CurrentLatitude,CurrentLongitude,AltitudeMeters,AccuracyMeters,Type")
|
||||
"MAC,SSID,AuthMode,FirstSeen,Channel,RSSI,CurrentLatitude,CurrentLongitude,AltitudeMeters,AccuracyMeters,Type\n")
|
||||
|
||||
writer = csv.writer(dummy, delimiter=",", quoting=csv.QUOTE_NONE, escapechar="\\")
|
||||
writer.writerow([
|
||||
@ -64,7 +69,7 @@ def _transform_wigle_entry(gps_data, pcap_data, plugin_version):
|
||||
gps_data['Latitude'],
|
||||
gps_data['Longitude'],
|
||||
gps_data['Altitude'],
|
||||
0, # accuracy?
|
||||
gps_data['Accuracy'],
|
||||
'WIFI'])
|
||||
return dummy.getvalue()
|
||||
|
||||
@ -84,7 +89,7 @@ def _send_to_wigle(lines, api_key, donate=True, timeout=30):
|
||||
headers = {'Authorization': f"Basic {api_key}",
|
||||
'Accept': 'application/json'}
|
||||
data = {'donate': 'on' if donate else 'false'}
|
||||
payload = {'file': dummy, 'type': 'text/csv'}
|
||||
payload = {'file': (pwnagotchi.name() + ".csv", dummy, 'multipart/form-data', {'Expires': '0'})}
|
||||
try:
|
||||
res = requests.post('https://api.wigle.net/api/v2/file/upload',
|
||||
data=data,
|
||||
@ -99,34 +104,32 @@ def _send_to_wigle(lines, api_key, donate=True, timeout=30):
|
||||
|
||||
|
||||
class Wigle(plugins.Plugin):
|
||||
__author__ = '33197631+dadav@users.noreply.github.com'
|
||||
__version__ = '2.0.0'
|
||||
__license__ = 'GPL3'
|
||||
__description__ = 'This plugin automatically uploads collected wifis to wigle.net'
|
||||
__author__ = "Dadav and updated by Jayofelony"
|
||||
__version__ = "3.0.1"
|
||||
__license__ = "GPL3"
|
||||
__description__ = "This plugin automatically uploads collected WiFi to wigle.net"
|
||||
|
||||
def __init__(self):
|
||||
self.ready = False
|
||||
self.report = StatusFile('/root/.wigle_uploads', data_format='json')
|
||||
self.skip = list()
|
||||
self.lock = Lock()
|
||||
self.options = dict()
|
||||
|
||||
def on_loaded(self):
|
||||
if 'api_key' not in self.options or ('api_key' in self.options and self.options['api_key'] is None):
|
||||
logging.debug("WIGLE: api_key isn't set. Can't upload to wigle.net")
|
||||
return
|
||||
|
||||
if not 'whitelist' in self.options:
|
||||
self.options['whitelist'] = list()
|
||||
|
||||
if not 'donate' in self.options:
|
||||
self.options['donate'] = True
|
||||
if 'donate' not in self.options:
|
||||
self.options['donate'] = False
|
||||
|
||||
self.ready = True
|
||||
logging.info("WIGLE: ready")
|
||||
|
||||
def on_internet_available(self, agent):
|
||||
"""
|
||||
Called in manual mode when there's internet connectivity
|
||||
Called when there's internet connectivity
|
||||
"""
|
||||
if not self.ready or self.lock.locked():
|
||||
return
|
||||
@ -140,9 +143,9 @@ class Wigle(plugins.Plugin):
|
||||
all_files = os.listdir(handshake_dir)
|
||||
all_gps_files = [os.path.join(handshake_dir, filename)
|
||||
for filename in all_files
|
||||
if filename.endswith('.gps.json') or filename.endswith('.paw-gps.json') or filename.endswith('.geo.json')]
|
||||
if filename.endswith('.gps.json') or filename.endswith('.geo.json')]
|
||||
|
||||
all_gps_files = remove_whitelisted(all_gps_files, self.options['whitelist'])
|
||||
all_gps_files = remove_whitelisted(all_gps_files, config['main']['whitelist'])
|
||||
new_gps_files = set(all_gps_files) - set(reported) - set(self.skip)
|
||||
if new_gps_files:
|
||||
logging.info("WIGLE: Internet connectivity detected. Uploading new handshakes to wigle.net")
|
||||
@ -151,8 +154,6 @@ class Wigle(plugins.Plugin):
|
||||
for gps_file in new_gps_files:
|
||||
if gps_file.endswith('.gps.json'):
|
||||
pcap_filename = gps_file.replace('.gps.json', '.pcap')
|
||||
if gps_file.endswith('.paw-gps.json'):
|
||||
pcap_filename = gps_file.replace('.paw-gps.json', '.pcap')
|
||||
if gps_file.endswith('.geo.json'):
|
||||
pcap_filename = gps_file.replace('.geo.json', '.pcap')
|
||||
if not os.path.exists(pcap_filename):
|
||||
|
@ -76,9 +76,6 @@ class WpaSec(plugins.Plugin):
|
||||
logging.error("WPA_SEC: API-URL isn't set. Can't upload, no endpoint configured.")
|
||||
return
|
||||
|
||||
if 'whitelist' not in self.options:
|
||||
self.options['whitelist'] = list()
|
||||
|
||||
self.ready = True
|
||||
logging.info("WPA_SEC: plugin loaded")
|
||||
|
||||
@ -101,9 +98,8 @@ class WpaSec(plugins.Plugin):
|
||||
reported = self.report.data_field_or('reported', default=list())
|
||||
handshake_dir = config['bettercap']['handshakes']
|
||||
handshake_filenames = os.listdir(handshake_dir)
|
||||
handshake_paths = [os.path.join(handshake_dir, filename) for filename in handshake_filenames if
|
||||
filename.endswith('.pcap')]
|
||||
handshake_paths = remove_whitelisted(handshake_paths, self.options['whitelist'])
|
||||
handshake_paths = [os.path.join(handshake_dir, filename) for filename in handshake_filenames if filename.endswith('.pcap')]
|
||||
handshake_paths = remove_whitelisted(handshake_paths, config['main']['whitelist'])
|
||||
handshake_new = set(handshake_paths) - set(reported) - set(self.skip)
|
||||
|
||||
if handshake_new:
|
||||
|
@ -264,11 +264,11 @@ class EPD:
|
||||
def display(self, image):
|
||||
if (image == None):
|
||||
return
|
||||
# Width = (self.width % 8 == 0)? (self.width / 8 ): (self.width / 8 + 1)
|
||||
# Width = (self.width % 8 == 0)? (self.width // 8 ): (self.width // 8 + 1)
|
||||
if (self.width % 8 == 0):
|
||||
Width = self.width / 8
|
||||
Width = self.width // 8
|
||||
else:
|
||||
Width = self.width / 8 + 1
|
||||
Width = self.width // 8 + 1
|
||||
|
||||
self.send_command(0x10)
|
||||
for j in range(0, self.height):
|
||||
@ -282,11 +282,11 @@ class EPD:
|
||||
self.TurnOnDisplay()
|
||||
|
||||
def Clear(self):
|
||||
# Width = (self.width % 8 == 0)? (self.width / 8 ): (self.width / 8 + 1)
|
||||
# Width = (self.width % 8 == 0)? (self.width // 8 ): (self.width // 8 + 1)
|
||||
if (self.width % 8 == 0):
|
||||
Width = self.width / 8
|
||||
Width = self.width // 8
|
||||
else:
|
||||
Width = self.width / 8 + 1
|
||||
Width = self.width // 8 + 1
|
||||
|
||||
Height = self.height
|
||||
|
||||
@ -313,11 +313,11 @@ class EPD:
|
||||
self.send_data(127) # y-end
|
||||
self.send_data(0x00)
|
||||
|
||||
# Width = (self.width % 8 == 0)? (self.width / 8 ): (self.width / 8 + 1)
|
||||
# Width = (self.width % 8 == 0)? (self.width // 8 ): (self.width // 8 + 1)
|
||||
if (self.width % 8 == 0):
|
||||
Width = self.width / 8
|
||||
Width = self.width // 8
|
||||
else:
|
||||
Width = self.width / 8 + 1
|
||||
Width = self.width // 8 + 1
|
||||
|
||||
Height = self.height
|
||||
# send data
|
||||
|
@ -100,7 +100,7 @@ class EPD:
|
||||
|
||||
def ReadBusy(self):
|
||||
logger.debug("e-Paper busy")
|
||||
while epdconfig.digital_read(self.busy_pin) == 1: # 1: idle, 0: busy
|
||||
while (epdconfig.digital_read(self.busy_pin) == 1): # 1: idle, 0: busy
|
||||
epdconfig.delay_ms(20)
|
||||
logger.debug("e-Paper busy release")
|
||||
|
||||
@ -134,7 +134,7 @@ class EPD:
|
||||
self.send_data(self.LUT_DATA_4Gray[i])
|
||||
|
||||
def init(self):
|
||||
if epdconfig.module_init() != 0:
|
||||
if (epdconfig.module_init() != 0):
|
||||
return -1
|
||||
|
||||
# EPD hardware init start
|
||||
@ -159,7 +159,7 @@ class EPD:
|
||||
return 0
|
||||
|
||||
def init_Fast(self):
|
||||
if epdconfig.module_init() != 0:
|
||||
if (epdconfig.module_init() != 0):
|
||||
return -1
|
||||
|
||||
# EPD hardware init start
|
||||
@ -204,12 +204,12 @@ class EPD:
|
||||
return 0
|
||||
|
||||
def Init_4Gray(self):
|
||||
if epdconfig.module_init() != 0:
|
||||
if (epdconfig.module_init() != 0):
|
||||
return -1
|
||||
self.reset()
|
||||
|
||||
self.send_command(0x12) # soft reset
|
||||
self.ReadBusy()
|
||||
self.ReadBusy();
|
||||
|
||||
self.send_command(0x74) # set analog block control
|
||||
self.send_data(0x54)
|
||||
@ -268,14 +268,14 @@ class EPD:
|
||||
imwidth, imheight = image_monocolor.size
|
||||
pixels = image_monocolor.load()
|
||||
# logger.debug("imwidth = %d, imheight = %d",imwidth,imheight)
|
||||
if imwidth == self.width and imheight == self.height:
|
||||
if (imwidth == self.width and imheight == self.height):
|
||||
logger.debug("Vertical")
|
||||
for y in range(imheight):
|
||||
for x in range(imwidth):
|
||||
# Set the bits for the column of pixels at the current position.
|
||||
if pixels[x, y] == 0:
|
||||
buf[int((x + y * self.width) / 8)] &= ~(0x80 >> (x % 8))
|
||||
elif imwidth == self.height and imheight == self.width:
|
||||
elif (imwidth == self.height and imheight == self.width):
|
||||
logger.debug("Horizontal")
|
||||
for y in range(imheight):
|
||||
for x in range(imwidth):
|
||||
@ -293,40 +293,40 @@ class EPD:
|
||||
pixels = image_monocolor.load()
|
||||
i = 0
|
||||
# logger.debug("imwidth = %d, imheight = %d",imwidth,imheight)
|
||||
if imwidth == self.width and imheight == self.height:
|
||||
if (imwidth == self.width and imheight == self.height):
|
||||
logger.debug("Vertical")
|
||||
for y in range(imheight):
|
||||
for x in range(imwidth):
|
||||
# Set the bits for the column of pixels at the current position.
|
||||
if pixels[x, y] == 0xC0:
|
||||
if (pixels[x, y] == 0xC0):
|
||||
pixels[x, y] = 0x80
|
||||
elif pixels[x, y] == 0x80:
|
||||
elif (pixels[x, y] == 0x80):
|
||||
pixels[x, y] = 0x40
|
||||
i = i + 1
|
||||
if i % 4 == 0:
|
||||
if (i % 4 == 0):
|
||||
buf[int((x + (y * self.width)) / 4)] = (
|
||||
(pixels[x - 3, y] & 0xc0) | (pixels[x - 2, y] & 0xc0) >> 2 | (
|
||||
pixels[x - 1, y] & 0xc0) >> 4 | (pixels[x, y] & 0xc0) >> 6)
|
||||
|
||||
elif imwidth == self.height and imheight == self.width:
|
||||
elif (imwidth == self.height and imheight == self.width):
|
||||
logger.debug("Horizontal")
|
||||
for x in range(imwidth):
|
||||
for y in range(imheight):
|
||||
newx = y
|
||||
newy = self.height - x - 1
|
||||
if pixels[x, y] == 0xC0:
|
||||
if (pixels[x, y] == 0xC0):
|
||||
pixels[x, y] = 0x80
|
||||
elif pixels[x, y] == 0x80:
|
||||
elif (pixels[x, y] == 0x80):
|
||||
pixels[x, y] = 0x40
|
||||
i = i + 1
|
||||
if i % 4 == 0:
|
||||
if (i % 4 == 0):
|
||||
buf[int((newx + (newy * self.width)) / 4)] = (
|
||||
(pixels[x, y - 3] & 0xc0) | (pixels[x, y - 2] & 0xc0) >> 2 | (
|
||||
pixels[x, y - 1] & 0xc0) >> 4 | (pixels[x, y] & 0xc0) >> 6)
|
||||
return buf
|
||||
|
||||
def Clear(self):
|
||||
if self.width % 8 == 0:
|
||||
if (self.width % 8 == 0):
|
||||
Width = self.width // 8
|
||||
else:
|
||||
Width = self.width // 8 + 1
|
||||
@ -338,7 +338,7 @@ class EPD:
|
||||
self.TurnOnDisplay()
|
||||
|
||||
def display(self, image):
|
||||
if self.width % 8 == 0:
|
||||
if (self.width % 8 == 0):
|
||||
Width = self.width // 8
|
||||
else:
|
||||
Width = self.width // 8 + 1
|
||||
@ -350,7 +350,7 @@ class EPD:
|
||||
self.TurnOnDisplay()
|
||||
|
||||
def display_Fast(self, image):
|
||||
if self.width % 8 == 0:
|
||||
if (self.width % 8 == 0):
|
||||
Width = self.width // 8
|
||||
else:
|
||||
Width = self.width // 8 + 1
|
||||
@ -362,7 +362,7 @@ class EPD:
|
||||
self.TurnOnDisplay_Fast()
|
||||
|
||||
def display_Base(self, image):
|
||||
if self.width % 8 == 0:
|
||||
if (self.width % 8 == 0):
|
||||
Width = self.width // 8
|
||||
else:
|
||||
Width = self.width // 8 + 1
|
||||
@ -379,7 +379,7 @@ class EPD:
|
||||
self.TurnOnDisplay()
|
||||
|
||||
def display_Base_color(self, color):
|
||||
if self.width % 8 == 0:
|
||||
if (self.width % 8 == 0):
|
||||
Width = self.width // 8
|
||||
else:
|
||||
Width = self.width // 8 + 1
|
||||
@ -396,7 +396,8 @@ class EPD:
|
||||
# self.TurnOnDisplay()
|
||||
|
||||
def display_Partial(self, Image, Xstart, Ystart, Xend, Yend):
|
||||
if (Xstart % 8 + Xend % 8 == 8 & Xstart % 8 > Xend % 8) | Xstart % 8 + Xend % 8 == 0 | (Xend - Xstart) % 8 == 0:
|
||||
if ((Xstart % 8 + Xend % 8 == 8 & Xstart % 8 > Xend % 8) | Xstart % 8 + Xend % 8 == 0 | (
|
||||
Xend - Xstart) % 8 == 0):
|
||||
Xstart = Xstart // 8
|
||||
Xend = Xend // 8
|
||||
else:
|
||||
@ -439,23 +440,23 @@ class EPD:
|
||||
self.send_command(0x24) # Write Black and White image to RAM
|
||||
for j in range(Height):
|
||||
for i in range(Width):
|
||||
if (j > Ystart - 1) & (j < (Yend + 1)) & (i > Xstart - 1) & (i < (Xend + 1)):
|
||||
if ((j > Ystart - 1) & (j < (Yend + 1)) & (i > Xstart - 1) & (i < (Xend + 1))):
|
||||
self.send_data(Image[i + j * Width])
|
||||
self.TurnOnDisplay_Partial()
|
||||
|
||||
def display_4Gray(self, image):
|
||||
self.send_command(0x24)
|
||||
for i in range(0, 5808): # 5808*4 46464
|
||||
for i in range(0, 48000): # 5808*4 46464
|
||||
temp3 = 0
|
||||
for j in range(0, 2):
|
||||
temp1 = image[i * 2 + j]
|
||||
for k in range(0, 2):
|
||||
temp2 = temp1 & 0xC0
|
||||
if temp2 == 0xC0:
|
||||
if (temp2 == 0xC0):
|
||||
temp3 |= 0x00
|
||||
elif temp2 == 0x00:
|
||||
elif (temp2 == 0x00):
|
||||
temp3 |= 0x01
|
||||
elif temp2 == 0x80:
|
||||
elif (temp2 == 0x80):
|
||||
temp3 |= 0x01
|
||||
else: # 0x40
|
||||
temp3 |= 0x00
|
||||
@ -463,31 +464,31 @@ class EPD:
|
||||
|
||||
temp1 <<= 2
|
||||
temp2 = temp1 & 0xC0
|
||||
if temp2 == 0xC0:
|
||||
if (temp2 == 0xC0):
|
||||
temp3 |= 0x00
|
||||
elif temp2 == 0x00:
|
||||
elif (temp2 == 0x00):
|
||||
temp3 |= 0x01
|
||||
elif temp2 == 0x80:
|
||||
elif (temp2 == 0x80):
|
||||
temp3 |= 0x01
|
||||
else: # 0x40
|
||||
temp3 |= 0x00
|
||||
if j != 1 or k != 1:
|
||||
if (j != 1 or k != 1):
|
||||
temp3 <<= 1
|
||||
temp1 <<= 2
|
||||
self.send_data(temp3)
|
||||
|
||||
self.send_command(0x26)
|
||||
for i in range(0, 5808): # 5808*4 46464
|
||||
for i in range(0, 48000): # 5808*4 46464
|
||||
temp3 = 0
|
||||
for j in range(0, 2):
|
||||
temp1 = image[i * 2 + j]
|
||||
for k in range(0, 2):
|
||||
temp2 = temp1 & 0xC0
|
||||
if temp2 == 0xC0:
|
||||
if (temp2 == 0xC0):
|
||||
temp3 |= 0x00
|
||||
elif temp2 == 0x00:
|
||||
elif (temp2 == 0x00):
|
||||
temp3 |= 0x01
|
||||
elif temp2 == 0x80:
|
||||
elif (temp2 == 0x80):
|
||||
temp3 |= 0x00
|
||||
else: # 0x40
|
||||
temp3 |= 0x01
|
||||
@ -495,15 +496,15 @@ class EPD:
|
||||
|
||||
temp1 <<= 2
|
||||
temp2 = temp1 & 0xC0
|
||||
if temp2 == 0xC0:
|
||||
if (temp2 == 0xC0):
|
||||
temp3 |= 0x00
|
||||
elif temp2 == 0x00:
|
||||
elif (temp2 == 0x00):
|
||||
temp3 |= 0x01
|
||||
elif temp2 == 0x80:
|
||||
elif (temp2 == 0x80):
|
||||
temp3 |= 0x00
|
||||
else: # 0x40
|
||||
temp3 |= 0x01
|
||||
if j != 1 or k != 1:
|
||||
if (j != 1 or k != 1):
|
||||
temp3 <<= 1
|
||||
temp1 <<= 2
|
||||
self.send_data(temp3)
|
||||
@ -516,3 +517,5 @@ class EPD:
|
||||
|
||||
epdconfig.delay_ms(2000)
|
||||
epdconfig.module_exit()
|
||||
|
||||
### END OF FILE ###
|
@ -28,7 +28,7 @@
|
||||
#
|
||||
|
||||
import logging
|
||||
from .. import epdconfig
|
||||
from . import epdconfig
|
||||
|
||||
# Display resolution
|
||||
EPD_WIDTH = 128
|
||||
@ -227,7 +227,7 @@ class EPD:
|
||||
self.send_data((y >> 8) & 0xFF)
|
||||
|
||||
def init(self):
|
||||
if epdconfig.module_init() != 0:
|
||||
if (epdconfig.module_init() != 0):
|
||||
return -1
|
||||
# EPD hardware init start
|
||||
self.reset()
|
||||
|
@ -184,7 +184,7 @@ class EPD:
|
||||
|
||||
self.send_command(0x01) # Driver output control
|
||||
self.send_data((self.height - 1) % 256)
|
||||
self.send_data((self.height - 1) / 256)
|
||||
self.send_data((self.height - 1) // 256)
|
||||
self.send_data(0x00)
|
||||
|
||||
self.send_command(0x11) # data entry mode
|
||||
|
@ -29,7 +29,7 @@
|
||||
|
||||
|
||||
import logging
|
||||
from .. import epdconfig
|
||||
from . import epdconfig
|
||||
|
||||
# Display resolution
|
||||
EPD_WIDTH = 800
|
||||
|
@ -1,11 +1,11 @@
|
||||
# *****************************************************************************
|
||||
# * | File : epd7in5.py
|
||||
# * | File : epd4in26.py
|
||||
# * | Author : Waveshare team
|
||||
# * | Function : Electronic paper driver
|
||||
# * | Info :
|
||||
# *----------------
|
||||
# * | This version: V4.0
|
||||
# * | Date : 2019-06-20
|
||||
# * | This version: V1.0
|
||||
# * | Date : 2023-12-20
|
||||
# # | Info : python demo
|
||||
# -----------------------------------------------------------------------------
|
||||
# Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
@ -29,11 +29,16 @@
|
||||
|
||||
|
||||
import logging
|
||||
from .. import epdconfig
|
||||
from . import epdconfig
|
||||
|
||||
# Display resolution
|
||||
EPD_WIDTH = 880
|
||||
EPD_HEIGHT = 528
|
||||
EPD_WIDTH = 800
|
||||
EPD_HEIGHT = 480
|
||||
|
||||
GRAY1 = 0xff # white
|
||||
GRAY2 = 0xC0
|
||||
GRAY3 = 0x80 # gray
|
||||
GRAY4 = 0x00 # Blackest
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
@ -46,15 +51,39 @@ class EPD:
|
||||
self.cs_pin = epdconfig.CS_PIN
|
||||
self.width = EPD_WIDTH
|
||||
self.height = EPD_HEIGHT
|
||||
self.GRAY1 = GRAY1 # white
|
||||
self.GRAY2 = GRAY2
|
||||
self.GRAY3 = GRAY3 # gray
|
||||
self.GRAY4 = GRAY4 # Blackest
|
||||
|
||||
LUT_DATA_4Gray = [ # #112bytes
|
||||
0x80, 0x48, 0x4A, 0x22, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x0A, 0x48, 0x68, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x88, 0x48, 0x60, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0xA8, 0x48, 0x45, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x07, 0x1E, 0x1C, 0x02, 0x00,
|
||||
0x05, 0x01, 0x05, 0x01, 0x02,
|
||||
0x08, 0x01, 0x01, 0x04, 0x04,
|
||||
0x00, 0x02, 0x00, 0x02, 0x01,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x01,
|
||||
0x22, 0x22, 0x22, 0x22, 0x22,
|
||||
0x17, 0x41, 0xA8, 0x32, 0x30,
|
||||
0x00, 0x00]
|
||||
|
||||
# Hardware reset
|
||||
def reset(self):
|
||||
epdconfig.digital_write(self.reset_pin, 1)
|
||||
epdconfig.delay_ms(200)
|
||||
epdconfig.delay_ms(20)
|
||||
epdconfig.digital_write(self.reset_pin, 0)
|
||||
epdconfig.delay_ms(2)
|
||||
epdconfig.digital_write(self.reset_pin, 1)
|
||||
epdconfig.delay_ms(200)
|
||||
epdconfig.delay_ms(20)
|
||||
|
||||
def send_command(self, command):
|
||||
epdconfig.digital_write(self.dc_pin, 0)
|
||||
@ -71,7 +100,7 @@ class EPD:
|
||||
def send_data2(self, data):
|
||||
epdconfig.digital_write(self.dc_pin, 1)
|
||||
epdconfig.digital_write(self.cs_pin, 0)
|
||||
epdconfig.spi_writebyte2(data)
|
||||
epdconfig.SPI.writebytes2(data)
|
||||
epdconfig.digital_write(self.cs_pin, 1)
|
||||
|
||||
def ReadBusy(self):
|
||||
@ -79,102 +108,408 @@ class EPD:
|
||||
busy = epdconfig.digital_read(self.busy_pin)
|
||||
while (busy == 1):
|
||||
busy = epdconfig.digital_read(self.busy_pin)
|
||||
epdconfig.delay_ms(200)
|
||||
epdconfig.delay_ms(20)
|
||||
epdconfig.delay_ms(20)
|
||||
logger.debug("e-Paper busy release")
|
||||
|
||||
def TurnOnDisplay(self):
|
||||
self.send_command(0x22) # Display Update Control
|
||||
self.send_data(0xF7)
|
||||
self.send_command(0x20) # Activate Display Update Sequence
|
||||
self.ReadBusy()
|
||||
|
||||
def TurnOnDisplay_Fast(self):
|
||||
self.send_command(0x22) # Display Update Control
|
||||
self.send_data(0xC7)
|
||||
self.send_command(0x20) # Activate Display Update Sequence
|
||||
self.ReadBusy()
|
||||
|
||||
def TurnOnDisplay_Part(self):
|
||||
self.send_command(0x22) # Display Update Control
|
||||
self.send_data(0xFF)
|
||||
self.send_command(0x20) # Activate Display Update Sequence
|
||||
self.ReadBusy()
|
||||
|
||||
def TurnOnDisplay_4GRAY(self):
|
||||
self.send_command(0x22) # Display Update Control
|
||||
self.send_data(0xC7)
|
||||
self.send_command(0x20) # Activate Display Update Sequence
|
||||
self.ReadBusy()
|
||||
|
||||
'''
|
||||
function : Setting the display window
|
||||
parameter:
|
||||
xstart : X-axis starting position
|
||||
ystart : Y-axis starting position
|
||||
xend : End position of X-axis
|
||||
yend : End position of Y-axis
|
||||
'''
|
||||
|
||||
def SetWindow(self, x_start, y_start, x_end, y_end):
|
||||
self.send_command(0x44) # SET_RAM_X_ADDRESS_START_END_POSITION
|
||||
self.send_data(x_start & 0xFF)
|
||||
self.send_data((x_start >> 8) & 0x03)
|
||||
self.send_data(x_end & 0xFF)
|
||||
self.send_data((x_end >> 8) & 0x03)
|
||||
|
||||
self.send_command(0x45) # SET_RAM_Y_ADDRESS_START_END_POSITION
|
||||
self.send_data(y_start & 0xFF)
|
||||
self.send_data((y_start >> 8) & 0xFF)
|
||||
self.send_data(y_end & 0xFF)
|
||||
self.send_data((y_end >> 8) & 0xFF)
|
||||
|
||||
'''
|
||||
function : Set Cursor
|
||||
parameter:
|
||||
x : X-axis starting position
|
||||
y : Y-axis starting position
|
||||
'''
|
||||
|
||||
def SetCursor(self, x, y):
|
||||
self.send_command(0x4E) # SET_RAM_X_ADDRESS_COUNTER
|
||||
# x point must be the multiple of 8 or the last 3 bits will be ignored
|
||||
self.send_data(x & 0xFF)
|
||||
self.send_data((x >> 8) & 0x03)
|
||||
|
||||
self.send_command(0x4F) # SET_RAM_Y_ADDRESS_COUNTER
|
||||
self.send_data(y & 0xFF)
|
||||
self.send_data((y >> 8) & 0xFF)
|
||||
|
||||
def init(self):
|
||||
if (epdconfig.module_init() != 0):
|
||||
return -1
|
||||
# EPD hardware init start
|
||||
self.reset()
|
||||
self.ReadBusy()
|
||||
|
||||
self.ReadBusy();
|
||||
self.send_command(0x12); # SWRESET
|
||||
self.ReadBusy();
|
||||
self.send_command(0x12) # SWRESET
|
||||
self.ReadBusy()
|
||||
|
||||
self.send_command(0x46); # Auto Write Red RAM
|
||||
self.send_data(0xf7);
|
||||
self.ReadBusy();
|
||||
self.send_command(0x47); # Auto Write B/W RAM
|
||||
self.send_data(0xf7);
|
||||
self.ReadBusy();
|
||||
self.send_command(0x18) # use the internal temperature sensor
|
||||
self.send_data(0x80)
|
||||
|
||||
self.send_command(0x0C); # Soft start setting
|
||||
self.send_data2([0xAE, 0xC7, 0xC3, 0xC0, 0x40])
|
||||
self.send_command(0x0C) # set soft start
|
||||
self.send_data(0xAE)
|
||||
self.send_data(0xC7)
|
||||
self.send_data(0xC3)
|
||||
self.send_data(0xC0)
|
||||
self.send_data(0x80)
|
||||
|
||||
self.send_command(0x01); # Set MUX as 527
|
||||
self.send_data2([0xAF, 0x02, 0x01])
|
||||
self.send_command(0x01) # drive output control
|
||||
self.send_data((self.height - 1) % 256) # Y
|
||||
self.send_data((self.height - 1) // 256) # Y
|
||||
self.send_data(0x02)
|
||||
|
||||
self.send_command(0x11); # Data entry mode
|
||||
self.send_data(0x01);
|
||||
self.send_command(0x3C) # Border Border setting
|
||||
self.send_data(0x01)
|
||||
|
||||
self.send_command(0x44);
|
||||
self.send_data2([0x00, 0x00, 0x6F, 0x03]) # RAM x address start at 0
|
||||
self.send_command(0x45);
|
||||
self.send_data2([0xAF, 0x02, 0x00, 0x00])
|
||||
self.send_command(0x11) # data entry mode
|
||||
self.send_data(0x01) # X-mode x+ y-
|
||||
|
||||
self.send_command(0x3C); # VBD
|
||||
self.send_data(0x05); # LUT1, for white
|
||||
self.SetWindow(0, self.height - 1, self.width - 1, 0)
|
||||
|
||||
self.send_command(0x18);
|
||||
self.send_data(0X80);
|
||||
self.SetCursor(0, 0)
|
||||
self.ReadBusy()
|
||||
|
||||
self.send_command(0x22);
|
||||
self.send_data(0XB1); # Load Temperature and waveform setting.
|
||||
self.send_command(0x20);
|
||||
self.ReadBusy();
|
||||
# EPD hardware init end
|
||||
return 0
|
||||
|
||||
self.send_command(0x4E); # set RAM x address count to 0;
|
||||
self.send_data2([0x00, 0x00])
|
||||
self.send_command(0x4F);
|
||||
self.send_data2([0x00, 0x00])
|
||||
def init_Fast(self):
|
||||
if (epdconfig.module_init() != 0):
|
||||
return -1
|
||||
# EPD hardware init start
|
||||
self.reset()
|
||||
self.ReadBusy()
|
||||
|
||||
self.send_command(0x12) # SWRESET
|
||||
self.ReadBusy()
|
||||
|
||||
self.send_command(0x18) # use the internal temperature sensor
|
||||
self.send_data(0x80)
|
||||
|
||||
self.send_command(0x0C) # set soft start
|
||||
self.send_data(0xAE)
|
||||
self.send_data(0xC7)
|
||||
self.send_data(0xC3)
|
||||
self.send_data(0xC0)
|
||||
self.send_data(0x80)
|
||||
|
||||
self.send_command(0x01) # drive output control
|
||||
self.send_data((self.height - 1) % 256) # Y
|
||||
self.send_data((self.height - 1) // 256) # Y
|
||||
self.send_data(0x02)
|
||||
|
||||
self.send_command(0x3C) # Border Border setting
|
||||
self.send_data(0x01)
|
||||
|
||||
self.send_command(0x11) # data entry mode
|
||||
self.send_data(0x01) # X-mode x+ y-
|
||||
|
||||
self.SetWindow(0, self.height - 1, self.width - 1, 0)
|
||||
|
||||
self.SetCursor(0, 0)
|
||||
self.ReadBusy()
|
||||
|
||||
# TEMP (1.5s)
|
||||
self.send_command(0x1A)
|
||||
self.send_data(0x5A)
|
||||
|
||||
self.send_command(0x22)
|
||||
self.send_data(0x91)
|
||||
self.send_command(0x20)
|
||||
|
||||
self.ReadBusy()
|
||||
|
||||
# EPD hardware init end
|
||||
return 0
|
||||
|
||||
def Lut(self):
|
||||
self.send_command(0x32)
|
||||
for count in range(0, 105):
|
||||
self.send_data(self.LUT_DATA_4Gray[count])
|
||||
|
||||
self.send_command(0x03) # VGH
|
||||
self.send_data(self.LUT_DATA_4Gray[105])
|
||||
|
||||
self.send_command(0x04) #
|
||||
self.send_data(self.LUT_DATA_4Gray[106]) # VSH1
|
||||
self.send_data(self.LUT_DATA_4Gray[107]) # VSH2
|
||||
self.send_data(self.LUT_DATA_4Gray[108]) # VSL
|
||||
|
||||
self.send_command(0x2C) # VCOM Voltage
|
||||
self.send_data(self.LUT_DATA_4Gray[109]) # 0x1C
|
||||
|
||||
def init_4GRAY(self):
|
||||
if (epdconfig.module_init() != 0):
|
||||
return -1
|
||||
# EPD hardware init start
|
||||
self.reset()
|
||||
self.ReadBusy()
|
||||
|
||||
self.send_command(0x12) # SWRESET
|
||||
self.ReadBusy()
|
||||
|
||||
self.send_command(0x18) # use the internal temperature sensor
|
||||
self.send_data(0x80)
|
||||
|
||||
self.send_command(0x0C) # set soft start
|
||||
self.send_data(0xAE)
|
||||
self.send_data(0xC7)
|
||||
self.send_data(0xC3)
|
||||
self.send_data(0xC0)
|
||||
self.send_data(0x80)
|
||||
|
||||
self.send_command(0x01) # drive output control
|
||||
self.send_data((self.height - 1) % 256) # Y
|
||||
self.send_data((self.height - 1) // 256) # Y
|
||||
self.send_data(0x02)
|
||||
|
||||
self.send_command(0x3C) # Border Border setting
|
||||
self.send_data(0x01)
|
||||
|
||||
self.send_command(0x11) # data entry mode
|
||||
self.send_data(0x01) # X-mode x+ y-
|
||||
|
||||
self.SetWindow(0, self.height - 1, self.width - 1, 0)
|
||||
|
||||
self.SetCursor(0, 0)
|
||||
self.ReadBusy()
|
||||
|
||||
self.Lut()
|
||||
# EPD hardware init end
|
||||
return 0
|
||||
|
||||
def getbuffer(self, image):
|
||||
img = image
|
||||
imwidth, imheight = img.size
|
||||
if (imwidth == self.width and imheight == self.height):
|
||||
img = img.convert('1')
|
||||
elif (imwidth == self.height and imheight == self.width):
|
||||
img = img.rotate(90, expand=True).convert('1')
|
||||
else:
|
||||
logger.warning("Wrong image dimensions: must be " + str(self.width) + "x" + str(self.height))
|
||||
# return a blank buffer
|
||||
return [0xff] * int(self.width * self.height / 8)
|
||||
# logger.debug("bufsiz = ",int(self.width/8) * self.height)
|
||||
buf = [0xFF] * (int(self.width / 8) * self.height)
|
||||
image_monocolor = image.convert('1')
|
||||
imwidth, imheight = image_monocolor.size
|
||||
pixels = image_monocolor.load()
|
||||
# logger.debug("imwidth = %d, imheight = %d",imwidth,imheight)
|
||||
if imwidth == self.width and imheight == self.height:
|
||||
logger.debug("Horizontal")
|
||||
for y in range(imheight):
|
||||
for x in range(imwidth):
|
||||
# Set the bits for the column of pixels at the current position.
|
||||
if pixels[x, y] == 0:
|
||||
buf[int((x + y * self.width) / 8)] &= ~(0x80 >> (x % 8))
|
||||
elif imwidth == self.height and imheight == self.width:
|
||||
logger.debug("Vertical")
|
||||
for y in range(imheight):
|
||||
for x in range(imwidth):
|
||||
newx = y
|
||||
newy = self.height - x - 1
|
||||
if pixels[x, y] == 0:
|
||||
buf[int((newx + newy * self.width) / 8)] &= ~(0x80 >> (y % 8))
|
||||
return buf
|
||||
|
||||
buf = bytearray(img.tobytes('raw'))
|
||||
def getbuffer_4Gray(self, image):
|
||||
# logger.debug("bufsiz = ",int(self.width/8) * self.height)
|
||||
buf = [0xFF] * (int(self.width / 4) * self.height)
|
||||
image_monocolor = image.convert('L')
|
||||
imwidth, imheight = image_monocolor.size
|
||||
pixels = image_monocolor.load()
|
||||
i = 0
|
||||
# logger.debug("imwidth = %d, imheight = %d",imwidth,imheight)
|
||||
if (imwidth == self.width and imheight == self.height):
|
||||
logger.debug("Vertical")
|
||||
for y in range(imheight):
|
||||
for x in range(imwidth):
|
||||
# Set the bits for the column of pixels at the current position.
|
||||
if (pixels[x, y] == 0xC0):
|
||||
pixels[x, y] = 0x80
|
||||
elif (pixels[x, y] == 0x80):
|
||||
pixels[x, y] = 0x40
|
||||
i = i + 1
|
||||
if (i % 4 == 0):
|
||||
buf[int((x + (y * self.width)) / 4)] = (
|
||||
(pixels[x - 3, y] & 0xc0) | (pixels[x - 2, y] & 0xc0) >> 2 | (
|
||||
pixels[x - 1, y] & 0xc0) >> 4 | (pixels[x, y] & 0xc0) >> 6)
|
||||
|
||||
elif (imwidth == self.height and imheight == self.width):
|
||||
logger.debug("Horizontal")
|
||||
for x in range(imwidth):
|
||||
for y in range(imheight):
|
||||
newx = y
|
||||
newy = self.height - x - 1
|
||||
if (pixels[x, y] == 0xC0):
|
||||
pixels[x, y] = 0x80
|
||||
elif (pixels[x, y] == 0x80):
|
||||
pixels[x, y] = 0x40
|
||||
i = i + 1
|
||||
if (i % 4 == 0):
|
||||
buf[int((newx + (newy * self.width)) / 4)] = (
|
||||
(pixels[x, y - 3] & 0xc0) | (pixels[x, y - 2] & 0xc0) >> 2 | (
|
||||
pixels[x, y - 1] & 0xc0) >> 4 | (pixels[x, y] & 0xc0) >> 6)
|
||||
return buf
|
||||
|
||||
def display(self, image):
|
||||
self.send_command(0x4F);
|
||||
self.send_data2([0x00, 0x00])
|
||||
self.send_command(0x24);
|
||||
self.send_data2(image)
|
||||
self.send_command(0x22);
|
||||
self.send_data(0xF7); # Load LUT from MCU(0x32)
|
||||
self.send_command(0x20);
|
||||
epdconfig.delay_ms(10);
|
||||
self.ReadBusy();
|
||||
|
||||
def Clear(self):
|
||||
buf = [0xff] * int(self.width * self.height / 8)
|
||||
self.send_command(0x4F);
|
||||
self.send_data2([0x00, 0x00])
|
||||
self.send_command(0x24)
|
||||
self.send_data2(buf)
|
||||
self.send_data2(image)
|
||||
|
||||
self.TurnOnDisplay()
|
||||
|
||||
def display_Base(self, image):
|
||||
self.send_command(0x24)
|
||||
self.send_data2(image)
|
||||
|
||||
self.send_command(0x26)
|
||||
self.send_data2(buf)
|
||||
self.send_data2(image)
|
||||
|
||||
self.send_command(0x22);
|
||||
self.send_data(0xF7); # Load LUT from MCU(0x32)
|
||||
self.send_command(0x20);
|
||||
epdconfig.delay_ms(10);
|
||||
self.ReadBusy();
|
||||
self.TurnOnDisplay()
|
||||
|
||||
def display_Fast(self, image):
|
||||
self.send_command(0x24)
|
||||
self.send_data2(image)
|
||||
|
||||
self.TurnOnDisplay_Fast()
|
||||
|
||||
def display_Partial(self, Image):
|
||||
|
||||
# Reset
|
||||
self.reset()
|
||||
|
||||
self.send_command(0x18) # BorderWavefrom
|
||||
self.send_data(0x80)
|
||||
|
||||
self.send_command(0x3C) # BorderWavefrom
|
||||
self.send_data(0x80)
|
||||
|
||||
self.send_command(0x01) # drive output control
|
||||
self.send_data((self.height - 1) % 256) # Y
|
||||
self.send_data((self.height - 1) // 256) # Y
|
||||
|
||||
self.send_command(0x11) # data entry mode
|
||||
self.send_data(0x01) # X-mode x+ y-
|
||||
|
||||
self.SetWindow(0, self.height - 1, self.width - 1, 0)
|
||||
|
||||
self.SetCursor(0, 0)
|
||||
|
||||
self.send_command(0x24) # Write Black and White image to RAM
|
||||
self.send_data2(Image)
|
||||
|
||||
self.TurnOnDisplay_Part()
|
||||
|
||||
def display_4Gray(self, image):
|
||||
self.send_command(0x24)
|
||||
for i in range(0, 48000): # 5808*4 46464
|
||||
temp3 = 0
|
||||
for j in range(0, 2):
|
||||
temp1 = image[i * 2 + j]
|
||||
for k in range(0, 2):
|
||||
temp2 = temp1 & 0xC0
|
||||
if (temp2 == 0xC0):
|
||||
temp3 |= 0x00
|
||||
elif (temp2 == 0x00):
|
||||
temp3 |= 0x01
|
||||
elif (temp2 == 0x80):
|
||||
temp3 |= 0x01
|
||||
else: # 0x40
|
||||
temp3 |= 0x00
|
||||
temp3 <<= 1
|
||||
|
||||
temp1 <<= 2
|
||||
temp2 = temp1 & 0xC0
|
||||
if (temp2 == 0xC0):
|
||||
temp3 |= 0x00
|
||||
elif (temp2 == 0x00):
|
||||
temp3 |= 0x01
|
||||
elif (temp2 == 0x80):
|
||||
temp3 |= 0x01
|
||||
else: # 0x40
|
||||
temp3 |= 0x00
|
||||
if (j != 1 or k != 1):
|
||||
temp3 <<= 1
|
||||
temp1 <<= 2
|
||||
self.send_data(temp3)
|
||||
|
||||
self.send_command(0x26)
|
||||
for i in range(0, 48000): # 5808*4 46464
|
||||
temp3 = 0
|
||||
for j in range(0, 2):
|
||||
temp1 = image[i * 2 + j]
|
||||
for k in range(0, 2):
|
||||
temp2 = temp1 & 0xC0
|
||||
if (temp2 == 0xC0):
|
||||
temp3 |= 0x00
|
||||
elif (temp2 == 0x00):
|
||||
temp3 |= 0x01
|
||||
elif (temp2 == 0x80):
|
||||
temp3 |= 0x00
|
||||
else: # 0x40
|
||||
temp3 |= 0x01
|
||||
temp3 <<= 1
|
||||
|
||||
temp1 <<= 2
|
||||
temp2 = temp1 & 0xC0
|
||||
if (temp2 == 0xC0):
|
||||
temp3 |= 0x00
|
||||
elif (temp2 == 0x00):
|
||||
temp3 |= 0x01
|
||||
elif (temp2 == 0x80):
|
||||
temp3 |= 0x00
|
||||
else: # 0x40
|
||||
temp3 |= 0x01
|
||||
if (j != 1 or k != 1):
|
||||
temp3 <<= 1
|
||||
temp1 <<= 2
|
||||
self.send_data(temp3)
|
||||
|
||||
self.TurnOnDisplay_4GRAY()
|
||||
|
||||
def Clear(self):
|
||||
self.send_command(0x24)
|
||||
self.send_data2([0xFF] * (int(self.width / 8) * self.height))
|
||||
|
||||
self.send_command(0x26)
|
||||
self.send_data2([0xFF] * (int(self.width / 8) * self.height))
|
||||
|
||||
self.TurnOnDisplay()
|
||||
|
||||
def sleep(self):
|
||||
self.send_command(0x10);
|
||||
self.send_data(0x01);
|
||||
self.send_command(0x10) # DEEP_SLEEP
|
||||
self.send_data(0x01)
|
||||
|
||||
epdconfig.delay_ms(2000)
|
||||
epdconfig.module_exit()
|
||||
|
@ -243,7 +243,7 @@ class EPD:
|
||||
else:
|
||||
Xend = Xend // 8 * 8 + 1
|
||||
|
||||
Width = (Xend - Xstart) / 8
|
||||
Width = (Xend - Xstart) // 8
|
||||
Height = Yend - Ystart
|
||||
|
||||
self.send_command(0x50)
|
||||
|
@ -484,7 +484,7 @@ class EPD:
|
||||
else:
|
||||
Xend = Xend // 8 * 8 + 1
|
||||
|
||||
Width = (Xend - Xstart) / 8
|
||||
Width = (Xend - Xstart) // 8
|
||||
Height = Yend - Ystart
|
||||
|
||||
self.send_command(0x50)
|
||||
|
@ -81,59 +81,59 @@ class EPD:
|
||||
|
||||
self.reset()
|
||||
|
||||
self.send_command(0x12); # SWRESET
|
||||
self.ReadBusy(); # waiting for the electronic paper IC to release the idle signal
|
||||
self.send_command(0x12) # SWRESET
|
||||
self.ReadBusy() # waiting for the electronic paper IC to release the idle signal
|
||||
|
||||
self.send_command(0x46); # Auto Write RAM
|
||||
self.send_data(0xF7);
|
||||
self.ReadBusy(); # waiting for the electronic paper IC to release the idle signal
|
||||
self.send_command(0x46) # Auto Write RAM
|
||||
self.send_data(0xF7)
|
||||
self.ReadBusy() # waiting for the electronic paper IC to release the idle signal
|
||||
|
||||
self.send_command(0x47); # Auto Write RAM
|
||||
self.send_data(0xF7);
|
||||
self.ReadBusy(); # waiting for the electronic paper IC to release the idle signal
|
||||
self.send_command(0x47) # Auto Write RAM
|
||||
self.send_data(0xF7)
|
||||
self.ReadBusy() # waiting for the electronic paper IC to release the idle signal
|
||||
|
||||
self.send_command(0x0C); # Soft start setting
|
||||
self.send_data(0xAE);
|
||||
self.send_data(0xC7);
|
||||
self.send_data(0xC3);
|
||||
self.send_data(0xC0);
|
||||
self.send_data(0x40);
|
||||
self.send_command(0x0C) # Soft start setting
|
||||
self.send_data(0xAE)
|
||||
self.send_data(0xC7)
|
||||
self.send_data(0xC3)
|
||||
self.send_data(0xC0)
|
||||
self.send_data(0x40)
|
||||
|
||||
self.send_command(0x01); # Set MUX as 527
|
||||
self.send_data(0xAF);
|
||||
self.send_data(0x02);
|
||||
self.send_data(0x01);
|
||||
self.send_command(0x01) # Set MUX as 527
|
||||
self.send_data(0xAF)
|
||||
self.send_data(0x02)
|
||||
self.send_data(0x01)
|
||||
|
||||
self.send_command(0x11); # Data entry mode
|
||||
self.send_data(0x01);
|
||||
self.send_command(0x11) # Data entry mode
|
||||
self.send_data(0x01)
|
||||
|
||||
self.send_command(0x44);
|
||||
self.send_data(0x00); # RAM x address start at 0
|
||||
self.send_data(0x00);
|
||||
self.send_data(0x6F); # RAM x address end at 36Fh -> 879
|
||||
self.send_data(0x03);
|
||||
self.send_command(0x45);
|
||||
self.send_data(0xAF); # RAM y address start at 20Fh;
|
||||
self.send_data(0x02);
|
||||
self.send_data(0x00); # RAM y address end at 00h;
|
||||
self.send_data(0x00);
|
||||
self.send_command(0x44)
|
||||
self.send_data(0x00) # RAM x address start at 0
|
||||
self.send_data(0x00)
|
||||
self.send_data(0x6F) # RAM x address end at 36Fh -> 879
|
||||
self.send_data(0x03)
|
||||
self.send_command(0x45)
|
||||
self.send_data(0xAF) # RAM y address start at 20Fh
|
||||
self.send_data(0x02)
|
||||
self.send_data(0x00) # RAM y address end at 00h
|
||||
self.send_data(0x00)
|
||||
|
||||
self.send_command(0x3C); # VBD
|
||||
self.send_data(0x01); # LUT1, for white
|
||||
self.send_command(0x3C) # VBD
|
||||
self.send_data(0x01) # LUT1, for white
|
||||
|
||||
self.send_command(0x18);
|
||||
self.send_data(0X80);
|
||||
self.send_command(0x22);
|
||||
self.send_data(0XB1); # Load Temperature and waveform setting.
|
||||
self.send_command(0x20);
|
||||
self.ReadBusy(); # waiting for the electronic paper IC to release the idle signal
|
||||
self.send_command(0x18)
|
||||
self.send_data(0X80)
|
||||
self.send_command(0x22)
|
||||
self.send_data(0XB1) # Load Temperature and waveform setting.
|
||||
self.send_command(0x20)
|
||||
self.ReadBusy() # waiting for the electronic paper IC to release the idle signal
|
||||
|
||||
self.send_command(0x4E);
|
||||
self.send_data(0x00);
|
||||
self.send_data(0x00);
|
||||
self.send_command(0x4F);
|
||||
self.send_data(0xAF);
|
||||
self.send_data(0x02);
|
||||
self.send_command(0x4E)
|
||||
self.send_data(0x00)
|
||||
self.send_data(0x00)
|
||||
self.send_command(0x4F)
|
||||
self.send_data(0xAF)
|
||||
self.send_data(0x02)
|
||||
|
||||
return 0
|
||||
|
||||
@ -162,44 +162,44 @@ class EPD:
|
||||
return buf
|
||||
|
||||
def display(self, imageblack, imagered):
|
||||
self.send_command(0x4F);
|
||||
self.send_data(0xAf);
|
||||
self.send_command(0x4F)
|
||||
self.send_data(0xAf)
|
||||
|
||||
self.send_command(0x24)
|
||||
for i in range(0, int(self.width * self.height / 8)):
|
||||
self.send_data(imageblack[i]);
|
||||
self.send_data(imageblack[i])
|
||||
|
||||
self.send_command(0x26)
|
||||
for i in range(0, int(self.width * self.height / 8)):
|
||||
self.send_data(~imagered[i]);
|
||||
self.send_data(~imagered[i])
|
||||
|
||||
self.send_command(0x22);
|
||||
self.send_data(0xC7); # Load LUT from MCU(0x32)
|
||||
self.send_command(0x20);
|
||||
epdconfig.delay_ms(200); # !!!The delay here is necessary, 200uS at least!!!
|
||||
self.ReadBusy();
|
||||
self.send_command(0x22)
|
||||
self.send_data(0xC7) # Load LUT from MCU(0x32)
|
||||
self.send_command(0x20)
|
||||
epdconfig.delay_ms(200) # !!!The delay here is necessary, 200uS at least!!!
|
||||
self.ReadBusy()
|
||||
|
||||
def Clear(self):
|
||||
self.send_command(0x4F);
|
||||
self.send_data(0xAf);
|
||||
self.send_command(0x4F)
|
||||
self.send_data(0xAf)
|
||||
|
||||
self.send_command(0x24)
|
||||
for i in range(0, int(self.width * self.height / 8)):
|
||||
self.send_data(0xff);
|
||||
self.send_data(0xff)
|
||||
|
||||
self.send_command(0x26)
|
||||
for i in range(0, int(self.width * self.height / 8)):
|
||||
self.send_data(0x00);
|
||||
self.send_data(0x00)
|
||||
|
||||
self.send_command(0x22);
|
||||
self.send_data(0xC7); # Load LUT from MCU(0x32)
|
||||
self.send_command(0x20);
|
||||
epdconfig.delay_ms(200); # !!!The delay here is necessary, 200uS at least!!!
|
||||
self.ReadBusy();
|
||||
self.send_command(0x22)
|
||||
self.send_data(0xC7) # Load LUT from MCU(0x32)
|
||||
self.send_command(0x20)
|
||||
epdconfig.delay_ms(200) # !!!The delay here is necessary, 200uS at least!!!
|
||||
self.ReadBusy()
|
||||
|
||||
def sleep(self):
|
||||
self.send_command(0x10); # deep sleep
|
||||
self.send_data(0x01);
|
||||
self.send_command(0x10) # deep sleep
|
||||
self.send_data(0x01)
|
||||
|
||||
epdconfig.delay_ms(2000)
|
||||
epdconfig.module_exit()
|
||||
|
@ -7,7 +7,7 @@ from PIL import Image
|
||||
|
||||
class Waveshare213bV4(DisplayImpl):
|
||||
def __init__(self, config):
|
||||
super(Waveshare213bV4, self).__init__(config, 'waveshare213inb_v4')
|
||||
super(Waveshare213bV4, self).__init__(config, 'waveshare2in13b_v4')
|
||||
|
||||
def layout(self):
|
||||
if self.config['color'] == 'black':
|
||||
|
@ -153,8 +153,7 @@ class View(object):
|
||||
self.set('uptime', last_session.duration)
|
||||
self.set('channel', '-')
|
||||
self.set('aps', "%d" % last_session.associated)
|
||||
self.set('shakes', '%d (%s)' % (last_session.handshakes, \
|
||||
utils.total_unique_handshakes(self._config['bettercap']['handshakes'])))
|
||||
self.set('shakes', '%d (%s)' % (last_session.handshakes, utils.total_unique_handshakes(self._config['bettercap']['handshakes'])))
|
||||
self.set_closest_peer(last_session.last_peer, last_session.peers)
|
||||
self.update()
|
||||
|
||||
|
@ -1,60 +0,0 @@
|
||||
[build-system]
|
||||
requires = ["setuptools"]
|
||||
build-backend = "setuptools.build_meta"
|
||||
|
||||
[project]
|
||||
name = "pwnagotchi"
|
||||
dynamic = ["version"]
|
||||
dependencies = [
|
||||
"Pillow",
|
||||
"PyYAML",
|
||||
"RPi.GPIO",
|
||||
"file-read-backwards",
|
||||
"flask",
|
||||
"flask-cors",
|
||||
"flask-wtf",
|
||||
"gast",
|
||||
"gym",
|
||||
"inky",
|
||||
"pycryptodome",
|
||||
"pydrive2",
|
||||
"python-dateutil",
|
||||
"requests",
|
||||
"rpi_hardware_pwm",
|
||||
"scapy",
|
||||
"shimmy",
|
||||
"smbus2",
|
||||
"spidev",
|
||||
"stable_baselines3",
|
||||
"toml",
|
||||
"torch",
|
||||
"torchvision",
|
||||
"tweepy",
|
||||
"websockets"
|
||||
]
|
||||
requires-python = ">=3.9"
|
||||
authors = [
|
||||
{name = "Evilsocket", email = "evilsocket@gmail.com"},
|
||||
{name = "Jayofelony", email = "oudshoorn.jeroen@gmail.com"}
|
||||
]
|
||||
maintainers = [
|
||||
{name = "Jayofelony", email = "oudshoorn.jeroen@gmail.com"}
|
||||
]
|
||||
description = "(⌐■_■) - Deep Reinforcement Learning instrumenting bettercap for WiFI pwning."
|
||||
readme = "README.md"
|
||||
license = {file = "LICENSE.md"}
|
||||
classifiers = [
|
||||
'Programming Language :: Python :: 3',
|
||||
'Development Status :: 5 - Production/Stable',
|
||||
'License :: OSI Approved :: GNU General Public License (GPL)',
|
||||
'Environment :: Console',
|
||||
]
|
||||
|
||||
[project.urls]
|
||||
Homepage = "https://pwnagotchi.ai/"
|
||||
Documentation = "https://pwnagotchi.ai/"
|
||||
Repository = "https://github.com/jayofelony/pwnagotchi-bookworm"
|
||||
"Bug Tracker" = "https://github.com/jayofelony/pwnagotchi-bookworm/issues"
|
||||
|
||||
[project.scripts]
|
||||
pwnagotchi_cli = "bin.pwnagotchi:pwnagotchi_cli"
|
Reference in New Issue
Block a user