Merge branch 'jayofelony:master' into master

This commit is contained in:
Rai
2024-06-12 20:40:08 +10:00
committed by GitHub
51 changed files with 2345 additions and 1083 deletions

1
.github/FUNDING.yml vendored
View File

@ -1,4 +1,3 @@
# These are supported funding model platforms # These are supported funding model platforms
github: jayofelony github: jayofelony
custom: https://tikkie.me/pay/dubcto94hnskg539kar0

19
.idea/deployment.xml generated
View File

@ -1,23 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?> <?xml version="1.0" encoding="UTF-8"?>
<project version="4"> <project version="4">
<component name="PublishConfigData" serverName="pwnagotchi" filePermissions="493" folderPermissions="493" remoteFilesAllowedToDisappearOnAutoupload="false" confirmBeforeUploading="false"> <component name="PublishConfigData" filePermissions="493" folderPermissions="493" remoteFilesAllowedToDisappearOnAutoupload="false" confirmBeforeUploading="false">
<option name="confirmBeforeUploading" value="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> </component>
</project> </project>

View File

@ -1,4 +1,4 @@
PACKER_VERSION := 1.10.1 PACKER_VERSION := 1.11.0
PWN_HOSTNAME := pwnagotchi PWN_HOSTNAME := pwnagotchi
PWN_VERSION := $(shell cut -d"'" -f2 < pwnagotchi/_version.py) PWN_VERSION := $(shell cut -d"'" -f2 < pwnagotchi/_version.py)
@ -26,7 +26,7 @@ UNSHARE := $(UNSHARE) --uts
endif endif
# sudo apt-get install qemu-user-static qemu-utils # sudo apt-get install qemu-user-static qemu-utils
all: clean packer image all: packer image
update_langs: update_langs:
@for lang in pwnagotchi/locale/*/; do\ @for lang in pwnagotchi/locale/*/; do\
@ -40,22 +40,23 @@ compile_langs:
./scripts/language.sh compile $$(basename $$lang); \ ./scripts/language.sh compile $$(basename $$lang); \
done done
packer: clean packer:
curl https://releases.hashicorp.com/packer/$(PACKER_VERSION)/packer_$(PACKER_VERSION)_linux_amd64.zip -o /tmp/packer.zip curl https://releases.hashicorp.com/packer/$(PACKER_VERSION)/packer_$(PACKER_VERSION)_linux_amd64.zip -o /tmp/packer.zip
unzip /tmp/packer.zip -d /tmp unzip -o /tmp/packer.zip -d /tmp
sudo mv /tmp/packer /usr/bin/packer sudo mv /tmp/packer /usr/bin/packer
image: clean packer image: packer
export LC_ALL=en_GB.UTF-8 export LC_ALL=en_GB.UTF-8
cd builder && sudo /usr/bin/packer init combined.json.pkr.hcl && sudo $(UNSHARE) /usr/bin/packer build -var "pwn_hostname=$(PWN_HOSTNAME)" -var "pwn_version=$(PWN_VERSION)" combined.json.pkr.hcl cd builder && sudo /usr/bin/packer init combined.json.pkr.hcl && sudo $(UNSHARE) /usr/bin/packer build -var "pwn_hostname=$(PWN_HOSTNAME)" -var "pwn_version=$(PWN_VERSION)" combined.json.pkr.hcl
bullseye: clean packer 32bit: packer
export LC_ALL=en_GB.UTF-8 export LC_ALL=en_GB.UTF-8
cd builder && sudo /usr/bin/packer init raspberrypi32.json.pkr.hcl && sudo $(UNSHARE) /usr/bin/packer build -var "pwn_hostname=$(PWN_HOSTNAME)" -var "pwn_version=$(PWN_VERSION)" raspberrypi32.json.pkr.hcl cd builder && sudo /usr/bin/packer init raspberrypi32.json.pkr.hcl && QEMU_CPU=arm1176 sudo -E $(UNSHARE) /usr/bin/packer build -var "pwn_hostname=$(PWN_HOSTNAME)" -var "pwn_version=$(PWN_VERSION)" raspberrypi32.json.pkr.hcl
bookworm: clean packer 64bit: packer
export LC_ALL=en_GB.UTF-8 export LC_ALL=en_GB.UTF-8
cd builder && sudo /usr/bin/packer init raspberrypi64.json.pkr.hcl && sudo $(UNSHARE) /usr/bin/packer build -var "pwn_hostname=$(PWN_HOSTNAME)" -var "pwn_version=$(PWN_VERSION)" raspberrypi64.json.pkr.hcl cd builder && sudo /usr/bin/packer init raspberrypi64.json.pkr.hcl && sudo $(UNSHARE) /usr/bin/packer build -var "pwn_hostname=$(PWN_HOSTNAME)" -var "pwn_version=$(PWN_VERSION)" raspberrypi64.json.pkr.hcl
clean: clean:
- rm -rf /tmp/packer* - rm -rf /tmp/packer*
- rm -rf /tmp/LICENSE.txt

View File

@ -5,6 +5,8 @@ This is the main source for all forks:
[GH Sponsor](https://github.com/sponsors/jayofelony) [GH Sponsor](https://github.com/sponsors/jayofelony)
**Proudly partnering with [PiSugar](https://www.pisugar.com)!!**
--- ---
[Pwnagotchi](https://pwnagotchi.ai/) is an [A2C](https://hackernoon.com/intuitive-rl-intro-to-advantage-actor-critic-a2c-4ff545978752)-based "AI" leveraging [bettercap](https://www.bettercap.org/) that learns from its surrounding Wi-Fi environment to maximize the crackable WPA key material it captures (either passively, or by performing authentication and association attacks). This material is collected as PCAP files containing any form of handshake supported by [hashcat](https://hashcat.net/hashcat/), including [PMKIDs](https://www.evilsocket.net/2019/02/13/Pwning-WiFi-networks-with-bettercap-and-the-PMKID-client-less-attack/), [Pwnagotchi](https://pwnagotchi.ai/) is an [A2C](https://hackernoon.com/intuitive-rl-intro-to-advantage-actor-critic-a2c-4ff545978752)-based "AI" leveraging [bettercap](https://www.bettercap.org/) that learns from its surrounding Wi-Fi environment to maximize the crackable WPA key material it captures (either passively, or by performing authentication and association attacks). This material is collected as PCAP files containing any form of handshake supported by [hashcat](https://hashcat.net/hashcat/), including [PMKIDs](https://www.evilsocket.net/2019/02/13/Pwning-WiFi-networks-with-bettercap-and-the-PMKID-client-less-attack/),

View File

@ -60,7 +60,7 @@ def pwnagotchi_cli():
channels = agent.get_access_points_by_channel() channels = agent.get_access_points_by_channel()
# for each channel # for each channel
for ch, aps in channels: for ch, aps in channels:
time.sleep(0.2) time.sleep(0.5)
agent.set_channel(ch) agent.set_channel(ch)
if not agent.is_stale() and agent.any_activity(): if not agent.is_stale() and agent.any_activity():
@ -237,7 +237,7 @@ def pwnagotchi_cli():
f.write("ui.display.enabled = true\n") f.write("ui.display.enabled = true\n")
pwn_display_type = input("What display do you use?\n\n" pwn_display_type = input("What display do you use?\n\n"
"Be sure to check for the correct display type @ \n" "Be sure to check for the correct display type @ \n"
"https://github.com/jayofelony/pwnagotchi/blob/master/pwnagotchi/utils.py#L240-L431\n\n" "https://github.com/jayofelony/pwnagotchi/blob/master/pwnagotchi/utils.py#L240-L501\n\n"
"Display type: ") "Display type: ")
if pwn_display_type != "": if pwn_display_type != "":
f.write(f"ui.display.type = \"{pwn_display_type}\"\n") f.write(f"ui.display.type = \"{pwn_display_type}\"\n")

View File

@ -25,7 +25,7 @@ source "arm" "rpi64-pwnagotchi" {
file_checksum_type = "sha256" file_checksum_type = "sha256"
file_target_extension = "xz" file_target_extension = "xz"
file_unarchive_cmd = ["unxz", "$ARCHIVE_PATH"] file_unarchive_cmd = ["unxz", "$ARCHIVE_PATH"]
image_path = "../pwnagotchi-64bit.img" image_path = "../../../pwnagotchi-64bit.img"
qemu_binary_source_path = "/usr/libexec/qemu-binfmt/aarch64-binfmt-P" qemu_binary_source_path = "/usr/libexec/qemu-binfmt/aarch64-binfmt-P"
qemu_binary_destination_path = "/usr/libexec/qemu-binfmt/aarch64-binfmt-P" qemu_binary_destination_path = "/usr/libexec/qemu-binfmt/aarch64-binfmt-P"
image_build_method = "resize" image_build_method = "resize"
@ -50,12 +50,12 @@ source "arm" "rpi64-pwnagotchi" {
} }
source "arm" "rpi32-pwnagotchi" { source "arm" "rpi32-pwnagotchi" {
file_checksum_url = "https://downloads.raspberrypi.com/raspios_oldstable_lite_armhf/images/raspios_oldstable_lite_armhf-2024-03-12/2024-03-12-raspios-bullseye-armhf-lite.img.xz.sha256" file_checksum_url = "https://downloads.raspberrypi.com/raspios_lite_armhf/images/raspios_lite_armhf-2024-03-15/2024-03-15-raspios-bookworm-armhf-lite.img.xz.sha256"
file_urls = ["https://downloads.raspberrypi.com/raspios_oldstable_lite_armhf/images/raspios_oldstable_lite_armhf-2024-03-12/2024-03-12-raspios-bullseye-armhf-lite.img.xz"] file_urls = ["https://downloads.raspberrypi.com/raspios_lite_armhf/images/raspios_lite_armhf-2024-03-15/2024-03-15-raspios-bookworm-armhf-lite.img.xz"]
file_checksum_type = "sha256" file_checksum_type = "sha256"
file_target_extension = "xz" file_target_extension = "xz"
file_unarchive_cmd = ["unxz", "$ARCHIVE_PATH"] file_unarchive_cmd = ["unxz", "$ARCHIVE_PATH"]
image_path = "../pwnagotchi-32bit.img" image_path = "../../../pwnagotchi-32bit.img"
qemu_binary_source_path = "/usr/libexec/qemu-binfmt/arm-binfmt-P" qemu_binary_source_path = "/usr/libexec/qemu-binfmt/arm-binfmt-P"
qemu_binary_destination_path = "/usr/libexec/qemu-binfmt/arm-binfmt-P" qemu_binary_destination_path = "/usr/libexec/qemu-binfmt/arm-binfmt-P"
image_build_method = "resize" image_build_method = "resize"
@ -67,7 +67,7 @@ source "arm" "rpi32-pwnagotchi" {
start_sector = "8192" start_sector = "8192"
filesystem = "fat" filesystem = "fat"
size = "256M" size = "256M"
mountpoint = "/boot" mountpoint = "/boot/firmware"
} }
image_partitions { image_partitions {
name = "root" name = "root"
@ -101,6 +101,13 @@ build {
provisioner "shell" { provisioner "shell" {
inline = ["chmod +x /usr/bin/*"] inline = ["chmod +x /usr/bin/*"]
} }
provisioner "shell" {
inline = ["mkdir -p /usr/local/src/pwnagotchi"]
}
provisioner "file" {
destination = "/usr/local/src/pwnagotchi/"
source = "../"
}
provisioner "file" { provisioner "file" {
destination = "/etc/systemd/system/" destination = "/etc/systemd/system/"
@ -142,10 +149,16 @@ build {
"data/32bit/usr/bin/pwnlib", "data/32bit/usr/bin/pwnlib",
] ]
} }
provisioner "shell" {
inline = ["mkdir -p /usr/local/src/pwnagotchi"]
}
provisioner "file" {
destination = "/usr/local/src/pwnagotchi/"
source = "../"
}
provisioner "shell" { provisioner "shell" {
inline = ["chmod +x /usr/bin/*"] inline = ["chmod +x /usr/bin/*"]
} }
provisioner "file" { provisioner "file" {
destination = "/etc/systemd/system/" destination = "/etc/systemd/system/"
sources = [ sources = [
@ -167,7 +180,6 @@ build {
provisioner "ansible-local" { provisioner "ansible-local" {
command = "ANSIBLE_FORCE_COLOR=1 PYTHONUNBUFFERED=1 PWN_VERSION=${var.pwn_version} PWN_HOSTNAME=${var.pwn_hostname} ansible-playbook" command = "ANSIBLE_FORCE_COLOR=1 PYTHONUNBUFFERED=1 PWN_VERSION=${var.pwn_version} PWN_HOSTNAME=${var.pwn_hostname} ansible-playbook"
extra_arguments = ["--extra-vars \"ansible_python_interpreter=/usr/bin/python3\""] extra_arguments = ["--extra-vars \"ansible_python_interpreter=/usr/bin/python3\""]
playbook_dir = "extras/"
playbook_file = "raspberrypi32.yml" playbook_file = "raspberrypi32.yml"
} }
} }

View File

@ -1,67 +0,0 @@
# For more options and information see
# http://rptl.io/configtxt
# Some settings may impact device functionality. See link above for details
# Uncomment some or all of these to enable the optional hardware interfaces
#dtparam=i2c_arm=on
#dtparam=i2s=on
#dtparam=spi=on
# Enable audio (loads snd_bcm2835)
dtparam=audio=on
# Additional overlays and parameters are documented
# /boot/overlays/README
# Automatically load overlays for detected cameras
camera_auto_detect=1
# Automatically load overlays for detected DSI displays
display_auto_detect=1
# Automatically load initramfs files, if found
auto_initramfs=1
# Enable DRM VC4 V3D driver
dtoverlay=vc4-kms-v3d
max_framebuffers=2
# Don't have the firmware create an initial video= setting in cmdline.txt.
# Use the kernel's default instead.
disable_fw_kms_setup=1
# Run in 64-bit mode
arm_64bit=0
# Disable compensation for displays with overscan
disable_overscan=1
# Run as fast as firmware / board allows
arm_boost=1
[cm4]
# Enable host mode on the 2711 built-in XHCI USB controller.
# This line should be removed if the legacy DWC2 controller is required
# (e.g. for USB device mode) or if USB support is not required.
otg_mode=1
[all]
dtoverlay=dwc2
dtparam=i2c1=on
dtparam=i2c_arm=on
dtparam=spi=on
gpu_mem=1
dtoverlay=dwc2
#dtoverlay=disable-wifi
[pi0]
dtoverlay=spi1-3cs
#dtoverlay=disable-wifi
[pi3]
dtoverlay=spi1-3cs
#dtoverlay=disable-wifi
[pi4]
dtoverlay=spi1-3cs
#dtoverlay=disable-wifi

View File

@ -0,0 +1,5 @@
[main]
plugins=keyfile,ifupdown
[ifupdown]
managed=true

View File

@ -1,62 +0,0 @@
# A sample configuration for dhcpcd.
# See dhcpcd.conf(5) for details.
# Allow users of this group to interact with dhcpcd via the control socket.
#controlgroup wheel
# Inform the DHCP server of our hostname for DDNS.
hostname
# Use the hardware address of the interface for the Client ID.
clientid
# or
# Use the same DUID + IAID as set in DHCPv6 for DHCPv4 ClientID as per RFC4361.
# Some non-RFC compliant DHCP servers do not reply with this set.
# In this case, comment out duid and enable clientid above.
#duid
# Persist interface configuration when dhcpcd exits.
persistent
# Rapid commit support.
# Safe to enable by default because it requires the equivalent option set
# on the server to actually work.
option rapid_commit
# A list of options to request from the DHCP server.
option domain_name_servers, domain_name, domain_search, host_name
option classless_static_routes
# Respect the network MTU. This is applied to DHCP routes.
option interface_mtu
# Most distributions have NTP support.
#option ntp_servers
# A ServerID is required by RFC2131.
require dhcp_server_identifier
# Generate SLAAC address using the Hardware Address of the interface
#slaac hwaddr
# OR generate Stable Private IPv6 Addresses based from the DUID
slaac private
# !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
# !! DO NOT EDIT THESE LINES BELOW PLEASE !!
# !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
# static IP configuration:
denyinterfaces wlan0
interface eth0
static domain_name_servers=8.8.8.8 1.1.1.1
metric 201
interface usb0
static ip_address=10.0.0.2/24
static routers=10.0.0.1
static domain_name_servers=10.0.0.1 8.8.8.8 1.1.1.1
metric 202
interface bnep0
static domain_name_servers=8.8.8.8 1.1.1.1
metric 203

View File

@ -0,0 +1,2 @@
allow-hotplug eth0
iface eth0 inet dhcp

View File

@ -0,0 +1,2 @@
auto lo
iface lo inet loopback

View File

@ -0,0 +1,8 @@
allow-hotplug usb0
iface usb0 inet static
address 10.0.0.2
netmask 255.255.255.0
network 10.0.0.0
broadcast 10.0.0.255
gateway 10.0.0.1
metric 101

View File

@ -0,0 +1,2 @@
allow-hotplug wlan0
iface wlan0 inet static

View File

@ -1,70 +0,0 @@
# For more options and information see
# http://rptl.io/configtxt
# Some settings may impact device functionality. See link above for details
# Uncomment some or all of these to enable the optional hardware interfaces
#dtparam=i2c_arm=on
#dtparam=i2s=on
#dtparam=spi=on
# Enable audio (loads snd_bcm2835)
dtparam=audio=on
# Additional overlays and parameters are documented
# /boot/firmware/overlays/README
# Automatically load overlays for detected cameras
camera_auto_detect=1
# Automatically load overlays for detected DSI displays
display_auto_detect=1
# Automatically load initramfs files, if found
auto_initramfs=1
# Enable DRM VC4 V3D driver
dtoverlay=vc4-kms-v3d
max_framebuffers=2
# Don't have the firmware create an initial video= setting in cmdline.txt.
# Use the kernel's default instead.
disable_fw_kms_setup=1
# Run in 64-bit mode
arm_64bit=1
# Disable compensation for displays with overscan
disable_overscan=1
# Run as fast as firmware / board allows
arm_boost=1
[cm4]
# Enable host mode on the 2711 built-in XHCI USB controller.
# This line should be removed if the legacy DWC2 controller is required
# (e.g. for USB device mode) or if USB support is not required.
otg_mode=1
[all]
dtparam=i2c1=on
dtparam=i2c_arm=on
dtparam=spi=on
gpu_mem=1
dtoverlay=dwc2
#dtoverlay=disable-wifi
[pi0]
dtoverlay=spi0-0cs
#dtoverlay=disable-wifi
[pi3]
dtoverlay=spi0-0cs
#dtoverlay=disable-wifi
[pi4]
dtoverlay=spi0-0cs
#dtoverlay=disable-wifi
[pi5]
dtoverlay=spi0-0cs
#dtoverlay=disable-wifi

View File

@ -8,7 +8,6 @@ After=pwngrid-peer.service
Type=simple Type=simple
WorkingDirectory=~ WorkingDirectory=~
ExecStart=/usr/bin/pwnagotchi-launcher ExecStart=/usr/bin/pwnagotchi-launcher
ExecStopPost=/usr/bin/bash -c "if egrep -qi 'personality.clear_on_exit[ =]*true' /etc/pwnagotchi/config.toml ; then /usr/local/bin/pwnagotchi --clear; fi"
Restart=always Restart=always
RestartSec=30 RestartSec=30
TasksMax=infinity TasksMax=infinity

View File

@ -1,40 +0,0 @@
# Install nexmon to fix wireless scanning (takes 2.5G of space)
- name: clone nexmon repository
git:
repo: https://github.com/DrSchottky/nexmon.git
dest: /usr/local/src/nexmon
- name: make firmware
shell: "source ./setup_env.sh && make"
args:
executable: /bin/bash
chdir: /usr/local/src/nexmon/
- name: "make firmware patch ({{ item.name }})"
shell: "source ./setup_env.sh && cd /usr/local/src/nexmon/patches/{{ item.patch }}/nexmon/ && make"
args:
executable: /bin/bash
chdir: /usr/local/src/nexmon/
environment:
QEMU_UNAME: "{{ item.kernel }}"
ARCHFLAGS: "{{ item.arch_flags }}"
- name: "install new firmware ({{ item.name }})"
copy:
src: "/usr/local/src/nexmon/patches/{{ item.patch }}/nexmon/{{ item.firmware }}"
dest: "/usr/lib/firmware/brcm/{{ item.firmware }}"
follow: true
environment:
QEMU_UNAME: "{{ item.kernel }}"
ARCHFLAGS: "{{ item.arch_flags }}"
- name: backup original driver
command: "mv /usr/lib/modules/{{ item.kernel }}/kernel/drivers/net/wireless/broadcom/brcm80211/brcmfmac/brcmfmac.ko.xz /usr/lib/modules/{{ item.kernel }}/kernel/drivers/net/wireless/broadcom/brcm80211/brcmfmac/brcmfmac.ko.xz.orig"
- name: copy modified driver
copy:
src: "/usr/local/src/nexmon/patches/driver/brcmfmac_6.1.y-nexmon/brcmfmac.ko"
dest: "/usr/lib/modules/{{ item.kernel }}/kernel/drivers/net/wireless/broadcom/brcm80211/brcmfmac/brcmfmac.ko"
- name : load brcmfmac drivers
command: "/sbin/depmod -a {{ item.kernel }}"

View File

@ -1,8 +1,8 @@
packer { packer {
required_plugins { required_plugins {
arm = { arm = {
version = "1.0.0" version = ">=1.0.0"
source = "github.com/cdecoux/builder-arm" source = "github.com/michalfita/cross"
} }
ansible = { ansible = {
source = "github.com/hashicorp/ansible" source = "github.com/hashicorp/ansible"
@ -20,12 +20,12 @@ variable "pwn_version" {
} }
source "arm" "rpi32-pwnagotchi" { source "arm" "rpi32-pwnagotchi" {
file_checksum_url = "https://downloads.raspberrypi.com/raspios_oldstable_lite_armhf/images/raspios_oldstable_lite_armhf-2024-03-12/2024-03-12-raspios-bullseye-armhf-lite.img.xz.sha256" file_checksum_url = "https://downloads.raspberrypi.com/raspios_lite_armhf/images/raspios_lite_armhf-2024-03-15/2024-03-15-raspios-bookworm-armhf-lite.img.xz.sha256"
file_urls = ["https://downloads.raspberrypi.com/raspios_oldstable_lite_armhf/images/raspios_oldstable_lite_armhf-2024-03-12/2024-03-12-raspios-bullseye-armhf-lite.img.xz"] file_urls = ["https://downloads.raspberrypi.com/raspios_lite_armhf/images/raspios_lite_armhf-2024-03-15/2024-03-15-raspios-bookworm-armhf-lite.img.xz"]
file_checksum_type = "sha256" file_checksum_type = "sha256"
file_target_extension = "xz" file_target_extension = "xz"
file_unarchive_cmd = ["unxz", "$ARCHIVE_PATH"] file_unarchive_cmd = ["unxz", "$ARCHIVE_PATH"]
image_path = "../../pwnagotchi-32bit.img" image_path = "../../../pwnagotchi-32bit.img"
qemu_binary_source_path = "/usr/libexec/qemu-binfmt/arm-binfmt-P" qemu_binary_source_path = "/usr/libexec/qemu-binfmt/arm-binfmt-P"
qemu_binary_destination_path = "/usr/libexec/qemu-binfmt/arm-binfmt-P" qemu_binary_destination_path = "/usr/libexec/qemu-binfmt/arm-binfmt-P"
image_build_method = "resize" image_build_method = "resize"
@ -37,7 +37,7 @@ source "arm" "rpi32-pwnagotchi" {
start_sector = "8192" start_sector = "8192"
filesystem = "fat" filesystem = "fat"
size = "256M" size = "256M"
mountpoint = "/boot" mountpoint = "/boot/firmware"
} }
image_partitions { image_partitions {
name = "root" name = "root"
@ -51,6 +51,9 @@ source "arm" "rpi32-pwnagotchi" {
build { build {
name = "Raspberry Pi 32 Pwnagotchi" name = "Raspberry Pi 32 Pwnagotchi"
sources = ["source.arm.rpi32-pwnagotchi"] sources = ["source.arm.rpi32-pwnagotchi"]
provisioner "shell" {
inline = ["uname -m"]
}
provisioner "file" { provisioner "file" {
destination = "/usr/bin/" destination = "/usr/bin/"
sources = [ sources = [
@ -66,7 +69,13 @@ build {
provisioner "shell" { provisioner "shell" {
inline = ["chmod +x /usr/bin/*"] inline = ["chmod +x /usr/bin/*"]
} }
provisioner "shell" {
inline = ["mkdir -p /usr/local/src/pwnagotchi"]
}
provisioner "file" {
destination = "/usr/local/src/pwnagotchi/"
source = "../"
}
provisioner "file" { provisioner "file" {
destination = "/etc/systemd/system/" destination = "/etc/systemd/system/"
sources = [ sources = [
@ -88,7 +97,6 @@ build {
provisioner "ansible-local" { provisioner "ansible-local" {
command = "ANSIBLE_FORCE_COLOR=1 PYTHONUNBUFFERED=1 PWN_VERSION=${var.pwn_version} PWN_HOSTNAME=${var.pwn_hostname} ansible-playbook" command = "ANSIBLE_FORCE_COLOR=1 PYTHONUNBUFFERED=1 PWN_VERSION=${var.pwn_version} PWN_HOSTNAME=${var.pwn_hostname} ansible-playbook"
extra_arguments = ["--extra-vars \"ansible_python_interpreter=/usr/bin/python3\""] extra_arguments = ["--extra-vars \"ansible_python_interpreter=/usr/bin/python3\""]
playbook_dir = "extras/"
playbook_file = "raspberrypi32.yml" playbook_file = "raspberrypi32.yml"
} }
} }

View File

@ -4,46 +4,15 @@
gather_facts: true gather_facts: true
become: true become: true
vars: vars:
boards:
- {
kernel: "6.1.21+",
name: "PiZeroW",
firmware: "brcmfmac43430-sdio.bin",
patch: "bcm43430a1/7_45_41_46",
cpu: arm1176,
arch_flags: "-arch armv6l"
}
- {
kernel: "6.1.21-v7+",
name: "PiZero2W",
firmware: "brcmfmac43436-sdio.bin",
patch: "bcm43436b0/9_88_4_65",
cpu: any, #cortex-a53
arch_flags: "-arch armv7l"
}
- {
kernel: "6.1.21-v7l+",
name: "Pi4b_32",
firmware: "brcmfmac43455-sdio.bin",
patch: "bcm43455c0/7_45_206",
cpu: any, #cortex-a72
arch_flags: "-arch armv7l"
}
kernel: kernel:
min: "6.1" min: "6.6"
full: "6.1.21+" full: "6.6.31+rpt-rpi-v6"
full_2w: "6.1.21-v7+"
full_4b: "6.1.21-v7l+"
arch: "v6l"
pwnagotchi: pwnagotchi:
hostname: "{{ lookup('env', 'PWN_HOSTNAME') | default('pwnagotchi', true) }}" hostname: "{{ lookup('env', 'PWN_HOSTNAME') | default('pwnagotchi', true) }}"
version: "{{ lookup('env', 'PWN_VERSION') | default('pwnagotchi-torch', true) }}" version: "{{ lookup('env', 'PWN_VERSION') | default('pwnagotchi', true) }}"
custom_plugin_dir: "/usr/local/share/pwnagotchi/custom-plugins"
services: services:
enable: enable:
- bettercap.service - bettercap.service
- bluetooth.service
- dphys-swapfile.service
- fstrim.timer - fstrim.timer
- pwnagotchi.service - pwnagotchi.service
- pwngrid-peer.service - pwngrid-peer.service
@ -52,25 +21,24 @@
- apt-daily-upgrade.timer - apt-daily-upgrade.timer
- apt-daily.service - apt-daily.service
- apt-daily.timer - apt-daily.timer
- bluetooth.service
- ifup@wlan0.service - ifup@wlan0.service
- triggerhappy.service
- wpa_supplicant.service
packages: packages:
caplets: caplets:
source: "https://github.com/jayofelony/caplets.git" source: "https://github.com/jayofelony/caplets.git"
branch: "lite" # or master
bettercap: bettercap:
source: "https://github.com/jayofelony/bettercap.git" source: "https://github.com/jayofelony/bettercap.git"
url: "https://github.com/jayofelony/bettercap/releases/download/2.32.2/bettercap-2.32.2-armhf.zip" branch: "lite" # or master
ui: "https://github.com/bettercap/ui/releases/download/v1.3.0/ui.zip" pwngrid:
opwngrid:
source: "https://github.com/jayofelony/pwngrid.git" source: "https://github.com/jayofelony/pwngrid.git"
url: "https://github.com/jayofelony/pwngrid/releases/download/v1.10.7/pwngrid-1.10.7-armhf.zip" url: "https://github.com/jayofelony/pwngrid/releases/download/v1.10.7/pwngrid-1.10.7-armhf.zip"
torch: torch:
wheel: "torch-2.1.0a0+gitunknown-cp39-cp39-linux_armv6l.whl" wheel: "torch-2.1.0a0+gita8e7c98-cp311-cp311-linux_armv6l.whl"
url: "https://github.com/Sniffleupagus/Torch4Pizero/releases/download/v1.0.0/torch-2.1.0a0+gitunknown-cp39-cp39-linux_armv6l.whl" url: "https://github.com/Sniffleupagus/Torch4Pizero/releases/download/py0torch-bookworm-2024-05/torch-2.1.0a0+gita8e7c98-cp311-cp311-linux_armv6l.whl"
torchvision: torchvision:
wheel: "torchvision-0.16.0a0-cp39-cp39-linux_armv6l.whl" wheel: "torchvision-0.16.0+fbb4cc5-cp311-cp311-linux_armv6l.whl"
url: "https://github.com/Sniffleupagus/Torch4Pizero/releases/download/v1.0.0/torchvision-0.16.0a0-cp39-cp39-linux_armv6l.whl" url: "https://github.com/Sniffleupagus/Torch4Pizero/releases/download/py0torch-bookworm-2024-05/torchvision-0.16.0+fbb4cc5-cp311-cp311-linux_armv6l.whl"
apt: apt:
downgrade: downgrade:
- libpcap-dev_1.9.1-4_armhf.deb - libpcap-dev_1.9.1-4_armhf.deb
@ -85,109 +53,145 @@
- firmware-realtek - firmware-realtek
- libpcap-dev - libpcap-dev
- libpcap0.8 - libpcap0.8
- libpcap0.8-dev
- libpcap0.8-dbg - libpcap0.8-dbg
- libpcap0.8-dev
remove: remove:
- avahi-daemon
- nfs-common - nfs-common
- triggerhappy - triggerhappy
- wpasupplicant - wpasupplicant
install: install:
- aircrack-ng
- autoconf - autoconf
- bc
- bison - bison
- bluez - bluez
- bluez-tools - bluez-tools
- build-essential - build-essential
- curl - curl
- dkms
- dphys-swapfile - dphys-swapfile
- espeak-ng
- evtest
- fbi - fbi
- firmware-atheros
- firmware-brcm80211
- firmware-libertas
- firmware-misc-nonfree
- firmware-realtek
- flex - flex
- fonts-dejavu
- fonts-dejavu-core
- fonts-dejavu-extra
- fonts-freefont-ttf
- g++ - g++
- gawk - gawk
- gcc-arm-none-eabi - gcc-arm-none-eabi
- git - git
- libatlas-base-dev - libatlas-base-dev
- libavcodec58
- libavformat58
- libblas-dev
- libbluetooth-dev
- libbz2-dev
- libc-ares-dev
- libc6-dev - libc6-dev
- libcpuinfo-dev - libcpuinfo-dev
- libcurl-ocaml-dev
- libdbus-1-dev - libdbus-1-dev
- libdbus-glib-1-dev - libdbus-glib-1-dev
- libeigen3-dev
- libelf-dev
- libffi-dev
- libfl-dev - libfl-dev
- libfuse-dev
- libgdbm-dev
- libgl1-mesa-glx
- libgmp3-dev - libgmp3-dev
- libgstreamer1.0-0
- libhdf5-dev
- liblapack-dev
- libncursesw5-dev
- libnetfilter-queue-dev - libnetfilter-queue-dev
- libopenblas-dev - libopenblas-dev # https://stackoverflow.com/questions/14570011/explain-why-numpy-should-not-be-imported-from-source-directory
- libopenjp2-7 - libopenjp2-7
- libopenmpi-dev
- libopenmpi3
- libpcap-dev - libpcap-dev
- libprotobuf-dev
- libraspberrypi-bin - libraspberrypi-bin
- libraspberrypi-dev - libraspberrypi-dev
- libraspberrypi-doc - libraspberrypi-doc
- libraspberrypi0 - libraspberrypi0
- libsleef-dev - libsleef-dev
- libsqlite3-dev
- libssl-dev - libssl-dev
- libswscale5 - libssl-ocaml-dev
- libtiff5
- libtool - libtool
- libts-bin
- libusb-1.0-0-dev - libusb-1.0-0-dev
- lsof
- make - make
- ntp - ntp
- python3-dbus - pkg-config
- python3-flask - python3-dev
- python3-flask-cors
- python3-flaskext.wtf
- python3-pil
- python3-pip - python3-pip
- python3-protobuf - python3-protobuf
- python3-smbus - python3-setuptools
- qpdf - qpdf
- raspberrypi-kernel-headers - raspberrypi-kernel-headers
- rsync - rsync
- screen
- tcpdump - tcpdump
- texinfo - texinfo
- time
- tk-dev
- unzip - unzip
- vim
- wget - wget
- wl - wl
- xxd - xxd
- zlib1g-dev - zlib1g-dev
environment:
ARCHFLAGS: "-arch armv6l"
tasks: tasks:
# First we install packages
- name: install packages
apt:
name: "{{ packages.apt.install }}"
state: latest
update_cache: yes
install_recommends: no
- name: update pip3, setuptools, wheel
shell: "python3 -m pip install --upgrade pip setuptools wheel --break-system-packages"
args:
executable: /bin/bash
chdir: /usr/local/src
- name: install 32bit torch
shell: "python3 -m pip install {{ packages.torch.url }} {{ packages.torchvision.url }} --break-system-packages"
args:
executable: /bin/bash
environment:
QEMU_UNAME: "{{ kernel.full }}"
ARCHFLAGS: "-arch armv6l"
# Now we set up /boot/firmware
- name: Create pi user - name: Create pi user
copy: copy:
dest: /boot/userconf dest: /boot/firmware/userconf
content: | content: |
pi:$6$3jNr0GA9KIyt4hmM$efeVIopdMQ8DGgEPCWWlbx3mJJNAYci1lEXGdlky0xPyjqwKNbwTL5SrCcpb4144C4IvzWjn7Iv.QjqmU7iyT/ pi:$5$733Efsksay$SEFUKemv8FaNAu6X4GUfxdSzSDh6PbpOcdtNe5b7Nt0
- name: enable ssh on boot
file:
path: /boot/firmware/ssh
state: touch
- name: remove current rc.local
file:
path: /etc/rc.local
state: absent
- name: change root partition
replace:
dest: /boot/firmware/cmdline.txt
backup: no
regexp: "root=PARTUUID=[a-zA-Z0-9\\-]+"
replace: "root=/dev/mmcblk0p2"
- name: configure /boot/firmware/cmdline.txt
lineinfile:
path: /boot/firmware/cmdline.txt
backrefs: True
state: present
backup: no
regexp: '(.*)$'
line: '\1 modules-load=dwc2,g_ether'
- name: setup /boot/firmware/config.txt
blockinfile:
path: /boot/firmware/config.txt
insertafter: EOF
block: |
dtparam=i2c1=on
dtparam=i2c_arm=on
dtparam=spi=on
gpu_mem=1
dtoverlay=dwc2
#dtoverlay=disable-wifi
enable_uart=1
[pi0]
dtoverlay=spi0-0cs
#dtoverlay=disable-wifi
- name: change hostname - name: change hostname
lineinfile: lineinfile:
@ -206,31 +210,14 @@
state: present state: present
when: hostname.changed when: hostname.changed
- name: Create custom plugin directory # Now we disable sap and a2dp, we don't use them on rpi
file: - name: disable sap plugin for bluetooth.service
path: '{{ pwnagotchi.custom_plugin_dir }}' lineinfile:
state: directory dest: /lib/systemd/system/bluetooth.service
regexp: '^ExecStart=/usr/libexec/bluetooth/bluetoothd$'
- name: remove current rc.local line: 'ExecStart=/usr/libexec/bluetooth/bluetoothd --noplugin=sap,a2dp'
file:
path: /etc/rc.local
state: absent
- name: update apt package cache
apt:
update_cache: yes
- name: install packages
apt:
name: "{{ packages.apt.install }}"
state: present state: present
- name: update pip3, setuptools, wheel
shell: "python3 -m pip install --upgrade pip setuptools wheel"
args:
executable: /bin/bash
chdir: /usr/local/src
########################################### ###########################################
# #
# libpcap v1.9 - build from source # libpcap v1.9 - build from source
@ -264,17 +251,61 @@
dest: /usr/local/lib/libpcap.so.0.8 dest: /usr/local/lib/libpcap.so.0.8
state: link state: link
############################################################### # install latest hcxtools
# Install nexmon to fix wireless scanning (takes 2.5G of space) - name: clone hcxtools
############################################################### git:
repo: https://github.com/ZerBea/hcxtools.git
dest: /usr/local/src/hcxtools
# Install nexmon for all boards - name: install hcxtools
- name: build and install nexmon as needed shell: "make && make install"
include_tasks: nexmon.yml args:
loop: "{{ boards }}" executable: /bin/bash
chdir: /usr/local/src/hcxtools
- name: remove hcxtools directory
file:
state: absent
path: /usr/local/src/hcxtools
# Installing nexmon
- name: clone nexmon repository
git:
repo: https://github.com/DrSchottky/nexmon.git
dest: /usr/local/src/nexmon
- name: make firmware
shell: "source ./setup_env.sh && make"
args:
executable: /bin/bash
chdir: /usr/local/src/nexmon/
environment:
QEMU_UNAME: "{{ kernel.full }}"
ARCHFLAGS: "-arch armv6l"
- name: make firmware patch (bcm43430a1)
shell: "source ./setup_env.sh && cd /usr/local/src/nexmon/patches/bcm43430a1/7_45_41_46/nexmon/ && make"
args:
executable: /bin/bash
chdir: /usr/local/src/nexmon/
environment:
QEMU_UNAME: "{{ kernel.full }}"
ARCHFLAGS: "-arch armv6l"
- name: install new firmware (bcm43430a1)
copy:
src: /usr/local/src/nexmon/patches/bcm43430a1/7_45_41_46/nexmon/brcmfmac43430-sdio.bin
dest: /usr/lib/firmware/brcm/brcmfmac43430-sdio.bin
follow: true
- 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"
environment:
QEMU_UNAME: "{{ kernel.full }}"
ARCHFLAGS: "-arch armv6l"
# some pizero2w have the pizeroW wifi chip
# could this be a link instead of a copy? and force, only if not a link?
- name: copy 43430-sdio as 43436s-sdio for the special 43430/1 /2 - name: copy 43430-sdio as 43436s-sdio for the special 43430/1 /2
copy: copy:
src: /usr/lib/firmware/brcm/brcmfmac43430-sdio.bin src: /usr/lib/firmware/brcm/brcmfmac43430-sdio.bin
@ -289,9 +320,14 @@
loop: loop:
- /usr/lib/firmware/brcm/brcmfmac43430-sdio.clm_blob - /usr/lib/firmware/brcm/brcmfmac43430-sdio.clm_blob
- /usr/lib/firmware/brcm/brcmfmac43430-sdio.raspberrypi,model-zero-w.clm_blob - /usr/lib/firmware/brcm/brcmfmac43430-sdio.raspberrypi,model-zero-w.clm_blob
- /usr/lib/firmware/brcm/brcmfmac43430b0-sdio.raspberrypi,model-zero-2-w.clm_blob
- /usr/lib/firmware/brcm/brcmfmac43436-sdio.raspberrypi,model-zero-2-w.clm_blob - name: backup original driver
- /usr/lib/firmware/brcm/brcmfmac43430-sdio.raspberrypi,3-model-b.clm_blob 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: 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 # To shrink the final image, remove the nexmon directory (takes 2.5G of space) post build and installation
- name: Delete nexmon content & directory - name: Delete nexmon content & directory
@ -299,58 +335,36 @@
state: absent state: absent
path: /usr/local/src/nexmon/ path: /usr/local/src/nexmon/
- name: clone pwnagotchi repository - name: Create custom config directory
git: file:
repo: https://github.com/jayofelony/pwnagotchi.git path: /etc/pwnagotchi/conf.d/
dest: /usr/local/src/pwnagotchi state: directory
register: pwnagotchigit
#- name: clone pwnagotchi repository
# git:
# repo: https://github.com/jayofelony/pwnagotchi.git
# dest: /usr/local/src/pwnagotchi
# is this even necessary? Can't we just link from /home/pi/pwnagotchi to /usr/local/{bin,lib,etc}
# then just git update in the home dir and encourage hacking?
# make owned by pi.pi, and custom plugins.
- name: build pwnagotchi wheel - name: build pwnagotchi wheel
command: "python3 setup.py sdist bdist_wheel" command: "pip3 install . --no-cache-dir --break-system-packages"
args: args:
chdir: /usr/local/src/pwnagotchi chdir: /usr/local/src/pwnagotchi
when: (pwnagotchigit.changed) or (pip_packages['pwnagotchi'] is undefined) or (pip_packages['pwnagotchi'] != pwnagotchi_version)
- name: download torch whl
get_url:
url: "{{ packages.torch.url }}"
dest: /usr/local/src/
- name: download torchvision whl
get_url:
url: "{{ packages.torchvision.url }}"
dest: /usr/local/src/
- name: install 32-bit pwnagotchi wheel and dependencies with 32-bit torch wheels
pip:
name:
- "{{ lookup('fileglob', '/usr/local/src/pwnagotchi/dist/pwnagotchi*.whl') }}"
- "{{ packages.torch.url }}"
- "{{ packages.torchvision.url }}"
extra_args: "--no-cache-dir"
environment:
QEMU_CPU: arm1176
QEMU_UNAME: "{{ kernel.full }}"
when: (pwnagotchigit.changed) or (pip_packages['pwnagotchi'] is undefined) or (pip_packages['pwnagotchi'] != pwnagotchi_version)
- name: create /usr/local/share/pwnagotchi/ folder - name: create /usr/local/share/pwnagotchi/ folder
file: file:
path: /usr/local/share/pwnagotchi/ path: /usr/local/share/pwnagotchi/
state: directory state: directory
- name: Create custom plugin directory
file:
path: /usr/local/share/pwnagotchi/custom-plugins/
state: directory
- name: remove pwnagotchi folder - name: remove pwnagotchi folder
file: file:
state: absent state: absent
path: /usr/local/src/pwnagotchi path: /usr/local/src/pwnagotchi
- name: remove torch whl
file:
state: absent
path: "{{ lookup('fileglob', '/usr/local/src/torch*.whl') }}"
########################################## ##########################################
# #
# pwngrid, bettercap # pwngrid, bettercap
@ -359,14 +373,14 @@
- name: Install go-1.21 - name: Install go-1.21
unarchive: unarchive:
src: https://go.dev/dl/go1.21.6.linux-armv6l.tar.gz src: https://go.dev/dl/go1.22.3.linux-armv6l.tar.gz
dest: /usr/local dest: /usr/local
remote_src: yes remote_src: yes
register: golang register: golang
- name: Update .bashrc for go-1.21 - name: Update .bashrc for go-1.21
blockinfile: blockinfile:
dest: /home/pi/.bashrc dest: /etc/profile
state: present state: present
block: | block: |
export GOPATH=$HOME/go export GOPATH=$HOME/go
@ -374,25 +388,42 @@
when: golang.changed when: golang.changed
- name: download pwngrid - name: download pwngrid
unarchive: git:
remote_src: yes repo: "{{ packages.pwngrid.source }}"
src: "{{ packages.opwngrid.url }}" dest: /usr/local/src/pwngrid
dest: /usr/local/bin/
mode: 0755
- name: download and install bettercap - name: install pwngrid
unarchive: shell: "export GOPATH=$HOME/go && export PATH=/usr/local/go/bin:$PATH:$GOPATH/bin && go mod tidy && make && make install"
src: "{{ packages.bettercap.url }}" args:
dest: /usr/local/bin executable: /bin/bash
remote_src: yes chdir: /usr/local/src/pwngrid
exclude:
- README.md - name: remove pwngrid folder
- LICENSE.md file:
mode: 0755 state: absent
path: /usr/local/src/pwngrid
- name: download bettercap
git:
repo: "{{ packages.bettercap.source }}"
version: "{{ packages.bettercap.branch }} "
dest: /usr/local/src/bettercap
- name: install bettercap 2.32.4
shell: "export GOPATH=$HOME/go && export PATH=/usr/local/go/bin:$PATH:$GOPATH/bin && go mod tidy && make && make install"
args:
executable: /bin/bash
chdir: /usr/local/src/bettercap
- name: remove bettercap folder
file:
state: absent
path: /usr/local/src/bettercap
- name: clone bettercap caplets - name: clone bettercap caplets
git: git:
repo: "{{ packages.caplets.source }}" repo: "{{ packages.caplets.source }}"
version: "{{ packages.caplets.branch }}"
dest: /tmp/caplets dest: /tmp/caplets
register: capletsgit register: capletsgit
@ -402,31 +433,11 @@
target: install target: install
when: capletsgit.changed when: capletsgit.changed
- name: download and install bettercap ui
unarchive:
src: "{{ packages.bettercap.ui }}"
dest: /usr/local/share/bettercap/
remote_src: yes
mode: 0755
# to always have the bettercap webui available (because why not?)
- name: copy pwnagotchi-manual over pwnagotchi-auto caplet
ansible.builtin.copy:
src: /usr/local/share/bettercap/caplets/pwnagotchi-manual.cap
dest: /usr/local/share/bettercap/caplets/pwnagotchi-auto.cap
force: true
ignore_errors: true
- name: create /etc/pwnagotchi folder - name: create /etc/pwnagotchi folder
file: file:
path: /etc/pwnagotchi path: /etc/pwnagotchi
state: directory state: directory
- name: create log folder
file:
path: /home/pi/logs
state: directory
- name: check if user configuration exists - name: check if user configuration exists
stat: stat:
path: /etc/pwnagotchi/config.toml path: /etc/pwnagotchi/config.toml
@ -442,32 +453,16 @@
# ui.display.type = "waveshare_4" # ui.display.type = "waveshare_4"
when: not user_config.stat.exists when: not user_config.stat.exists
- name: Delete motd
file:
state: absent
path: /etc/motd
- name: Delete motd 10-uname - name: Delete motd 10-uname
file: file:
state: absent state: absent
path: /etc/update-motd.d/10-uname path: /etc/update-motd.d/10-uname
- name: enable ssh on boot
file:
path: /boot/ssh
state: touch
- name: change root partition
replace:
dest: /boot/cmdline.txt
backup: no
regexp: "root=PARTUUID=[a-zA-Z0-9\\-]+"
replace: "root=/dev/mmcblk0p2"
- name: configure /boot/cmdline.txt
lineinfile:
path: /boot/cmdline.txt
backrefs: True
state: present
backup: no
regexp: '(.*)$'
line: '\1 modules-load=dwc2,g_ether'
- name: add firmware packages to hold - name: add firmware packages to hold
dpkg_selections: dpkg_selections:
name: "{{ item }}" name: "{{ item }}"
@ -487,16 +482,7 @@
enabled: true enabled: true
state: stopped state: stopped
with_items: "{{ services.enable }}" with_items: "{{ services.enable }}"
register: enabled
#- name: remove golang build libraries
# file:
# state: absent
# path: /root/go
#- name: remove golang
# file:
# state: absent
# path: /usr/local/go
- name: make /root readable, becauase that's where all the files are - name: make /root readable, becauase that's where all the files are
file: file:
@ -510,39 +496,25 @@
group: pi group: pi
recurse: true recurse: true
- name: remove unnecessary apt packages
apt:
name: "{{ packages.apt.remove }}"
state: absent
purge: yes
- name: remove dependencies that are no longer required
apt:
autoremove: yes
- name: clean apt cache
apt:
autoclean: true
- name: remove golang build libraries
file:
state: absent
path: /root/go
- name: remove pre-collected packages zip - name: remove pre-collected packages zip
file: file:
path: /root/go_pkgs.tgz path: /root/go_pkgs.tgz
state: absent state: absent
- name: remove golang - name: remove /root/go folder
file:
state: absent
path: /root/go
- name: remove /usr/local/go folder
file: file:
state: absent state: absent
path: /usr/local/go path: /usr/local/go
- name: remove /root/.cache (pip cache) - name: remove pip cache
file: file:
state: absent state: absent
path: /root/.cache path: /root/.cache/pip
- name: remove ssh keys - name: remove ssh keys
file: file:
@ -556,7 +528,26 @@
args: args:
executable: /bin/bash 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: handlers:
- name: reload systemd services - name: reload systemd services
systemd: systemd:
daemon_reload: yes daemon_reload: yes
when: enabled.changed

View File

@ -1,8 +1,8 @@
packer { packer {
required_plugins { required_plugins {
arm = { arm = {
version = "1.0.0" version = ">=1.0.0"
source = "github.com/cdecoux/builder-arm" source = "github.com/michalfita/cross"
} }
ansible = { ansible = {
source = "github.com/hashicorp/ansible" source = "github.com/hashicorp/ansible"
@ -73,7 +73,13 @@ build {
provisioner "shell" { provisioner "shell" {
inline = ["chmod +x /usr/bin/*"] inline = ["chmod +x /usr/bin/*"]
} }
provisioner "shell" {
inline = ["mkdir -p /usr/local/src/pwnagotchi"]
}
provisioner "file" {
destination = "/usr/local/src/pwnagotchi/"
source = "../"
}
provisioner "file" { provisioner "file" {
destination = "/etc/systemd/system/" destination = "/etc/systemd/system/"
sources = [ sources = [

View File

@ -6,8 +6,8 @@
vars: vars:
kernel: kernel:
min: "6.6" min: "6.6"
full: "6.6.20+rpt-rpi-v8" full: "6.6.31+rpt-rpi-v8"
full_pi5: "6.6.20+rpt-rpi-2712" full_pi5: "6.6.31+rpt-rpi-2712"
pwnagotchi: pwnagotchi:
hostname: "{{ lookup('env', 'PWN_HOSTNAME') | default('pwnagotchi', true) }}" hostname: "{{ lookup('env', 'PWN_HOSTNAME') | default('pwnagotchi', true) }}"
version: "{{ lookup('env', 'PWN_VERSION') | default('pwnagotchi', true) }}" version: "{{ lookup('env', 'PWN_VERSION') | default('pwnagotchi', true) }}"
@ -27,10 +27,11 @@
packages: packages:
caplets: caplets:
source: "https://github.com/jayofelony/caplets.git" source: "https://github.com/jayofelony/caplets.git"
branch: "lite" # or master
bettercap: bettercap:
source: "https://github.com/jayofelony/bettercap.git" source: "https://github.com/jayofelony/bettercap.git"
url: "https://github.com/jayofelony/bettercap/releases/download/2.32.4/bettercap-2.32.4.zip" url: "https://github.com/jayofelony/bettercap/releases/download/2.32.4/bettercap-2.32.4.zip"
ui: "https://github.com/bettercap/ui/releases/download/v1.3.0/ui.zip" branch: "lite" # or master
pwngrid: pwngrid:
source: "https://github.com/jayofelony/pwngrid.git" source: "https://github.com/jayofelony/pwngrid.git"
url: "https://github.com/jayofelony/pwngrid/releases/download/v1.10.5/pwngrid-1.10.5-aarch64.zip" url: "https://github.com/jayofelony/pwngrid/releases/download/v1.10.5/pwngrid-1.10.5-aarch64.zip"
@ -51,23 +52,18 @@
- libpcap0.8-dbg - libpcap0.8-dbg
- libpcap0.8-dev - libpcap0.8-dev
remove: remove:
- avahi-daemon
- dhpys-swapfile - dhpys-swapfile
- libcurl-ocaml-dev
- libssl-ocaml-dev
- nfs-common - nfs-common
- triggerhappy - triggerhappy
- wpasupplicant - wpasupplicant
install: install:
- aircrack-ng - aircrack-ng
- autoconf - autoconf
- bc
- bison - bison
- bluez - bluez
- bluez-tools - bluez-tools
- build-essential - build-essential
- curl - curl
- dkms
- dphys-swapfile - dphys-swapfile
- fbi - fbi
- firmware-atheros - firmware-atheros
@ -76,86 +72,38 @@
- firmware-misc-nonfree - firmware-misc-nonfree
- firmware-realtek - firmware-realtek
- flex - flex
- fonts-dejavu
- fonts-dejavu-core
- fonts-dejavu-extra
- fonts-freefont-ttf
- g++ - g++
- gawk - gawk
- gcc-arm-none-eabi - gcc-arm-none-eabi
- git - git
- hcxtools
- libatlas-base-dev
- libavcodec59
- libavformat59
- libblas-dev
- libbluetooth-dev
- libbz2-dev
- libc-ares-dev
- libc6-dev - libc6-dev
- libcap-dev
- libcurl-ocaml-dev - libcurl-ocaml-dev
- libdbus-1-dev - libdbus-1-dev
- libdbus-glib-1-dev - libdbus-glib-1-dev
- libeigen3-dev
- libelf-dev
- libffi-dev
- libfl-dev - libfl-dev
- libfuse-dev
- libgdbm-dev
- libgl1-mesa-glx
- libgmp3-dev - libgmp3-dev
- libgstreamer1.0-0
- libhdf5-dev
- liblapack-dev
- libncursesw5-dev
- libnetfilter-queue-dev - libnetfilter-queue-dev
- libopenblas-dev
- libopenjp2-7
- libopenmpi-dev
- libopenmpi3
- libpcap-dev - libpcap-dev
- libraspberrypi-bin - libraspberrypi-bin
- libraspberrypi-dev - libraspberrypi-dev
- libraspberrypi-doc - libraspberrypi-doc
- libraspberrypi0 - libraspberrypi0
- libsqlite3-dev
- libssl-dev - libssl-dev
- libssl-ocaml-dev - libssl-ocaml-dev
- libswscale5
- libtiff6
- libtool - libtool
- libusb-1.0-0-dev - libusb-1.0-0-dev
- lsof
- make - make
- ntp - ntp
- python3-dbus - pkg-config
- python3-flask - python3-dev
- python3-flask-cors
- python3-flaskext.wtf
- python3-gast
- python3-pil
- python3-pip - python3-pip
- python3-pycryptodome
- python3-requests
- python3-scapy
- python3-setuptools - python3-setuptools
- python3-smbus
- python3-smbus2
- python3-spidev
- python3-tweepy
- python3-werkzeug
- python3-yaml
- qpdf - qpdf
- raspberrypi-kernel-headers - raspberrypi-kernel-headers
- rsync - rsync
- screen
- tcpdump - tcpdump
- texinfo - texinfo
- time
- tk-dev
- unzip - unzip
- vim
- wget - wget
- wl - wl
- xxd - xxd
@ -168,9 +116,9 @@
- name: install packages - name: install packages
apt: apt:
name: "{{ packages.apt.install }}" name: "{{ packages.apt.install }}"
state: present state: latest
update_cache: yes update_cache: yes
install_recommends: false install_recommends: no
- name: update pip3, setuptools, wheel - name: update pip3, setuptools, wheel
shell: "python3 -m pip install --upgrade pip setuptools wheel --break-system-packages" shell: "python3 -m pip install --upgrade pip setuptools wheel --break-system-packages"
@ -178,12 +126,22 @@
executable: /bin/bash executable: /bin/bash
chdir: /usr/local/src chdir: /usr/local/src
- name: build pwnagotchi wheel
command: "pip3 install . --no-cache-dir --break-system-packages"
args:
chdir: /usr/local/src/pwnagotchi
- name: remove pwnagotchi folder
file:
state: absent
path: /usr/local/src/pwnagotchi
# Now we set up /boot/firmware # Now we set up /boot/firmware
- name: Create pi user - name: Create pi user
copy: copy:
dest: /boot/firmware/userconf dest: /boot/firmware/userconf
content: | content: |
pi:$6$3jNr0GA9KIyt4hmM$efeVIopdMQ8DGgEPCWWlbx3mJJNAYci1lEXGdlky0xPyjqwKNbwTL5SrCcpb4144C4IvzWjn7Iv.QjqmU7iyT/ pi:$5$733Efsksay$SEFUKemv8FaNAu6X4GUfxdSzSDh6PbpOcdtNe5b7Nt0
- name: enable ssh on boot - name: enable ssh on boot
file: file:
@ -211,6 +169,35 @@
regexp: '(.*)$' regexp: '(.*)$'
line: '\1 modules-load=dwc2,g_ether' line: '\1 modules-load=dwc2,g_ether'
- name: setup /boot/firmware/config.txt
blockinfile:
path: /boot/firmware/config.txt
insertafter: EOF
block: |
dtparam=i2c1=on
dtparam=i2c_arm=on
dtparam=spi=on
gpu_mem=1
dtoverlay=dwc2
#dtoverlay=disable-wifi
enable_uart=1
[pi0]
dtoverlay=spi0-0cs
#dtoverlay=disable-wifi
[pi3]
dtoverlay=spi0-0cs
#dtoverlay=disable-wifi
[pi4]
dtoverlay=spi0-0cs
#dtoverlay=disable-wifi
[pi5]
dtoverlay=spi0-0cs
#dtoverlay=disable-wifi
- name: change hostname - name: change hostname
lineinfile: lineinfile:
dest: /etc/hostname dest: /etc/hostname
@ -270,7 +257,6 @@
state: link state: link
# install latest hcxtools # install latest hcxtools
- name: clone hcxtools - name: clone hcxtools
git: git:
repo: https://github.com/ZerBea/hcxtools.git repo: https://github.com/ZerBea/hcxtools.git
@ -287,13 +273,13 @@
state: absent state: absent
path: /usr/local/src/hcxtools path: /usr/local/src/hcxtools
# Installing nexmon
- name: clone nexmon repository - name: clone nexmon repository
git: git:
repo: https://github.com/DrSchottky/nexmon.git repo: https://github.com/DrSchottky/nexmon.git
dest: /usr/local/src/nexmon dest: /usr/local/src/nexmon
# FIRST WE BUILD DRIVER FOR RPi5 # FIRST WE BUILD DRIVER FOR RPi5
- name: make firmware, RPi5 - name: make firmware, RPi5
shell: "source ./setup_env.sh && make" shell: "source ./setup_env.sh && make"
args: args:
@ -320,11 +306,6 @@
QEMU_UNAME: "{{ kernel.full_pi5 }}" QEMU_UNAME: "{{ kernel.full_pi5 }}"
ARCHFLAGS: "-arch aarch64" 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 - 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" 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"
@ -339,7 +320,6 @@
path: /usr/local/src/nexmon/ path: /usr/local/src/nexmon/
# NOW WE BUILD DRIVERS FOR RPi4, RPizero2w and RPi3 # NOW WE BUILD DRIVERS FOR RPi4, RPizero2w and RPi3
- name: clone nexmon repository - name: clone nexmon repository
git: git:
repo: https://github.com/DrSchottky/nexmon.git repo: https://github.com/DrSchottky/nexmon.git
@ -370,7 +350,6 @@
follow: true follow: true
# NOW WE BUILD DRIVERS FOR RPiZero2W, RPi 3 # NOW WE BUILD DRIVERS FOR RPiZero2W, RPi 3
- name: make firmware patch (bcm43436b0) - name: make firmware patch (bcm43436b0)
shell: "source ./setup_env.sh && cd /usr/local/src/nexmon/patches/bcm43436b0/9_88_4_65/nexmon/ && make" shell: "source ./setup_env.sh && cd /usr/local/src/nexmon/patches/bcm43436b0/9_88_4_65/nexmon/ && make"
args: args:
@ -395,6 +374,12 @@
QEMU_UNAME: "{{ kernel.full }}" QEMU_UNAME: "{{ kernel.full }}"
ARCHFLAGS: "-arch aarch64" ARCHFLAGS: "-arch aarch64"
- name: install new firmware (bcm43430a1)
copy:
src: /usr/local/src/nexmon/patches/bcm43430a1/7_45_41_46/nexmon/brcmfmac43430-sdio.bin
dest: /usr/lib/firmware/brcm/brcmfmac43430-sdio.bin
follow: true
- name: copy modified driver, RPi4 - name: copy modified driver, RPi4
copy: copy:
src: "/usr/local/src/nexmon/patches/driver/brcmfmac_{{ kernel.min }}.y-nexmon/brcmfmac.ko" src: "/usr/local/src/nexmon/patches/driver/brcmfmac_{{ kernel.min }}.y-nexmon/brcmfmac.ko"
@ -403,12 +388,6 @@
QEMU_UNAME: "{{ kernel.full }}" QEMU_UNAME: "{{ kernel.full }}"
ARCHFLAGS: "-arch aarch64" ARCHFLAGS: "-arch aarch64"
- name: install new firmware (bcm43430a1)
copy:
src: /usr/local/src/nexmon/patches/bcm43430a1/7_45_41_46/nexmon/brcmfmac43430-sdio.bin
dest: /usr/lib/firmware/brcm/brcmfmac43430-sdio.bin
follow: true
- name: copy 43430-sdio as 43436s-sdio for the special 43430/1 /2 - name: copy 43430-sdio as 43436s-sdio for the special 43430/1 /2
copy: copy:
src: /usr/lib/firmware/brcm/brcmfmac43430-sdio.bin src: /usr/lib/firmware/brcm/brcmfmac43430-sdio.bin
@ -428,6 +407,7 @@
- /usr/lib/firmware/brcm/brcmfmac43436-sdio.clm_blob - /usr/lib/firmware/brcm/brcmfmac43436-sdio.clm_blob
- /usr/lib/firmware/brcm/brcmfmac43436-sdio.raspberrypi,model-zero-2-w.clm_blob - /usr/lib/firmware/brcm/brcmfmac43436-sdio.raspberrypi,model-zero-2-w.clm_blob
- /usr/lib/firmware/brcm/brcmfmac43455-sdio.clm_blob - /usr/lib/firmware/brcm/brcmfmac43455-sdio.clm_blob
- /usr/lib/firmware/brcm/BCM43430A1.raspberrypi,model-zero-2-w.hcd
- name: backup original driver - 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" 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"
@ -443,39 +423,24 @@
state: absent state: absent
path: /usr/local/src/nexmon/ path: /usr/local/src/nexmon/
- name: Create custom plugin directory
file:
path: /usr/local/share/pwnagotchi/custom-plugins/
state: directory
- name: Create custom config directory - name: Create custom config directory
file: file:
path: /etc/pwnagotchi/conf.d/ path: /etc/pwnagotchi/conf.d/
state: directory state: directory
- name: clone pwnagotchi repository
git:
repo: https://github.com/jayofelony/pwnagotchi.git
dest: /usr/local/src/pwnagotchi
- name: build pwnagotchi wheel
command: "pip3 install . --no-cache-dir --break-system-packages"
args:
chdir: /usr/local/src/pwnagotchi
- name: remove pwnagotchi folder
file:
state: absent
path: /usr/local/src/pwnagotchi
- name: create /usr/local/share/pwnagotchi/ folder - name: create /usr/local/share/pwnagotchi/ folder
file: file:
path: /usr/local/share/pwnagotchi/ path: /usr/local/share/pwnagotchi/
state: directory state: directory
- name: Create custom plugin directory
file:
path: /usr/local/share/pwnagotchi/custom-plugins/
state: directory
- name: Install go-1.21 - name: Install go-1.21
unarchive: unarchive:
src: https://go.dev/dl/go1.21.5.linux-arm64.tar.gz src: https://go.dev/dl/go1.22.3.linux-arm64.tar.gz
dest: /usr/local dest: /usr/local
remote_src: yes remote_src: yes
register: golang register: golang
@ -508,6 +473,7 @@
- name: download bettercap - name: download bettercap
git: git:
repo: "{{ packages.bettercap.source }}" repo: "{{ packages.bettercap.source }}"
version: "{{ packages.bettercap.branch }}"
dest: /usr/local/src/bettercap dest: /usr/local/src/bettercap
- name: install bettercap 2.32.4 - name: install bettercap 2.32.4
@ -534,6 +500,7 @@
- name: clone bettercap caplets - name: clone bettercap caplets
git: git:
repo: "{{ packages.caplets.source }}" repo: "{{ packages.caplets.source }}"
version: "{{ packages.caplets.branch }}"
dest: /tmp/caplets dest: /tmp/caplets
register: capletsgit register: capletsgit
@ -543,21 +510,6 @@
target: install target: install
when: capletsgit.changed when: capletsgit.changed
- name: download and install bettercap ui
unarchive:
src: "{{ packages.bettercap.ui }}"
dest: /usr/local/share/bettercap/
remote_src: yes
mode: 0755
# to always have the bettercap webui available (because why not?)
- name: copy pwnagotchi-manual over pwnagotchi-auto caplet
ansible.builtin.copy:
src: /usr/local/share/bettercap/caplets/pwnagotchi-manual.cap
dest: /usr/local/share/bettercap/caplets/pwnagotchi-auto.cap
force: true
ignore_errors: true
- name: create /etc/pwnagotchi folder - name: create /etc/pwnagotchi folder
file: file:
path: /etc/pwnagotchi path: /etc/pwnagotchi

View File

@ -1 +1 @@
__version__ = '2.8.9' __version__ = '2.9.2'

View File

@ -150,8 +150,6 @@ personality.bond_encounters_factor = 20000
personality.throttle_a = 0.4 personality.throttle_a = 0.4
personality.throttle_d = 0.9 personality.throttle_d = 0.9
personality.clear_on_exit = true # clear display when shutting down cleanly
ui.invert = false # false = black background, true = white background ui.invert = false # false = black background, true = white background
ui.fps = 0.0 ui.fps = 0.0

View File

@ -1,18 +1,18 @@
# SOME DESCRIPTIVE TITLE. # pwnagotchi Brazilian Portuguese translation file.
# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER # Copyright (C) 2024
# This file is distributed under the same license as the PACKAGE package. # This file is distributed under the same license as the pwnagotchi package.
# FIRST AUTHOR <EMAIL@ADDRESS>, YEAR. # Fabiano F O <fabfernandes@hotmail.com>, 2024.
# #
msgid "" msgid ""
msgstr "" msgstr ""
"Project-Id-Version: PACKAGE VERSION\n" "Project-Id-Version: PACKAGE VERSION\n"
"Report-Msgid-Bugs-To: \n" "Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2023-11-17 15:46+0100\n" "POT-Creation-Date: 2024-03-25 22:30+0100\n"
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
"Last-Translator: Foxy <EMAIL@ADDRESS>\n" "Last-Translator: Fabiano F O <fabfernandes@hotmail.com>\n"
"Language-Team: LANGUAGE <LL@li.org>\n" "Language-Team: LANGUAGE <LL@li.org>\n"
"Language: Portuguese (Brazil)\n" "Language: Brazilian Portuguese\n"
"MIME-Version: 1.0\n" "MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=utf-8\n" "Content-Type: text/plain; charset=utf-8\n"
"Content-Transfer-Encoding: 8bit\n" "Content-Transfer-Encoding: 8bit\n"
@ -21,13 +21,13 @@ msgid "ZzzzZZzzzzZzzz"
msgstr "ZzzzZZzzzzZzzz" msgstr "ZzzzZZzzzzZzzz"
msgid "Hi, I'm Pwnagotchi! Starting ..." msgid "Hi, I'm Pwnagotchi! Starting ..."
msgstr "Olá, Eu sou Pwnagotchi! Iniciando ..." msgstr "Olá, sou Pwnagotchi! Iniciando ..."
msgid "New day, new hunt, new pwns!" msgid "New day, new hunt, new pwns!"
msgstr "Um novo dia, Uma nova caça e novos pwns!" msgstr "Novo dia, Nova caçada, Novos pwns!"
msgid "Hack the Planet!" msgid "Hack the Planet!"
msgstr "Burle o Planeta!" msgstr "Hackeie o Planeta!"
msgid "AI ready." msgid "AI ready."
msgstr "IA pronta." msgstr "IA pronta."
@ -36,64 +36,64 @@ msgid "The neural network is ready."
msgstr "A rede neural está pronta." msgstr "A rede neural está pronta."
msgid "Generating keys, do not turn off ..." msgid "Generating keys, do not turn off ..."
msgstr "Criando chaves, não desligue o sistema ..." msgstr "Gerando chaves, não desligue ..."
#, python-brace-format #, python-brace-format
msgid "Hey, channel {channel} is free! Your AP will say thanks." msgid "Hey, channel {channel} is free! Your AP will say thanks."
msgstr "Ei, canal {channel} está livre! Seu AP vai agradecer." msgstr "Ei, o canal {channel} está livre! Seu AP vai agradecer."
msgid "Reading last session logs ..." msgid "Reading last session logs ..."
msgstr "Lendo os logs da ultima sessão" msgstr "Lendo os logs da última sessão ..."
#, python-brace-format #, python-brace-format
msgid "Read {lines_so_far} log lines so far ..." msgid "Read {lines_so_far} log lines so far ..."
msgstr "Leia {lines_so_far} linha de logs até agora ..." msgstr "Li {lines_so_far} linhas de logs até agora ..."
msgid "I'm bored ..." msgid "I'm bored ..."
msgstr "Eu estou entediado ..." msgstr "Estou entediado ..."
msgid "Let's go for a walk!" msgid "Let's go for a walk!"
msgstr "Vamos ir numa caminhada!" msgstr "Vamos dar um passeio!"
msgid "This is the best day of my life!" msgid "This is the best day of my life!"
msgstr "Esse é o melhor dia da minha vida!" msgstr "Este é o melhor dia da minha vida!"
msgid "Shitty day :/" msgid "Shitty day :/"
msgstr "Dia ruim :/" msgstr "Que dia ruim :/"
msgid "I'm extremely bored ..." msgid "I'm extremely bored ..."
msgstr "Eu estou extremamente entediado ..." msgstr "Estou extremamente entediado ..."
msgid "I'm very sad ..." msgid "I'm very sad ..."
msgstr "Eu estou muito triste ..." msgstr "Estou muito triste ..."
msgid "I'm sad" msgid "I'm sad"
msgstr "Eu estou triste" msgstr "Estou triste"
msgid "Leave me alone ..." msgid "Leave me alone ..."
msgstr "Me deixe em paz ..." msgstr "Me deixe em paz ..."
msgid "I'm mad at you!" msgid "I'm mad at you!"
msgstr "Eu estou bravo com você!" msgstr "Estou bravo com você!"
msgid "I'm living the life!" msgid "I'm living the life!"
msgstr "Eu estou vivendo a vida!" msgstr "Estou aproveitando a vida!"
msgid "I pwn therefore I am." msgid "I pwn therefore I am."
msgstr "Eu pwn então Eu sou." msgstr "Eu pwn, logo existo."
msgid "So many networks!!!" msgid "So many networks!!!"
msgstr "Tantas redes!!!" msgstr "Uau! Quantas redes!!"
msgid "I'm having so much fun!" msgid "I'm having so much fun!"
msgstr "Eu estou tendo muita diversão" msgstr "Estou me divertindo muito!"
msgid "My crime is that of curiosity ..." msgid "My crime is that of curiosity ..."
msgstr "Meu crime é de curiosidade ..." msgstr "Meu crime é a curiosidade ..."
#, python-brace-format #, python-brace-format
msgid "Hello {name}! Nice to meet you." msgid "Hello {name}! Nice to meet you."
msgstr "Olá {name}! É bom em conhecê-lo" msgstr "Olá {name}! Prazer em conhecê-lo."
#, python-brace-format #, python-brace-format
msgid "Yo {name}! Sup?" msgid "Yo {name}! Sup?"
@ -101,33 +101,33 @@ msgstr "Ei {name}! Como vai?"
#, python-brace-format #, python-brace-format
msgid "Hey {name} how are you doing?" msgid "Hey {name} how are you doing?"
msgstr "Ei {name} como você está indo?" msgstr "Ei {name}, como você está?"
#, python-brace-format #, python-brace-format
msgid "Unit {name} is nearby!" msgid "Unit {name} is nearby!"
msgstr "" msgstr "A unidade {name} está próxima!"
#, python-brace-format #, python-brace-format
msgid "Uhm ... goodbye {name}" msgid "Uhm ... goodbye {name}"
msgstr "" msgstr "Hmm ... tchau {name}"
#, python-brace-format #, python-brace-format
msgid "{name} is gone ..." msgid "{name} is gone ..."
msgstr "" msgstr "{name} desapareceu ..."
#, python-brace-format #, python-brace-format
msgid "Whoops ... {name} is gone." msgid "Whoops ... {name} is gone."
msgstr "" msgstr "Oops ... {name} desapareceu."
#, python-brace-format #, python-brace-format
msgid "{name} missed!" msgid "{name} missed!"
msgstr "{name} errou!" msgstr "Perdi {name}!"
msgid "Missed!" msgid "Missed!"
msgstr "Errei!" msgstr "Perdi!"
msgid "Good friends are a blessing!" msgid "Good friends are a blessing!"
msgstr "Bom amigos são uma bensão!" msgstr "Bons amigos são uma bênção!"
msgid "I love my friends!" msgid "I love my friends!"
msgstr "Eu amo meus amigos!" msgstr "Eu amo meus amigos!"
@ -136,7 +136,7 @@ msgid "Nobody wants to play with me ..."
msgstr "Ninguém quer brincar comigo ..." msgstr "Ninguém quer brincar comigo ..."
msgid "I feel so alone ..." msgid "I feel so alone ..."
msgstr "Estou me sentindo sozinho" msgstr "Me sinto tão sozinho ..."
msgid "Where's everybody?!" msgid "Where's everybody?!"
msgstr "Onde está todo mundo?!" msgstr "Onde está todo mundo?!"
@ -160,27 +160,27 @@ msgstr "Zzz"
#, python-brace-format #, python-brace-format
msgid "Waiting for {secs}s ..." msgid "Waiting for {secs}s ..."
msgstr "Esperando por {secs}s ..." msgstr "Aguardando {secs}s ..."
#, python-brace-format #, python-brace-format
msgid "Looking around ({secs}s)" msgid "Looking around ({secs}s)"
msgstr "Olhando por volta ({secs}s)" msgstr "Olhando em volta ... ({secs}s)"
#, python-brace-format #, python-brace-format
msgid "Hey {what} let's be friends!" msgid "Hey {what} let's be friends!"
msgstr "Ei {what} vamos ser amigos!" msgstr "Ei {what}, vamos ser amigos!"
#, python-brace-format #, python-brace-format
msgid "Associating to {what}" msgid "Associating to {what}"
msgstr "Associando para {what}" msgstr "Associando a {what}"
#, python-brace-format #, python-brace-format
msgid "Yo {what}!" msgid "Yo {what}!"
msgstr "Ei {what}!" msgstr "Olá {what}!"
#, python-brace-format #, python-brace-format
msgid "Just decided that {mac} needs no WiFi!" msgid "Just decided that {mac} needs no WiFi!"
msgstr "Apenas decidindo que {mac} não precisa de WiFi!" msgstr "Acabei de decidir que {mac} não precisa de WiFi!"
#, python-brace-format #, python-brace-format
msgid "Deauthenticating {mac}" msgid "Deauthenticating {mac}"
@ -192,11 +192,11 @@ msgstr "Banindo {mac}!"
#, python-brace-format #, python-brace-format
msgid "Cool, we got {num} new handshake{plural}!" msgid "Cool, we got {num} new handshake{plural}!"
msgstr "Legal, conseguimos {num} novos handshake{plural}!" msgstr "Legal, conseguimos {num} novo{plural} handshake{plural}!"
#, python-brace-format #, python-brace-format
msgid "You have {count} new message{plural}!" msgid "You have {count} new message{plural}!"
msgstr "Você tem {count} novas messagem{plural}!" msgstr "Você tem {count} nova{plural} messagem{plural}!"
msgid "Oops, something went wrong ... Rebooting ..." msgid "Oops, something went wrong ... Rebooting ..."
msgstr "Oops, algo deu errado ... Reiniciando ..." msgstr "Oops, algo deu errado ... Reiniciando ..."
@ -207,7 +207,7 @@ msgstr "Enviando dados para {to} ..."
#, python-brace-format #, python-brace-format
msgid "Downloading from {name} ..." msgid "Downloading from {name} ..."
msgstr "Instalando para {name} ..." msgstr "Baixando de {name} ..."
#, python-brace-format #, python-brace-format
msgid "Kicked {num} stations\n" msgid "Kicked {num} stations\n"
@ -225,11 +225,11 @@ msgid "Got {num} handshakes\n"
msgstr "Peguei {num} handshakes\n" msgstr "Peguei {num} handshakes\n"
msgid "Met 1 peer" msgid "Met 1 peer"
msgstr "Encontrei 1 pessoa" msgstr "Conheci 1 peer"
#, python-brace-format #, python-brace-format
msgid "Met {num} peers" msgid "Met {num} peers"
msgstr "Encontrei {num} pessoas" msgstr "Conheci {num} peers"
#, python-brace-format #, python-brace-format
msgid "" msgid ""
@ -237,8 +237,8 @@ msgid ""
"{associated} new friends and ate {handshakes} handshakes! #pwnagotchi " "{associated} new friends and ate {handshakes} handshakes! #pwnagotchi "
"#pwnlog #pwnlife #hacktheplanet #skynet" "#pwnlog #pwnlife #hacktheplanet #skynet"
msgstr "" msgstr ""
"Estou navegando há {duration} e expulsei {deauthed} clientes! Também conheci " "Estou pwning há {duration} e expulsei {deauthed} clientes! Também conheci "
"{associamos} novos amigos e comi {handshakes} handshakes! #pwnagotchi " "{associated} novos amigos e comi {handshakes} handshakes! #pwnagotchi "
"#pwnlog #pwnlife #hacktheplanet #skynet" "#pwnlog #pwnlife #hacktheplanet #skynet"
msgid "hours" msgid "hours"

Binary file not shown.

View File

@ -0,0 +1,260 @@
# SOME DESCRIPTIVE TITLE.
# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER
# This file is distributed under the same license as the PACKAGE package.
# FIRST AUTHOR <dragan.miljkovic29@gmail.com>, 2024.
#
#, fuzzy
msgid ""
msgstr ""
"Project-Id-Version: Srpski prevod v1\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2024-02-16 15:26-0300\n"
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
"Language-Team: RS <dragan.miljkovic29@gmail.com>\n"
"Language: Serbian\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
msgid "ZzzzZZzzzzZzzz"
msgstr "ZzzzZZzzzzZzzz"
msgid "Hi, I'm Pwnagotchi! Starting ..."
msgstr "Zdravo, ja sam Pwnagotchi! Započinjem ..."
msgid "New day, new hunt, new pwns!"
msgstr "Novi dan, novi lov, novi pnwovi!"
msgid "Hack the Planet!"
msgstr "Hakuj Planetu!"
msgid "AI ready."
msgstr "AI spreman."
msgid "The neural network is ready."
msgstr "Neuronska mreža je spremna."
msgid "Generating keys, do not turn off ..."
msgstr "Generišem ključeve, ne isključuj me..."
#, python-brace-format
msgid "Hey, channel {channel} is free! Your AP will say thanks."
msgstr "Hej, kanal {channel} je slobodan! Tvoj AP će ti se zahvaliti."
msgid "Reading last session logs ..."
msgstr "Čitanje logova poslednje sesije ..."
#, python-brace-format
msgid "Read {lines_so_far} log lines so far ..."
msgstr "Pročitao {lines_so_far} log linija do sada ..."
msgid "I'm bored ..."
msgstr "Dosadno mi je ..."
msgid "Let's go for a walk!"
msgstr "Hajmo u šetnju!"
msgid "This is the best day of my life!"
msgstr "Ovo je najbolji dan mog života!"
msgid "Shitty day :/"
msgstr "Sranje dan :/"
msgid "I'm extremely bored ..."
msgstr "Užasno mi je dosadno ..."
msgid "I'm very sad ..."
msgstr "Jako sam tužan ..."
msgid "I'm sad"
msgstr "Tužan sam"
msgid "Leave me alone ..."
msgstr "Ostavi me na miru ..."
msgid "I'm mad at you!"
msgstr "Ljut sam na tebe!"
msgid "I'm living the life!"
msgstr "Živim ga!"
msgid "I pwn therefore I am."
msgstr "Ja pwnujem dakle postojim."
msgid "So many networks!!!"
msgstr "Tako mnogo mreža!!!"
msgid "I'm having so much fun!"
msgstr "Previše se zabavljam!"
msgid "My crime is that of curiosity ..."
msgstr "Moj zločin je radoznalost ..."
#, python-brace-format
msgid "Hello {name}! Nice to meet you."
msgstr "Zdravo {name}! Drago mi je da te upoznam."
#, python-brace-format
msgid "Yo {name}! Sup?"
msgstr "Ej {name}! Šta ima?"
#, python-brace-format
msgid "Hey {name} how are you doing?"
msgstr "Hej {name} kako si?"
#, python-brace-format
msgid "Unit {name} is nearby!"
msgstr "Jedinica {name} je blizu!"
#, python-brace-format
msgid "Uhm ... goodbye {name}"
msgstr "Umm ... doviđenja {name}"
#, python-brace-format
msgid "{name} is gone ..."
msgstr "{name} je nestao ..."
#, python-brace-format
msgid "Whoops ... {name} is gone."
msgstr "Ups ... {name} je nestao."
#, python-brace-format
msgid "{name} missed!"
msgstr "{name} promašen!"
msgid "Missed!"
msgstr "Promašen!"
msgid "Good friends are a blessing!"
msgstr "Dobri prijatelji su blagoslov!"
msgid "I love my friends!"
msgstr "Volim svoje prijatelje!"
msgid "Nobody wants to play with me ..."
msgstr "Niko ne želi da se igra sa mnom ..."
msgid "I feel so alone ..."
msgstr "Osećam se toliko usamljeno ..."
msgid "Where's everybody?!"
msgstr "Gde su svi?!"
#, python-brace-format
msgid "Napping for {secs}s ..."
msgstr "Dremam {secs}s ..."
msgid "Zzzzz"
msgstr "Zzzzz"
#, python-brace-format
msgid "ZzzZzzz ({secs}s)"
msgstr "ZzzZzzz ({secs}s)"
msgid "Good night."
msgstr "Laku noć."
msgid "Zzz"
msgstr "Zzz"
#, python-brace-format
msgid "Waiting for {secs}s ..."
msgstr "Čekam {secs}s ..."
#, python-brace-format
msgid "Looking around ({secs}s)"
msgstr "Gledam unaokolo ({secs}s)"
#, python-brace-format
msgid "Hey {what} let's be friends!"
msgstr "Hej {what} hajde da budemo prijatelji!"
#, python-brace-format
msgid "Associating to {what}"
msgstr "Povezujem se sa {what}"
#, python-brace-format
msgid "Yo {what}!"
msgstr "Ej {what}!"
#, python-brace-format
msgid "Just decided that {mac} needs no WiFi!"
msgstr "Upravo sam odlučio da {mac} ne zahteva WiFi!"
#, python-brace-format
msgid "Deauthenticating {mac}"
msgstr "Deauthenticating {mac}"
#, python-brace-format
msgid "Kickbanning {mac}!"
msgstr "Kickbanning {mac}!"
#, python-brace-format
msgid "Cool, we got {num} new handshake{plural}!"
msgstr "Kul, imamo {num} novih handshakeova"
#, python-brace-format
msgid "You have {count} new message{plural}!"
msgstr "Imas {count} novih poruka"
msgid "Oops, something went wrong ... Rebooting ..."
msgstr "Ups, nešto je poslo po zlu ... Restartujem ..."
#, python-brace-format
msgid "Uploading data to {to} ..."
msgstr "Otpremam podatke na {to} ..."
#, python-brace-format
msgid "Downloading from {name} ..."
msgstr "Preuzimam od {name} ..."
#, python-brace-format
msgid "Kicked {num} stations\n"
msgstr "Kickovano {num} stanica\n"
msgid "Made >999 new friends\n"
msgstr "Stekao sam >999 novih prijatelja\n"
#, python-brace-format
msgid "Made {num} new friends\n"
msgstr "Stekao sam {num} novih prijatelja\n"
#, python-brace-format
msgid "Got {num} handshakes\n"
msgstr "Imam {num} handshakeova\n"
msgid "Met 1 peer"
msgstr "Sreo 1 peera"
#, python-brace-format
msgid "Met {num} peers"
msgstr "Sreo {num} peerova"
#, python-brace-format
msgid ""
"I've been pwning for {duration} and kicked {deauthed} clients! I've also met "
"{associated} new friends and ate {handshakes} handshakes! #pwnagotchi "
"#pwnlog #pwnlife #hacktheplanet #skynet"
msgstr ""
"Pwnujem već {duration} i kickovao sam {deauthed} klijenata! Takođe sam sreo "
"{associated} novih prijatelja i pojeo {handshakes} handshakeova! #pwnagotchi "
"#pwnlog #pwnlife #hacktheplanet #skynet"
msgid "hours"
msgstr "sati"
msgid "minutes"
msgstr "minuta"
msgid "seconds"
msgstr "sekundi"
msgid "hour"
msgstr "sat"
msgid "minute"
msgstr "minut"
msgid "second"
msgstr "sekunda"

View File

@ -9,8 +9,8 @@ msgstr ""
"Project-Id-Version: PACKAGE VERSION\n" "Project-Id-Version: PACKAGE VERSION\n"
"Report-Msgid-Bugs-To: \n" "Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2023-11-16 21:10+0100\n" "POT-Creation-Date: 2023-11-16 21:10+0100\n"
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" "PO-Revision-Date: 2024-03-27 18:40+0800\n"
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n" "Last-Translator: AlanLeung <admin@mcnot.pro>\n"
"Language-Team: LANGUAGE <LL@li.org>\n" "Language-Team: LANGUAGE <LL@li.org>\n"
"Language: Twi\n" "Language: Twi\n"
"MIME-Version: 1.0\n" "MIME-Version: 1.0\n"
@ -18,218 +18,218 @@ msgstr ""
"Content-Transfer-Encoding: 8bit\n" "Content-Transfer-Encoding: 8bit\n"
msgid "ZzzzZZzzzzZzzz" msgid "ZzzzZZzzzzZzzz"
msgstr "" msgstr "ZzzzZZzzzzZzzz"
msgid "Hi, I'm Pwnagotchi! Starting ..." msgid "Hi, I'm Pwnagotchi! Starting ..."
msgstr "" msgstr "HI!我是Pwnagotchi!\n程式啟動..."
msgid "New day, new hunt, new pwns!" msgid "New day, new hunt, new pwns!"
msgstr "" msgstr "新的一天!\n新的狩獵!新的入侵!"
msgid "Hack the Planet!" msgid "Hack the Planet!"
msgstr "" msgstr "我要駭入\n地球的所有人!"
msgid "AI ready." msgid "AI ready."
msgstr "" msgstr "人工智慧已啟動。"
msgid "The neural network is ready." msgid "The neural network is ready."
msgstr "" msgstr "神經網路已啟動。"
msgid "Generating keys, do not turn off ..." msgid "Generating keys, do not turn off ..."
msgstr "" msgstr "產生金鑰中,\n請勿關閉..."
#, python-brace-format #, python-brace-format
msgid "Hey, channel {channel} is free! Your AP will say thanks." msgid "Hey, channel {channel} is free! Your AP will say thanks."
msgstr "" msgstr "嘿,{channel}很順暢!\n你的WIFI會感謝你的。"
msgid "Reading last session logs ..." msgid "Reading last session logs ..."
msgstr "" msgstr "正在閱讀最後的會話紀錄..."
#, python-brace-format #, python-brace-format
msgid "Read {lines_so_far} log lines so far ..." msgid "Read {lines_so_far} log lines so far ..."
msgstr "" msgstr "目前已經閱讀了 {lines_so_far} 行的紀錄..."
msgid "I'm bored ..." msgid "I'm bored ..."
msgstr "" msgstr "我好無聊..."
msgid "Let's go for a walk!" msgid "Let's go for a walk!"
msgstr "" msgstr "我們! 散步! 散步散步散步散步"
msgid "This is the best day of my life!" msgid "This is the best day of my life!"
msgstr "" msgstr "這是我生命中最棒的一天!"
msgid "Shitty day :/" msgid "Shitty day :/"
msgstr "" msgstr "糟糕的一天 :/"
msgid "I'm extremely bored ..." msgid "I'm extremely bored ..."
msgstr "" msgstr "我超無聊的...炒雞 炒雞的那種"
msgid "I'm very sad ..." msgid "I'm very sad ..."
msgstr "" msgstr "我好難過..."
msgid "I'm sad" msgid "I'm sad"
msgstr "" msgstr "嗚嗚嗚...."
msgid "Leave me alone ..." msgid "Leave me alone ..."
msgstr "" msgstr "尼奏凱啦臭臭"
msgid "I'm mad at you!" msgid "I'm mad at you!"
msgstr "" msgstr "喔氣氣氣氣氣ˋ^ˊ"
msgid "I'm living the life!" msgid "I'm living the life!"
msgstr "" msgstr "真是充實的一生!"
msgid "I pwn therefore I am." msgid "I pwn therefore I am."
msgstr "" msgstr "我駭故我在."
msgid "So many networks!!!" msgid "So many networks!!!"
msgstr "" msgstr "好多網路啊!!!吃! 吃他! 吃光光!!!"
msgid "I'm having so much fun!" msgid "I'm having so much fun!"
msgstr "" msgstr "我玩的超級開心!"
msgid "My crime is that of curiosity ..." msgid "My crime is that of curiosity ..."
msgstr "" msgstr "我的缺點就是\n太好奇了..."
#, python-brace-format #, python-brace-format
msgid "Hello {name}! Nice to meet you." msgid "Hello {name}! Nice to meet you."
msgstr "" msgstr "尼豪{name}!\n很高興認識你!!!!"
#, python-brace-format #, python-brace-format
msgid "Yo {name}! Sup?" msgid "Yo {name}! Sup?"
msgstr "" msgstr "嗨 {name}! 你來攻打我的村莊?"
#, python-brace-format #, python-brace-format
msgid "Hey {name} how are you doing?" msgid "Hey {name} how are you doing?"
msgstr "" msgstr "嗨 {name} 你最近過得如何˙ˇ˙?"
#, python-brace-format #, python-brace-format
msgid "Unit {name} is nearby!" msgid "Unit {name} is nearby!"
msgstr "" msgstr "{name}\n就在附近!"
#, python-brace-format #, python-brace-format
msgid "Uhm ... goodbye {name}" msgid "Uhm ... goodbye {name}"
msgstr "" msgstr "哦嗚 ... \n拜拜{name}"
#, python-brace-format #, python-brace-format
msgid "{name} is gone ..." msgid "{name} is gone ..."
msgstr "" msgstr "{name}\n不見了 ..."
#, python-brace-format #, python-brace-format
msgid "Whoops ... {name} is gone." msgid "Whoops ... {name} is gone."
msgstr "" msgstr "哦歐...\n{name}\n不見了。"
#, python-brace-format #, python-brace-format
msgid "{name} missed!" msgid "{name} missed!"
msgstr "" msgstr "我剛剛錯過了{name}!"
msgid "Missed!" msgid "Missed!"
msgstr "" msgstr "又錯過了!"
msgid "Good friends are a blessing!" msgid "Good friends are a blessing!"
msgstr "" msgstr "有個好朋友\n真幸福!"
msgid "I love my friends!" msgid "I love my friends!"
msgstr "" msgstr "我喜歡\n我的朋友!"
msgid "Nobody wants to play with me ..." msgid "Nobody wants to play with me ..."
msgstr "" msgstr "沒人想跟我玩..."
msgid "I feel so alone ..." msgid "I feel so alone ..."
msgstr "" msgstr "我覺得好孤單..."
msgid "Where's everybody?!" msgid "Where's everybody?!"
msgstr "" msgstr "大家都去哪裡了?!"
#, python-brace-format #, python-brace-format
msgid "Napping for {secs}s ..." msgid "Napping for {secs}s ..."
msgstr "" msgstr "我想瞇{secs}秒一下..."
msgid "Zzzzz" msgid "Zzzzz"
msgstr "" msgstr "Zzzzz"
#, python-brace-format #, python-brace-format
msgid "ZzzZzzz ({secs}s)" msgid "ZzzZzzz ({secs}s)"
msgstr "" msgstr "ZzzZzzz({secs}秒)"
msgid "Good night." msgid "Good night."
msgstr "" msgstr "晚安!"
msgid "Zzz" msgid "Zzz"
msgstr "" msgstr "Zzz"
#, python-brace-format #, python-brace-format
msgid "Waiting for {secs}s ..." msgid "Waiting for {secs}s ..."
msgstr "" msgstr "等我{secs}秒..."
#, python-brace-format #, python-brace-format
msgid "Looking around ({secs}s)" msgid "Looking around ({secs}s)"
msgstr "" msgstr "環顧四周({secs}秒)"
#, python-brace-format #, python-brace-format
msgid "Hey {what} let's be friends!" msgid "Hey {what} let's be friends!"
msgstr "" msgstr "嗨\n{what}\n讓我們來當朋友吧!"
#, python-brace-format #, python-brace-format
msgid "Associating to {what}" msgid "Associating to {what}"
msgstr "" msgstr "正在連接\n{what}"
#, python-brace-format #, python-brace-format
msgid "Yo {what}!" msgid "Yo {what}!"
msgstr "" msgstr "喲,\n{what}!"
#, python-brace-format #, python-brace-format
msgid "Just decided that {mac} needs no WiFi!" msgid "Just decided that {mac} needs no WiFi!"
msgstr "" msgstr "我要讓\n{mac}\n斷線!\n他不需要上網!"
#, python-brace-format #, python-brace-format
msgid "Deauthenticating {mac}" msgid "Deauthenticating {mac}"
msgstr "" msgstr "解除\n{mac}\n的授權中"
#, python-brace-format #, python-brace-format
msgid "Kickbanning {mac}!" msgid "Kickbanning {mac}!"
msgstr "" msgstr "把\n{mac}\n踢出中!"
#, python-brace-format #, python-brace-format
msgid "Cool, we got {num} new handshake{plural}!" msgid "Cool, we got {num} new handshake{plural}!"
msgstr "" msgstr "酷耶,我們抓到{num}個\n新的握手包{plural}!"
#, python-brace-format #, python-brace-format
msgid "You have {count} new message{plural}!" msgid "You have {count} new message{plural}!"
msgstr "" msgstr "你有{count}個新訊息{plural}!"
msgid "Oops, something went wrong ... Rebooting ..." msgid "Oops, something went wrong ... Rebooting ..."
msgstr "" msgstr "哦歐,有些地方出錯了...\n重新啟動中..."
#, python-brace-format #, python-brace-format
msgid "Uploading data to {to} ..." msgid "Uploading data to {to} ..."
msgstr "" msgstr "正在上傳資料到 {to} ..."
#, python-brace-format #, python-brace-format
msgid "Downloading from {name} ..." msgid "Downloading from {name} ..."
msgstr "" msgstr "正在從 {name} 下載資料..."
#, python-brace-format #, python-brace-format
msgid "Kicked {num} stations\n" msgid "Kicked {num} stations\n"
msgstr "" msgstr "踢了 {num} 個設備\n"
msgid "Made >999 new friends\n" msgid "Made >999 new friends\n"
msgstr "" msgstr "交了 >999 個新朋友\n"
#, python-brace-format #, python-brace-format
msgid "Made {num} new friends\n" msgid "Made {num} new friends\n"
msgstr "" msgstr "交了 {num} 個新朋友\n"
#, python-brace-format #, python-brace-format
msgid "Got {num} handshakes\n" msgid "Got {num} handshakes\n"
msgstr "" msgstr "捕獲了 {num} 個握手包\n"
msgid "Met 1 peer" msgid "Met 1 peer"
msgstr "" msgstr "遇到了 個同好"
#, python-brace-format #, python-brace-format
msgid "Met {num} peers" msgid "Met {num} peers"
msgstr "" msgstr "遇到了 {num} 個同好"
#, python-brace-format #, python-brace-format
msgid "" msgid ""
@ -237,21 +237,24 @@ msgid ""
"{associated} new friends and ate {handshakes} handshakes! #pwnagotchi " "{associated} new friends and ate {handshakes} handshakes! #pwnagotchi "
"#pwnlog #pwnlife #hacktheplanet #skynet" "#pwnlog #pwnlife #hacktheplanet #skynet"
msgstr "" msgstr ""
"我花了{duration}的時間\n駭入和踢了{deauthed}好多設備."
"我還交了好多{associated}新朋友,\n而且抓到了{handshakes}握手包!"
"#pwnagotchi#入侵日志 #駭客人生 #入侵整個星球 #天網 #我好棒˙ˇ˙"
msgid "hours" msgid "hours"
msgstr "" msgstr ""
msgid "minutes" msgid "minutes"
msgstr "" msgstr ""
msgid "seconds" msgid "seconds"
msgstr "" msgstr ""
msgid "hour" msgid "hour"
msgstr "" msgstr ""
msgid "minute" msgid "minute"
msgstr "" msgstr ""
msgid "second" msgid "second"
msgstr "" msgstr ""

View File

@ -29,7 +29,6 @@ class FixServices(plugins.Plugin):
def __init__(self): def __init__(self):
self.options = dict() 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.pattern2 = re.compile(r'wifi error while hopping to channel')
self.pattern3 = re.compile(r'Firmware has halted or crashed') self.pattern3 = re.compile(r'Firmware has halted or crashed')
self.pattern4 = re.compile(r'error 400: could not find interface wlan0mon') self.pattern4 = re.compile(r'error 400: could not find interface wlan0mon')
@ -55,20 +54,6 @@ class FixServices(plugins.Plugin):
if ",UP," in str(cmd_output): if ",UP," in str(cmd_output):
logging.debug("wlan0mon is up.") logging.debug("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.debug('[Fix_Services] Blind-Bug detected. Restarting.')
try:
self._tryTurningItOffAndOnAgain(agent)
except Exception as err:
logging.warning("[Fix_Services turnOffAndOn] %s" % repr(err))
else:
logging.debug("[Fix_Services] Logs look good!")
except Exception as err: except Exception as err:
logging.error("[Fix_Services ip link show wlan0mon]: %s" % repr(err)) logging.error("[Fix_Services ip link show wlan0mon]: %s" % repr(err))
try: try:
@ -106,7 +91,7 @@ class FixServices(plugins.Plugin):
other_last_lines = ''.join(list(TextIOWrapper(subprocess.Popen(['journalctl', '-n10'], other_last_lines = ''.join(list(TextIOWrapper(subprocess.Popen(['journalctl', '-n10'],
stdout=subprocess.PIPE).stdout))[-10:]) stdout=subprocess.PIPE).stdout))[-10:])
other_other_last_lines = ''.join( other_other_last_lines = ''.join(
list(TextIOWrapper(subprocess.Popen(['tail', '-n10', '/var/log/pwnagotchi.log'], list(TextIOWrapper(subprocess.Popen(['tail', '-n10', '/etc/pwnagotchi/log/pwnagotchi.log'],
stdout=subprocess.PIPE).stdout))[-10:]) stdout=subprocess.PIPE).stdout))[-10:])
# don't check if we ran a reset recently # don't check if we ran a reset recently
logging.debug("[Fix_Services]**** epoch") logging.debug("[Fix_Services]**** epoch")
@ -116,20 +101,8 @@ class FixServices(plugins.Plugin):
logging.debug("[Fix_Services]**** checking") logging.debug("[Fix_Services]**** checking")
# Look for pattern 1
if len(self.pattern.findall(last_lines)) >= 3:
logging.debug("[Fix_Services]**** Should trigger a reload of the wlan0mon device:\n%s" % last_lines)
if hasattr(agent, 'view'):
display.set('status', 'Blind-Bug detected. Restarting.')
display.update(force=True)
logging.debug('[Fix_Services] Blind-Bug detected. Restarting.')
try:
self._tryTurningItOffAndOnAgain(agent)
except Exception as err:
logging.warning("[Fix_Services] TTOAOA: %s" % repr(err))
# Look for pattern 2 # Look for pattern 2
elif len(self.pattern2.findall(other_last_lines)) >= 5: if len(self.pattern2.findall(other_last_lines)) >= 5:
logging.debug("[Fix_Services]**** Should trigger a reload of the wlan0mon device:\n%s" % last_lines) logging.debug("[Fix_Services]**** Should trigger a reload of the wlan0mon device:\n%s" % last_lines)
if hasattr(agent, 'view'): if hasattr(agent, 'view'):
display.set('status', 'Wifi channel stuck. Restarting recon.') display.set('status', 'Wifi channel stuck. Restarting recon.')

View File

@ -14,8 +14,9 @@ class GPIOButtons(plugins.Plugin):
self.running = False self.running = False
self.ports = {} self.ports = {}
self.commands = None self.commands = None
self.options = dict()
def runCommand(self, channel): def runcommand(self, channel):
command = self.ports[channel] command = self.ports[channel]
logging.info(f"Button Pressed! Running command: {command}") logging.info(f"Button Pressed! Running command: {command}")
process = subprocess.Popen(command, shell=True, stdin=None, stdout=open("/dev/null", "w"), stderr=None, process = subprocess.Popen(command, shell=True, stdin=None, stdout=open("/dev/null", "w"), stderr=None,
@ -35,8 +36,8 @@ class GPIOButtons(plugins.Plugin):
gpio = int(gpio) gpio = int(gpio)
self.ports[gpio] = command self.ports[gpio] = command
GPIO.setup(gpio, GPIO.IN, GPIO.PUD_UP) GPIO.setup(gpio, GPIO.IN, GPIO.PUD_UP)
GPIO.add_event_detect(gpio, GPIO.FALLING, callback=self.runCommand, bouncetime=600) GPIO.add_event_detect(gpio, GPIO.FALLING, callback=self.runcommand, bouncetime=600)
#set pimoroni display hat mini LED off/dim # set pimoroni display hat mini LED off/dim
GPIO.setup(17, GPIO.IN, pull_up_down=GPIO.PUD_DOWN) GPIO.setup(17, GPIO.IN, pull_up_down=GPIO.PUD_DOWN)
GPIO.setup(22, GPIO.IN, pull_up_down=GPIO.PUD_DOWN) GPIO.setup(22, GPIO.IN, pull_up_down=GPIO.PUD_DOWN)
GPIO.setup(27, GPIO.IN, pull_up_down=GPIO.PUD_DOWN) GPIO.setup(27, GPIO.IN, pull_up_down=GPIO.PUD_DOWN)

View File

@ -47,7 +47,7 @@ class Grid(plugins.Plugin):
__version__ = '1.0.1' __version__ = '1.0.1'
__license__ = 'GPL3' __license__ = 'GPL3'
__description__ = 'This plugin signals the unit cryptographic identity and list of pwned networks and list of pwned ' \ __description__ = 'This plugin signals the unit cryptographic identity and list of pwned networks and list of pwned ' \
'networks to api.pwnagotchi.ai ' 'networks to opwngrid.xyz '
def __init__(self): def __init__(self):
self.options = dict() self.options = dict()
@ -89,7 +89,7 @@ class Grid(plugins.Plugin):
logging.debug("checking pcap's") logging.debug("checking pcap's")
config = agent.config() config = agent.config()
pcap_files = glob.glob(os.path.join(agent.config()['bettercap']['handshakes'], "*.pcap")) pcap_files = glob.glob(os.path.join(config['bettercap']['handshakes'], "*.pcap"))
num_networks = len(pcap_files) num_networks = len(pcap_files)
reported = self.report.data_field_or('reported', default=[]) reported = self.report.data_field_or('reported', default=[])
num_reported = len(reported) num_reported = len(reported)

View File

@ -130,7 +130,7 @@ class MemTemp(plugins.Plugin):
except Exception: except Exception:
# Set default position based on screen type # Set default position based on screen type
if ui.is_waveshare_v2(): if ui.is_waveshare_v2():
h_pos = (178, 84) h_pos = (175, 84)
v_pos = (197, 74) v_pos = (197, 74)
elif ui.is_waveshare_v1(): elif ui.is_waveshare_v1():
h_pos = (170, 80) h_pos = (170, 80)

View File

@ -18,12 +18,14 @@ def systemd_dropin(name, content):
systemctl("daemon-reload") systemctl("daemon-reload")
def systemctl(command, unit=None): def systemctl(command, unit=None):
if unit: if unit:
os.system("/bin/systemctl %s %s" % (command, unit)) os.system("/bin/systemctl %s %s" % (command, unit))
else: else:
os.system("/bin/systemctl %s" % command) os.system("/bin/systemctl %s" % command)
def run_task(name, options): def run_task(name, options):
task_service_name = "switcher-%s-task.service" % name task_service_name = "switcher-%s-task.service" % name
# save all the commands to a shell script # save all the commands to a shell script
@ -57,7 +59,7 @@ def run_task(name, options):
""" % (name, task_service_name, name)) """ % (name, task_service_name, name))
if 'reboot' in options and options['reboot']: if 'reboot' in options and options['reboot']:
# create a indication file! # create an indication file!
# if this file is set, we want the switcher-tasks to run # if this file is set, we want the switcher-tasks to run
open('/root/.switcher', 'a').close() open('/root/.switcher', 'a').close()
@ -98,6 +100,7 @@ def run_task(name, options):
systemctl("daemon-reload") systemctl("daemon-reload")
systemctl("start", task_service_name) systemctl("start", task_service_name)
class Switcher(plugins.Plugin): class Switcher(plugins.Plugin):
__author__ = '33197631+dadav@users.noreply.github.com' __author__ = '33197631+dadav@users.noreply.github.com'
__version__ = '0.0.1' __version__ = '0.0.1'

View File

@ -10,13 +10,17 @@ class Widget(object):
def draw(self, canvas, drawer): def draw(self, canvas, drawer):
raise Exception("not implemented") raise Exception("not implemented")
# canvas.paste: https://pillow.readthedocs.io/en/stable/reference/Image.html#PIL.Image.Image.paste
# takes mask variable, to identify color system. (not used for pwnagotchi yet)
# Pwn should use "1" since its mainly black or white displays.
class Bitmap(Widget): class Bitmap(Widget):
def __init__(self, path, xy, color=0): def __init__(self, path, xy, color=0):
super().__init__(xy, color) super().__init__(xy, color)
self.image = Image.open(path) self.image = Image.open(path)
def draw(self, canvas, drawer): def draw(self, canvas, drawer):
if self.color == 0xFF:
self.image = ImageOps.invert(self.image)
canvas.paste(self.image, self.xy) canvas.paste(self.image, self.xy)

View File

@ -265,6 +265,12 @@ class Display(View):
def is_waveshareoledlcd(self): def is_waveshareoledlcd(self):
return self._implementation.name == 'waveshareoledlcd' return self._implementation.name == 'waveshareoledlcd'
def is_waveshareoledlcdvert(self):
return self._implementation.name == 'waveshareoledlcdvert'
def is_i2coled(self):
return self._implementation.name == 'i2coled'
def is_waveshare35lcd(self): def is_waveshare35lcd(self):
return self._implementation.name == 'waveshare35lcd' return self._implementation.name == 'waveshare35lcd'

View File

@ -104,6 +104,14 @@ def display_for(config):
from pwnagotchi.ui.hw.waveshareoledlcd import Waveshareoledlcd from pwnagotchi.ui.hw.waveshareoledlcd import Waveshareoledlcd
return Waveshareoledlcd(config) return Waveshareoledlcd(config)
elif config['ui']['display']['type'] == 'waveshareoledlcdvert':
from pwnagotchi.ui.hw.waveshareoledlcdvert import Waveshareoledlcdvert
return Waveshareoledlcdvert(config)
elif config['ui']['display']['type'] == 'i2coled':
from pwnagotchi.ui.hw.i2coled import I2COled
return I2COled(config)
elif config['ui']['display']['type'] == 'waveshare1in02': elif config['ui']['display']['type'] == 'waveshare1in02':
from pwnagotchi.ui.hw.waveshare1in02 import Waveshare1in02 from pwnagotchi.ui.hw.waveshare1in02 import Waveshare1in02
return Waveshare1in02(config) return Waveshare1in02(config)

View File

@ -34,10 +34,10 @@ class DisplayHatMini(DisplayImpl):
def initialize(self): def initialize(self):
logging.info("initializing Display Hat Mini") logging.info("initializing Display Hat Mini")
from pwnagotchi.ui.hw.libs.pimoroni.displayhatmini.ST7789 import ST7789 from pwnagotchi.ui.hw.libs.pimoroni.displayhatmini.ST7789 import ST7789
self._display = ST7789(0, 1, 9, 13) self._display = ST7789(0, 1, 9, 13, width=self._layout['width'], height=self._layout['height'], rotation=0)
def render(self, canvas): def render(self, canvas):
self._display.display(canvas) self._display.display(canvas)
def clear(self): def clear(self):
self._display.clear() pass

View File

@ -0,0 +1,67 @@
# Created for the Pwnagotchi project by RasTacsko
# HW libraries are based on the adafruit python SSD1306 repo:
# https://github.com/adafruit/Adafruit_Python_SSD1306
# SMBus parts coming from BLavery's lib_oled96 repo:
# https://github.com/BLavery/lib_oled96
# I2C address, width and height import from config.toml made by NurseJackass
import logging
import pwnagotchi.ui.fonts as fonts
from pwnagotchi.ui.hw.base import DisplayImpl
#
# Default is 128x64 display on i2c address 0x3C
#
# Configure i2c address and dimensions in config.toml:
#
# ui.display.type = "i2coled"
# ui.display.i2c_addr = 0x3C
# ui.display.width = 128
# ui.display.height = 64
#
class I2COled(DisplayImpl):
def __init__(self, config):
self._config = config['ui']['display']
super(I2COled, self).__init__(config, 'i2coled')
def layout(self):
fonts.setup(8, 8, 8, 10, 10, 8)
self._layout['width'] = self._config['width'] if 'width' in self._config else 128
self._layout['height'] = self._config['height'] if 'height' in self._config else 64
self._layout['face'] = (0, 30)
self._layout['name'] = (0, 10)
self._layout['channel'] = (72, 10)
self._layout['aps'] = (0, 0)
self._layout['uptime'] = (87, 0)
self._layout['line1'] = [0, 9, 128, 9]
self._layout['line2'] = [0, 54, 128, 54]
self._layout['friend_face'] = (0, 41)
self._layout['friend_name'] = (40, 43)
self._layout['shakes'] = (0, 55)
self._layout['mode'] = (107, 10)
self._layout['status'] = {
'pos': (37, 19),
'font': fonts.status_font(fonts.Small),
'max': 18
}
return self._layout
def initialize(self):
i2caddr = self._config['i2c_addr'] if 'i2c_addr' in self._config else 0x3C
width = self._config['width'] if 'width' in self._config else 128
height = self._config['height'] if 'height' in self._config else 64
logging.info("initializing %dx%d I2C Oled Display on address 0x%X" % (width, height, i2caddr))
from pwnagotchi.ui.hw.libs.i2coled.epd import EPD
self._display = EPD(address=i2caddr, width=width, height=height)
self._display.Init()
self._display.Clear()
def render(self, canvas):
self._display.display(canvas)
def clear(self):
self._display.clear()

View File

@ -0,0 +1,312 @@
# Copyright (c) 2014 Adafruit Industries
# Author: Tony DiCola
#
# Permission is hereby granted, free of charge, to any person obtaining a copy
# of this software and associated documentation files (the "Software"), to deal
# in the Software without restriction, including without limitation the rights
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
# copies of the Software, and to permit persons to whom the Software is
# furnished to do so, subject to the following conditions:
#
# The above copyright notice and this permission notice shall be included in
# all copies or substantial portions of the Software.
#
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
# THE SOFTWARE.
#
# SMBus parts coming from BLavery's lib_oled96 repo:
# https://github.com/BLavery/lib_oled96
#
# Modified for the Pwnagotchi project by RasTacsko
# Using SMBus, spidev RPi.GPIO for I2C communication instead of Adafruit libraries
# spidev maybe not necessary... needs some checking!!!
# ToDo:
# rotation support for vertical layouts
# checking luma oled library for support other chipsets/resolutions
from __future__ import division
import logging
import time
import RPi.GPIO as GPIO
# import spidev
from smbus import SMBus
i2cbus = SMBus(1)
# Constants
SSD1306_I2C_ADDRESS = 0x3C # 011110+SA0+RW - 0x3C or 0x3D
SSD1306_SETCONTRAST = 0x81
SSD1306_DISPLAYALLON_RESUME = 0xA4
SSD1306_DISPLAYALLON = 0xA5
SSD1306_NORMALDISPLAY = 0xA6
SSD1306_INVERTDISPLAY = 0xA7
SSD1306_DISPLAYOFF = 0xAE
SSD1306_DISPLAYON = 0xAF
SSD1306_SETDISPLAYOFFSET = 0xD3
SSD1306_SETCOMPINS = 0xDA
SSD1306_SETVCOMDETECT = 0xDB
SSD1306_SETDISPLAYCLOCKDIV = 0xD5
SSD1306_SETPRECHARGE = 0xD9
SSD1306_SETMULTIPLEX = 0xA8
SSD1306_SETLOWCOLUMN = 0x00
SSD1306_SETHIGHCOLUMN = 0x10
SSD1306_SETSTARTLINE = 0x40
SSD1306_MEMORYMODE = 0x20
SSD1306_COLUMNADDR = 0x21
SSD1306_PAGEADDR = 0x22
SSD1306_COMSCANINC = 0xC0
SSD1306_COMSCANDEC = 0xC8
SSD1306_SEGREMAP = 0xA0
SSD1306_CHARGEPUMP = 0x8D
SSD1306_EXTERNALVCC = 0x1
SSD1306_SWITCHCAPVCC = 0x2
# Scrolling constants
SSD1306_ACTIVATE_SCROLL = 0x2F
SSD1306_DEACTIVATE_SCROLL = 0x2E
SSD1306_SET_VERTICAL_SCROLL_AREA = 0xA3
SSD1306_RIGHT_HORIZONTAL_SCROLL = 0x26
SSD1306_LEFT_HORIZONTAL_SCROLL = 0x27
SSD1306_VERTICAL_AND_RIGHT_HORIZONTAL_SCROLL = 0x29
SSD1306_VERTICAL_AND_LEFT_HORIZONTAL_SCROLL = 0x2A
class SSD1306Base(object):
"""Base class for SSD1306-based OLED displays. Implementors should subclass
and provide an implementation for the _initialize function.
"""
def __init__(self, width, height, address=SSD1306_I2C_ADDRESS, bus=None):
self._log = logging.getLogger('Adafruit_SSD1306.SSD1306Base')
self.cmd_mode = 0x00
self.data_mode = 0x40
self.bus = i2cbus
self.addr = address
self.width = width
self.height = height
self._pages = height//8
self._buffer = [0]*(width*self._pages)
def _initialize(self):
raise NotImplementedError
def command(self, *cmd):
"""Send command byte to display."""
# I2C write.
assert(len(cmd) <= 31)
try:
self.bus.write_i2c_block_data(self.addr, self.cmd_mode, list(cmd))
except Exception as e:
logging.exception(e)
def data(self, data):
"""Send byte of data to display."""
# I2C write.
try:
for i in range(0, len(data), 31):
self.bus.write_i2c_block_data(self.addr, self.data_mode, list(data[i:i+31]))
except Exception as e:
logging.exception(e)
def begin(self, vccstate=SSD1306_SWITCHCAPVCC):
"""Initialize display."""
# Save vcc state.
self._vccstate = vccstate
# Reset and initialize display.
self._initialize()
# Turn on the display.
self.command(SSD1306_DISPLAYON)
def ShowImage(self):
"""
The image on the "canvas" is flushed through to the hardware display.
Takes the 1-bit image and dumps it to the SSD1306 OLED display.
"""
self.command(SSD1306_COLUMNADDR)
self.command(0) # Column start address. (0 = reset)
self.command(self.width-1) # Column end address.
self.command(SSD1306_PAGEADDR)
self.command(0) # Page start address. (0 = reset)
self.command(self._pages-1) # Page end address.
try:
for i in range(0, len(self._buffer), 16):
self.bus.write_i2c_block_data(self.addr, self.data_mode, self._buffer[i:i+16])
except Exception as e:
logging.exception(e)
def getbuffer(self, image):
"""Set buffer to value of Python Imaging Library image. The image should
be in 1 bit mode and a size equal to the display size.
"""
if image.mode != '1':
raise ValueError('Image must be in mode 1.')
imwidth, imheight = image.size
if imwidth != self.width or imheight != self.height:
raise ValueError('Image must be same dimensions as display ({0}x{1}).' \
.format(self.width, self.height))
# Grab all the pixels from the image, faster than getpixel.
pix = image.load()
# Iterate through the memory pages
index = 0
for page in range(self._pages):
# Iterate through all x axis columns.
for x in range(self.width):
# Set the bits for the column of pixels at the current position.
bits = 0
# Don't use range here as it's a bit slow
for bit in [0, 1, 2, 3, 4, 5, 6, 7]:
bits = bits << 1
bits |= 0 if pix[(x, page*8+7-bit)] == 0 else 1
# Update buffer byte and increment to next byte.
self._buffer[index] = bits
index += 1
def clear(self):
"""Clear contents of image buffer."""
self._buffer = [0]*(self.width*self._pages)
def set_contrast(self, contrast):
"""Sets the contrast of the display. Contrast should be a value between
0 and 255."""
if contrast < 0 or contrast > 255:
raise ValueError('Contrast must be a value from 0 to 255 (inclusive).')
self.command(SSD1306_SETCONTRAST)
self.command(contrast)
def dim(self, dim):
"""Adjusts contrast to dim the display if dim is True, otherwise sets the
contrast to normal brightness if dim is False.
"""
# Assume dim display.
contrast = 0
# Adjust contrast based on VCC if not dimming.
if not dim:
if self._vccstate == SSD1306_EXTERNALVCC:
contrast = 0x9F
else:
contrast = 0xCF
self.set_contrast(contrast)
class SSD1306_128_64(SSD1306Base):
def __init__(self, width, height, address=None, bus=None):
# Call base class constructor.
super(SSD1306_128_64, self).__init__(128, 64, address, bus)
def _initialize(self):
# 128x64 pixel specific initialization.
self.command(SSD1306_DISPLAYOFF) # 0xAE
self.command(SSD1306_SETDISPLAYCLOCKDIV) # 0xD5
self.command(0x80) # the suggested ratio 0x80
self.command(SSD1306_SETMULTIPLEX) # 0xA8
self.command(0x3F)
self.command(SSD1306_SETDISPLAYOFFSET) # 0xD3
self.command(0x0) # no offset
self.command(SSD1306_SETSTARTLINE | 0x0) # line #0
self.command(SSD1306_CHARGEPUMP) # 0x8D
if self._vccstate == SSD1306_EXTERNALVCC:
self.command(0x10)
else:
self.command(0x14)
self.command(SSD1306_MEMORYMODE) # 0x20
self.command(0x00) # 0x0 act like ks0108
self.command(SSD1306_SEGREMAP | 0x1)
self.command(SSD1306_COMSCANDEC)
self.command(SSD1306_SETCOMPINS) # 0xDA
self.command(0x12)
self.command(SSD1306_SETCONTRAST) # 0x81
if self._vccstate == SSD1306_EXTERNALVCC:
self.command(0x9F)
else:
self.command(0xCF)
self.command(SSD1306_SETPRECHARGE) # 0xd9
if self._vccstate == SSD1306_EXTERNALVCC:
self.command(0x22)
else:
self.command(0xF1)
self.command(SSD1306_SETVCOMDETECT) # 0xDB
self.command(0x40)
self.command(SSD1306_DISPLAYALLON_RESUME) # 0xA4
self.command(SSD1306_NORMALDISPLAY) # 0xA6
class SSD1306_128_32(SSD1306Base):
def __init__(self, width, height, address=None, bus=None):
# Call base class constructor.
super(SSD1306_128_32, self).__init__(128, 32, address, bus)
def _initialize(self):
# 128x32 pixel specific initialization.
self.command(SSD1306_DISPLAYOFF) # 0xAE
self.command(SSD1306_SETDISPLAYCLOCKDIV) # 0xD5
self.command(0x80) # the suggested ratio 0x80
self.command(SSD1306_SETMULTIPLEX) # 0xA8
self.command(0x1F)
self.command(SSD1306_SETDISPLAYOFFSET) # 0xD3
self.command(0x0) # no offset
self.command(SSD1306_SETSTARTLINE | 0x0) # line #0
self.command(SSD1306_CHARGEPUMP) # 0x8D
if self._vccstate == SSD1306_EXTERNALVCC:
self.command(0x10)
else:
self.command(0x14)
self.command(SSD1306_MEMORYMODE) # 0x20
self.command(0x00) # 0x0 act like ks0108
self.command(SSD1306_SEGREMAP | 0x1)
self.command(SSD1306_COMSCANDEC)
self.command(SSD1306_SETCOMPINS) # 0xDA
self.command(0x02)
self.command(SSD1306_SETCONTRAST) # 0x81
self.command(0x8F)
self.command(SSD1306_SETPRECHARGE) # 0xd9
if self._vccstate == SSD1306_EXTERNALVCC:
self.command(0x22)
else:
self.command(0xF1)
self.command(SSD1306_SETVCOMDETECT) # 0xDB
self.command(0x40)
self.command(SSD1306_DISPLAYALLON_RESUME) # 0xA4
self.command(SSD1306_NORMALDISPLAY) # 0xA6
class SSD1306_96_16(SSD1306Base):
def __init__(self, width, height, address=None, bus=None):
# Call base class constructor.
super(SSD1306_96_16, self).__init__(96, 16, address, bus)
def _initialize(self):
# 128x32 pixel specific initialization.
self.command(SSD1306_DISPLAYOFF) # 0xAE
self.command(SSD1306_SETDISPLAYCLOCKDIV) # 0xD5
self.command(0x60) # the suggested ratio 0x60
self.command(SSD1306_SETMULTIPLEX) # 0xA8
self.command(0x0F)
self.command(SSD1306_SETDISPLAYOFFSET) # 0xD3
self.command(0x0) # no offset
self.command(SSD1306_SETSTARTLINE | 0x0) # line #0
self.command(SSD1306_CHARGEPUMP) # 0x8D
if self._vccstate == SSD1306_EXTERNALVCC:
self.command(0x10)
else:
self.command(0x14)
self.command(SSD1306_MEMORYMODE) # 0x20
self.command(0x00) # 0x0 act like ks0108
self.command(SSD1306_SEGREMAP | 0x1)
self.command(SSD1306_COMSCANDEC)
self.command(SSD1306_SETCOMPINS) # 0xDA
self.command(0x02)
self.command(SSD1306_SETCONTRAST) # 0x81
self.command(0x8F)
self.command(SSD1306_SETPRECHARGE) # 0xd9
if self._vccstate == SSD1306_EXTERNALVCC:
self.command(0x22)
else:
self.command(0xF1)
self.command(SSD1306_SETVCOMDETECT) # 0xDB
self.command(0x40)
self.command(SSD1306_DISPLAYALLON_RESUME) # 0xA4
self.command(SSD1306_NORMALDISPLAY) # 0xA6

View File

@ -0,0 +1,34 @@
from . import SSD1306
# Display resolution, change if the screen resolution is changed!
EPD_WIDTH = 128
EPD_HEIGHT = 64
# Available screen resolutions:
# disp = SSD1306.SSD1306_128_32(128, 32, address=0x3C)
# disp = SSD1306.SSD1306_96_16(96, 16, address=0x3C)
# If you change for different resolution, you have to modify the layout in pwnagotchi/ui/hw/i2coled.py
class EPD(object):
def __init__(self, address=0x3D, width=EPD_WIDTH, height=EPD_HEIGHT):
self.width = width
self.height = height
# choose subclass based on dimensions
if height == 32:
self.disp = SSD1306.SSD1306_128_32(width, height, address)
elif height == 16:
self.disp = SSD1306.SSD1306_96_16(width, height, address)
else:
self.disp = SSD1306.SSD1306_128_64(width, height, address)
def Init(self):
self.disp.begin()
def Clear(self):
self.disp.clear()
def display(self, image):
self.disp.getbuffer(image)
self.disp.ShowImage()

View File

@ -33,6 +33,8 @@ import sys
import time import time
import subprocess import subprocess
from ctypes import *
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)
@ -43,6 +45,8 @@ class RaspberryPi:
CS_PIN = 8 CS_PIN = 8
BUSY_PIN = 24 BUSY_PIN = 24
PWR_PIN = 18 PWR_PIN = 18
MOSI_PIN = 10
SCLK_PIN = 11
def __init__(self): def __init__(self):
import spidev import spidev
@ -98,9 +102,41 @@ class RaspberryPi:
def spi_writebyte2(self, data): def spi_writebyte2(self, data):
self.SPI.writebytes2(data) self.SPI.writebytes2(data)
def module_init(self): def DEV_SPI_write(self, data):
self.DEV_SPI.DEV_SPI_SendData(data)
def DEV_SPI_nwrite(self, data):
self.DEV_SPI.DEV_SPI_SendnData(data)
def DEV_SPI_read(self):
return self.DEV_SPI.DEV_SPI_ReadData()
def module_init(self, cleanup=False):
self.GPIO_PWR_PIN.on() self.GPIO_PWR_PIN.on()
if cleanup:
find_dirs = [
os.path.dirname(os.path.realpath(__file__)),
'/usr/local/lib',
'/usr/lib',
]
self.DEV_SPI = None
for find_dir in find_dirs:
val = int(os.popen('getconf LONG_BIT').read())
logging.debug("System is %d bit" % val)
if val == 64:
so_filename = os.path.join(find_dir, 'DEV_Config_64.so')
else:
so_filename = os.path.join(find_dir, 'DEV_Config_32.so')
if os.path.exists(so_filename):
self.DEV_SPI = CDLL(so_filename)
break
if self.DEV_SPI is None:
RuntimeError('Cannot find DEV_Config.so')
self.DEV_SPI.DEV_Module_Init()
else:
# SPI device, bus = 0, device = 0 # SPI device, bus = 0, device = 0
self.SPI.open(0, 0) self.SPI.open(0, 0)
self.SPI.max_speed_hz = 4000000 self.SPI.max_speed_hz = 4000000
@ -128,5 +164,3 @@ implementation = RaspberryPi()
for func in [x for x in dir(implementation) if not x.startswith('_')]: for func in [x for x in dir(implementation) if not x.startswith('_')]:
setattr(sys.modules[__name__], func, getattr(implementation, func)) setattr(sys.modules[__name__], func, getattr(implementation, func))
### END OF FILE ###

View File

@ -446,7 +446,7 @@ class EPD:
def display_4Gray(self, image): def display_4Gray(self, image):
self.send_command(0x24) self.send_command(0x24)
for i in range(0, 48000): # 5808*4 46464 for i in range(0, 5808): # 5808*4 46464
temp3 = 0 temp3 = 0
for j in range(0, 2): for j in range(0, 2):
temp1 = image[i * 2 + j] temp1 = image[i * 2 + j]
@ -478,7 +478,7 @@ class EPD:
self.send_data(temp3) self.send_data(temp3)
self.send_command(0x26) self.send_command(0x26)
for i in range(0, 48000): # 5808*4 46464 for i in range(0, 5808): # 5808*4 46464
temp3 = 0 temp3 = 0
for j in range(0, 2): for j in range(0, 2):
temp1 = image[i * 2 + j] temp1 = image[i * 2 + j]

View File

@ -28,7 +28,7 @@
# #
import logging import logging
from pwnagotchi.ui.hw.libs.waveshare.epaper import epdconfig from .. import epdconfig
# Display resolution # Display resolution
EPD_WIDTH = 400 EPD_WIDTH = 400
@ -45,6 +45,10 @@ class EPD:
self.cs_pin = epdconfig.CS_PIN self.cs_pin = epdconfig.CS_PIN
self.width = EPD_WIDTH self.width = EPD_WIDTH
self.height = EPD_HEIGHT self.height = EPD_HEIGHT
self.flag = 0
if (epdconfig.module_init(cleanup=True) != 0):
return -1
# Hardware reset # Hardware reset
def reset(self): def reset(self):
@ -58,13 +62,13 @@ class EPD:
def send_command(self, command): def send_command(self, command):
epdconfig.digital_write(self.dc_pin, 0) epdconfig.digital_write(self.dc_pin, 0)
epdconfig.digital_write(self.cs_pin, 0) epdconfig.digital_write(self.cs_pin, 0)
epdconfig.spi_writebyte([command]) epdconfig.DEV_SPI_write(command)
epdconfig.digital_write(self.cs_pin, 1) epdconfig.digital_write(self.cs_pin, 1)
def send_data(self, data): def send_data(self, data):
epdconfig.digital_write(self.dc_pin, 1) epdconfig.digital_write(self.dc_pin, 1)
epdconfig.digital_write(self.cs_pin, 0) epdconfig.digital_write(self.cs_pin, 0)
epdconfig.spi_writebyte([data]) epdconfig.DEV_SPI_write(data)
epdconfig.digital_write(self.cs_pin, 1) epdconfig.digital_write(self.cs_pin, 1)
# send a lot of data # send a lot of data
@ -76,23 +80,77 @@ class EPD:
def ReadBusy(self): def ReadBusy(self):
logger.debug("e-Paper busy") logger.debug("e-Paper busy")
self.send_command(0x71) if (self.flag == 1):
while (epdconfig.digital_read(self.busy_pin) == 0): # 0: idle, 1: busy while (epdconfig.digital_read(self.busy_pin) == 1):
self.send_command(0x71) epdconfig.delay_ms(100)
epdconfig.delay_ms(20)
else:
while (epdconfig.digital_read(self.busy_pin) == 0):
epdconfig.delay_ms(100)
logger.debug("e-Paper busy release") logger.debug("e-Paper busy release")
def TurnOnDisplay(self):
if (self.flag == 1):
self.send_command(0x22)
self.send_data(0xF7)
self.send_command(0x20)
self.ReadBusy()
else:
self.send_command(0x12)
epdconfig.delay_ms(100)
self.ReadBusy()
def init(self): def init(self):
if (epdconfig.module_init() != 0): i = 0x00
return -1
self.reset() self.reset()
self.send_command(0x2F)
epdconfig.delay_ms(100)
epdconfig.digital_write(self.dc_pin, 1)
epdconfig.digital_write(self.cs_pin, 0)
i = epdconfig.DEV_SPI_read()
epdconfig.digital_write(self.cs_pin, 1)
# print(i)
self.send_command(0x04); if (i == 0x01):
self.ReadBusy(); self.flag = 1
self.ReadBusy()
self.send_command(0x12)
self.ReadBusy()
self.send_command(0x00); self.send_command(0x3C)
self.send_data(0x0f); self.send_data(0x05)
self.send_command(0x18)
self.send_data(0x80)
self.send_command(0x11)
self.send_data(0x03)
self.send_command(0x44)
self.send_data(0x00)
self.send_data(self.width // 8 - 1)
self.send_command(0x45)
self.send_data(0x00)
self.send_data(0x00)
self.send_data((self.height - 1) % 256)
self.send_data((self.height - 1) // 256)
self.send_command(0x4E)
self.send_data(0x00)
self.send_command(0x4F)
self.send_data(0x00)
self.send_data(0x00)
self.ReadBusy()
else:
self.flag = 0
self.send_command(0x04) # POWER_ON
self.ReadBusy()
self.send_command(0x00) # panel setting
self.send_data(0x0f)
return 0 return 0
@ -121,39 +179,78 @@ class EPD:
return buf return buf
def display(self, imageblack, imagered): def display(self, imageblack, imagered):
high = self.height
if (self.width % 8 == 0):
wide = self.width // 8
else:
wide = self.width // 8 + 1
if (self.flag == 1):
self.send_command(0x24)
for j in range(0, high):
for i in range(0, wide):
self.send_data(imageblack[i + j * wide])
self.send_command(0x26)
for j in range(0, high):
for i in range(0, wide):
self.send_data(~imagered[i + j * wide])
else:
self.send_command(0x10) self.send_command(0x10)
self.send_data2(imageblack) for j in range(0, high):
for i in range(0, wide):
self.send_data(imageblack[i + j * wide])
self.send_command(0x13) self.send_command(0x13)
self.send_data2(imagered) for j in range(0, high):
for i in range(0, wide):
self.send_data(imagered[i + j * wide])
self.send_command(0x12) self.TurnOnDisplay()
epdconfig.delay_ms(20)
self.ReadBusy()
def Clear(self): def Clear(self):
if self.width % 8 == 0: high = self.height
linewidth = int(self.width / 8) if (self.width % 8 == 0):
wide = self.width // 8
else: else:
linewidth = int(self.width / 8) + 1 wide = self.width // 8 + 1
if (self.flag == 1):
self.send_command(0x24)
for j in range(0, high):
for i in range(0, wide):
self.send_data(0xff)
self.send_command(0x26)
for j in range(0, high):
for i in range(0, wide):
self.send_data(0x00)
else:
self.send_command(0x10) self.send_command(0x10)
self.send_data2([0xff] * int(self.height * linewidth)) for j in range(0, high):
for i in range(0, wide):
self.send_data(0xff)
self.send_command(0x13) self.send_command(0x13)
self.send_data2([0xff] * int(self.height * linewidth)) for j in range(0, high):
for i in range(0, wide):
self.send_data(0xff)
self.send_command(0x12) self.TurnOnDisplay()
epdconfig.delay_ms(20)
self.ReadBusy()
def sleep(self): def sleep(self):
self.send_command(0X50) if (self.flag == 1):
self.send_data(0xf7) # border floating self.send_command(0X10)
self.send_data(0x03)
self.send_command(0X02) # power off else:
self.ReadBusy() # waiting for the electronic paper IC to release the idle signal self.send_command(0X50)
self.send_command(0X07) # deep sleep self.send_data(0xf7)
self.send_command(0X02)
self.ReadBusy()
self.send_command(0X07)
self.send_data(0xA5) self.send_data(0xA5)
epdconfig.delay_ms(2000) epdconfig.delay_ms(2000)

View File

@ -0,0 +1,294 @@
# Copyright (c) 2014 Adafruit Industries
# Author: Tony DiCola
#
# Permission is hereby granted, free of charge, to any person obtaining a copy
# of this software and associated documentation files (the "Software"), to deal
# in the Software without restriction, including without limitation the rights
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
# copies of the Software, and to permit persons to whom the Software is
# furnished to do so, subject to the following conditions:
#
# The above copyright notice and this permission notice shall be included in
# all copies or substantial portions of the Software.
#
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
# THE SOFTWARE.
from __future__ import division
import logging
import time
import RPi.GPIO as GPIO
import spidev
from smbus import SMBus
i2cbus = SMBus(1)
# Constants
SSD1306_I2C_ADDRESS = 0x3C # 011110+SA0+RW - 0x3C or 0x3D
SSD1306_SETCONTRAST = 0x81
SSD1306_DISPLAYALLON_RESUME = 0xA4
SSD1306_DISPLAYALLON = 0xA5
SSD1306_NORMALDISPLAY = 0xA6
SSD1306_INVERTDISPLAY = 0xA7
SSD1306_DISPLAYOFF = 0xAE
SSD1306_DISPLAYON = 0xAF
SSD1306_SETDISPLAYOFFSET = 0xD3
SSD1306_SETCOMPINS = 0xDA
SSD1306_SETVCOMDETECT = 0xDB
SSD1306_SETDISPLAYCLOCKDIV = 0xD5
SSD1306_SETPRECHARGE = 0xD9
SSD1306_SETMULTIPLEX = 0xA8
SSD1306_SETLOWCOLUMN = 0x00
SSD1306_SETHIGHCOLUMN = 0x10
SSD1306_SETSTARTLINE = 0x40
SSD1306_MEMORYMODE = 0x20
SSD1306_COLUMNADDR = 0x21
SSD1306_PAGEADDR = 0x22
SSD1306_COMSCANINC = 0xC0
SSD1306_COMSCANDEC = 0xC8
SSD1306_SEGREMAP = 0xA0
SSD1306_CHARGEPUMP = 0x8D
SSD1306_EXTERNALVCC = 0x1
SSD1306_SWITCHCAPVCC = 0x2
# Scrolling constants
SSD1306_ACTIVATE_SCROLL = 0x2F
SSD1306_DEACTIVATE_SCROLL = 0x2E
SSD1306_SET_VERTICAL_SCROLL_AREA = 0xA3
SSD1306_RIGHT_HORIZONTAL_SCROLL = 0x26
SSD1306_LEFT_HORIZONTAL_SCROLL = 0x27
SSD1306_VERTICAL_AND_RIGHT_HORIZONTAL_SCROLL = 0x29
SSD1306_VERTICAL_AND_LEFT_HORIZONTAL_SCROLL = 0x2A
class SSD1306Base(object):
"""Base class for SSD1306-based OLED displays. Implementors should subclass
and provide an implementation for the _initialize function.
"""
def __init__(self, width, height, address=SSD1306_I2C_ADDRESS, bus=None):
self._log = logging.getLogger('Adafruit_SSD1306.SSD1306Base')
# self._rst = None
self.cmd_mode = 0x00
self.data_mode = 0x40
self.bus = i2cbus
self.addr = address
self.width = width
self.height = height
self._pages = height//8
self._buffer = [0]*(width*self._pages)
def _initialize(self):
raise NotImplementedError
def command(self, *cmd):
"""Send command byte to display."""
# I2C write.
assert(len(cmd) <= 31)
self.bus.write_i2c_block_data(self.addr, self.cmd_mode, list(cmd))
def data(self, data):
"""Send byte of data to display."""
# I2C write.
for i in range(0, len(data), 31):
self.bus.write_i2c_block_data(self.addr, self.data_mode, list(data[i:i+31]))
def begin(self, vccstate=SSD1306_SWITCHCAPVCC):
"""Initialize display."""
# Save vcc state.
self._vccstate = vccstate
# Reset and initialize display.
self._initialize()
# Turn on the display.
self.command(SSD1306_DISPLAYON)
def ShowImage(self):
"""
The image on the "canvas" is flushed through to the hardware display.
Takes the 1-bit image and dumps it to the SSD1306 OLED display.
"""
self.command(SSD1306_COLUMNADDR)
self.command(0) # Column start address. (0 = reset)
self.command(self.width-1) # Column end address.
self.command(SSD1306_PAGEADDR)
self.command(0) # Page start address. (0 = reset)
self.command(self._pages-1) # Page end address.
for i in range(0, len(self._buffer), 16):
self.bus.write_i2c_block_data(self.addr, self.data_mode, self._buffer[i:i+16])
def getbuffer(self, image):
"""Set buffer to value of Python Imaging Library image. The image should
be in 1 bit mode and a size equal to the display size.
"""
if image.mode != '1':
raise ValueError('Image must be in mode 1.')
imwidth, imheight = image.size
if imwidth != self.width or imheight != self.height:
raise ValueError('Image must be same dimensions as display ({0}x{1}).' \
.format(self.width, self.height))
# Grab all the pixels from the image, faster than getpixel.
pix = image.load()
# Iterate through the memory pages
index = 0
for page in range(self._pages):
# Iterate through all x axis columns.
for x in range(self.width):
# Set the bits for the column of pixels at the current position.
bits = 0
# Don't use range here as it's a bit slow
for bit in [0, 1, 2, 3, 4, 5, 6, 7]:
bits = bits << 1
bits |= 0 if pix[(x, page*8+7-bit)] == 0 else 1
# Update buffer byte and increment to next byte.
self._buffer[index] = bits
index += 1
def clear(self):
"""Clear contents of image buffer."""
self._buffer = [0]*(self.width*self._pages)
def set_contrast(self, contrast):
"""Sets the contrast of the display. Contrast should be a value between
0 and 255."""
if contrast < 0 or contrast > 255:
raise ValueError('Contrast must be a value from 0 to 255 (inclusive).')
self.command(SSD1306_SETCONTRAST)
self.command(contrast)
def dim(self, dim):
"""Adjusts contrast to dim the display if dim is True, otherwise sets the
contrast to normal brightness if dim is False.
"""
# Assume dim display.
contrast = 0
# Adjust contrast based on VCC if not dimming.
if not dim:
if self._vccstate == SSD1306_EXTERNALVCC:
contrast = 0x9F
else:
contrast = 0xCF
self.set_contrast(contrast)
class SSD1306_128_64(SSD1306Base):
def __init__(self, width, height, address=None, bus=None):
# Call base class constructor.
super(SSD1306_128_64, self).__init__(128, 64, address, bus)
def _initialize(self):
# 128x64 pixel specific initialization.
self.command(SSD1306_DISPLAYOFF) # 0xAE
self.command(SSD1306_SETDISPLAYCLOCKDIV) # 0xD5
self.command(0x80) # the suggested ratio 0x80
self.command(SSD1306_SETMULTIPLEX) # 0xA8
self.command(0x3F)
self.command(SSD1306_SETDISPLAYOFFSET) # 0xD3
self.command(0x0) # no offset
self.command(SSD1306_SETSTARTLINE | 0x0) # line #0
self.command(SSD1306_CHARGEPUMP) # 0x8D
if self._vccstate == SSD1306_EXTERNALVCC:
self.command(0x10)
else:
self.command(0x14)
self.command(SSD1306_MEMORYMODE) # 0x20
self.command(0x00) # 0x0 act like ks0108
self.command(SSD1306_SEGREMAP | 0x1)
self.command(SSD1306_COMSCANDEC)
self.command(SSD1306_SETCOMPINS) # 0xDA
self.command(0x12)
self.command(SSD1306_SETCONTRAST) # 0x81
if self._vccstate == SSD1306_EXTERNALVCC:
self.command(0x9F)
else:
self.command(0xCF)
self.command(SSD1306_SETPRECHARGE) # 0xd9
if self._vccstate == SSD1306_EXTERNALVCC:
self.command(0x22)
else:
self.command(0xF1)
self.command(SSD1306_SETVCOMDETECT) # 0xDB
self.command(0x40)
self.command(SSD1306_DISPLAYALLON_RESUME) # 0xA4
self.command(SSD1306_NORMALDISPLAY) # 0xA6
class SSD1306_128_32(SSD1306Base):
def __init__(self, width, height, address=None, bus=None):
# Call base class constructor.
super(SSD1306_128_64, self).__init__(128, 64, address, bus)
def _initialize(self):
# 128x32 pixel specific initialization.
self.command(SSD1306_DISPLAYOFF) # 0xAE
self.command(SSD1306_SETDISPLAYCLOCKDIV) # 0xD5
self.command(0x80) # the suggested ratio 0x80
self.command(SSD1306_SETMULTIPLEX) # 0xA8
self.command(0x1F)
self.command(SSD1306_SETDISPLAYOFFSET) # 0xD3
self.command(0x0) # no offset
self.command(SSD1306_SETSTARTLINE | 0x0) # line #0
self.command(SSD1306_CHARGEPUMP) # 0x8D
if self._vccstate == SSD1306_EXTERNALVCC:
self.command(0x10)
else:
self.command(0x14)
self.command(SSD1306_MEMORYMODE) # 0x20
self.command(0x00) # 0x0 act like ks0108
self.command(SSD1306_SEGREMAP | 0x1)
self.command(SSD1306_COMSCANDEC)
self.command(SSD1306_SETCOMPINS) # 0xDA
self.command(0x02)
self.command(SSD1306_SETCONTRAST) # 0x81
self.command(0x8F)
self.command(SSD1306_SETPRECHARGE) # 0xd9
if self._vccstate == SSD1306_EXTERNALVCC:
self.command(0x22)
else:
self.command(0xF1)
self.command(SSD1306_SETVCOMDETECT) # 0xDB
self.command(0x40)
self.command(SSD1306_DISPLAYALLON_RESUME) # 0xA4
self.command(SSD1306_NORMALDISPLAY) # 0xA6
class SSD1306_96_16(SSD1306Base):
def __init__(self, width, height, address=None, bus=None):
# Call base class constructor.
super(SSD1306_96_16, self).__init__(96, 16, address, bus)
def _initialize(self):
# 128x32 pixel specific initialization.
self.command(SSD1306_DISPLAYOFF) # 0xAE
self.command(SSD1306_SETDISPLAYCLOCKDIV) # 0xD5
self.command(0x60) # the suggested ratio 0x60
self.command(SSD1306_SETMULTIPLEX) # 0xA8
self.command(0x0F)
self.command(SSD1306_SETDISPLAYOFFSET) # 0xD3
self.command(0x0) # no offset
self.command(SSD1306_SETSTARTLINE | 0x0) # line #0
self.command(SSD1306_CHARGEPUMP) # 0x8D
if self._vccstate == SSD1306_EXTERNALVCC:
self.command(0x10)
else:
self.command(0x14)
self.command(SSD1306_MEMORYMODE) # 0x20
self.command(0x00) # 0x0 act like ks0108
self.command(SSD1306_SEGREMAP | 0x1)
self.command(SSD1306_COMSCANDEC)
self.command(SSD1306_SETCOMPINS) # 0xDA
self.command(0x02)
self.command(SSD1306_SETCONTRAST) # 0x81
self.command(0x8F)
self.command(SSD1306_SETPRECHARGE) # 0xd9
if self._vccstate == SSD1306_EXTERNALVCC:
self.command(0x22)
else:
self.command(0xF1)
self.command(SSD1306_SETVCOMDETECT) # 0xDB
self.command(0x40)
self.command(SSD1306_DISPLAYALLON_RESUME) # 0xA4
self.command(SSD1306_NORMALDISPLAY) # 0xA6

View File

@ -0,0 +1,352 @@
# Copyright (c) 2014 Adafruit Industries
# Author: Tony DiCola
#
# Permission is hereby granted, free of charge, to any person obtaining a copy
# of this software and associated documentation files (the "Software"), to deal
# in the Software without restriction, including without limitation the rights
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
# copies of the Software, and to permit persons to whom the Software is
# furnished to do so, subject to the following conditions:
#
# The above copyright notice and this permission notice shall be included in
# all copies or substantial portions of the Software.
#
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
# THE SOFTWARE.
import numbers
import time
import numpy as np
import spidev
import RPi.GPIO as GPIO
__version__ = '0.0.4'
BG_SPI_CS_BACK = 0
BG_SPI_CS_FRONT = 1
SPI_CLOCK_HZ = 16000000
ST7789_NOP = 0x00
ST7789_SWRESET = 0x01
ST7789_RDDID = 0x04
ST7789_RDDST = 0x09
ST7789_SLPIN = 0x10
ST7789_SLPOUT = 0x11
ST7789_PTLON = 0x12
ST7789_NORON = 0x13
ST7789_INVOFF = 0x20
ST7789_INVON = 0x21
ST7789_DISPOFF = 0x28
ST7789_DISPON = 0x29
ST7789_CASET = 0x2A
ST7789_RASET = 0x2B
ST7789_RAMWR = 0x2C
ST7789_RAMRD = 0x2E
ST7789_PTLAR = 0x30
ST7789_MADCTL = 0x36
ST7789_COLMOD = 0x3A
ST7789_FRMCTR1 = 0xB1
ST7789_FRMCTR2 = 0xB2
ST7789_FRMCTR3 = 0xB3
ST7789_INVCTR = 0xB4
ST7789_DISSET5 = 0xB6
ST7789_GCTRL = 0xB7
ST7789_GTADJ = 0xB8
ST7789_VCOMS = 0xBB
ST7789_LCMCTRL = 0xC0
ST7789_IDSET = 0xC1
ST7789_VDVVRHEN = 0xC2
ST7789_VRHS = 0xC3
ST7789_VDVS = 0xC4
ST7789_VMCTR1 = 0xC5
ST7789_FRCTRL2 = 0xC6
ST7789_CABCCTRL = 0xC7
ST7789_RDID1 = 0xDA
ST7789_RDID2 = 0xDB
ST7789_RDID3 = 0xDC
ST7789_RDID4 = 0xDD
ST7789_GMCTRP1 = 0xE0
ST7789_GMCTRN1 = 0xE1
ST7789_PWCTR6 = 0xFC
class ST7789(object):
"""Representation of an ST7789 TFT LCD."""
def __init__(self, port, cs, dc, backlight, rst=None, width=320,
height=240, rotation=270, invert=True, spi_speed_hz=60 * 1000 * 1000,
offset_left=0,
offset_top=0):
"""Create an instance of the display using SPI communication.
Must provide the GPIO pin number for the D/C pin and the SPI driver.
Can optionally provide the GPIO pin number for the reset pin as the rst parameter.
:param port: SPI port number
:param cs: SPI chip-select number (0 or 1 for BCM
:param backlight: Pin for controlling backlight
:param rst: Reset pin for ST7789
:param width: Width of display connected to ST7789
:param height: Height of display connected to ST7789
:param rotation: Rotation of display connected to ST7789
:param invert: Invert display
:param spi_speed_hz: SPI speed (in Hz)
"""
if rotation not in [0, 90, 180, 270]:
raise ValueError("Invalid rotation {}".format(rotation))
# if width != height and rotation in [90, 270]:
# raise ValueError("Invalid rotation {} for {}x{} resolution".format(rotation, width, height))
GPIO.setwarnings(False)
GPIO.setmode(GPIO.BCM)
self._spi = spidev.SpiDev(port, cs)
self._spi.mode = 0
self._spi.lsbfirst = False
self._spi.max_speed_hz = spi_speed_hz
self._dc = dc
self._rst = rst
self._width = width
self._height = height
self._rotation = rotation
self._invert = invert
self._offset_left = offset_left
self._offset_top = offset_top
# Set DC as output.
GPIO.setup(dc, GPIO.OUT)
# Setup backlight as output (if provided).
self._backlight = backlight
if backlight is not None:
GPIO.setup(backlight, GPIO.OUT)
GPIO.output(backlight, GPIO.LOW)
time.sleep(0.1)
GPIO.output(backlight, GPIO.HIGH)
# Setup reset as output (if provided).
if rst is not None:
GPIO.setup(self._rst, GPIO.OUT)
self.reset()
self._init()
def send(self, data, is_data=True, chunk_size=4096):
"""Write a byte or array of bytes to the display. Is_data parameter
controls if byte should be interpreted as display data (True) or command
data (False). Chunk_size is an optional size of bytes to write in a
single SPI transaction, with a default of 4096.
"""
# Set DC low for command, high for data.
GPIO.output(self._dc, is_data)
# Convert scalar argument to list so either can be passed as parameter.
if isinstance(data, numbers.Number):
data = [data & 0xFF]
# Write data a chunk at a time.
for start in range(0, len(data), chunk_size):
end = min(start + chunk_size, len(data))
self._spi.xfer(data[start:end])
def set_backlight(self, value):
"""Set the backlight on/off."""
if self._backlight is not None:
GPIO.output(self._backlight, value)
@property
def width(self):
return self._width if self._rotation == 0 or self._rotation == 180 else self._height
@property
def height(self):
return self._height if self._rotation == 0 or self._rotation == 180 else self._width
def command(self, data):
"""Write a byte or array of bytes to the display as command data."""
self.send(data, False)
def data(self, data):
"""Write a byte or array of bytes to the display as display data."""
self.send(data, True)
def reset(self):
"""Reset the display, if reset pin is connected."""
if self._rst is not None:
GPIO.output(self._rst, 1)
time.sleep(0.500)
GPIO.output(self._rst, 0)
time.sleep(0.500)
GPIO.output(self._rst, 1)
time.sleep(0.500)
def _init(self):
# Initialize the display.
self.command(ST7789_SWRESET) # Software reset
time.sleep(0.150) # delay 150 ms
self.command(ST7789_MADCTL)
self.data(0x70)
self.command(ST7789_FRMCTR2) # Frame rate ctrl - idle mode
self.data(0x0C)
self.data(0x0C)
self.data(0x00)
self.data(0x33)
self.data(0x33)
self.command(ST7789_COLMOD)
self.data(0x05)
self.command(ST7789_GCTRL)
self.data(0x14)
self.command(ST7789_VCOMS)
self.data(0x37)
self.command(ST7789_LCMCTRL) # Power control
self.data(0x2C)
self.command(ST7789_VDVVRHEN) # Power control
self.data(0x01)
self.command(ST7789_VRHS) # Power control
self.data(0x12)
self.command(ST7789_VDVS) # Power control
self.data(0x20)
self.command(0xD0)
self.data(0xA4)
self.data(0xA1)
self.command(ST7789_FRCTRL2)
self.data(0x0F)
self.command(ST7789_GMCTRP1) # Set Gamma
self.data(0xD0)
self.data(0x04)
self.data(0x0D)
self.data(0x11)
self.data(0x13)
self.data(0x2B)
self.data(0x3F)
self.data(0x54)
self.data(0x4C)
self.data(0x18)
self.data(0x0D)
self.data(0x0B)
self.data(0x1F)
self.data(0x23)
self.command(ST7789_GMCTRN1) # Set Gamma
self.data(0xD0)
self.data(0x04)
self.data(0x0C)
self.data(0x11)
self.data(0x13)
self.data(0x2C)
self.data(0x3F)
self.data(0x44)
self.data(0x51)
self.data(0x2F)
self.data(0x1F)
self.data(0x1F)
self.data(0x20)
self.data(0x23)
if self._invert:
self.command(ST7789_INVON) # Invert display
else:
self.command(ST7789_INVOFF) # Don't invert display
self.command(ST7789_SLPOUT)
self.command(ST7789_DISPON) # Display on
time.sleep(0.100) # 100 ms
def begin(self):
"""Set up the display
Deprecated. Included in __init__.
"""
pass
def set_window(self, x0=0, y0=0, x1=None, y1=None):
"""Set the pixel address window for proceeding drawing commands. x0 and
x1 should define the minimum and maximum x pixel bounds. y0 and y1
should define the minimum and maximum y pixel bound. If no parameters
are specified the default will be to update the entire display from 0,0
to width-1,height-1.
"""
if x1 is None:
x1 = self._width - 1
if y1 is None:
y1 = self._height - 1
y0 += self._offset_top
y1 += self._offset_top
x0 += self._offset_left
x1 += self._offset_left
self.command(ST7789_CASET) # Column addr set
self.data(x0 >> 8)
self.data(x0 & 0xFF) # XSTART
self.data(x1 >> 8)
self.data(x1 & 0xFF) # XEND
self.command(ST7789_RASET) # Row addr set
self.data(y0 >> 8)
self.data(y0 & 0xFF) # YSTART
self.data(y1 >> 8)
self.data(y1 & 0xFF) # YEND
self.command(ST7789_RAMWR) # write to RAM
def display(self, image):
"""Write the provided image to the hardware.
:param image: Should be RGB format and the same dimensions as the display hardware.
"""
# Set address bounds to entire display.
self.set_window()
# Convert image to 16bit RGB565 format and
# flatten into bytes.
pixelbytes = self.image_to_data(image, self._rotation)
# Write data to hardware.
for i in range(0, len(pixelbytes), 4096):
self.data(pixelbytes[i:i + 4096])
def image_to_data(self, image, rotation=0):
if not isinstance(image, np.ndarray):
image = np.array(image.convert('RGB'))
# Rotate the image
pb = np.rot90(image, rotation // 90).astype('uint16')
# Mask and shift the 888 RGB into 565 RGB
red = (pb[..., [0]] & 0xf8) << 8
green = (pb[..., [1]] & 0xfc) << 3
blue = (pb[..., [2]] & 0xf8) >> 3
# Stick 'em together
result = red | green | blue
# Output the raw bytes
return result.byteswap().tobytes()

View File

@ -0,0 +1,23 @@
from . import SSD1306
# Display resolution
EPD_WIDTH = 128
EPD_HEIGHT = 64
disp = SSD1306.SSD1306_128_64(128, 64, address=0x3C)
class EPD(object):
def __init__(self):
self.width = EPD_WIDTH
self.height = EPD_HEIGHT
def Init(self):
disp.begin()
def Clear(self):
disp.clear()
def display(self, image):
disp.getbuffer(image)
disp.ShowImage()

View File

@ -0,0 +1,59 @@
# workinprogress based on the displayhatmini driver
# LCD support OK
# OLED support ongoing
# board GPIO:
# Key1: GPIO4 / pin7
# Key2: GPIO17 / pin11
# Key3: GPIO23 / pin16
# Key4: GPIO24 / pin18
# OLED SDA: GPIO2 / pin3
# OLED SCL: GPIO3 / pin5
# OLED info:
# driver: SSD1315 (I2C)
# resolution: 128x64
# I2C address: 0x3C 0x3D
# HW datasheet: https://www.waveshare.com/wiki/OLED/LCD_HAT_(A)
import logging
import pwnagotchi.ui.fonts as fonts
from pwnagotchi.ui.hw.base import DisplayImpl
class Waveshareoledlcdvert(DisplayImpl):
def __init__(self, config):
super(Waveshareoledlcdvert, self).__init__(config, 'waveshareoledlcdvert')
def layout(self):
fonts.setup(10, 9, 10, 35, 25, 9)
self._layout['width'] = 240
self._layout['height'] = 320
self._layout['face'] = (0, 40)
self._layout['name'] = (5, 20)
self._layout['channel'] = (0, 0)
self._layout['aps'] = (28, 0)
self._layout['uptime'] = (175, 0)
self._layout['line1'] = [0, 14, 240, 14]
self._layout['line2'] = [0, 108, 240, 108]
self._layout['friend_face'] = (0, 92)
self._layout['friend_name'] = (40, 94)
self._layout['shakes'] = (0, 109)
self._layout['mode'] = (215, 109)
self._layout['status'] = {
'pos': (125, 20),
'font': fonts.status_font(fonts.Medium),
'max': 20
}
return self._layout
def initialize(self):
logging.info("initializing Waveshare OLED/LCD hat vertical mode")
from pwnagotchi.ui.hw.libs.waveshare.oled.oledlcd.ST7789vert import ST7789
self._display = ST7789(0,0,22,18)
def render(self, canvas):
self._display.display(canvas)
def clear(self):
self._display.clear()

View File

@ -32,13 +32,10 @@ class View(object):
logging.debug("INVERT BLACK/WHITES:" + str(config['ui']['invert'])) logging.debug("INVERT BLACK/WHITES:" + str(config['ui']['invert']))
self.invert = 1 self.invert = 1
BLACK = 0x00 BLACK = 0x00
WHITE - 0xFF WHITE = 0xFF
self._black = 0x00 self._black = 0x00
self._white = 0xFF self._white = 0xFF
# setup faces from the configuration in case the user customized them # setup faces from the configuration in case the user customized them
faces.load_from_config(config['ui']['faces']) faces.load_from_config(config['ui']['faces'])
@ -112,7 +109,7 @@ class View(object):
self._state.has_element(key) self._state.has_element(key)
def add_element(self, key, elem): def add_element(self, key, elem):
if self.invert is 1 and elem.color: if self.invert is 1 and hasattr(elem, 'color'):
if elem.color == 0xff: if elem.color == 0xff:
elem.color = 0x00 elem.color = 0x00
elif elem.color == 0x00: elif elem.color == 0x00:

View File

@ -315,9 +315,15 @@ def load_config(args):
elif config['ui']['display']['type'] in ('waveshareoledlcd'): elif config['ui']['display']['type'] in ('waveshareoledlcd'):
config['ui']['display']['type'] = 'waveshareoledlcd' config['ui']['display']['type'] = 'waveshareoledlcd'
elif config['ui']['display']['type'] in ('i2coled'):
config['ui']['display']['type'] = 'i2coled'
elif config['ui']['display']['type'] in ('waveshare35lcd'): elif config['ui']['display']['type'] in ('waveshare35lcd'):
config['ui']['display']['type'] = 'waveshare35lcd' config['ui']['display']['type'] = 'waveshare35lcd'
elif config['ui']['display']['type'] in ('waveshareoledlcdvert'):
config['ui']['display']['type'] = 'waveshareoledlcdvert'
# E-INK DISPLAYS ------------------------------------------------------------------------ # E-INK DISPLAYS ------------------------------------------------------------------------
# Adafruit # Adafruit

View File

@ -8,7 +8,7 @@ dynamic = ["version"]
dependencies = [ dependencies = [
"Pillow", "Pillow",
"PyYAML", "PyYAML",
"RPi.GPIO", "rpi.lgpio",
"dbus-python", "dbus-python",
"file-read-backwards", "file-read-backwards",
"flask", "flask",
@ -29,8 +29,8 @@ dependencies = [
"spidev", "spidev",
"stable_baselines3", "stable_baselines3",
"toml", "toml",
"torch", "torch; platform_machine=='aarch64'",
"torchvision", "torchvision; platform_machine=='aarch64'",
"tweepy", "tweepy",
"websockets", "websockets",
] ]

View File

@ -18,9 +18,7 @@ dbus-python
toml toml
python-dateutil python-dateutil
websockets websockets
torch
torchvision
stable_baselines3 stable_baselines3
RPi.GPIO rpi-lgpio
rpi_hardware_pwm rpi_hardware_pwm
pydrive2 pydrive2