From 9ca2424df1f7dae54d5605d10fccbc7620203b04 Mon Sep 17 00:00:00 2001 From: python273 Date: Tue, 22 Oct 2019 20:53:15 +0300 Subject: [PATCH 001/168] Add non-blocking screen updating Signed-off-by: python273 --- pwnagotchi/ui/display.py | 22 ++++++++++++++++++++-- 1 file changed, 20 insertions(+), 2 deletions(-) diff --git a/pwnagotchi/ui/display.py b/pwnagotchi/ui/display.py index c0579221..c28b4d75 100644 --- a/pwnagotchi/ui/display.py +++ b/pwnagotchi/ui/display.py @@ -1,7 +1,8 @@ import os import logging -import pwnagotchi.plugins as plugins +import threading +import pwnagotchi.plugins as plugins import pwnagotchi.ui.hw as hw import pwnagotchi.ui.web as web from pwnagotchi.ui.view import View @@ -18,6 +19,14 @@ class Display(View): self.init_display() + self._canvas_next_event = threading.Event() + self._canvas_next = None + self._render_thread_instance = threading.Thread( + target=self._render_thread, + daemon=True + ) + self._render_thread_instance.start() + def is_inky(self): return self._implementation.name == 'inky' @@ -59,6 +68,14 @@ class Display(View): img = self._canvas if self._rotation == 0 else self._canvas.rotate(-self._rotation) return img + def _render_thread(self): + """Used for non-blocking screen updating.""" + + while True: + self._canvas_next_event.wait() + self._canvas_next_event.clear() + self._implementation.render(self._canvas_next) + def _on_view_rendered(self, img): web.update_frame(img) try: @@ -70,4 +87,5 @@ class Display(View): if self._enabled: self._canvas = (img if self._rotation == 0 else img.rotate(self._rotation)) if self._implementation is not None: - self._implementation.render(self._canvas) + self._canvas_next = self._canvas + self._canvas_next_event.set() From 4fa7e9f0779bc202d464db8693629cccd1a840a5 Mon Sep 17 00:00:00 2001 From: python273 Date: Tue, 22 Oct 2019 20:59:15 +0300 Subject: [PATCH 002/168] Add slight delay for waveshare v1 ReadBusy Signed-off-by: python273 --- .../ui/hw/libs/waveshare/v1/epd2in13bc.py | 31 ++++++++++--------- 1 file changed, 16 insertions(+), 15 deletions(-) diff --git a/pwnagotchi/ui/hw/libs/waveshare/v1/epd2in13bc.py b/pwnagotchi/ui/hw/libs/waveshare/v1/epd2in13bc.py index a0bff834..f17f0af0 100644 --- a/pwnagotchi/ui/hw/libs/waveshare/v1/epd2in13bc.py +++ b/pwnagotchi/ui/hw/libs/waveshare/v1/epd2in13bc.py @@ -47,11 +47,11 @@ class EPD: # Hardware reset def reset(self): epdconfig.digital_write(self.reset_pin, GPIO.HIGH) - epdconfig.delay_ms(200) + epdconfig.delay_ms(200) epdconfig.digital_write(self.reset_pin, GPIO.LOW) # module reset epdconfig.delay_ms(200) epdconfig.digital_write(self.reset_pin, GPIO.HIGH) - epdconfig.delay_ms(200) + epdconfig.delay_ms(200) def send_command(self, command): epdconfig.digital_write(self.dc_pin, GPIO.LOW) @@ -64,8 +64,9 @@ class EPD: epdconfig.digital_write(self.cs_pin, GPIO.LOW) epdconfig.spi_writebyte([data]) epdconfig.digital_write(self.cs_pin, GPIO.HIGH) - + def ReadBusy(self): + epdconfig.delay_ms(20) while(epdconfig.digital_read(self.busy_pin) == 0): # 0: idle, 1: busy epdconfig.delay_ms(100) @@ -79,16 +80,16 @@ class EPD: self.send_data(0x17) self.send_data(0x17) self.send_data(0x17) - + self.send_command(0x04) # POWER_ON self.ReadBusy() - + self.send_command(0x00) # PANEL_SETTING self.send_data(0x8F) - + self.send_command(0x50) # VCOM_AND_DATA_INTERVAL_SETTING self.send_data(0xF0) - + self.send_command(0x61) # RESOLUTION_SETTING self.send_data(self.width & 0xff) self.send_data(self.height >> 8) @@ -120,7 +121,7 @@ class EPD: for i in range(0, int(self.width * self.height / 8)): self.send_data(imageblack[i]) self.send_command(0x92) - + self.send_command(0x12) # REFRESH self.ReadBusy() @@ -129,26 +130,26 @@ class EPD: for i in range(0, int(self.width * self.height / 8)): self.send_data(imageblack[i]) self.send_command(0x92) - + self.send_command(0x13) for i in range(0, int(self.width * self.height / 8)): self.send_data(imagecolor[i]) self.send_command(0x92) - + self.send_command(0x12) # REFRESH self.ReadBusy() - + def Clear(self): self.send_command(0x10) for i in range(0, int(self.width * self.height / 8)): self.send_data(0xFF) - self.send_command(0x92) - + self.send_command(0x92) + self.send_command(0x13) for i in range(0, int(self.width * self.height / 8)): self.send_data(0xFF) self.send_command(0x92) - + self.send_command(0x12) # REFRESH self.ReadBusy() @@ -157,7 +158,7 @@ class EPD: self.ReadBusy() self.send_command(0x07) # DEEP_SLEEP self.send_data(0xA5) # check code - + # epdconfig.module_exit() ### END OF FILE ### From 23ef17d4c79da51ea43b0dffc09647efc8f7184a Mon Sep 17 00:00:00 2001 From: Simone Margaritelli Date: Wed, 23 Oct 2019 15:14:55 +0200 Subject: [PATCH 003/168] fix: using normal status to signal unread messages in order to avoid BT overlap bug --- pwnagotchi/plugins/default/grid.py | 14 ++------------ pwnagotchi/ui/view.py | 5 +++++ pwnagotchi/voice.py | 4 ++++ 3 files changed, 11 insertions(+), 12 deletions(-) diff --git a/pwnagotchi/plugins/default/grid.py b/pwnagotchi/plugins/default/grid.py index cfb5d8fc..5b32635c 100644 --- a/pwnagotchi/plugins/default/grid.py +++ b/pwnagotchi/plugins/default/grid.py @@ -66,18 +66,8 @@ def is_excluded(what): def on_ui_update(ui): - new_value = ' %d (%d)' % (UNREAD_MESSAGES, TOTAL_MESSAGES) - if not ui.has_element('mailbox') and UNREAD_MESSAGES > 0: - if ui.is_inky(): - pos = (80, 0) - else: - pos = (100, 0) - ui.add_element('mailbox', - LabeledValue(color=BLACK, label='MSG', value=new_value, - position=pos, - label_font=fonts.Bold, - text_font=fonts.Medium)) - ui.set('mailbox', new_value) + if UNREAD_MESSAGES > 0: + ui.on_unread_messages(UNREAD_MESSAGES, TOTAL_MESSAGES) def set_reported(reported, net_id): diff --git a/pwnagotchi/ui/view.py b/pwnagotchi/ui/view.py index a2f2d2cf..dcb31a20 100644 --- a/pwnagotchi/ui/view.py +++ b/pwnagotchi/ui/view.py @@ -300,6 +300,11 @@ class View(object): self.set('status', self._voice.on_handshakes(new_shakes)) self.update() + def on_unread_messages(self, count, total): + self.set('face', faces.EXCITED) + self.set('status', self._voice.on_unread_messages(count, total)) + self.update() + def on_rebooting(self): self.set('face', faces.BROKEN) self.set('status', self._voice.on_rebooting()) diff --git a/pwnagotchi/voice.py b/pwnagotchi/voice.py index 61689cf9..530c815a 100644 --- a/pwnagotchi/voice.py +++ b/pwnagotchi/voice.py @@ -129,6 +129,10 @@ class Voice: s = 's' if new_shakes > 1 else '' return self._('Cool, we got {num} new handshake{plural}!').format(num=new_shakes, plural=s) + def on_unread_messages(self, count, total): + s = 's' if count > 1 else '' + return self._('You have {count} new message{plural}!').format(num=count, plural=s) + def on_rebooting(self): return self._("Ops, something went wrong ... Rebooting ...") From 3ad426916f203c5b96afa08a1ba627cad38d2d33 Mon Sep 17 00:00:00 2001 From: Simone Margaritelli Date: Wed, 23 Oct 2019 15:20:16 +0200 Subject: [PATCH 004/168] small refactoring to facilitate integration of the bond equation --- pwnagotchi/defaults.yml | 1 + pwnagotchi/mesh/peer.py | 50 ++++++++++++++++++++++++++-------------- pwnagotchi/mesh/utils.py | 16 +++++++++---- scripts/backup.sh | 1 + 4 files changed, 46 insertions(+), 22 deletions(-) diff --git a/pwnagotchi/defaults.yml b/pwnagotchi/defaults.yml index 6bd94351..6e919650 100644 --- a/pwnagotchi/defaults.yml +++ b/pwnagotchi/defaults.yml @@ -31,6 +31,7 @@ main: - /root/brain.json - /root/.api-report.json - /root/handshakes/ + - /root/peers/ - /etc/pwnagotchi/ - /var/log/pwnagotchi.log commands: diff --git a/pwnagotchi/mesh/peer.py b/pwnagotchi/mesh/peer.py index 14f00577..a3ddd553 100644 --- a/pwnagotchi/mesh/peer.py +++ b/pwnagotchi/mesh/peer.py @@ -1,17 +1,27 @@ import time import logging +import datetime import pwnagotchi.ui.faces as faces +def parse_rfc3339(dt): + return datetime.datetime.strptime(dt.split('.')[0], "%Y-%m-%dT%H:%M:%S") + + class Peer(object): def __init__(self, obj): - self.first_seen = time.time() - self.last_seen = self.first_seen - self.session_id = obj['session_id'] - self.last_channel = obj['channel'] - self.rssi = obj['rssi'] - self.adv = obj['advertisement'] + now = time.time() + just_met = datetime.now().strftime("%Y-%m-%dT%H:%M:%S") + self.first_met = parse_rfc3339(obj.get('met_at', just_met)) + self.first_seen = parse_rfc3339(obj.get('detected_at', just_met)) + self.prev_seen = parse_rfc3339(obj.get('prev_seen_at', just_met)) + self.last_seen = now # should be seen_at + self.encounters = obj.get('encounters', 0) + self.session_id = obj.get('session_id', '') + self.last_channel = obj.get('channel', 1) + self.rssi = obj.get('rssi', 0) + self.adv = obj.get('advertisement', {}) def update(self, new): if self.name() != new.name(): @@ -24,39 +34,45 @@ class Peer(object): self.rssi = new.rssi self.session_id = new.session_id self.last_seen = time.time() + self.prev_seen = new.prev_seen + self.first_met = new.first_met + self.encounters = new.encounters def inactive_for(self): return time.time() - self.last_seen - def _adv_field(self, name, default='???'): - return self.adv[name] if name in self.adv else default + def first_encounter(self): + return self.encounters == 1 + + def days_since_first_met(self): + return (datetime.datetime.now() - self.first_met).days def face(self): - return self._adv_field('face', default=faces.FRIEND) + return self.adv.get('face', faces.FRIEND) def name(self): - return self._adv_field('name') + return self.adv.get('name') def identity(self): - return self._adv_field('identity') + return self.adv.get('identity') def version(self): - return self._adv_field('version') + return self.adv.get('version') def pwnd_run(self): - return int(self._adv_field('pwnd_run', default=0)) + return int(self.adv.get('pwnd_run', 0)) def pwnd_total(self): - return int(self._adv_field('pwnd_tot', default=0)) + return int(self.adv.get('pwnd_tot', 0)) def uptime(self): - return self._adv_field('uptime', default=0) + return self.adv.get('uptime', 0) def epoch(self): - return self._adv_field('epoch', default=0) + return self.adv.get('epoch', 0) def full_name(self): return '%s@%s' % (self.name(), self.identity()) def is_closer(self, other): - return self.rssi > other.rssi + return self.rssi > other.rssi \ No newline at end of file diff --git a/pwnagotchi/mesh/utils.py b/pwnagotchi/mesh/utils.py index 3849acc5..8591f4bf 100644 --- a/pwnagotchi/mesh/utils.py +++ b/pwnagotchi/mesh/utils.py @@ -53,6 +53,14 @@ class AsyncAdvertiser(object): self._advertisement['face'] = new grid.set_advertisement_data(self._advertisement) + def _on_new_peer(self, peer): + self._view.on_new_peer(peer) + plugins.on('peer_detected', self, peer) + + def _on_lost_peer(self, peer): + self._view.on_lost_peer(peer) + plugins.on('peer_lost', self, peer) + def _adv_poller(self): while True: logging.debug("polling pwngrid-peer for peers ...") @@ -72,24 +80,22 @@ class AsyncAdvertiser(object): to_delete = [] for ident, peer in self._peers.items(): if ident not in new_peers: - self._view.on_lost_peer(peer) - plugins.on('peer_lost', self, peer) to_delete.append(ident) for ident in to_delete: + self._on_lost_peer(peer) del self._peers[ident] for ident, peer in new_peers.items(): # check who's new if ident not in self._peers: self._peers[ident] = peer - self._view.on_new_peer(peer) - plugins.on('peer_detected', self, peer) + self._on_new_peer(peer) # update the rest else: self._peers[ident].update(peer) except Exception as e: - logging.exception("error while polling pwngrid-peer") + logging.warning("error while polling pwngrid-peer: %s" % e) time.sleep(1) diff --git a/scripts/backup.sh b/scripts/backup.sh index f66ca783..71c4f612 100755 --- a/scripts/backup.sh +++ b/scripts/backup.sh @@ -10,6 +10,7 @@ FILES_TO_BACKUP=( /root/brain.json /root/.api-report.json /root/handshakes + /root/peers /etc/pwnagotchi/ /var/log/pwnagotchi.log ) From a78a4b0b3e5cfbdfea481e140e6c02e0bdb1f72d Mon Sep 17 00:00:00 2001 From: Simone Margaritelli Date: Wed, 23 Oct 2019 15:26:53 +0200 Subject: [PATCH 005/168] fix: fixes a race condition in the launcher scripts and enables MANU if eth0 is up (closes #365) --- builder/pwnagotchi.yml | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/builder/pwnagotchi.yml b/builder/pwnagotchi.yml index a9fb322c..b4c9680a 100644 --- a/builder/pwnagotchi.yml +++ b/builder/pwnagotchi.yml @@ -304,7 +304,7 @@ # blink 10 times to signal ready state /usr/bin/bootblink 10 & # start a detached screen session with bettercap - if ifconfig | grep usb0 | grep RUNNING; then + if [[ ifconfig | grep usb0 | grep RUNNING ]] || [[ $(cat /sys/class/net/eth0/carrier) ]]; then # if override file exists, go into auto mode if [ -f /root/.pwnagotchi-auto ]; then rm /root/.pwnagotchi-auto @@ -322,13 +322,10 @@ mode: 0755 content: | #!/usr/bin/env bash - # blink 10 times to signal ready state - /usr/bin/bootblink 10 & /usr/bin/monstart - if ifconfig | grep usb0 | grep RUNNING; then + if [[ ifconfig | grep usb0 | grep RUNNING ]] || [[ $(cat /sys/class/net/eth0/carrier) ]]; then # if override file exists, go into auto mode if [ -f /root/.pwnagotchi-auto ]; then - rm /root/.pwnagotchi-auto /usr/bin/bettercap -no-colors -caplet pwnagotchi-auto -iface mon0 else /usr/bin/bettercap -no-colors -caplet pwnagotchi-manual -iface mon0 From 277906a6738553263db0174d003dd1367137423e Mon Sep 17 00:00:00 2001 From: Simone Margaritelli Date: Wed, 23 Oct 2019 15:37:12 +0200 Subject: [PATCH 006/168] fix: fixed bogus support for waveshare lcd displays (fixes #364) --- .../ui/hw/libs/waveshare/lcdhat/ST7789.py | 85 ++++++++++--------- .../ui/hw/libs/waveshare/lcdhat/config.py | 61 +------------ pwnagotchi/ui/hw/libs/waveshare/lcdhat/epd.py | 21 ++--- 3 files changed, 53 insertions(+), 114 deletions(-) diff --git a/pwnagotchi/ui/hw/libs/waveshare/lcdhat/ST7789.py b/pwnagotchi/ui/hw/libs/waveshare/lcdhat/ST7789.py index 0edd1d12..88409ecc 100644 --- a/pwnagotchi/ui/hw/libs/waveshare/lcdhat/ST7789.py +++ b/pwnagotchi/ui/hw/libs/waveshare/lcdhat/ST7789.py @@ -7,24 +7,25 @@ import numpy as np class ST7789(object): """class for ST7789 240*240 1.3inch OLED displays.""" - def __init__(self,spi,rst = 27,dc = 25,bl = 24): + def __init__(self, spi, rst=27, dc=25, bl=24): self.width = 240 self.height = 240 - #Initialize DC RST pin + # Initialize DC RST pin self._dc = dc self._rst = rst self._bl = bl GPIO.setmode(GPIO.BCM) GPIO.setwarnings(False) - GPIO.setup(self._dc,GPIO.OUT) - GPIO.setup(self._rst,GPIO.OUT) - GPIO.setup(self._bl,GPIO.OUT) + GPIO.setup(self._dc, GPIO.OUT) + GPIO.setup(self._rst, GPIO.OUT) + GPIO.setup(self._bl, GPIO.OUT) GPIO.output(self._bl, GPIO.HIGH) - #Initialize SPI + # Initialize SPI self._spi = spi self._spi.max_speed_hz = 40000000 """ Write register address and data """ + def command(self, cmd): GPIO.output(self._dc, GPIO.LOW) self._spi.writebytes([cmd]) @@ -34,13 +35,13 @@ class ST7789(object): self._spi.writebytes([val]) def Init(self): - """Initialize dispaly""" + """Initialize dispaly""" self.reset() self.command(0x36) - self.data(0x70) #self.data(0x00) + self.data(0x70) # self.data(0x00) - self.command(0x3A) + self.command(0x3A) self.data(0x05) self.command(0xB2) @@ -51,7 +52,7 @@ class ST7789(object): self.data(0x33) self.command(0xB7) - self.data(0x35) + self.data(0x35) self.command(0xBB) self.data(0x19) @@ -63,13 +64,13 @@ class ST7789(object): self.data(0x01) self.command(0xC3) - self.data(0x12) + self.data(0x12) self.command(0xC4) self.data(0x20) self.command(0xC6) - self.data(0x0F) + self.data(0x0F) self.command(0xD0) self.data(0xA4) @@ -106,7 +107,7 @@ class ST7789(object): self.data(0x1F) self.data(0x20) self.data(0x23) - + self.command(0x21) self.command(0x11) @@ -115,51 +116,51 @@ class ST7789(object): def reset(self): """Reset the display""" - GPIO.output(self._rst,GPIO.HIGH) + GPIO.output(self._rst, GPIO.HIGH) time.sleep(0.01) - GPIO.output(self._rst,GPIO.LOW) + GPIO.output(self._rst, GPIO.LOW) time.sleep(0.01) - GPIO.output(self._rst,GPIO.HIGH) + GPIO.output(self._rst, GPIO.HIGH) time.sleep(0.01) - + def SetWindows(self, Xstart, Ystart, Xend, Yend): - #set the X coordinates + # set the X coordinates self.command(0x2A) - self.data(0x00) #Set the horizontal starting point to the high octet - self.data(Xstart & 0xff) #Set the horizontal starting point to the low octet - self.data(0x00) #Set the horizontal end to the high octet - self.data((Xend - 1) & 0xff) #Set the horizontal end to the low octet - - #set the Y coordinates + self.data(0x00) # Set the horizontal starting point to the high octet + self.data(Xstart & 0xff) # Set the horizontal starting point to the low octet + self.data(0x00) # Set the horizontal end to the high octet + self.data((Xend - 1) & 0xff) # Set the horizontal end to the low octet + + # set the Y coordinates self.command(0x2B) self.data(0x00) self.data((Ystart & 0xff)) self.data(0x00) - self.data((Yend - 1) & 0xff ) + self.data((Yend - 1) & 0xff) - self.command(0x2C) - - def ShowImage(self,Image,Xstart,Ystart): + self.command(0x2C) + + def ShowImage(self, Image, Xstart, Ystart): """Set buffer to value of Python Imaging Library image.""" """Write display buffer to physical display""" 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)) + ({0}x{1}).'.format(self.width, self.height)) img = np.asarray(Image) - pix = np.zeros((self.width,self.height,2), dtype = np.uint8) - pix[...,[0]] = np.add(np.bitwise_and(img[...,[0]],0xF8),np.right_shift(img[...,[1]],5)) - pix[...,[1]] = np.add(np.bitwise_and(np.left_shift(img[...,[1]],3),0xE0),np.right_shift(img[...,[2]],3)) + pix = np.zeros((self.width, self.height, 2), dtype=np.uint8) + pix[..., [0]] = np.add(np.bitwise_and(img[..., [0]], 0xF8), np.right_shift(img[..., [1]], 5)) + pix[..., [1]] = np.add(np.bitwise_and(np.left_shift(img[..., [1]], 3), 0xE0), np.right_shift(img[..., [2]], 3)) pix = pix.flatten().tolist() - self.SetWindows ( 0, 0, self.width, self.height) - GPIO.output(self._dc,GPIO.HIGH) - for i in range(0,len(pix),4096): - self._spi.writebytes(pix[i:i+4096]) - + self.SetWindows(0, 0, self.width, self.height) + GPIO.output(self._dc, GPIO.HIGH) + for i in range(0, len(pix), 4096): + self._spi.writebytes(pix[i:i + 4096]) + def clear(self): """Clear contents of image buffer""" - _buffer = [0xff]*(self.width * self.height * 2) - self.SetWindows ( 0, 0, self.width, self.height) - GPIO.output(self._dc,GPIO.HIGH) - for i in range(0,len(_buffer),4096): - self._spi.writebytes(_buffer[i:i+4096]) + _buffer = [0xff] * (self.width * self.height * 2) + self.SetWindows(0, 0, self.width, self.height) + GPIO.output(self._dc, GPIO.HIGH) + for i in range(0, len(_buffer), 4096): + self._spi.writebytes(_buffer[i:i + 4096]) diff --git a/pwnagotchi/ui/hw/libs/waveshare/lcdhat/config.py b/pwnagotchi/ui/hw/libs/waveshare/lcdhat/config.py index a319364f..24b5cc88 100644 --- a/pwnagotchi/ui/hw/libs/waveshare/lcdhat/config.py +++ b/pwnagotchi/ui/hw/libs/waveshare/lcdhat/config.py @@ -7,70 +7,15 @@ # * | Date : 2019-10-18 # * | Info : # ******************************************************************************/ - -import RPi.GPIO as GPIO -import time -from smbus import SMBus import spidev -import ctypes -# import spidev - # Pin definition -RST_PIN = 27 -DC_PIN = 25 -BL_PIN = 24 +RST_PIN = 27 +DC_PIN = 25 +BL_PIN = 24 Device_SPI = 1 Device_I2C = 0 Device = Device_SPI spi = spidev.SpiDev(0, 0) - -def digital_write(pin, value): - GPIO.output(pin, value) - -def digital_read(pin): - return GPIO.input(BUSY_PIN) - -def delay_ms(delaytime): - time.sleep(delaytime / 1000.0) - -def spi_writebyte(data): - # SPI.writebytes(data) - spi.writebytes([data[0]]) - -def i2c_writebyte(reg, value): - bus.write_byte_data(address, reg, value) - - # time.sleep(0.01) -def module_init(): - # print("module_init") - - GPIO.setmode(GPIO.BCM) - GPIO.setwarnings(False) - GPIO.setup(RST_PIN, GPIO.OUT) - GPIO.setup(DC_PIN, GPIO.OUT) - - - # SPI.max_speed_hz = 2000000 - # SPI.mode = 0b00 - # i2c_writebyte(0xff,0xff) - # spi.SYSFS_software_spi_begin() - # spi.SYSFS_software_spi_setDataMode(0); - # spi.SYSFS_software_spi_setClockDivider(1); - #spi.max_speed_hz = 2000000 - #spi.mode = 0b00 - - GPIO.output(BL_PIN, 1) - GPIO.output(DC_PIN, 0) - return 0 - -def module_exit(): - spi.SYSFS_software_spi_end() - GPIO.output(RST_PIN, 0) - GPIO.output(DC_PIN, 0) - - - -### END OF FILE ### diff --git a/pwnagotchi/ui/hw/libs/waveshare/lcdhat/epd.py b/pwnagotchi/ui/hw/libs/waveshare/lcdhat/epd.py index cd0d81f4..1a559f46 100644 --- a/pwnagotchi/ui/hw/libs/waveshare/lcdhat/epd.py +++ b/pwnagotchi/ui/hw/libs/waveshare/lcdhat/epd.py @@ -1,28 +1,21 @@ from . import ST7789 from . import config -# Display resolution -EPD_WIDTH = 240 -EPD_HEIGHT = 240 - -disp = ST7789.ST7789(config.spi,config.RST_PIN, config.DC_PIN, config.BL_PIN) class EPD(object): - def __init__(self): self.reset_pin = config.RST_PIN self.dc_pin = config.DC_PIN - #self.busy_pin = config.BUSY_PIN - #self.cs_pin = config.CS_PIN - self.width = EPD_WIDTH - self.height = EPD_HEIGHT + self.width = 240 + self.height = 240 + self.st7789 = ST7789.ST7789(config.spi, config.RST_PIN, config.DC_PIN, config.BL_PIN) def init(self): - disp.Init() + self.st7789.Init() - def Clear(self): - disp.clear() + def clear(self): + self.st7789.clear() def display(self, image): rgb_im = image.convert('RGB') - disp.ShowImage(rgb_im,0,0) + self.st7789.ShowImage(rgb_im, 0, 0) From 062de3b61888fc0d8f055059dea1899ed15c67d3 Mon Sep 17 00:00:00 2001 From: Zenzen San <56201767+zenzen666@users.noreply.github.com> Date: Wed, 23 Oct 2019 15:59:11 +0000 Subject: [PATCH 007/168] minor logging correction `on loaded` the plugin was showing a different name: `cleancap plugin loaded` I've changed it with the name of the plugin to be consistent and easy to find what going on from pwnagotchi.log --- pwnagotchi/plugins/default/AircrackOnly.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pwnagotchi/plugins/default/AircrackOnly.py b/pwnagotchi/plugins/default/AircrackOnly.py index b1baa999..a8402097 100644 --- a/pwnagotchi/plugins/default/AircrackOnly.py +++ b/pwnagotchi/plugins/default/AircrackOnly.py @@ -18,7 +18,7 @@ import os OPTIONS = dict() def on_loaded(): - logging.info("cleancap plugin loaded") + logging.info("aircrackonly plugin loaded") def on_handshake(agent, filename, access_point, client_station): display = agent._view From 36c3ea5bbc12c6b7e58cd36bc04fbbc5e6bbd994 Mon Sep 17 00:00:00 2001 From: Simone Margaritelli Date: Wed, 23 Oct 2019 18:19:43 +0200 Subject: [PATCH 008/168] new: new grateful status that can override sad/bored/lonely if units with a strong bond are nearby --- pwnagotchi/agent.py | 100 +++------------------------------ pwnagotchi/ai/train.py | 2 - pwnagotchi/automata.py | 118 +++++++++++++++++++++++++++++++++++++++ pwnagotchi/defaults.yml | 4 ++ pwnagotchi/mesh/utils.py | 3 + pwnagotchi/ui/faces.py | 1 + pwnagotchi/ui/view.py | 5 ++ pwnagotchi/voice.py | 5 ++ 8 files changed, 144 insertions(+), 94 deletions(-) create mode 100644 pwnagotchi/automata.py diff --git a/pwnagotchi/agent.py b/pwnagotchi/agent.py index 16fa722a..b3dd8cf4 100644 --- a/pwnagotchi/agent.py +++ b/pwnagotchi/agent.py @@ -8,6 +8,7 @@ import _thread import pwnagotchi import pwnagotchi.utils as utils import pwnagotchi.plugins as plugins +from pwnagotchi.automata import Automata from pwnagotchi.log import LastSession from pwnagotchi.bettercap import Client from pwnagotchi.mesh.utils import AsyncAdvertiser @@ -16,13 +17,14 @@ from pwnagotchi.ai.train import AsyncTrainer RECOVERY_DATA_FILE = '/root/.pwnagotchi-recovery' -class Agent(Client, AsyncAdvertiser, AsyncTrainer): +class Agent(Client, Automata, AsyncAdvertiser, AsyncTrainer): def __init__(self, view, config, keypair): Client.__init__(self, config['bettercap']['hostname'], config['bettercap']['scheme'], config['bettercap']['port'], config['bettercap']['username'], config['bettercap']['password']) + Automata.__init__(self, config, view) AsyncAdvertiser.__init__(self, config, view, keypair) AsyncTrainer.__init__(self, config) @@ -49,36 +51,6 @@ class Agent(Client, AsyncAdvertiser, AsyncTrainer): def supported_channels(self): return self._supported_channels - def set_starting(self): - self._view.on_starting() - - def set_ready(self): - plugins.on('ready', self) - - def set_free_channel(self, channel): - self._view.on_free_channel(channel) - plugins.on('free_channel', self, channel) - - def set_bored(self): - self._view.on_bored() - plugins.on('bored', self) - - def set_sad(self): - self._view.on_sad() - plugins.on('sad', self) - - def set_excited(self): - self._view.on_excited() - plugins.on('excited', self) - - def set_lonely(self): - self._view.on_lonely() - plugins.on('lonely', self) - - def set_rebooting(self): - self._view.on_rebooting() - plugins.on('rebooting', self) - def setup_events(self): logging.info("connecting to %s ..." % self.url) @@ -155,11 +127,6 @@ class Agent(Client, AsyncAdvertiser, AsyncTrainer): self.next_epoch() self.set_ready() - def wait_for(self, t, sleeping=True): - plugins.on('sleep' if sleeping else 'wait', self, t) - self._view.wait(t, sleeping) - self._epoch.track(sleep=True, inc=t) - def recon(self): recon_time = self._config['personality']['recon_time'] max_inactive = self._config['personality']['max_inactive_scale'] @@ -277,6 +244,11 @@ class Agent(Client, AsyncAdvertiser, AsyncTrainer): def _update_peers(self): self._view.set_closest_peer(self._closest_peer, len(self._peers)) + def _reboot(self): + self.set_rebooting() + self._save_recovery_data() + pwnagotchi.reboot() + def _save_recovery_data(self): logging.warning("writing recovery data to %s ..." % RECOVERY_DATA_FILE) with open(RECOVERY_DATA_FILE, 'w') as fp: @@ -391,21 +363,6 @@ class Agent(Client, AsyncAdvertiser, AsyncTrainer): return self._history[who] < self._config['personality']['max_interactions'] - def _on_miss(self, who): - logging.info("it looks like %s is not in range anymore :/" % who) - self._epoch.track(miss=True) - self._view.on_miss(who) - - def _on_error(self, who, e): - error = "%s" % e - # when we're trying to associate or deauth something that is not in range anymore - # (if we are moving), we get the following error from bettercap: - # error 400: 50:c7:bf:2e:d3:37 is an unknown BSSID or it is in the association skip list. - if 'is an unknown BSSID' in error: - self._on_miss(who) - else: - logging.error("%s" % e) - def associate(self, ap, throttle=0): if self.is_stale(): logging.debug("recon is stale, skipping assoc(%s)" % ap['mac']) @@ -482,44 +439,3 @@ class Agent(Client, AsyncAdvertiser, AsyncTrainer): except Exception as e: logging.error("error: %s" % e) - - def is_stale(self): - return self._epoch.num_missed > self._config['personality']['max_misses_for_recon'] - - def any_activity(self): - return self._epoch.any_activity - - def _reboot(self): - self.set_rebooting() - self._save_recovery_data() - pwnagotchi.reboot() - - def next_epoch(self): - was_stale = self.is_stale() - did_miss = self._epoch.num_missed - - self._epoch.next() - - # after X misses during an epoch, set the status to lonely - if was_stale: - logging.warning("agent missed %d interactions -> lonely" % did_miss) - self.set_lonely() - # after X times being bored, the status is set to sad - elif self._epoch.inactive_for >= self._config['personality']['sad_num_epochs']: - logging.warning("%d epochs with no activity -> sad" % self._epoch.inactive_for) - self.set_sad() - # after X times being inactive, the status is set to bored - elif self._epoch.inactive_for >= self._config['personality']['bored_num_epochs']: - logging.warning("%d epochs with no activity -> bored" % self._epoch.inactive_for) - self.set_bored() - # after X times being active, the status is set to happy / excited - elif self._epoch.active_for >= self._config['personality']['excited_num_epochs']: - logging.warning("%d epochs with activity -> excited" % self._epoch.active_for) - self.set_excited() - - plugins.on('epoch', self, self._epoch.epoch - 1, self._epoch.data()) - - if self._epoch.blind_for >= self._config['main']['mon_max_blind_epochs']: - logging.critical("%d epochs without visible access points -> rebooting ..." % self._epoch.blind_for) - self._reboot() - self._epoch.blind_for = 0 diff --git a/pwnagotchi/ai/train.py b/pwnagotchi/ai/train.py index 96e6789d..ed644d6d 100644 --- a/pwnagotchi/ai/train.py +++ b/pwnagotchi/ai/train.py @@ -8,7 +8,6 @@ import logging import pwnagotchi.plugins as plugins import pwnagotchi.ai as ai -from pwnagotchi.ai.epoch import Epoch class Stats(object): @@ -88,7 +87,6 @@ class AsyncTrainer(object): def __init__(self, config): self._config = config self._model = None - self._epoch = Epoch(config) self._is_training = False self._training_epochs = 0 self._nn_path = self._config['ai']['path'] diff --git a/pwnagotchi/automata.py b/pwnagotchi/automata.py new file mode 100644 index 00000000..b0ae92fd --- /dev/null +++ b/pwnagotchi/automata.py @@ -0,0 +1,118 @@ +import logging + +import pwnagotchi.plugins as plugins +from pwnagotchi.ai.epoch import Epoch + + +# basic mood system +class Automata(object): + def __init__(self, config, view): + self._config = config + self._view = view + self._epoch = Epoch(config) + + def _on_miss(self, who): + logging.info("it looks like %s is not in range anymore :/" % who) + self._epoch.track(miss=True) + self._view.on_miss(who) + + def _on_error(self, who, e): + error = "%s" % e + # when we're trying to associate or deauth something that is not in range anymore + # (if we are moving), we get the following error from bettercap: + # error 400: 50:c7:bf:2e:d3:37 is an unknown BSSID or it is in the association skip list. + if 'is an unknown BSSID' in error: + self._on_miss(who) + else: + logging.error("%s" % e) + + def set_starting(self): + self._view.on_starting() + + def set_ready(self): + plugins.on('ready', self) + + def _has_support_network_for(self, factor): + bond_factor = self._config['personality']['bond_encounters_factor'] + total_encounters = sum(peer.encounters for _, peer in self._peers.items()) + support_factor = total_encounters / bond_factor + return support_factor >= factor + + # triggered when it's a sad/bad day but you have good friends around ^_^ + def set_grateful(self): + self._view.on_grateful() + plugins.on('grateful', self) + + def set_lonely(self): + if not self._has_support_network_for(1.0): + self._view.on_lonely() + plugins.on('lonely', self) + else: + self.set_grateful() + + def set_bored(self): + factor = self._epoch.inactive_for / self._config['personality']['bored_num_epochs'] + if not self._has_support_network_for(factor): + logging.warning("%d epochs with no activity -> bored" % self._epoch.inactive_for) + self._view.on_bored() + plugins.on('bored', self) + else: + self.set_grateful() + + def set_sad(self): + factor = self._epoch.inactive_for / self._config['personality']['sad_num_epochs'] + if not self._has_support_network_for(factor): + logging.warning("%d epochs with no activity -> sad" % self._epoch.inactive_for) + self._view.on_sad() + plugins.on('sad', self) + else: + self.set_grateful() + + def set_excited(self): + logging.warning("%d epochs with activity -> excited" % self._epoch.active_for) + self._view.on_excited() + plugins.on('excited', self) + + def set_rebooting(self): + self._view.on_rebooting() + plugins.on('rebooting', self) + + def wait_for(self, t, sleeping=True): + plugins.on('sleep' if sleeping else 'wait', self, t) + self._view.wait(t, sleeping) + self._epoch.track(sleep=True, inc=t) + + def is_stale(self): + return self._epoch.num_missed > self._config['personality']['max_misses_for_recon'] + + def any_activity(self): + return self._epoch.any_activity + + def next_epoch(self): + was_stale = self.is_stale() + did_miss = self._epoch.num_missed + + self._epoch.next() + + # after X misses during an epoch, set the status to lonely + if was_stale: + logging.warning("agent missed %d interactions -> lonely" % did_miss) + self.set_lonely() + # after X times being bored, the status is set to sad + elif self._epoch.inactive_for >= self._config['personality']['sad_num_epochs']: + self.set_sad() + # after X times being inactive, the status is set to bored + elif self._epoch.inactive_for >= self._config['personality']['bored_num_epochs']: + self.set_bored() + # after X times being active, the status is set to happy / excited + elif self._epoch.active_for >= self._config['personality']['excited_num_epochs']: + self.set_excited() + elif self._has_support_network_for(1.0): + self.set_grateful() + + plugins.on('epoch', self, self._epoch.epoch - 1, self._epoch.data()) + + if self._epoch.blind_for >= self._config['main']['mon_max_blind_epochs']: + logging.critical("%d epochs without visible access points -> rebooting ..." % self._epoch.blind_for) + self._reboot() + self._epoch.blind_for = 0 diff --git a/pwnagotchi/defaults.yml b/pwnagotchi/defaults.yml index 6e919650..a22874cf 100644 --- a/pwnagotchi/defaults.yml +++ b/pwnagotchi/defaults.yml @@ -161,6 +161,9 @@ personality: bored_num_epochs: 15 # number of inactive epochs that triggers the sad state sad_num_epochs: 25 + # number of encounters (times met on a channel) with another unit before considering it a friend and bond + # also used for cumulative bonding score of nearby units + bond_encounters_factor: 5000 # ui configuration ui: @@ -176,6 +179,7 @@ ui: cool: '(⌐■_■)' happy: '(•‿‿•)' excited: '(ᵔ◡◡ᵔ)' + grateful: '(^‿‿^)' motivated: '(☼‿‿☼)' demotivated: '(≖__≖)' smart: '(✜‿‿✜)' diff --git a/pwnagotchi/mesh/utils.py b/pwnagotchi/mesh/utils.py index 8591f4bf..9a67f38f 100644 --- a/pwnagotchi/mesh/utils.py +++ b/pwnagotchi/mesh/utils.py @@ -53,6 +53,9 @@ class AsyncAdvertiser(object): self._advertisement['face'] = new grid.set_advertisement_data(self._advertisement) + def cumulative_encounters(self): + return sum(peer.encounters for _, peer in self._peers.items()) + def _on_new_peer(self, peer): self._view.on_new_peer(peer) plugins.on('peer_detected', self, peer) diff --git a/pwnagotchi/ui/faces.py b/pwnagotchi/ui/faces.py index d4cbd32a..d90b5498 100644 --- a/pwnagotchi/ui/faces.py +++ b/pwnagotchi/ui/faces.py @@ -7,6 +7,7 @@ BORED = '(-__-)' INTENSE = '(°▃▃°)' COOL = '(⌐■_■)' HAPPY = '(•‿‿•)' +GRATEFUL = '(^‿‿^)' EXCITED = '(ᵔ◡◡ᵔ)' MOTIVATED = '(☼‿‿☼)' DEMOTIVATED = '(≖__≖)' diff --git a/pwnagotchi/ui/view.py b/pwnagotchi/ui/view.py index dcb31a20..3542c8db 100644 --- a/pwnagotchi/ui/view.py +++ b/pwnagotchi/ui/view.py @@ -290,6 +290,11 @@ class View(object): self.set('status', self._voice.on_miss(who)) self.update() + def on_grateful(self): + self.set('face', faces.GRATEFUL) + self.set('status', self._voice.on_grateful()) + self.update() + def on_lonely(self): self.set('face', faces.LONELY) self.set('status', self._voice.on_lonely()) diff --git a/pwnagotchi/voice.py b/pwnagotchi/voice.py index 530c815a..cf98fdad 100644 --- a/pwnagotchi/voice.py +++ b/pwnagotchi/voice.py @@ -85,6 +85,11 @@ class Voice: self._('{name} missed!').format(name=who), self._('Missed!')]) + def on_grateful(self): + return random.choice([ + self._('Good friends are a blessing!'), + self._('I love my friends!')]) + def on_lonely(self): return random.choice([ self._('Nobody wants to play with me ...'), From f734c9cd68d94fb48602dc4267965ff5acbe0645 Mon Sep 17 00:00:00 2001 From: Simone Margaritelli Date: Wed, 23 Oct 2019 18:21:23 +0200 Subject: [PATCH 009/168] new: bumped pwngrid required version to 1.10.0 --- builder/pwnagotchi.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/builder/pwnagotchi.yml b/builder/pwnagotchi.yml index b4c9680a..44df05f7 100644 --- a/builder/pwnagotchi.yml +++ b/builder/pwnagotchi.yml @@ -36,7 +36,7 @@ url: "https://github.com/bettercap/bettercap/releases/download/v2.25/bettercap_linux_armv6l_2.25.zip" ui: "https://github.com/bettercap/ui/releases/download/v1.3.0/ui.zip" pwngrid: - url: "https://github.com/evilsocket/pwngrid/releases/download/v1.9.0/pwngrid_linux_armhf_v1.9.0.zip" + url: "https://github.com/evilsocket/pwngrid/releases/download/v1.9.0/pwngrid_linux_armhf_v1.10.0.zip" apt: hold: - firmware-atheros From f2ceec0f8f54d33d6953068f52ccf13e138f55e0 Mon Sep 17 00:00:00 2001 From: Xavier Stouder Date: Wed, 23 Oct 2019 18:47:47 +0200 Subject: [PATCH 010/168] fix: update french translation Signed-off-by: Xavier Stouder --- pwnagotchi/locale/fr/LC_MESSAGES/voice.po | 27 +++++++++++++++++------ pwnagotchi/locale/voice.pot | 6 ++++- 2 files changed, 25 insertions(+), 8 deletions(-) diff --git a/pwnagotchi/locale/fr/LC_MESSAGES/voice.po b/pwnagotchi/locale/fr/LC_MESSAGES/voice.po index 26f46788..7109a70c 100644 --- a/pwnagotchi/locale/fr/LC_MESSAGES/voice.po +++ b/pwnagotchi/locale/fr/LC_MESSAGES/voice.po @@ -3,12 +3,12 @@ # This file is distributed under the same license as the pwnagotchi package. # FIRST AUTHOR <7271496+quantumsheep@users.noreply.github.com>, 2019. # -#, fuzzy +#, msgid "" msgstr "" "Project-Id-Version: 0.0.1\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2019-10-05 14:10+0200\n" +"POT-Creation-Date: 2019-10-23 18:37+0200\n" "PO-Revision-Date: 2019-10-03 10:34+0200\n" "Last-Translator: quantumsheep <7271496+quantumsheep@users.noreply.github." "com>\n" @@ -19,7 +19,7 @@ msgstr "" "Content-Transfer-Encoding: 8bit\n" msgid "ZzzzZZzzzzZzzz" -msgstr "" +msgstr "ZzzzZZzzzzZzzz" msgid "Hi, I'm Pwnagotchi! Starting ..." msgstr "Bonjour, je suis Pwnagotchi! Démarrage ..." @@ -36,6 +36,9 @@ msgstr "L'IA est prête." msgid "The neural network is ready." msgstr "Le réseau neuronal est prêt." +msgid "Generating keys, do not turn off ..." +msgstr "Génération des clés, ne pas éteindre..." + #, python-brace-format msgid "Hey, channel {channel} is free! Your AP will say thanks." msgstr "Hey, le channel {channel} est libre! Ton point d'accès va te remercier." @@ -77,12 +80,12 @@ msgid "My crime is that of curiosity ..." msgstr "Mon crime, c'est la curiosité ..." #, python-brace-format -msgid "Hello {name}! Nice to meet you. {name}" -msgstr "Bonjour {name}! Ravi de te rencontrer. {name}" +msgid "Hello {name}! Nice to meet you." +msgstr "Bonjour {name}! Ravi de te rencontrer." #, python-brace-format -msgid "Unit {name} is nearby! {name}" -msgstr "L'unité {name} est proche! {name}" +msgid "Unit {name} is nearby!" +msgstr "L'unité {name} est proche!" #, python-brace-format msgid "Uhm ... goodbye {name}" @@ -123,6 +126,12 @@ msgstr "" msgid "ZzzZzzz ({secs}s)" msgstr "" +msgid "Good night." +msgstr "Bonne nuit." + +msgid "Zzz" +msgstr "Zzz" + #, python-brace-format msgid "Waiting for {secs}s ..." msgstr "Attends pendant {secs}s ..." @@ -159,6 +168,10 @@ msgstr "Je kick et je bannis {mac}!" msgid "Cool, we got {num} new handshake{plural}!" msgstr "Cool, on a {num} nouveaux handshake{plural}!" +#, python-brace-format +msgid "You have {count} new message{plural}!" +msgstr "Tu as {num} nouveaux message{plural}!" + msgid "Ops, something went wrong ... Rebooting ..." msgstr "Oups, quelque chose s'est mal passé ... Redémarrage ..." diff --git a/pwnagotchi/locale/voice.pot b/pwnagotchi/locale/voice.pot index 10de71fe..7d20973c 100644 --- a/pwnagotchi/locale/voice.pot +++ b/pwnagotchi/locale/voice.pot @@ -8,7 +8,7 @@ msgid "" msgstr "" "Project-Id-Version: PACKAGE VERSION\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2019-10-21 10:49+0200\n" +"POT-Creation-Date: 2019-10-23 18:37+0200\n" "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" "Last-Translator: FULL NAME \n" "Language-Team: LANGUAGE \n" @@ -167,6 +167,10 @@ msgstr "" msgid "Cool, we got {num} new handshake{plural}!" msgstr "" +#, python-brace-format +msgid "You have {count} new message{plural}!" +msgstr "" + msgid "Ops, something went wrong ... Rebooting ..." msgstr "" From ab871f9d2d444ddd0c7b3e7936781080e6ff043c Mon Sep 17 00:00:00 2001 From: Xavier Stouder Date: Wed, 23 Oct 2019 18:52:40 +0200 Subject: [PATCH 011/168] fix: fix type of ellpsis in french Signed-off-by: Xavier Stouder --- pwnagotchi/locale/fr/LC_MESSAGES/voice.mo | Bin 3922 -> 4512 bytes pwnagotchi/locale/fr/LC_MESSAGES/voice.po | 26 +++++++++++----------- 2 files changed, 13 insertions(+), 13 deletions(-) diff --git a/pwnagotchi/locale/fr/LC_MESSAGES/voice.mo b/pwnagotchi/locale/fr/LC_MESSAGES/voice.mo index 2ff57511228209251c6d8f3e3f1de75b9ff0d139..d447e5253257b4bd7906e3678c22bd65d8383c1f 100644 GIT binary patch delta 1996 zcma*nTZmOv9LMoBI_Wq!FL}#sW{sncqtj8#QX~VF(n3;GCi219p0mc;&N+Jz`!ecG z(_j!3J?SPwh_D{QLl>p#A$ln|^rR8kLk7|neDS3qNPURDzwI`B30lnBpR?E5d#(Tf zzt)*6UFUkIeq7M}mZ5E-FQ;E?GG;HnGm{JLYO^tO@CRIkzhOV#!o}EopD`~y@jo;Z|1icGnawo$e0H$;$+wno%hx)!+jK5lVx`X-1r@+w_yEph-xT0lRH>gpRp1qRXRpj3GFgII@0R^p0Ni+{Y!|Ulf?uW8P~-OhiQ~wVpH>j zG;sy7iCsAiDh2WMP|J8z%bvbxwp$k`*{^d}HHU%qviYssH}7iHgGOEXFjR4|Ems?L zk_wz1^r)GO-X_jQE?~7Ja#1O}*t)Z8H1vbSR{Feh zxp6+WW8R&IYA!1KF3M)K^)J?J&pMK{u?u1w`Zr`_%7sh-&&aw=b{R*>v*xtL0FGl?Gh~@EP&gNB)?-V(^+6@}Y#J}sH0rD|CfcyEBAB+IE{)Vmyh8OQAcx&Co^tl=axSU$ z76=3?rBE1D^hFYLDB}M+yH)7Gncq3HbH4fJ zo3rP8A1<5xtE=&(q78EOa2-u4^*R38Ku7y^j#4f78!p60xEf#JBAnk?8()K+Ja5KT zxEm$jAsoh&sPR6|$5%+aNj0ZQsWbyi@I&mcy9pQYybqhvLs=lgUvL^_q0Mt^iHxJn zKaPvhqQtw5t#}(V_y8r|6U?%{dd`hZY++O~&Z5SR_%ZHBN$>}h%+H|AyMh|;pj6@| zF2#mQz$#V zil5>gl!P1GY6-1HsmPZoJ0C+y+(TLC1WrmO=eQAnMhSQmZ{hE_0Z+Bp0^LV>@fpgz ze{czQQ65Qb5Tz0uk)o=vaRA3~1y189coBQ?k2Lj{%-=8|nGTbMEU+DyVF9x^g|grU zlt4G}BYcW-bpN81cmdm#I73KCHG&j4c}FAPTv8!Oe(#m@mj&d*lAXxQ@^wpNbBb>) zX>x8HZ?a3O{I0FxZY38dn6$pSf+QwY;E$5DL3$~_R7j3hzK!ltI@yWrx}S@qPtJ(uP^<}0cHMyzSsI-6v#Pya{S2t*wJh6b)4?eCX95!c(E&ad5$i`fm;sUh!^ak z%!)=(_ZtlAk@k_q(~$()K4L~c&i&guO~5~n^GIBYSi-U6X_=P>{P diff --git a/pwnagotchi/locale/fr/LC_MESSAGES/voice.po b/pwnagotchi/locale/fr/LC_MESSAGES/voice.po index 7109a70c..b891adf8 100644 --- a/pwnagotchi/locale/fr/LC_MESSAGES/voice.po +++ b/pwnagotchi/locale/fr/LC_MESSAGES/voice.po @@ -22,7 +22,7 @@ msgid "ZzzzZZzzzzZzzz" msgstr "ZzzzZZzzzzZzzz" msgid "Hi, I'm Pwnagotchi! Starting ..." -msgstr "Bonjour, je suis Pwnagotchi! Démarrage ..." +msgstr "Bonjour, je suis Pwnagotchi! Démarrage..." msgid "New day, new hunt, new pwns!" msgstr "Nouveau jour, nouvelle chasse, nouveaux pwns !" @@ -44,7 +44,7 @@ msgid "Hey, channel {channel} is free! Your AP will say thanks." msgstr "Hey, le channel {channel} est libre! Ton point d'accès va te remercier." msgid "I'm bored ..." -msgstr "Je m'ennuie ..." +msgstr "Je m'ennuie..." msgid "Let's go for a walk!" msgstr "Allons faire un tour!" @@ -56,10 +56,10 @@ msgid "Shitty day :/" msgstr "Journée de merde :/" msgid "I'm extremely bored ..." -msgstr "Je m'ennuie énormément ..." +msgstr "Je m'ennuie énormément..." msgid "I'm very sad ..." -msgstr "Je suis très triste ..." +msgstr "Je suis très triste..." msgid "I'm sad" msgstr "Je suis triste" @@ -77,7 +77,7 @@ msgid "I'm having so much fun!" msgstr "Je m'amuse tellement!" msgid "My crime is that of curiosity ..." -msgstr "Mon crime, c'est la curiosité ..." +msgstr "Mon crime, c'est la curiosité..." #, python-brace-format msgid "Hello {name}! Nice to meet you." @@ -89,15 +89,15 @@ msgstr "L'unité {name} est proche!" #, python-brace-format msgid "Uhm ... goodbye {name}" -msgstr "Hum ... au revoir {name}" +msgstr "Hum... au revoir {name}" #, python-brace-format msgid "{name} is gone ..." -msgstr "{name} est parti ..." +msgstr "{name} est part ..." #, python-brace-format msgid "Whoops ... {name} is gone." -msgstr "Oups ... {name} est parti." +msgstr "Oups... {name} est parti." #, python-brace-format msgid "{name} missed!" @@ -107,17 +107,17 @@ msgid "Missed!" msgstr "Raté!" msgid "Nobody wants to play with me ..." -msgstr "Personne ne veut jouer avec moi ..." +msgstr "Personne ne veut jouer avec moi..." msgid "I feel so alone ..." -msgstr "Je me sens si seul ..." +msgstr "Je me sens si seul..." msgid "Where's everybody?!" msgstr "Où est tout le monde?!" #, python-brace-format msgid "Napping for {secs}s ..." -msgstr "Fais la sieste pendant {secs}s ..." +msgstr "Fais la sieste pendant {secs}s..." msgid "Zzzzz" msgstr "" @@ -134,7 +134,7 @@ msgstr "Zzz" #, python-brace-format msgid "Waiting for {secs}s ..." -msgstr "Attends pendant {secs}s ..." +msgstr "Attends pendant {secs}s..." #, python-brace-format msgid "Looking around ({secs}s)" @@ -173,7 +173,7 @@ msgid "You have {count} new message{plural}!" msgstr "Tu as {num} nouveaux message{plural}!" msgid "Ops, something went wrong ... Rebooting ..." -msgstr "Oups, quelque chose s'est mal passé ... Redémarrage ..." +msgstr "Oups, quelque chose s'est mal passé... Redémarrage..." #, python-brace-format msgid "Kicked {num} stations\n" From 202cd1f32e32cb1d1eebdaccc982a579b60f4f5c Mon Sep 17 00:00:00 2001 From: Xavier Stouder Date: Wed, 23 Oct 2019 18:54:12 +0200 Subject: [PATCH 012/168] fix: translate a word Signed-off-by: Xavier Stouder --- pwnagotchi/locale/fr/LC_MESSAGES/voice.mo | Bin 4512 -> 4510 bytes pwnagotchi/locale/fr/LC_MESSAGES/voice.po | 2 +- 2 files changed, 1 insertion(+), 1 deletion(-) diff --git a/pwnagotchi/locale/fr/LC_MESSAGES/voice.mo b/pwnagotchi/locale/fr/LC_MESSAGES/voice.mo index d447e5253257b4bd7906e3678c22bd65d8383c1f..317fdefb735b3a2445b05c0a1ed7c24087a1e9e4 100644 GIT binary patch delta 381 zcmXZYKTCp96vy#HkEVh?J?7sGf_zZq8Jwg*1)Us=z`&tJ8iaxt2O$D|2f8>2A<}LL zg0pM&B3xQpq@}IF@4-7@lo!@2K~EqRwwHjeoZE<%ErWBof5}Cb5i%c!THIGCpIG`W3VI zg_pQReIW1bFRtJjbrW^IiyCl@8fcCg{BSz^gKrudNjLq^MqbsVzVPwnL*{Oj8R-P6Vt*x1>L@8DL?&J45rvBUT%K6|_9e2K`|D`Mjg0~ov(xyKjuVHzWtMVy1&5?9(>Y~M-G->k55=Iw(ymDh%sE_ z2kucHc(%?LCoxL>jk@1J4cJEwG{rD3t;%_@rJ+HNn7|Ne9GF7FN(Nn+Lk;$0EaD|~ f*;vCX>IU9n8~F_B%?FNKkE>d$*UR&Xckes_aH=OK diff --git a/pwnagotchi/locale/fr/LC_MESSAGES/voice.po b/pwnagotchi/locale/fr/LC_MESSAGES/voice.po index b891adf8..6abda55f 100644 --- a/pwnagotchi/locale/fr/LC_MESSAGES/voice.po +++ b/pwnagotchi/locale/fr/LC_MESSAGES/voice.po @@ -41,7 +41,7 @@ msgstr "Génération des clés, ne pas éteindre..." #, python-brace-format msgid "Hey, channel {channel} is free! Your AP will say thanks." -msgstr "Hey, le channel {channel} est libre! Ton point d'accès va te remercier." +msgstr "Hey, le canal {channel} est libre! Ton point d'accès va te remercier." msgid "I'm bored ..." msgstr "Je m'ennuie..." From 1d53dc7c4ed330da0e228478e37b831fdd80dc01 Mon Sep 17 00:00:00 2001 From: Xavier Stouder Date: Wed, 23 Oct 2019 18:58:23 +0200 Subject: [PATCH 013/168] fix: fix space before ! Signed-off-by: Xavier Stouder --- pwnagotchi/locale/fr/LC_MESSAGES/voice.mo | Bin 4510 -> 4525 bytes pwnagotchi/locale/fr/LC_MESSAGES/voice.po | 30 +++++++++++----------- 2 files changed, 15 insertions(+), 15 deletions(-) diff --git a/pwnagotchi/locale/fr/LC_MESSAGES/voice.mo b/pwnagotchi/locale/fr/LC_MESSAGES/voice.mo index 317fdefb735b3a2445b05c0a1ed7c24087a1e9e4..275d056f743de4a049cddd0a73d894ef3df7d4fc 100644 GIT binary patch delta 539 zcmXZWPbhMsV(s8xks_BZwG(lfl*?L) z6H!Xa&4sJ9i{h*}I5_xI^8VUWPoLL*zvtQa`zb|EBj+EEd`u~I=25B*pRfmikVi$m z)%yYTb53CtGuVvVIEqKujc?e9K~1R!jG>N0*or9(;1c?u>_YIBQ$LUe3qZffZlAy6BS&Sr9j>14po( z^EAqU97@45N}(%k!3s)|ca$P-T6AIrotQv6s&TBrNvy?;?R*zMr0I(72D&(JVm)r5 z3@BY3I4zgMEIxYn$z^YxEPJ2*W>_V%=GxqL>1^9>+sI%zoQEBi!JEociFHX$tVV~1jkVYo3@;9Wnrfgw@hIfbp|VN z6Zsip|N7V#E|brWBf0GClxNOY-6uv&V>OX67NcOw11%!Iv>7>M56P^nyWxL7m58Sd tbHzxSMl706b9!Sb8sCzSu3fpH=jEJxO+LB1WT_`CAIrTm;u$UWi9gDuO Date: Wed, 23 Oct 2019 18:58:58 +0200 Subject: [PATCH 014/168] fix: add translation Signed-off-by: Xavier Stouder --- pwnagotchi/locale/fr/LC_MESSAGES/voice.mo | Bin 4525 -> 4564 bytes pwnagotchi/locale/fr/LC_MESSAGES/voice.po | 2 +- 2 files changed, 1 insertion(+), 1 deletion(-) diff --git a/pwnagotchi/locale/fr/LC_MESSAGES/voice.mo b/pwnagotchi/locale/fr/LC_MESSAGES/voice.mo index 275d056f743de4a049cddd0a73d894ef3df7d4fc..2c14a3dac91cb753c477b6e19c5b2be0216e5564 100644 GIT binary patch delta 1128 zcmXxjPe>GD7{~Evo5^){HPbC~-OAb3+S0OG`?pXL5n*O|3p(_VAUYT#WJ02Q=}-oN z3_;StGKc~r#OTl;DM_d<5k;4vE*-3cECtae==j_yeiBMOZ}DtPJ^Bt3V~32OgZuC~He-}>C~yu{@^(}L zM=^!nsES-dReY?H`fI@h9;h_aID;Q?8;(!@I-52rA?2p*NB9WOq@AWfB!&7HMTE*#(#j?l}#SeDK;y zB{wox3?;UOTZyP$t-&8ByMHqyMdUD*E`7El@&%lF>2bt delta 1091 zcmXxiPe@cz6vy#1&9iaJX&n1A`KO#5YckVj@=rt1rZCV&g9I)FkrZ4M3PBr#W)NJ2 zuJYwVh$tdgnUE3vSp+U?(L#q5DuO^FThvYyr0?(Xe9W8Az3;ty?z!jQ<>Xwl@U6m` zHm)pp4fmITSq>ir`Qut~%)+>e2k{3s;3gizWQkclp2xk|kNWNgmg5-K;uI?W3)Ebn zFkx1((%okJdC-6wcmX4L72|l#cN7oMzlR~5MNRM=-{N~bfwy^CfsauMzC?}th==hz zD()ukVSbC1nnig~gFLg-cnn)nANHU!AHf7pVk167CAxxIz#8hif0)2HWm5uK-yAC5 z5bFCpRx!U#(KwECsH!jFKKzVS+18M1**daF`-@6+!|$^UuOC9KJc=4$hdlwa460Ih z*&kJS2Agmm3rA>trO}96$j?$`W_sR&G3>?^4q_!v;$?h_r|>r_aGY|e)J>=aE@Cxy zqbf0ss`R-3{(gk|t0Yf&u!!&Q9OfCVQoce3TtZFo6Saris6^sKQ-T@P1eZ{i=|yef zfbTdq)1SpUETZPwh*Ez|kfuHg*nvl}9~B^vO7IaXq1RZ2MN}exP~*a6p~II%ZDALZ zqg_F5T_0-e27Rxi61|!B8>7g%u`$%)m_P-1h-{&k|I*FHb={1|GczMVjK6)rBm7L-f?o_G%X#8vkOJ% Pm$UF8d@JO3#U}m(b3$RG diff --git a/pwnagotchi/locale/fr/LC_MESSAGES/voice.po b/pwnagotchi/locale/fr/LC_MESSAGES/voice.po index 8fcedfc5..e2624262 100644 --- a/pwnagotchi/locale/fr/LC_MESSAGES/voice.po +++ b/pwnagotchi/locale/fr/LC_MESSAGES/voice.po @@ -150,7 +150,7 @@ msgstr "Association à {what}" #, python-brace-format msgid "Yo {what}!" -msgstr "" +msgstr "Yo {what} !" #, python-brace-format msgid "Just decided that {mac} needs no WiFi!" From ea14cb370ee11c23f8918c166b1bac62985036e8 Mon Sep 17 00:00:00 2001 From: Xavier Stouder Date: Wed, 23 Oct 2019 19:04:28 +0200 Subject: [PATCH 015/168] fix: forgot some exclamation mark fix Signed-off-by: Xavier Stouder --- pwnagotchi/locale/fr/LC_MESSAGES/voice.mo | Bin 4564 -> 4568 bytes pwnagotchi/locale/fr/LC_MESSAGES/voice.po | 8 ++++---- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/pwnagotchi/locale/fr/LC_MESSAGES/voice.mo b/pwnagotchi/locale/fr/LC_MESSAGES/voice.mo index 2c14a3dac91cb753c477b6e19c5b2be0216e5564..d45922a50ff38f34742b0801674fcd2240d156c7 100644 GIT binary patch delta 432 zcmXZYy-Pw-7{~E*74w2G_m;1vh-suCI3?~54Gs1uM6^a*prBmGXlN0q?vjWSI4I)K zWJ-&(LH-$nwflYYPM`CfbDrn?IN#ZIc68>lj+9dOex-6~;V};I66aVzJ)l$sFEET( z7{nV)-~&eS84Y}M&c_&|{=@~Yv5c=YCvg>2*8ji_8#2H{cN~)_K}vXt*C^+==*1eI zV#~3MMd~li;1=bfsJRbZMVWVpDQu$LvrTKCaK?rt`oRD!TCz zC60)KoygkSL8L4h&t`7zqJi0mB2ZFtCu+E)2xYxX6TYAUG-M zNF-w70~m<$DF}#v9Dx6zlkoml#hfl|~Pb zv4Ix0v4CC7;0RMViQZo@OTEMo{KhJdX+Fg-O9lS}w_L~qo$eFNp#+(Mbx!6FV&`p0&#Pq^Sh60I?hJCqaUC{mjr|K=d diff --git a/pwnagotchi/locale/fr/LC_MESSAGES/voice.po b/pwnagotchi/locale/fr/LC_MESSAGES/voice.po index e2624262..ecbc04ca 100644 --- a/pwnagotchi/locale/fr/LC_MESSAGES/voice.po +++ b/pwnagotchi/locale/fr/LC_MESSAGES/voice.po @@ -22,13 +22,13 @@ msgid "ZzzzZZzzzzZzzz" msgstr "ZzzzZZzzzzZzzz" msgid "Hi, I'm Pwnagotchi! Starting ..." -msgstr "Bonjour, je suis Pwnagotchi! Démarrage..." +msgstr "Bonjour, je suis Pwnagotchi ! Démarrage..." msgid "New day, new hunt, new pwns!" msgstr "Nouveau jour, nouvelle chasse, nouveaux pwns !" msgid "Hack the Planet!" -msgstr "Hack la planète!" +msgstr "Hack la planète !" msgid "AI ready." msgstr "L'IA est prête." @@ -47,10 +47,10 @@ msgid "I'm bored ..." msgstr "Je m'ennuie..." msgid "Let's go for a walk!" -msgstr "Allons faire un tour!" +msgstr "Allons faire un tour !" msgid "This is the best day of my life!" -msgstr "C'est le meilleur jour de ma vie!" +msgstr "C'est le meilleur jour de ma vie !" msgid "Shitty day :/" msgstr "Journée de merde :/" From eda8a18788647c9e1bdb467b38bd273bf85e3afc Mon Sep 17 00:00:00 2001 From: Simone Margaritelli Date: Wed, 23 Oct 2019 19:08:43 +0200 Subject: [PATCH 016/168] misc: small fix or general refactoring i did not bother commenting --- pwnagotchi/mesh/peer.py | 2 +- pwnagotchi/mesh/utils.py | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/pwnagotchi/mesh/peer.py b/pwnagotchi/mesh/peer.py index a3ddd553..61a44132 100644 --- a/pwnagotchi/mesh/peer.py +++ b/pwnagotchi/mesh/peer.py @@ -12,7 +12,7 @@ def parse_rfc3339(dt): class Peer(object): def __init__(self, obj): now = time.time() - just_met = datetime.now().strftime("%Y-%m-%dT%H:%M:%S") + just_met = datetime.datetime.now().strftime("%Y-%m-%dT%H:%M:%S") self.first_met = parse_rfc3339(obj.get('met_at', just_met)) self.first_seen = parse_rfc3339(obj.get('detected_at', just_met)) self.prev_seen = parse_rfc3339(obj.get('prev_seen_at', just_met)) diff --git a/pwnagotchi/mesh/utils.py b/pwnagotchi/mesh/utils.py index 9a67f38f..a8f652f3 100644 --- a/pwnagotchi/mesh/utils.py +++ b/pwnagotchi/mesh/utils.py @@ -100,5 +100,6 @@ class AsyncAdvertiser(object): except Exception as e: logging.warning("error while polling pwngrid-peer: %s" % e) + logging.debug(e, exc_info=True) time.sleep(1) From 3ab2088c79a20c83a3178061e05bd9c09cf925d5 Mon Sep 17 00:00:00 2001 From: Simone Margaritelli Date: Wed, 23 Oct 2019 19:09:36 +0200 Subject: [PATCH 017/168] misc: small fix or general refactoring i did not bother commenting --- pwnagotchi/mesh/utils.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pwnagotchi/mesh/utils.py b/pwnagotchi/mesh/utils.py index a8f652f3..bb77eb5d 100644 --- a/pwnagotchi/mesh/utils.py +++ b/pwnagotchi/mesh/utils.py @@ -86,7 +86,7 @@ class AsyncAdvertiser(object): to_delete.append(ident) for ident in to_delete: - self._on_lost_peer(peer) + self._on_lost_peer(self._peers[ident]) del self._peers[ident] for ident, peer in new_peers.items(): From b85e4174dca7d837362bcf710a51984efe0bc079 Mon Sep 17 00:00:00 2001 From: Simone Margaritelli Date: Wed, 23 Oct 2019 19:21:42 +0200 Subject: [PATCH 018/168] misc: small fix or general refactoring i did not bother commenting --- pwnagotchi/mesh/peer.py | 17 +++++++++++++---- 1 file changed, 13 insertions(+), 4 deletions(-) diff --git a/pwnagotchi/mesh/peer.py b/pwnagotchi/mesh/peer.py index 61a44132..c82058ed 100644 --- a/pwnagotchi/mesh/peer.py +++ b/pwnagotchi/mesh/peer.py @@ -13,9 +13,18 @@ class Peer(object): def __init__(self, obj): now = time.time() just_met = datetime.datetime.now().strftime("%Y-%m-%dT%H:%M:%S") - self.first_met = parse_rfc3339(obj.get('met_at', just_met)) - self.first_seen = parse_rfc3339(obj.get('detected_at', just_met)) - self.prev_seen = parse_rfc3339(obj.get('prev_seen_at', just_met)) + + try: + self.first_met = parse_rfc3339(obj.get('met_at', just_met)) + self.first_seen = parse_rfc3339(obj.get('detected_at', just_met)) + self.prev_seen = parse_rfc3339(obj.get('prev_seen_at', just_met)) + except Exception as e: + logging.warning("error while parsing peer timestamps: %s" % e) + logging.debug(e, exc_info=True) + self.first_met = just_met + self.first_seen = just_met + self.prev_seen = just_met + self.last_seen = now # should be seen_at self.encounters = obj.get('encounters', 0) self.session_id = obj.get('session_id', '') @@ -75,4 +84,4 @@ class Peer(object): return '%s@%s' % (self.name(), self.identity()) def is_closer(self, other): - return self.rssi > other.rssi \ No newline at end of file + return self.rssi > other.rssi From d20f6c8a52c598b13c035f26a187c542f23487fa Mon Sep 17 00:00:00 2001 From: Simone Margaritelli Date: Wed, 23 Oct 2019 19:28:05 +0200 Subject: [PATCH 019/168] misc: small fix or general refactoring i did not bother commenting --- pwnagotchi/plugins/default/grid.py | 2 +- pwnagotchi/utils.py | 5 ++++- 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/pwnagotchi/plugins/default/grid.py b/pwnagotchi/plugins/default/grid.py index 5b32635c..d111f9ab 100644 --- a/pwnagotchi/plugins/default/grid.py +++ b/pwnagotchi/plugins/default/grid.py @@ -135,4 +135,4 @@ def on_internet_available(agent): logging.debug("grid: reporting disabled") except Exception as e: - logging.error("grid api: %s" % e) + logging.error("grid api: %s" % e) \ No newline at end of file diff --git a/pwnagotchi/utils.py b/pwnagotchi/utils.py index ab7acfd3..8532ae44 100644 --- a/pwnagotchi/utils.py +++ b/pwnagotchi/utils.py @@ -108,7 +108,10 @@ def setup_logging(args, config): console_handler = logging.StreamHandler() console_handler.setFormatter(formatter) root.addHandler(console_handler) - + # https://stackoverflow.com/questions/24344045/how-can-i-completely-remove-any-logging-from-requests-module-in-python?noredirect=1&lq=1 + requests_log = logging.getLogger("requests") + requests_log.addHandler(logging.NullHandler()) + requests_log.propagate = False def secs_to_hhmmss(secs): mins, secs = divmod(secs, 60) From 68d7686a03d33be255bbe21bb0318a4341e62b9f Mon Sep 17 00:00:00 2001 From: Simone Margaritelli Date: Wed, 23 Oct 2019 19:32:24 +0200 Subject: [PATCH 020/168] misc: small fix or general refactoring i did not bother commenting --- pwnagotchi/agent.py | 3 ++- pwnagotchi/automata.py | 2 ++ pwnagotchi/bettercap.py | 33 +++++++++++++++++---------------- pwnagotchi/mesh/utils.py | 4 ++-- 4 files changed, 23 insertions(+), 19 deletions(-) diff --git a/pwnagotchi/agent.py b/pwnagotchi/agent.py index b3dd8cf4..53aa803c 100644 --- a/pwnagotchi/agent.py +++ b/pwnagotchi/agent.py @@ -284,12 +284,13 @@ class Agent(Client, Automata, AsyncAdvertiser, AsyncTrainer): self.run('events.clear') - logging.debug("event polling started ...") while True: time.sleep(1) new_shakes = 0 + logging.debug("polling events ...") + try: s = self.session() self._update_uptime(s) diff --git a/pwnagotchi/automata.py b/pwnagotchi/automata.py index b0ae92fd..2d35392a 100644 --- a/pwnagotchi/automata.py +++ b/pwnagotchi/automata.py @@ -89,6 +89,8 @@ class Automata(object): return self._epoch.any_activity def next_epoch(self): + logging.debug("agent.next_epoch()") + was_stale = self.is_stale() did_miss = self._epoch.num_missed diff --git a/pwnagotchi/bettercap.py b/pwnagotchi/bettercap.py index d55fc334..05213aee 100644 --- a/pwnagotchi/bettercap.py +++ b/pwnagotchi/bettercap.py @@ -3,6 +3,20 @@ import requests from requests.auth import HTTPBasicAuth +def decode(r, verbose_errors=True): + try: + return r.json() + except Exception as e: + if r.status_code == 200: + logging.error("error while decoding json: error='%s' resp='%s'" % (e, r.text)) + else: + err = "error %d: %s" % (r.status_code, r.text.strip()) + if verbose_errors: + logging.info(err) + raise Exception(err) + return r.text + + class Client(object): def __init__(self, hostname='localhost', scheme='http', port=8081, username='user', password='pass'): self.hostname = hostname @@ -13,27 +27,14 @@ class Client(object): self.url = "%s://%s:%d/api" % (scheme, hostname, port) self.auth = HTTPBasicAuth(username, password) - def _decode(self, r, verbose_errors=True): - try: - return r.json() - except Exception as e: - if r.status_code == 200: - logging.error("error while decoding json: error='%s' resp='%s'" % (e, r.text)) - else: - err = "error %d: %s" % (r.status_code, r.text.strip()) - if verbose_errors: - logging.info(err) - raise Exception(err) - return r.text - def session(self): r = requests.get("%s/session" % self.url, auth=self.auth) - return self._decode(r) + return decode(r) def events(self): r = requests.get("%s/events" % self.url, auth=self.auth) - return self._decode(r) + return decode(r) def run(self, command, verbose_errors=True): r = requests.post("%s/session" % self.url, auth=self.auth, json={'cmd': command}) - return self._decode(r, verbose_errors=verbose_errors) + return decode(r, verbose_errors=verbose_errors) diff --git a/pwnagotchi/mesh/utils.py b/pwnagotchi/mesh/utils.py index bb77eb5d..7b5c92d1 100644 --- a/pwnagotchi/mesh/utils.py +++ b/pwnagotchi/mesh/utils.py @@ -66,9 +66,9 @@ class AsyncAdvertiser(object): def _adv_poller(self): while True: - logging.debug("polling pwngrid-peer for peers ...") - try: + logging.debug("polling pwngrid-peer for peers ...") + grid_peers = grid.peers() new_peers = {} From 885fddfce8a99c4be996b0152c3f7876be721251 Mon Sep 17 00:00:00 2001 From: Simone Margaritelli Date: Wed, 23 Oct 2019 19:35:51 +0200 Subject: [PATCH 021/168] misc: small fix or general refactoring i did not bother commenting --- pwnagotchi/utils.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/pwnagotchi/utils.py b/pwnagotchi/utils.py index 8532ae44..4f3d10da 100644 --- a/pwnagotchi/utils.py +++ b/pwnagotchi/utils.py @@ -108,7 +108,9 @@ def setup_logging(args, config): console_handler = logging.StreamHandler() console_handler.setFormatter(formatter) root.addHandler(console_handler) + # https://stackoverflow.com/questions/24344045/how-can-i-completely-remove-any-logging-from-requests-module-in-python?noredirect=1&lq=1 + logging.getLogger("urllib3").propagate = False requests_log = logging.getLogger("requests") requests_log.addHandler(logging.NullHandler()) requests_log.propagate = False From d16180189e6f3629d40568c5ba9afad7c6a18850 Mon Sep 17 00:00:00 2001 From: Simone Margaritelli Date: Wed, 23 Oct 2019 19:41:14 +0200 Subject: [PATCH 022/168] misc: small fix or general refactoring i did not bother commenting --- pwnagotchi/mesh/peer.py | 12 ++++++------ pwnagotchi/mesh/utils.py | 4 +++- 2 files changed, 9 insertions(+), 7 deletions(-) diff --git a/pwnagotchi/mesh/peer.py b/pwnagotchi/mesh/peer.py index c82058ed..6415752c 100644 --- a/pwnagotchi/mesh/peer.py +++ b/pwnagotchi/mesh/peer.py @@ -53,20 +53,20 @@ class Peer(object): def first_encounter(self): return self.encounters == 1 - def days_since_first_met(self): - return (datetime.datetime.now() - self.first_met).days - def face(self): return self.adv.get('face', faces.FRIEND) def name(self): - return self.adv.get('name') + return self.adv.get('name', '???') def identity(self): - return self.adv.get('identity') + return self.adv.get('identity', '???') + + def full_name(self): + return "%s@%s" % (self.name(), self.identity()) def version(self): - return self.adv.get('version') + return self.adv.get('version', '1.0.0a') def pwnd_run(self): return int(self.adv.get('pwnd_run', 0)) diff --git a/pwnagotchi/mesh/utils.py b/pwnagotchi/mesh/utils.py index 7b5c92d1..a3c9bdad 100644 --- a/pwnagotchi/mesh/utils.py +++ b/pwnagotchi/mesh/utils.py @@ -57,10 +57,12 @@ class AsyncAdvertiser(object): return sum(peer.encounters for _, peer in self._peers.items()) def _on_new_peer(self, peer): + logging.info("new peer %s detected (%d encounters)" % (peer.full_name(), peer.encounters)) self._view.on_new_peer(peer) plugins.on('peer_detected', self, peer) def _on_lost_peer(self, peer): + logging.info("lost peer %s" % peer.full_name()) self._view.on_lost_peer(peer) plugins.on('peer_lost', self, peer) @@ -102,4 +104,4 @@ class AsyncAdvertiser(object): logging.warning("error while polling pwngrid-peer: %s" % e) logging.debug(e, exc_info=True) - time.sleep(1) + time.sleep(3) From 84616827ad0b38442fa69af9163e799dd6a68114 Mon Sep 17 00:00:00 2001 From: Simone Margaritelli Date: Wed, 23 Oct 2019 19:46:26 +0200 Subject: [PATCH 023/168] misc: small fix or general refactoring i did not bother commenting --- pwnagotchi/plugins/__init__.py | 4 +++- pwnagotchi/voice.py | 2 +- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/pwnagotchi/plugins/__init__.py b/pwnagotchi/plugins/__init__.py index 6dd7b648..b2219747 100644 --- a/pwnagotchi/plugins/__init__.py +++ b/pwnagotchi/plugins/__init__.py @@ -21,6 +21,7 @@ def on(event_name, *args, **kwargs): plugin.__dict__[cb_name](*args, **kwargs) except Exception as e: logging.error("error while running %s.%s : %s" % (plugin_name, cb_name, e)) + logging.error(e, exc_info=True) def load_from_file(filename): @@ -47,7 +48,8 @@ def load_from_path(path, enabled=()): def load(config): - enabled = [name for name, options in config['main']['plugins'].items() if 'enabled' in options and options['enabled']] + enabled = [name for name, options in config['main']['plugins'].items() if + 'enabled' in options and options['enabled']] custom_path = config['main']['custom_plugins'] if 'custom_plugins' in config['main'] else None # load default plugins loaded = load_from_path(default_path, enabled=enabled) diff --git a/pwnagotchi/voice.py b/pwnagotchi/voice.py index cf98fdad..b718e4e2 100644 --- a/pwnagotchi/voice.py +++ b/pwnagotchi/voice.py @@ -136,7 +136,7 @@ class Voice: def on_unread_messages(self, count, total): s = 's' if count > 1 else '' - return self._('You have {count} new message{plural}!').format(num=count, plural=s) + return self._('You have {count} new message{plural}!').format(count=count, plural=s) def on_rebooting(self): return self._("Ops, something went wrong ... Rebooting ...") From d636c2b1f2b21ae70d1a12141c7c137525b47e81 Mon Sep 17 00:00:00 2001 From: Simone Margaritelli Date: Wed, 23 Oct 2019 19:53:23 +0200 Subject: [PATCH 024/168] misc: small fix or general refactoring i did not bother commenting --- pwnagotchi/plugins/default/grid.py | 3 ++- pwnagotchi/voice.py | 9 ++++++--- 2 files changed, 8 insertions(+), 4 deletions(-) diff --git a/pwnagotchi/plugins/default/grid.py b/pwnagotchi/plugins/default/grid.py index d111f9ab..4c411b44 100644 --- a/pwnagotchi/plugins/default/grid.py +++ b/pwnagotchi/plugins/default/grid.py @@ -67,6 +67,7 @@ def is_excluded(what): def on_ui_update(ui): if UNREAD_MESSAGES > 0: + logging.debug("[grid] unread:%d total:%d" % (UNREAD_MESSAGES, TOTAL_MESSAGES)) ui.on_unread_messages(UNREAD_MESSAGES, TOTAL_MESSAGES) @@ -135,4 +136,4 @@ def on_internet_available(agent): logging.debug("grid: reporting disabled") except Exception as e: - logging.error("grid api: %s" % e) \ No newline at end of file + logging.error("grid api: %s" % e) diff --git a/pwnagotchi/voice.py b/pwnagotchi/voice.py index b718e4e2..249ac21b 100644 --- a/pwnagotchi/voice.py +++ b/pwnagotchi/voice.py @@ -70,9 +70,12 @@ class Voice: self._('My crime is that of curiosity ...')]) def on_new_peer(self, peer): - return random.choice([ - self._('Hello {name}! Nice to meet you.').format(name=peer.name()), - self._('Unit {name} is nearby!').format(name=peer.name())]) + if peer.first_encounter(): + return random.choice([ + self._('Hello {name}! Nice to meet you.').format(name=peer.name())]) + else: + return random.choice([ + self._('Unit {name} is nearby!').format(name=peer.name())]) def on_lost_peer(self, peer): return random.choice([ From 9a72a8868bd4c282425db553ef7c7510e60c41d2 Mon Sep 17 00:00:00 2001 From: Simone Margaritelli Date: Wed, 23 Oct 2019 20:02:42 +0200 Subject: [PATCH 025/168] misc: small fix or general refactoring i did not bother commenting --- pwnagotchi/automata.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pwnagotchi/automata.py b/pwnagotchi/automata.py index 2d35392a..556af636 100644 --- a/pwnagotchi/automata.py +++ b/pwnagotchi/automata.py @@ -109,7 +109,7 @@ class Automata(object): # after X times being active, the status is set to happy / excited elif self._epoch.active_for >= self._config['personality']['excited_num_epochs']: self.set_excited() - elif self._has_support_network_for(1.0): + elif self._epoch.active_for >= 5 and self._has_support_network_for(1.0): self.set_grateful() plugins.on('epoch', self, self._epoch.epoch - 1, self._epoch.data()) From 04b2ee5b444d56b259c7c12ac45c7d9838d7fbb4 Mon Sep 17 00:00:00 2001 From: dadav <33197631+dadav@users.noreply.github.com> Date: Wed, 23 Oct 2019 20:57:26 +0200 Subject: [PATCH 026/168] fix en and de --- pwnagotchi/locale/de/LC_MESSAGES/voice.po | 19 ++++++++++++++++--- pwnagotchi/locale/voice.pot | 8 +++++++- 2 files changed, 23 insertions(+), 4 deletions(-) diff --git a/pwnagotchi/locale/de/LC_MESSAGES/voice.po b/pwnagotchi/locale/de/LC_MESSAGES/voice.po index 8b96f335..7fdb0c00 100644 --- a/pwnagotchi/locale/de/LC_MESSAGES/voice.po +++ b/pwnagotchi/locale/de/LC_MESSAGES/voice.po @@ -7,7 +7,7 @@ msgid "" msgstr "" "Project-Id-Version: 0.0.1\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2019-10-09 17:42+0200\n" +"POT-Creation-Date: 2019-10-23 20:56+0200\n" "PO-Revision-Date: 2019-09-29 14:00+0200\n" "Last-Translator: dadav <33197631+dadav@users.noreply.github.com>\n" "Language-Team: DE <33197631+dadav@users.noreply.github.com>\n" @@ -34,6 +34,9 @@ msgstr "KI bereit." msgid "The neural network is ready." msgstr "Das neurale Netz ist bereit." +msgid "Generating keys, do not turn off ..." +msgstr "" + #, python-brace-format msgid "Hey, channel {channel} is free! Your AP will say thanks." msgstr "Hey, Channel {channel} ist frei! Dein AP wir des dir danken." @@ -75,11 +78,11 @@ msgid "My crime is that of curiosity ..." msgstr "Mein Verbrechen ist das der Neugier ..." #, python-brace-format -msgid "Hello {name}! Nice to meet you. {name}" +msgid "Hello {name}! Nice to meet you." msgstr "Hallo {name}, nett Dich kennenzulernen." #, python-brace-format -msgid "Unit {name} is nearby! {name}" +msgid "Unit {name} is nearby!" msgstr "Gerät {name} ist in der nähe!!" #, python-brace-format @@ -101,6 +104,12 @@ msgstr "{name} verpasst!" msgid "Missed!" msgstr "Verpasst!" +msgid "Good friends are a blessing!" +msgstr "" + +msgid "I love my friends!" +msgstr "" + msgid "Nobody wants to play with me ..." msgstr "Niemand will mit mir spielen ..." @@ -163,6 +172,10 @@ msgstr "Kicke {mac}!" msgid "Cool, we got {num} new handshake{plural}!" msgstr "Cool, wir haben {num} neue Handshake{plural}!" +#, python-brace-format +msgid "You have {count} new message{plural}!" +msgstr "Cool, wir haben {num} neue Handshake{plural}!" + msgid "Ops, something went wrong ... Rebooting ..." msgstr "Ops, da ist etwas schief gelaufen ...Starte neu ..." diff --git a/pwnagotchi/locale/voice.pot b/pwnagotchi/locale/voice.pot index 7d20973c..6d2a1da1 100644 --- a/pwnagotchi/locale/voice.pot +++ b/pwnagotchi/locale/voice.pot @@ -8,7 +8,7 @@ msgid "" msgstr "" "Project-Id-Version: PACKAGE VERSION\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2019-10-23 18:37+0200\n" +"POT-Creation-Date: 2019-10-23 20:56+0200\n" "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" "Last-Translator: FULL NAME \n" "Language-Team: LANGUAGE \n" @@ -105,6 +105,12 @@ msgstr "" msgid "Missed!" msgstr "" +msgid "Good friends are a blessing!" +msgstr "" + +msgid "I love my friends!" +msgstr "" + msgid "Nobody wants to play with me ..." msgstr "" From f39c154b2318e14408ee89031336cddf01aa9d00 Mon Sep 17 00:00:00 2001 From: dadav <33197631+dadav@users.noreply.github.com> Date: Wed, 23 Oct 2019 20:59:22 +0200 Subject: [PATCH 027/168] fix en and de --- pwnagotchi/locale/de/LC_MESSAGES/voice.mo | Bin 4204 -> 4547 bytes pwnagotchi/locale/de/LC_MESSAGES/voice.po | 6 +++--- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/pwnagotchi/locale/de/LC_MESSAGES/voice.mo b/pwnagotchi/locale/de/LC_MESSAGES/voice.mo index 8944e859db04407d31442b9cb1e369c182ff987f..683dca252c8c344d352b7143eb61c867eda6786e 100644 GIT binary patch delta 1533 zcmYk+U2Ke59LMo9+HPNM>%A6rM!k)fQR=N$Qnd*|yrn@ZZXTV}b{NmYJTquSCY#;3 z5-uiNT!{!*B37?d!UcCth+ev2B^x1O$&zp(`2NOIangT3=Q;D7xBodauk5|%!jtjk ze;G;}F`f7~X3TcHR>nWdi*jSC@g+{ecbLHH3S%Z?E6&EvsQv-g;%U_T=dl5=<4nAZ zdhaQ&Hl|=cQL!|v_{o?FxEsgg0n`8|aV!>b3SNr5g%f#xgq8RlHPL@KgkNwj4)LMJ zdxl!@dsM&LDq|X%-&iUdxD9J?C)VRW)W8{xV-EG+0BQ%<(c&;J#8;?=Hj*xFWG?Ex zHE3}UY5~V1&*Bv3H&>~s!yWtuM^FoSjdSoLl6BKWBgrz&$mUD}wZN9>d3E&Mj@t1? z{2BM+uQ6lN$e+2w`DnfySXfNuJ{9fkf7FCEb>CLrf;5?}I2F54DGqTS{)w~j8m`5M zsFc@{2JO5R8*v9}{50xFe@BgXZY=p%6aqG43(pQ1Xv!4a%v8=CMjGKU$%OX^pT zN@)T$(K=MWE*ytPP!s-vB+*EX4e8|pi;gH7vp-=(H%w|-LXQ{Ak}ax@(gmH%m8Y@i^$mKCUTUe z$;aR_LM2IP=RcMuJhVj(T8tJ>_tG6wv*s&k676gSq0}ab7DDH!vmGtEm`bIJcA&DH zP+GM^l~n|ZEG<~ie80^MqKQy8^^>xISV=^sP&$vL(bIg4M|GuLXF82g>PAa56&;a^ z?#yV>Mb&Lz7&Yj}Mj6vLN0)jLVF}Z*$!49G>Q9=EFf0y~?^@@4m-TXKztg=KZ z(f8tm4=NI6#m`lp&0Rs>_IZ4IqC3d@xs%p+$Lt~cd%f<&;Y>d3Wd^rZzm0WhEosO2 zTS`mu)7^bJ>*d3+yU)wyod0bx`JA&GvM%qZoMj0q>(ajMbiK}x53a4PicQR<-F}ur W^Y<6ygY9*X%ZeXrlEtYFonHal$G1oT delta 1227 zcmY+^Pe@cz6vy#1%^!1|ank-wR%6d5CvBRvOcJzm5oVic5F+}s5UE8qBGMv^cw3`v z(ak`u=9FL6mJ2E97PZB z<9ajSo^zvx-(Vb9Fp1xs4H2^q%+pwheW(ov@fqeZgE78r#vatchERWZ9yjA8YTa8H z#Rr&Re|y9YADidWh6|`4enOqx*49p(!YtFnsDoZYGO%l?-`z$JOQ-|9#uP52*879{ zeUfr+!7N7E--fuMYL-Wmwo^z_b`Ev0F*m>L=98#Qr?3%cu%EWgqB2|LqK!Ua4u7H! z&`zJ!szY2_@HqM^@;ejapyE@<2g*=6V%2F z_!cXueI8RjZfz+}{k6bH9;jqWN4-j-{;(UHa2U1W86-)YKrJ+d>hTVV%-rER2KYE28OHJ+PyxOtcJAkuC- zf?8)7`Pf-5bjs3%643s(oA3y#{NGRp8eKK2S1qP@q?>3Z_7d%chF&U-HAEYsmrp~P zYp6Bbi7cUHQ~_UoP)U`IN~1FCW!y?=C|gxTZBZ-s6P?5!Vs-R#bAV8()wT{o@6GDa zzY@Jy``iOnM%Bov;k2kaRAwu#gf13m!udMy`uJ$!!i{1je5dS3GQocj9@dYPTcckC S Date: Wed, 23 Oct 2019 23:27:25 +0200 Subject: [PATCH 028/168] add escape char --- pwnagotchi/plugins/default/wigle.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pwnagotchi/plugins/default/wigle.py b/pwnagotchi/plugins/default/wigle.py index 61bc3ded..1ce072dc 100644 --- a/pwnagotchi/plugins/default/wigle.py +++ b/pwnagotchi/plugins/default/wigle.py @@ -63,7 +63,7 @@ def _transform_wigle_entry(gps_data, pcap_data): dummy.write("WigleWifi-1.4,appRelease=20190201,model=Kismet,release=2019.02.01.{},device=kismet,display=kismet,board=kismet,brand=kismet\n") dummy.write("MAC,SSID,AuthMode,FirstSeen,Channel,RSSI,CurrentLatitude,CurrentLongitude,AltitudeMeters,AccuracyMeters,Type") - writer = csv.writer(dummy, delimiter=",", quoting=csv.QUOTE_NONE) + writer = csv.writer(dummy, delimiter=",", quoting=csv.QUOTE_NONE, escapechar="\\") writer.writerow([ pcap_data[WifiInfo.BSSID], pcap_data[WifiInfo.ESSID], From d306877c7a843b0c8b5b2ccb3bd93617242fd223 Mon Sep 17 00:00:00 2001 From: spees Date: Thu, 24 Oct 2019 10:26:58 +0200 Subject: [PATCH 029/168] Small fix Last change broke the launchers. --- builder/pwnagotchi.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/builder/pwnagotchi.yml b/builder/pwnagotchi.yml index 44df05f7..7fbc959e 100644 --- a/builder/pwnagotchi.yml +++ b/builder/pwnagotchi.yml @@ -304,7 +304,7 @@ # blink 10 times to signal ready state /usr/bin/bootblink 10 & # start a detached screen session with bettercap - if [[ ifconfig | grep usb0 | grep RUNNING ]] || [[ $(cat /sys/class/net/eth0/carrier) ]]; then + if [[ $(ifconfig | grep usb0 | grep RUNNING) ]] || [[ $(cat /sys/class/net/eth0/carrier) ]]; then # if override file exists, go into auto mode if [ -f /root/.pwnagotchi-auto ]; then rm /root/.pwnagotchi-auto @@ -323,7 +323,7 @@ content: | #!/usr/bin/env bash /usr/bin/monstart - if [[ ifconfig | grep usb0 | grep RUNNING ]] || [[ $(cat /sys/class/net/eth0/carrier) ]]; then + if [[ $(ifconfig | grep usb0 | grep RUNNING) ]] || [[ $(cat /sys/class/net/eth0/carrier) ]]; then # if override file exists, go into auto mode if [ -f /root/.pwnagotchi-auto ]; then /usr/bin/bettercap -no-colors -caplet pwnagotchi-auto -iface mon0 From 2beef332517a0c6d982ab80079cefc80478b2c60 Mon Sep 17 00:00:00 2001 From: Simone Margaritelli Date: Thu, 24 Oct 2019 12:21:48 +0200 Subject: [PATCH 030/168] fix: checking inbox correctly from the grid plugin --- pwnagotchi/plugins/default/grid.py | 115 ++++++++++++++++------------- 1 file changed, 62 insertions(+), 53 deletions(-) diff --git a/pwnagotchi/plugins/default/grid.py b/pwnagotchi/plugins/default/grid.py index 4c411b44..e0c6aa96 100644 --- a/pwnagotchi/plugins/default/grid.py +++ b/pwnagotchi/plugins/default/grid.py @@ -65,18 +65,64 @@ def is_excluded(what): return False -def on_ui_update(ui): - if UNREAD_MESSAGES > 0: - logging.debug("[grid] unread:%d total:%d" % (UNREAD_MESSAGES, TOTAL_MESSAGES)) - ui.on_unread_messages(UNREAD_MESSAGES, TOTAL_MESSAGES) - - def set_reported(reported, net_id): global REPORT reported.append(net_id) REPORT.update(data={'reported': reported}) +def check_inbox(agent): + global REPORT, UNREAD_MESSAGES, TOTAL_MESSAGES + + logging.debug("checking mailbox ...") + + messages = grid.inbox() + TOTAL_MESSAGES = len(messages) + UNREAD_MESSAGES = len([m for m in messages if m['seen_at'] is None]) + + if UNREAD_MESSAGES: + logging.debug("[grid] unread:%d total:%d" % (UNREAD_MESSAGES, TOTAL_MESSAGES)) + agent.view().on_unread_messages(UNREAD_MESSAGES, TOTAL_MESSAGES) + + +def check_handshakes(agent): + logging.debug("checking pcaps") + + pcap_files = glob.glob(os.path.join(agent.config()['bettercap']['handshakes'], "*.pcap")) + num_networks = len(pcap_files) + reported = REPORT.data_field_or('reported', default=[]) + num_reported = len(reported) + num_new = num_networks - num_reported + + if num_new > 0: + if OPTIONS['report']: + logging.info("grid: %d new networks to report" % num_new) + logging.debug("OPTIONS: %s" % OPTIONS) + logging.debug(" exclude: %s" % OPTIONS['exclude']) + + for pcap_file in pcap_files: + net_id = os.path.basename(pcap_file).replace('.pcap', '') + if net_id not in reported: + if is_excluded(net_id): + logging.debug("skipping %s due to exclusion filter" % pcap_file) + set_reported(reported, net_id) + continue + + essid, bssid = parse_pcap(pcap_file) + if bssid: + if is_excluded(essid) or is_excluded(bssid): + logging.debug("not reporting %s due to exclusion filter" % pcap_file) + set_reported(reported, net_id) + else: + if grid.report_ap(essid, bssid): + set_reported(reported, net_id) + time.sleep(1.5) + else: + logging.warning("no bssid found?!") + else: + logging.debug("grid: reporting disabled") + + def on_internet_available(agent): global REPORT, UNREAD_MESSAGES, TOTAL_MESSAGES @@ -86,54 +132,17 @@ def on_internet_available(agent): grid.update_data(agent.last_session) except Exception as e: logging.error("error connecting to the pwngrid-peer service: %s" % e) + logging.debug(e, exc_info=True) return try: - logging.debug("checking mailbox ...") - - messages = grid.inbox() - TOTAL_MESSAGES = len(messages) - UNREAD_MESSAGES = len([m for m in messages if m['seen_at'] is None]) - - if TOTAL_MESSAGES: - on_ui_update(agent.view()) - logging.debug(" %d unread messages of %d total" % (UNREAD_MESSAGES, TOTAL_MESSAGES)) - - logging.debug("checking pcaps") - - pcap_files = glob.glob(os.path.join(agent.config()['bettercap']['handshakes'], "*.pcap")) - num_networks = len(pcap_files) - reported = REPORT.data_field_or('reported', default=[]) - num_reported = len(reported) - num_new = num_networks - num_reported - - if num_new > 0: - if OPTIONS['report']: - logging.info("grid: %d new networks to report" % num_new) - logging.debug("OPTIONS: %s" % OPTIONS) - logging.debug(" exclude: %s" % OPTIONS['exclude']) - - for pcap_file in pcap_files: - net_id = os.path.basename(pcap_file).replace('.pcap', '') - if net_id not in reported: - if is_excluded(net_id): - logging.debug("skipping %s due to exclusion filter" % pcap_file) - set_reported(reported, net_id) - continue - - essid, bssid = parse_pcap(pcap_file) - if bssid: - if is_excluded(essid) or is_excluded(bssid): - logging.debug("not reporting %s due to exclusion filter" % pcap_file) - set_reported(reported, net_id) - else: - if grid.report_ap(essid, bssid): - set_reported(reported, net_id) - time.sleep(1.5) - else: - logging.warning("no bssid found?!") - else: - logging.debug("grid: reporting disabled") - + check_inbox(agent) except Exception as e: - logging.error("grid api: %s" % e) + logging.error("[grid] error while checking inbox: %s" % e) + logging.debug(e, exc_info=True) + + try: + check_handshakes(agent) + except Exception as e: + logging.error("[grid] error while checking pcaps: %s" % e) + logging.debug(e, exc_info=True) From ab9ddb45132d2f9bbe72b7148090de9db7eecffa Mon Sep 17 00:00:00 2001 From: Simone Margaritelli Date: Thu, 24 Oct 2019 12:24:38 +0200 Subject: [PATCH 031/168] fix: throttled manual mode grid connections to avoid rate limits --- bin/pwnagotchi | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bin/pwnagotchi b/bin/pwnagotchi index d6303d56..0f3928b4 100755 --- a/bin/pwnagotchi +++ b/bin/pwnagotchi @@ -66,7 +66,7 @@ if __name__ == '__main__': display.on_manual_mode(agent.last_session) while True: - time.sleep(1) + time.sleep(5) if grid.is_connected(): plugins.on('internet_available', agent) From 3e0833698ab8bdcf6637a82d6c86b57a48662d84 Mon Sep 17 00:00:00 2001 From: Simone Margaritelli Date: Thu, 24 Oct 2019 12:26:18 +0200 Subject: [PATCH 032/168] fix: forcing view update when calling manual mode --- pwnagotchi/ui/view.py | 1 + 1 file changed, 1 insertion(+) diff --git a/pwnagotchi/ui/view.py b/pwnagotchi/ui/view.py index 3542c8db..c4fc0662 100644 --- a/pwnagotchi/ui/view.py +++ b/pwnagotchi/ui/view.py @@ -147,6 +147,7 @@ class View(object): self.set('shakes', '%d (%s)' % (last_session.handshakes, \ utils.total_unique_handshakes(self._config['bettercap']['handshakes']))) self.set_closest_peer(last_session.last_peer, last_session.peers) + self.update() def is_normal(self): return self._state.get('face') not in ( From 0df5e54f91d640df6dc7dabd3333775e5b790dc0 Mon Sep 17 00:00:00 2001 From: Simone Margaritelli Date: Thu, 24 Oct 2019 12:29:39 +0200 Subject: [PATCH 033/168] misc: small fix or general refactoring i did not bother commenting --- bin/pwnagotchi | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/bin/pwnagotchi b/bin/pwnagotchi index 0f3928b4..6d1bd41b 100755 --- a/bin/pwnagotchi +++ b/bin/pwnagotchi @@ -63,9 +63,8 @@ if __name__ == '__main__': agent.last_session.min_reward, agent.last_session.max_reward)) - display.on_manual_mode(agent.last_session) - while True: + display.on_manual_mode(agent.last_session) time.sleep(5) if grid.is_connected(): plugins.on('internet_available', agent) From 2239406272ce5adc3bbe69eaf3f13802dc895e0c Mon Sep 17 00:00:00 2001 From: Simone Margaritelli Date: Thu, 24 Oct 2019 12:33:03 +0200 Subject: [PATCH 034/168] misc: small fix or general refactoring i did not bother commenting --- pwnagotchi/ui/view.py | 1 + 1 file changed, 1 insertion(+) diff --git a/pwnagotchi/ui/view.py b/pwnagotchi/ui/view.py index c4fc0662..a8ad6626 100644 --- a/pwnagotchi/ui/view.py +++ b/pwnagotchi/ui/view.py @@ -310,6 +310,7 @@ class View(object): self.set('face', faces.EXCITED) self.set('status', self._voice.on_unread_messages(count, total)) self.update() + time.sleep(5.0) def on_rebooting(self): self.set('face', faces.BROKEN) From 4addefd57d81c7fa02e418638976f14537a6eca7 Mon Sep 17 00:00:00 2001 From: Simone Margaritelli Date: Thu, 24 Oct 2019 12:48:04 +0200 Subject: [PATCH 035/168] misc: small fix or general refactoring i did not bother commenting --- pwnagotchi/mesh/peer.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/pwnagotchi/mesh/peer.py b/pwnagotchi/mesh/peer.py index 6415752c..01428bf5 100644 --- a/pwnagotchi/mesh/peer.py +++ b/pwnagotchi/mesh/peer.py @@ -6,6 +6,8 @@ import pwnagotchi.ui.faces as faces def parse_rfc3339(dt): + if dt == "0001-01-01T00:00:00Z": + return datetime.datetime.now() return datetime.datetime.strptime(dt.split('.')[0], "%Y-%m-%dT%H:%M:%S") From f00c861844e775d9d500b85b42eca6f369f8e749 Mon Sep 17 00:00:00 2001 From: Simone Margaritelli Date: Thu, 24 Oct 2019 12:53:52 +0200 Subject: [PATCH 036/168] misc: small fix or general refactoring i did not bother commenting --- pwnagotchi/automata.py | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/pwnagotchi/automata.py b/pwnagotchi/automata.py index 556af636..b500fe31 100644 --- a/pwnagotchi/automata.py +++ b/pwnagotchi/automata.py @@ -45,9 +45,11 @@ class Automata(object): def set_lonely(self): if not self._has_support_network_for(1.0): + logging.info("unit is lonely") self._view.on_lonely() plugins.on('lonely', self) else: + logging.info("unit is grateful instead of lonely") self.set_grateful() def set_bored(self): @@ -57,6 +59,7 @@ class Automata(object): self._view.on_bored() plugins.on('bored', self) else: + logging.info("unit is grateful instead of bored") self.set_grateful() def set_sad(self): @@ -66,6 +69,7 @@ class Automata(object): self._view.on_sad() plugins.on('sad', self) else: + logging.info("unit is grateful instead of sad") self.set_grateful() def set_excited(self): @@ -109,7 +113,7 @@ class Automata(object): # after X times being active, the status is set to happy / excited elif self._epoch.active_for >= self._config['personality']['excited_num_epochs']: self.set_excited() - elif self._epoch.active_for >= 5 and self._has_support_network_for(1.0): + elif self._epoch.active_for >= 5 and self._has_support_network_for(5.0): self.set_grateful() plugins.on('epoch', self, self._epoch.epoch - 1, self._epoch.data()) From 032e183ff711dd01422727d3248944f637cefbf5 Mon Sep 17 00:00:00 2001 From: Simone Margaritelli Date: Thu, 24 Oct 2019 13:02:50 +0200 Subject: [PATCH 037/168] new: face expression when a new unit is detected depends on the units bond level --- pwnagotchi/mesh/peer.py | 3 +++ pwnagotchi/ui/view.py | 3 ++- pwnagotchi/voice.py | 2 ++ 3 files changed, 7 insertions(+), 1 deletion(-) diff --git a/pwnagotchi/mesh/peer.py b/pwnagotchi/mesh/peer.py index 01428bf5..6c1afee2 100644 --- a/pwnagotchi/mesh/peer.py +++ b/pwnagotchi/mesh/peer.py @@ -55,6 +55,9 @@ class Peer(object): def first_encounter(self): return self.encounters == 1 + def is_good_friend(self, config): + return self.encounters >= config['personality']['bond_encounters_factor'] + def face(self): return self.adv.get('face', faces.FRIEND) diff --git a/pwnagotchi/ui/view.py b/pwnagotchi/ui/view.py index a8ad6626..67c03fac 100644 --- a/pwnagotchi/ui/view.py +++ b/pwnagotchi/ui/view.py @@ -202,9 +202,10 @@ class View(object): self.update() def on_new_peer(self, peer): - self.set('face', faces.FRIEND) + self.set('face', faces.FRIEND if peer.is_good_friend(self._config) else faces.EXCITED) self.set('status', self._voice.on_new_peer(peer)) self.update() + time.sleep(3) def on_lost_peer(self, peer): self.set('face', faces.LONELY) diff --git a/pwnagotchi/voice.py b/pwnagotchi/voice.py index 249ac21b..c832f192 100644 --- a/pwnagotchi/voice.py +++ b/pwnagotchi/voice.py @@ -75,6 +75,8 @@ class Voice: self._('Hello {name}! Nice to meet you.').format(name=peer.name())]) else: return random.choice([ + self._('Yo {name}! Sup?').format(name=peer.name()), + self._('Hey {name} how are you doing?').format(name=peer.name()), self._('Unit {name} is nearby!').format(name=peer.name())]) def on_lost_peer(self, peer): From 6d70a24aae88ffc8b3258bcea13f6b524b651454 Mon Sep 17 00:00:00 2001 From: Simone Margaritelli Date: Thu, 24 Oct 2019 13:09:54 +0200 Subject: [PATCH 038/168] misc: small fix or general refactoring i did not bother commenting --- pwnagotchi/ui/view.py | 14 +++++++++++++- 1 file changed, 13 insertions(+), 1 deletion(-) diff --git a/pwnagotchi/ui/view.py b/pwnagotchi/ui/view.py index 67c03fac..6f6180cb 100644 --- a/pwnagotchi/ui/view.py +++ b/pwnagotchi/ui/view.py @@ -2,6 +2,7 @@ import _thread from threading import Lock import time import logging +import random from PIL import ImageDraw import pwnagotchi.utils as utils @@ -202,7 +203,18 @@ class View(object): self.update() def on_new_peer(self, peer): - self.set('face', faces.FRIEND if peer.is_good_friend(self._config) else faces.EXCITED) + face = '' + # first time they met, neutral mood + if peer.first_encounter(): + face = random.choice((faces.AWAKE, faces.COOL)) + # a good friend, positive expression + elif peer.is_good_friend(): + face = random.choice((faces.MOTIVATED, faces.FRIEND, faces.HAPPY)) + # normal friend, neutral-positive + else: + face = random.choice((faces.EXCITED, faces.HAPPY)) + + self.set('face', face) self.set('status', self._voice.on_new_peer(peer)) self.update() time.sleep(3) From d5ac988498eeef2463c047af27f8d59e8e734007 Mon Sep 17 00:00:00 2001 From: Simone Margaritelli Date: Thu, 24 Oct 2019 13:20:55 +0200 Subject: [PATCH 039/168] misc: small fix or general refactoring i did not bother commenting --- pwnagotchi/ui/view.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pwnagotchi/ui/view.py b/pwnagotchi/ui/view.py index 6f6180cb..11047f3a 100644 --- a/pwnagotchi/ui/view.py +++ b/pwnagotchi/ui/view.py @@ -208,7 +208,7 @@ class View(object): if peer.first_encounter(): face = random.choice((faces.AWAKE, faces.COOL)) # a good friend, positive expression - elif peer.is_good_friend(): + elif peer.is_good_friend(self._config): face = random.choice((faces.MOTIVATED, faces.FRIEND, faces.HAPPY)) # normal friend, neutral-positive else: From ce0c248cf4f3e9453a820231f55746df415c7eb2 Mon Sep 17 00:00:00 2001 From: Simone Margaritelli Date: Thu, 24 Oct 2019 13:22:05 +0200 Subject: [PATCH 040/168] misc: small fix or general refactoring i did not bother commenting --- pwnagotchi/mesh/utils.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/pwnagotchi/mesh/utils.py b/pwnagotchi/mesh/utils.py index a3c9bdad..ca0cc0ae 100644 --- a/pwnagotchi/mesh/utils.py +++ b/pwnagotchi/mesh/utils.py @@ -67,6 +67,9 @@ class AsyncAdvertiser(object): plugins.on('peer_lost', self, peer) def _adv_poller(self): + # give the system a few seconds to start the first time so that any expressions + # due to nearby units will be rendered properly + time.sleep(20) while True: try: logging.debug("polling pwngrid-peer for peers ...") From 97cccf5a1d2c75837ebf171e6d2426fc3a71e005 Mon Sep 17 00:00:00 2001 From: Simone Margaritelli Date: Thu, 24 Oct 2019 13:42:43 +0200 Subject: [PATCH 041/168] new: face expression while looking around will change dependig if the unit is with friends or alone --- pwnagotchi/agent.py | 1 + pwnagotchi/automata.py | 3 +++ pwnagotchi/defaults.yml | 2 ++ pwnagotchi/ui/faces.py | 2 ++ pwnagotchi/ui/view.py | 9 +++++++-- 5 files changed, 15 insertions(+), 2 deletions(-) diff --git a/pwnagotchi/agent.py b/pwnagotchi/agent.py index 53aa803c..5b7c6ecd 100644 --- a/pwnagotchi/agent.py +++ b/pwnagotchi/agent.py @@ -33,6 +33,7 @@ class Agent(Client, Automata, AsyncAdvertiser, AsyncTrainer): self._current_channel = 0 self._supported_channels = utils.iface_channels(config['main']['iface']) self._view = view + self._view.set_agent(self) self._access_points = [] self._last_pwnd = None self._history = {} diff --git a/pwnagotchi/automata.py b/pwnagotchi/automata.py index b500fe31..ee703dc2 100644 --- a/pwnagotchi/automata.py +++ b/pwnagotchi/automata.py @@ -32,6 +32,9 @@ class Automata(object): def set_ready(self): plugins.on('ready', self) + def in_good_mood(self): + return self._has_support_network_for(1.0) + def _has_support_network_for(self, factor): bond_factor = self._config['personality']['bond_encounters_factor'] total_encounters = sum(peer.encounters for _, peer in self._peers.items()) diff --git a/pwnagotchi/defaults.yml b/pwnagotchi/defaults.yml index a22874cf..8929538c 100644 --- a/pwnagotchi/defaults.yml +++ b/pwnagotchi/defaults.yml @@ -171,6 +171,8 @@ ui: faces: look_r: '( ⚆_⚆)' look_l: '(☉_☉ )' + look_r_happy: '( ◕‿◕)' + look_l_happy: '(◕‿◕ )' sleep: '(⇀‿‿↼)' sleep2: '(≖‿‿≖)' awake: '(◕‿‿◕)' diff --git a/pwnagotchi/ui/faces.py b/pwnagotchi/ui/faces.py index d90b5498..f94eb0b7 100644 --- a/pwnagotchi/ui/faces.py +++ b/pwnagotchi/ui/faces.py @@ -1,5 +1,7 @@ LOOK_R = '( ⚆_⚆)' LOOK_L = '(☉_☉ )' +LOOK_R_HAPPY = '( ◕‿◕)' +LOOK_L_HAPPY = '(◕‿◕ )' SLEEP = '(⇀‿‿↼)' SLEEP2 = '(≖‿‿≖)' AWAKE = '(◕‿‿◕)' diff --git a/pwnagotchi/ui/view.py b/pwnagotchi/ui/view.py index 11047f3a..e7fb80c1 100644 --- a/pwnagotchi/ui/view.py +++ b/pwnagotchi/ui/view.py @@ -26,6 +26,7 @@ class View(object): # setup faces from the configuration in case the user customized them faces.load_from_config(config['ui']['faces']) + self._agent = None self._render_cbs = [] self._config = config self._canvas = None @@ -89,6 +90,9 @@ class View(object): ROOT = self + def set_agent(self, agent): + self._agent = agent + def has_element(self, key): self._state.has_element(key) @@ -248,10 +252,11 @@ class View(object): self.set('status', self._voice.on_awakening()) else: self.set('status', self._voice.on_waiting(int(secs))) + good_mood = self._agent.in_good_mood() if step % 2 == 0: - self.set('face', faces.LOOK_R) + self.set('face', faces.LOOK_R_HAPPY if good_mood else faces.LOOK_R) else: - self.set('face', faces.LOOK_L) + self.set('face', faces.LOOK_L_HAPPY if good_mood else faces.LOOK_L) time.sleep(part) secs -= part From 30f9c167786953bc2142a8416c5f628d0e13bd84 Mon Sep 17 00:00:00 2001 From: Simone Margaritelli Date: Thu, 24 Oct 2019 14:00:22 +0200 Subject: [PATCH 042/168] new: bumped pwngrid required version to 1.10.1 --- builder/pwnagotchi.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/builder/pwnagotchi.yml b/builder/pwnagotchi.yml index 7fbc959e..5893e17e 100644 --- a/builder/pwnagotchi.yml +++ b/builder/pwnagotchi.yml @@ -36,7 +36,7 @@ url: "https://github.com/bettercap/bettercap/releases/download/v2.25/bettercap_linux_armv6l_2.25.zip" ui: "https://github.com/bettercap/ui/releases/download/v1.3.0/ui.zip" pwngrid: - url: "https://github.com/evilsocket/pwngrid/releases/download/v1.9.0/pwngrid_linux_armhf_v1.10.0.zip" + url: "https://github.com/evilsocket/pwngrid/releases/download/v1.10.1/pwngrid_linux_armhf_v1.10.1.zip" apt: hold: - firmware-atheros From 608aad482073205a4fcc2df9d525cfc811c1cfe2 Mon Sep 17 00:00:00 2001 From: Simone Margaritelli Date: Thu, 24 Oct 2019 15:18:04 +0200 Subject: [PATCH 043/168] new: observing and reporting total number of peers met per each epoch --- pwnagotchi/ai/epoch.py | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/pwnagotchi/ai/epoch.py b/pwnagotchi/ai/epoch.py index 11c2b6da..8da6fece 100644 --- a/pwnagotchi/ai/epoch.py +++ b/pwnagotchi/ai/epoch.py @@ -37,6 +37,8 @@ class Epoch(object): self.num_hops = 0 # number of seconds sleeping self.num_slept = 0 + # number of peers seen during this epoch + self.num_peers = 0 # any activity at all during this epoch? self.any_activity = False # when the current epoch started @@ -74,10 +76,11 @@ class Epoch(object): else: self.blind_for = 0 + self.num_peers = len(peers) + num_aps = len(aps) + 1e-10 num_sta = sum(len(ap['clients']) for ap in aps) + 1e-10 - num_peers = len(peers) + 1e-10 - + num_peers = self.num_peers + 1e-10 # avoid division by 0 aps_per_chan = [0.0] * wifi.NumChannels sta_per_chan = [0.0] * wifi.NumChannels peers_per_chan = [0.0] * wifi.NumChannels @@ -162,6 +165,7 @@ class Epoch(object): 'active_for_epochs': self.active_for, 'missed_interactions': self.num_missed, 'num_hops': self.num_hops, + 'num_peers': self.num_peers, 'num_deauths': self.num_deauths, 'num_associations': self.num_assocs, 'num_handshakes': self.num_shakes, @@ -173,7 +177,7 @@ class Epoch(object): self._epoch_data['reward'] = self._reward(self.epoch + 1, self._epoch_data) self._epoch_data_ready.set() - logging.info("[epoch %d] duration=%s slept_for=%s blind=%d inactive=%d active=%d hops=%d missed=%d " + logging.info("[epoch %d] duration=%s slept_for=%s blind=%d inactive=%d active=%d peers=%d hops=%d missed=%d " "deauths=%d assocs=%d handshakes=%d cpu=%d%% mem=%d%% temperature=%dC reward=%s" % ( self.epoch, utils.secs_to_hhmmss(self.epoch_duration), @@ -181,6 +185,7 @@ class Epoch(object): self.blind_for, self.inactive_for, self.active_for, + self.num_peers, self.num_hops, self.num_missed, self.num_deauths, @@ -195,6 +200,7 @@ class Epoch(object): self.epoch_started = now self.did_deauth = False self.num_deauths = 0 + self.num_peers = 0 self.did_associate = False self.num_assocs = 0 self.num_missed = 0 From dc5a626bd5ad33996077af1e721ddfdd2392ebb4 Mon Sep 17 00:00:00 2001 From: Simone Margaritelli Date: Thu, 24 Oct 2019 15:34:52 +0200 Subject: [PATCH 044/168] misc: small fix or general refactoring i did not bother commenting --- pwnagotchi/ai/epoch.py | 19 +++++++++++++++++-- 1 file changed, 17 insertions(+), 2 deletions(-) diff --git a/pwnagotchi/ai/epoch.py b/pwnagotchi/ai/epoch.py index 8da6fece..23b1d95c 100644 --- a/pwnagotchi/ai/epoch.py +++ b/pwnagotchi/ai/epoch.py @@ -39,6 +39,10 @@ class Epoch(object): self.num_slept = 0 # number of peers seen during this epoch self.num_peers = 0 + # cumulative bond factor + self.tot_bond_factor = 0.0 # cum_bond_factor sounded really bad ... + # average bond factor + self.avg_bond_factor = 0.0 # any activity at all during this epoch? self.any_activity = False # when the current epoch started @@ -76,7 +80,11 @@ class Epoch(object): else: self.blind_for = 0 + bond_unit_scale = self.config['personality']['bond_encounters_factor'] + self.num_peers = len(peers) + self.tot_bond_factor = sum((peer.encounters for peer in peers)) / bond_unit_scale + self.avg_bond_factor = self.tot_bond_factor / self.num_peers num_aps = len(aps) + 1e-10 num_sta = sum(len(ap['clients']) for ap in aps) + 1e-10 @@ -166,6 +174,8 @@ class Epoch(object): 'missed_interactions': self.num_missed, 'num_hops': self.num_hops, 'num_peers': self.num_peers, + 'tot_bond': self.tot_bond_factor, + 'avg_bond': self.avg_bond_factor, 'num_deauths': self.num_deauths, 'num_associations': self.num_assocs, 'num_handshakes': self.num_shakes, @@ -177,8 +187,9 @@ class Epoch(object): self._epoch_data['reward'] = self._reward(self.epoch + 1, self._epoch_data) self._epoch_data_ready.set() - logging.info("[epoch %d] duration=%s slept_for=%s blind=%d inactive=%d active=%d peers=%d hops=%d missed=%d " - "deauths=%d assocs=%d handshakes=%d cpu=%d%% mem=%d%% temperature=%dC reward=%s" % ( + logging.info("[epoch %d] duration=%s slept_for=%s blind=%d inactive=%d active=%d peers=%d tot_bond=%.2f " + "avg_bond=%.2f hops=%d missed=%d deauths=%d assocs=%d handshakes=%d cpu=%d%% mem=%d%% " + "temperature=%dC reward=%s" % ( self.epoch, utils.secs_to_hhmmss(self.epoch_duration), utils.secs_to_hhmmss(self.num_slept), @@ -186,6 +197,8 @@ class Epoch(object): self.inactive_for, self.active_for, self.num_peers, + self.tot_bond_factor, + self.avg_bond_factor, self.num_hops, self.num_missed, self.num_deauths, @@ -201,6 +214,8 @@ class Epoch(object): self.did_deauth = False self.num_deauths = 0 self.num_peers = 0 + self.tot_bond_factor = 0.0 + self.avg_bond_factor = 0.0 self.did_associate = False self.num_assocs = 0 self.num_missed = 0 From 094dde0e8cb6c34290abeb432efaa1cc8910dbf5 Mon Sep 17 00:00:00 2001 From: Simone Margaritelli Date: Thu, 24 Oct 2019 15:47:21 +0200 Subject: [PATCH 045/168] fix: 'effective configuration' is a debug log now --- bin/pwnagotchi | 3 +++ pwnagotchi/utils.py | 5 ++--- 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/bin/pwnagotchi b/bin/pwnagotchi index 6d1bd41b..dd1b9b32 100755 --- a/bin/pwnagotchi +++ b/bin/pwnagotchi @@ -3,6 +3,7 @@ if __name__ == '__main__': import argparse import time import logging + import yaml import pwnagotchi import pwnagotchi.grid as grid @@ -34,6 +35,8 @@ if __name__ == '__main__': config = utils.load_config(args) utils.setup_logging(args, config) + logging.debug("effective configuration:\n\n%s\n\n" % yaml.dump(config, default_flow_style=False)) + plugins.load(config) display = Display(config=config, state={'name': '%s>' % pwnagotchi.name()}) diff --git a/pwnagotchi/utils.py b/pwnagotchi/utils.py index 4f3d10da..1c430f4d 100644 --- a/pwnagotchi/utils.py +++ b/pwnagotchi/utils.py @@ -89,8 +89,6 @@ def load_config(args): print("unsupported display type %s" % config['ui']['display']['type']) exit(1) - print("Effective Configuration:") - print(yaml.dump(config, default_flow_style=False)) return config @@ -115,6 +113,7 @@ def setup_logging(args, config): requests_log.addHandler(logging.NullHandler()) requests_log.propagate = False + def secs_to_hhmmss(secs): mins, secs = divmod(secs, 60) hours, mins = divmod(mins, 60) @@ -272,7 +271,7 @@ class StatusFile(object): return self._updated is not None and ((datetime.now() - self._updated).seconds / 60) < minutes def newer_then_hours(self, hours): - return self._updated is not None and ((datetime.now() - self._updated).seconds / (60*60)) < hours + return self._updated is not None and ((datetime.now() - self._updated).seconds / (60 * 60)) < hours def newer_then_days(self, days): return self._updated is not None and (datetime.now() - self._updated).days < days From 30e3898f3cee90e1ffd6110afd24b652edebdff3 Mon Sep 17 00:00:00 2001 From: Simone Margaritelli Date: Thu, 24 Oct 2019 15:55:00 +0200 Subject: [PATCH 046/168] misc: small fix or general refactoring i did not bother commenting --- pwnagotchi/defaults.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pwnagotchi/defaults.yml b/pwnagotchi/defaults.yml index 8929538c..3d45abd7 100644 --- a/pwnagotchi/defaults.yml +++ b/pwnagotchi/defaults.yml @@ -163,7 +163,7 @@ personality: sad_num_epochs: 25 # number of encounters (times met on a channel) with another unit before considering it a friend and bond # also used for cumulative bonding score of nearby units - bond_encounters_factor: 5000 + bond_encounters_factor: 20000 # ui configuration ui: From 61b957ac779741a53531fe1bb4b1c298db369c49 Mon Sep 17 00:00:00 2001 From: Simone Margaritelli Date: Thu, 24 Oct 2019 17:23:33 +0200 Subject: [PATCH 047/168] fix: prevent user contributed plugins to crash the main process while loading --- pwnagotchi/plugins/__init__.py | 20 ++++++++++++-------- 1 file changed, 12 insertions(+), 8 deletions(-) diff --git a/pwnagotchi/plugins/__init__.py b/pwnagotchi/plugins/__init__.py index b2219747..d0805311 100644 --- a/pwnagotchi/plugins/__init__.py +++ b/pwnagotchi/plugins/__init__.py @@ -35,14 +35,18 @@ def load_from_file(filename): def load_from_path(path, enabled=()): global loaded for filename in glob.glob(os.path.join(path, "*.py")): - name, plugin = load_from_file(filename) - if name in loaded: - raise Exception("plugin %s already loaded from %s" % (name, plugin.__file__)) - elif name not in enabled: - # print("plugin %s is not enabled" % name) - pass - else: - loaded[name] = plugin + try: + name, plugin = load_from_file(filename) + if name in loaded: + raise Exception("plugin %s already loaded from %s" % (name, plugin.__file__)) + elif name not in enabled: + # print("plugin %s is not enabled" % name) + pass + else: + loaded[name] = plugin + except Exception as e: + logging.warning("error while loading %s: %s" % (filename, e)) + logging.debug(e, exc_info=True) return loaded From 0f171c35cef34a75ba5e1e71283fdad9dd1b10e9 Mon Sep 17 00:00:00 2001 From: Simone Margaritelli Date: Thu, 24 Oct 2019 17:48:37 +0200 Subject: [PATCH 048/168] new: unit's name can be now set via main.name configuration parameter --- bin/pwnagotchi | 2 ++ pwnagotchi/__init__.py | 31 +++++++++++++++++++++++++++++++ pwnagotchi/defaults.yml | 2 ++ 3 files changed, 35 insertions(+) diff --git a/bin/pwnagotchi b/bin/pwnagotchi index dd1b9b32..1d481379 100755 --- a/bin/pwnagotchi +++ b/bin/pwnagotchi @@ -37,6 +37,8 @@ if __name__ == '__main__': logging.debug("effective configuration:\n\n%s\n\n" % yaml.dump(config, default_flow_style=False)) + pwnagotchi.set_name(config['main']['name']) + plugins.load(config) display = Display(config=config, state={'name': '%s>' % pwnagotchi.name()}) diff --git a/pwnagotchi/__init__.py b/pwnagotchi/__init__.py index caa4bc97..cc1262be 100644 --- a/pwnagotchi/__init__.py +++ b/pwnagotchi/__init__.py @@ -9,6 +9,37 @@ version = '1.1.0b' _name = None +def set_name(new_name): + if new_name is None: + return + + new_name = new_name.strip() + if new_name == '': + return + + current = name() + if new_name != current: + global _name + + logging.info("setting unit hostname '%s' -> '%s'" % (current, new_name)) + with open('/etc/hostname', 'wt') as fp: + fp.write(new_name) + + with open('/etc/hosts', 'rt') as fp: + prev = fp.read() + logging.debug("old hosts:\n%s\n" % prev) + + with open('/etc/hosts', 'wt') as fp: + patched = prev.replace(current, new_name, -1) + logging.debug("new hosts:\n%s\n" % patched) + fp.write(patched) + + _name = new_name + logging.info("restarting avahi ...") + os.system("service avahi-daemon restart") + logging.info("hostname set") + + def name(): global _name if _name is None: diff --git a/pwnagotchi/defaults.yml b/pwnagotchi/defaults.yml index 3d45abd7..32db5adc 100644 --- a/pwnagotchi/defaults.yml +++ b/pwnagotchi/defaults.yml @@ -6,6 +6,8 @@ # # main algorithm configuration main: + # if set this will set the hostname of the unit + name: 'pwnagotchi' # currently implemented: en (default), de, el, fr, it, mk, nl, ru, se, pt-BR, es, pt lang: en # custom plugins path, if null only default plugins with be loaded From a25395a945cd54ad8bdd7f1b89295c68add60077 Mon Sep 17 00:00:00 2001 From: Simone Margaritelli Date: Thu, 24 Oct 2019 17:52:43 +0200 Subject: [PATCH 049/168] misc: small fix or general refactoring i did not bother commenting --- pwnagotchi/__init__.py | 1 + 1 file changed, 1 insertion(+) diff --git a/pwnagotchi/__init__.py b/pwnagotchi/__init__.py index cc1262be..f79ecee5 100644 --- a/pwnagotchi/__init__.py +++ b/pwnagotchi/__init__.py @@ -34,6 +34,7 @@ def set_name(new_name): logging.debug("new hosts:\n%s\n" % patched) fp.write(patched) + os.system("hostname '%s'" % new_name) _name = new_name logging.info("restarting avahi ...") os.system("service avahi-daemon restart") From 4445ef6432d43f856890ba191aa308f5ccfab341 Mon Sep 17 00:00:00 2001 From: Simone Margaritelli Date: Thu, 24 Oct 2019 17:53:02 +0200 Subject: [PATCH 050/168] misc: small fix or general refactoring i did not bother commenting --- pwnagotchi/defaults.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pwnagotchi/defaults.yml b/pwnagotchi/defaults.yml index 32db5adc..33b12672 100644 --- a/pwnagotchi/defaults.yml +++ b/pwnagotchi/defaults.yml @@ -7,7 +7,7 @@ # main algorithm configuration main: # if set this will set the hostname of the unit - name: 'pwnagotchi' + name: '' # currently implemented: en (default), de, el, fr, it, mk, nl, ru, se, pt-BR, es, pt lang: en # custom plugins path, if null only default plugins with be loaded From 97733cbf43c3a6d722f3850f645457ffed694159 Mon Sep 17 00:00:00 2001 From: Simone Margaritelli Date: Thu, 24 Oct 2019 18:12:10 +0200 Subject: [PATCH 051/168] fix: hostname validation when provided by config --- pwnagotchi/__init__.py | 5 +++++ pwnagotchi/defaults.yml | 2 +- 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/pwnagotchi/__init__.py b/pwnagotchi/__init__.py index f79ecee5..c4dea27c 100644 --- a/pwnagotchi/__init__.py +++ b/pwnagotchi/__init__.py @@ -2,6 +2,7 @@ import subprocess import os import logging import time +import re import pwnagotchi.ui.view as view version = '1.1.0b' @@ -17,6 +18,10 @@ def set_name(new_name): if new_name == '': return + if not re.match(r'^[a-zA-Z0-9\-]{2,25}$', new_name): + logging.warning("name '%s' is invalid: min length is 2, max length 25, only a-zA-Z0-9- allowed", new_name) + return + current = name() if new_name != current: global _name diff --git a/pwnagotchi/defaults.yml b/pwnagotchi/defaults.yml index 33b12672..f7634f1f 100644 --- a/pwnagotchi/defaults.yml +++ b/pwnagotchi/defaults.yml @@ -6,7 +6,7 @@ # # main algorithm configuration main: - # if set this will set the hostname of the unit + # if set this will set the hostname of the unit. min length is 2, max length 25, only a-zA-Z0-9- allowed name: '' # currently implemented: en (default), de, el, fr, it, mk, nl, ru, se, pt-BR, es, pt lang: en From ec430a5cbac0b0db73c6a1e3d4e3cde127ad60ac Mon Sep 17 00:00:00 2001 From: Simone Margaritelli Date: Thu, 24 Oct 2019 19:54:55 +0200 Subject: [PATCH 052/168] misc: small fix or general refactoring i did not bother commenting --- pwnagotchi/ai/epoch.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/pwnagotchi/ai/epoch.py b/pwnagotchi/ai/epoch.py index 23b1d95c..8f5380a5 100644 --- a/pwnagotchi/ai/epoch.py +++ b/pwnagotchi/ai/epoch.py @@ -83,12 +83,13 @@ class Epoch(object): bond_unit_scale = self.config['personality']['bond_encounters_factor'] self.num_peers = len(peers) + num_peers = self.num_peers + 1e-10 # avoid division by 0 + self.tot_bond_factor = sum((peer.encounters for peer in peers)) / bond_unit_scale - self.avg_bond_factor = self.tot_bond_factor / self.num_peers + self.avg_bond_factor = self.tot_bond_factor / num_peers num_aps = len(aps) + 1e-10 num_sta = sum(len(ap['clients']) for ap in aps) + 1e-10 - num_peers = self.num_peers + 1e-10 # avoid division by 0 aps_per_chan = [0.0] * wifi.NumChannels sta_per_chan = [0.0] * wifi.NumChannels peers_per_chan = [0.0] * wifi.NumChannels From 0c176ca308f925a9397b6735ba41970d2a613690 Mon Sep 17 00:00:00 2001 From: Cassiano Aquino Date: Thu, 24 Oct 2019 20:29:09 +0100 Subject: [PATCH 053/168] parse /proc/meminfo to gather memory usage --- pwnagotchi/__init__.py | 26 +++++++++++++++----------- 1 file changed, 15 insertions(+), 11 deletions(-) diff --git a/pwnagotchi/__init__.py b/pwnagotchi/__init__.py index c4dea27c..cda5bc3f 100644 --- a/pwnagotchi/__init__.py +++ b/pwnagotchi/__init__.py @@ -58,21 +58,25 @@ def uptime(): with open('/proc/uptime') as fp: return int(fp.read().split('.')[0]) - def mem_usage(): - out = subprocess.getoutput("free -m") - for line in out.split("\n"): - line = line.strip() - if line.startswith("Mem:"): - parts = list(map(int, line.split()[1:])) - tot = parts[0] - used = parts[1] - free = parts[2] - return used / tot + with open('/proc/meminfo') as fp: + for line in fp: + line = line.strip() + if line.startswith("MemTotal:"): + kb_mem_total = int(line.split()[1]) + if line.startswith("MemFree:"): + kb_mem_free = int(line.split()[1]) + if line.startswith("MemAvailable:"): + kb_mem_available = int(line.split()[1]) + if line.startswith("Buffers:"): + kb_main_buffers = int(line.split()[1]) + if line.startswith("Cached:"): + kb_main_cached = int(line.split()[1]) + kb_mem_used = kb_mem_total - kb_mem_free - kb_main_cached - kb_main_buffers + return round(kb_mem_used/kb_mem_total,2) return 0 - def cpu_load(): with open('/proc/stat', 'rt') as fp: for line in fp: From f8f66089680abd04611024ca160b3176675231db Mon Sep 17 00:00:00 2001 From: Cassiano Aquino Date: Thu, 24 Oct 2019 20:35:30 +0100 Subject: [PATCH 054/168] one decimal place --- pwnagotchi/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pwnagotchi/__init__.py b/pwnagotchi/__init__.py index cda5bc3f..3bca6e31 100644 --- a/pwnagotchi/__init__.py +++ b/pwnagotchi/__init__.py @@ -73,7 +73,7 @@ def mem_usage(): if line.startswith("Cached:"): kb_main_cached = int(line.split()[1]) kb_mem_used = kb_mem_total - kb_mem_free - kb_main_cached - kb_main_buffers - return round(kb_mem_used/kb_mem_total,2) + return round(kb_mem_used/kb_mem_total,1) return 0 From 6f013bb1efbee0b31c64a07462cdfa1c1737c58f Mon Sep 17 00:00:00 2001 From: dadav <33197631+dadav@users.noreply.github.com> Date: Thu, 24 Oct 2019 22:43:28 +0200 Subject: [PATCH 055/168] Fix lot of bt-tether problems --- pwnagotchi/plugins/default/bt-tether.py | 180 ++++++++++++++---------- 1 file changed, 109 insertions(+), 71 deletions(-) diff --git a/pwnagotchi/plugins/default/bt-tether.py b/pwnagotchi/plugins/default/bt-tether.py index e53dab59..2a7e595f 100644 --- a/pwnagotchi/plugins/default/bt-tether.py +++ b/pwnagotchi/plugins/default/bt-tether.py @@ -141,9 +141,14 @@ class BTNap: """ Set power of devices to on/off """ + logging.debug("BT-TETHER: Changing bluetooth device to %s", str(on)) - devs = list(BTNap.find_adapter()) - devs = dict((BTNap.prop_get(dev, 'Address'), dev) for dev in devs) + try: + devs = list(BTNap.find_adapter()) + devs = dict((BTNap.prop_get(dev, 'Address'), dev) for dev in devs) + except BTError as bt_err: + logging.error(bt_err) + return None for dev_addr, dev in devs.items(): BTNap.prop_set(dev, 'Powered', on) @@ -158,52 +163,62 @@ class BTNap: """ Check if already connected """ + logging.debug("BT-TETHER: Checking if device is connected.") + bt_dev = self.power(True) if not bt_dev: - return False + logging.debug("BT-TETHER: No bluetooth device found.") + return None, False try: dev_remote = BTNap.find_device(self._mac, bt_dev) - return bool(BTNap.prop_get(dev_remote, 'Connected')) + return dev_remote, bool(BTNap.prop_get(dev_remote, 'Connected')) except BTError: - pass - return False + logging.debug("BT-TETHER: Device is not connected.") + return None, False def is_paired(self): """ Check if already connected """ + logging.debug("BT-TETHER: Checking if device is paired") + bt_dev = self.power(True) if not bt_dev: + logging.debug("BT-TETHER: No bluetooth device found.") return False try: dev_remote = BTNap.find_device(self._mac, bt_dev) return bool(BTNap.prop_get(dev_remote, 'Paired')) except BTError: - pass + logging.debug("BT-TETHER: Device is not paired.") return False + def wait_for_device(self, timeout=15): """ Wait for device returns device if found None if not """ + logging.debug("BT-TETHER: Waiting for device") + bt_dev = self.power(True) if not bt_dev: + logging.debug("BT-TETHER: No bluetooth device found.") return None try: + logging.debug("BT-TETHER: Starting discovery ...") bt_dev.StartDiscovery() - except Exception: - # can fail with org.bluez.Error.NotReady / org.bluez.Error.Failed - # TODO: add loop? - pass + except Exception as bt_ex: + logging.error(bt_ex) + raise bt_ex dev_remote = None @@ -211,74 +226,66 @@ class BTNap: while timeout > -1: try: dev_remote = BTNap.find_device(self._mac, bt_dev) - logging.debug('Using remote device (addr: %s): %s', + logging.debug("BT-TETHER: Using remote device (addr: %s): %s", BTNap.prop_get(dev_remote, 'Address'), dev_remote.object_path ) break except BTError: - pass + logging.debug("BT-TETHER: Not found yet ...") time.sleep(1) timeout -= 1 try: + logging.debug("BT-TETHER: Stoping Discovery ...") bt_dev.StopDiscovery() - except Exception: - # can fail with org.bluez.Error.NotReady / org.bluez.Error.Failed / org.bluez.Error.NotAuthorized - pass + except Exception as bt_ex: + logging.error(bt_ex) + raise bt_ex return dev_remote - - def connect(self, reconnect=False): - """ - Connect to device - - return True if connected; False if failed - """ - - # check if device is close - dev_remote = self.wait_for_device() - - if not dev_remote: - return False - + @staticmethod + def pair(device): + logging.debug('BT-TETHER: Trying to pair ...') try: - dev_remote.Pair() + device.Pair() logging.info('BT-TETHER: Successful paired with device ;)') - time.sleep(10) # wait for bnep0 - except Exception: - # can fail because of AlreadyExists etc. - pass - - try: - dev_remote.ConnectProfile('nap') - except Exception: - pass - - net = dbus.Interface(dev_remote, 'org.bluez.Network1') - - try: - net.Connect('nap') + return True except dbus.exceptions.DBusException as err: - if err.get_dbus_name() != 'org.bluez.Error.Failed': - raise + if err.get_dbus_name() == 'org.bluez.Error.AlreadyExists': + logging.debug('BT-TETHER: Already paired ...') + return True + except Exception: + pass + return False + + + @staticmethod + def nap(device): + logging.debug('BT-TETHER: Trying to nap ...') + + try: + logging.debug('BT-TETHER: Connecting to profile ...') + device.ConnectProfile('nap') + except Exception: # raises exception, but still works + pass + + net = dbus.Interface(device, 'org.bluez.Network1') + + try: + logging.debug('BT-TETHER: Connecting to nap network ...') + net.Connect('nap') + return True + except dbus.exceptions.DBusException as err: + if err.get_dbus_name() == 'org.bluez.Error.AlreadyConnected': + return True connected = BTNap.prop_get(net, 'Connected') - if not connected: return False - - if reconnect: - net.Disconnect() - return self.connect(reconnect=False) - return True -################################################# -################################################# -################################################# - class SystemdUnitWrapper: """ systemd wrapper @@ -445,20 +452,51 @@ def on_ui_update(ui): bt = BTNap(OPTIONS['mac']) logging.debug('BT-TETHER: Check if already connected and paired') - if bt.is_connected() and bt.is_paired(): - logging.debug('BT-TETHER: Already connected and paired') - ui.set('bluetooth', 'CON') - else: - logging.debug('BT-TETHER: Try to connect to mac') - if bt.connect(): - logging.info('BT-TETHER: Successfuly connected') - else: - logging.error('BT-TETHER: Could not connect') + dev_remote, connected = bt.is_connected() + + if connected: + logging.debug('BT-TETHER: Already connected.') + ui.set('bluetooth', 'C') + return + + try: + logging.info('BT-TETHER: Search device ...') + dev_remote = bt.wait_for_device() + if dev_remote is None: + logging.info('BT-TETHER: Could not find device.') ui.set('bluetooth', 'NF') return + except Exception as bt_ex: + logging.error(bt_ex) + ui.set('bluetooth', 'NF') + return + + paired = bt.is_paired() + if not paired: + if BTNap.pair(dev_remote): + logging.info('BT-TETHER: Paired with device.') + else: + logging.info('BT-TETHER: Pairing failed ...') + ui.set('bluetooth', 'PE') + return + else: + logging.debug('BT-TETHER: Already paired.') + btnap_iface = IfaceWrapper('bnep0') logging.debug('BT-TETHER: Check interface') + if not btnap_iface.exists(): + # connected and paired but not napping + logging.debug('BT-TETHER: Try to connect to nap ...') + if BTNap.nap(dev_remote): + logging.info('BT-TETHER: Napping!') + ui.set('bluetooth', 'C') + time.sleep(5) + else: + logging.info('BT-TETHER: Napping failed ...') + ui.set('bluetooth', 'NF') + return + if btnap_iface.exists(): logging.debug('BT-TETHER: Interface found') @@ -467,11 +505,11 @@ def on_ui_update(ui): logging.debug('BT-TETHER: Try to set ADDR to interface') if not btnap_iface.set_addr(addr): - ui.set('bluetooth', 'ERR1') + ui.set('bluetooth', 'AE') logging.error("BT-TETHER: Could not set ip of bnep0 to %s", addr) return - else: - logging.debug('BT-TETHER: Set ADDR to interface') + + logging.debug('BT-TETHER: Set ADDR to interface') # change route if sharking if OPTIONS['share_internet']: @@ -485,10 +523,10 @@ def on_ui_update(ui): resolv.seek(0) resolv.write(nameserver + 'nameserver 9.9.9.9\n') - ui.set('bluetooth', 'CON') + ui.set('bluetooth', 'C') else: logging.error('BT-TETHER: bnep0 not found') - ui.set('bluetooth', 'ERR2') + ui.set('bluetooth', 'BE') def on_ui_setup(ui): From 6bb8fede0c72177c4ed67956caeaa21876f42df2 Mon Sep 17 00:00:00 2001 From: Alex Muthmann Date: Fri, 25 Oct 2019 10:33:12 +0200 Subject: [PATCH 056/168] Show information on failed backup on display --- pwnagotchi/plugins/default/auto-backup.py | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/pwnagotchi/plugins/default/auto-backup.py b/pwnagotchi/plugins/default/auto-backup.py index 7c9901c2..1007461e 100644 --- a/pwnagotchi/plugins/default/auto-backup.py +++ b/pwnagotchi/plugins/default/auto-backup.py @@ -57,9 +57,10 @@ def on_internet_available(agent): raise OSError(f"Command failed (rc: {process.returncode})") logging.info("AUTO-BACKUP: backup done") + display.set('status', 'Backup done!') + display.update() STATUS.update() except OSError as os_e: logging.info(f"AUTO-BACKUP: Error: {os_e}") - - display.set('status', 'Backup done!') - display.update() + display.set('status', 'Backup failed!') + display.update() From d51d3f61ac0c202afbf13bb2a2b99325da6b8cf8 Mon Sep 17 00:00:00 2001 From: Alex Muthmann Date: Fri, 25 Oct 2019 10:55:45 +0200 Subject: [PATCH 057/168] #391: Verify if the configured files exist --- pwnagotchi/plugins/default/auto-backup.py | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/pwnagotchi/plugins/default/auto-backup.py b/pwnagotchi/plugins/default/auto-backup.py index 7c9901c2..41ceced0 100644 --- a/pwnagotchi/plugins/default/auto-backup.py +++ b/pwnagotchi/plugins/default/auto-backup.py @@ -39,8 +39,11 @@ def on_internet_available(agent): if READY: if STATUS.newer_then_days(OPTIONS['interval']): return - - files_to_backup = " ".join(OPTIONS['files']) + + # Only backup existing files to prevent errors + existing_files = list(filter(lambda f: os.path.exists(f), OPTIONS['files'])) + files_to_backup = " ".join(existing_files) + try: display = agent.view() From 06d8cc63fb42dea960b72f5b472c64d60f0355f4 Mon Sep 17 00:00:00 2001 From: Simone Margaritelli Date: Fri, 25 Oct 2019 11:40:58 +0200 Subject: [PATCH 058/168] misc: added debug logs for AI loading times --- pwnagotchi/ai/__init__.py | 29 +++++++++++++++++++++-------- 1 file changed, 21 insertions(+), 8 deletions(-) diff --git a/pwnagotchi/ai/__init__.py b/pwnagotchi/ai/__init__.py index 190025fc..7390193e 100644 --- a/pwnagotchi/ai/__init__.py +++ b/pwnagotchi/ai/__init__.py @@ -1,14 +1,13 @@ import os +import time +import warnings +import logging # https://stackoverflow.com/questions/40426502/is-there-a-way-to-suppress-the-messages-tensorflow-prints/40426709 os.environ['TF_CPP_MIN_LOG_LEVEL'] = '3' # or any {'0', '1', '2'} -import warnings - # https://stackoverflow.com/questions/15777951/how-to-suppress-pandas-future-warning warnings.simplefilter(action='ignore', category=FutureWarning) -import logging - def load(config, agent, epoch, from_disk=True): config = config['ai'] @@ -18,25 +17,39 @@ def load(config, agent, epoch, from_disk=True): logging.info("[ai] bootstrapping dependencies ...") + start = time.time() from stable_baselines import A2C - from stable_baselines.common.policies import MlpLstmPolicy - from stable_baselines.common.vec_env import DummyVecEnv + logging.debug("[ai] A2C imported in %.2fs" % (time.time() - start)) + start = time.time() + from stable_baselines.common.policies import MlpLstmPolicy + logging.debug("[ai] MlpLstmPolicy imported in %.2fs" % (time.time() - start)) + + start = time.time() + from stable_baselines.common.vec_env import DummyVecEnv + logging.debug("[ai] DummyVecEnv imported in %.2fs" % (time.time() - start)) + + start = time.time() import pwnagotchi.ai.gym as wrappers + logging.debug("[ai] gym wrapper imported in %.2fs" % (time.time() - start)) env = wrappers.Environment(agent, epoch) env = DummyVecEnv([lambda: env]) - logging.info("[ai] bootstrapping model ...") + logging.info("[ai] creating model ...") + start = time.time() a2c = A2C(MlpLstmPolicy, env, **config['params']) + logging.debug("[ai] A2C crated in %.2fs" % (time.time() - start)) if from_disk and os.path.exists(config['path']): logging.info("[ai] loading %s ..." % config['path']) + start = time.time() a2c.load(config['path'], env) + logging.debug("[ai] A2C loaded in %.2fs" % (time.time() - start)) else: logging.info("[ai] model created:") for key, value in config['params'].items(): logging.info(" %s: %s" % (key, value)) - return a2c \ No newline at end of file + return a2c From 7954bb5fcfc4d56d7d5f35877d790953f2a6b2b2 Mon Sep 17 00:00:00 2001 From: Simone Margaritelli Date: Fri, 25 Oct 2019 12:15:56 +0200 Subject: [PATCH 059/168] misc: small fix or general refactoring i did not bother commenting --- pwnagotchi/ai/__init__.py | 75 +++++++++++++++++++++------------------ 1 file changed, 40 insertions(+), 35 deletions(-) diff --git a/pwnagotchi/ai/__init__.py b/pwnagotchi/ai/__init__.py index 7390193e..4dbdc9b5 100644 --- a/pwnagotchi/ai/__init__.py +++ b/pwnagotchi/ai/__init__.py @@ -15,41 +15,46 @@ def load(config, agent, epoch, from_disk=True): logging.info("ai disabled") return False - logging.info("[ai] bootstrapping dependencies ...") + try: + logging.info("[ai] bootstrapping dependencies ...") - start = time.time() - from stable_baselines import A2C - logging.debug("[ai] A2C imported in %.2fs" % (time.time() - start)) - - start = time.time() - from stable_baselines.common.policies import MlpLstmPolicy - logging.debug("[ai] MlpLstmPolicy imported in %.2fs" % (time.time() - start)) - - start = time.time() - from stable_baselines.common.vec_env import DummyVecEnv - logging.debug("[ai] DummyVecEnv imported in %.2fs" % (time.time() - start)) - - start = time.time() - import pwnagotchi.ai.gym as wrappers - logging.debug("[ai] gym wrapper imported in %.2fs" % (time.time() - start)) - - env = wrappers.Environment(agent, epoch) - env = DummyVecEnv([lambda: env]) - - logging.info("[ai] creating model ...") - - start = time.time() - a2c = A2C(MlpLstmPolicy, env, **config['params']) - logging.debug("[ai] A2C crated in %.2fs" % (time.time() - start)) - - if from_disk and os.path.exists(config['path']): - logging.info("[ai] loading %s ..." % config['path']) start = time.time() - a2c.load(config['path'], env) - logging.debug("[ai] A2C loaded in %.2fs" % (time.time() - start)) - else: - logging.info("[ai] model created:") - for key, value in config['params'].items(): - logging.info(" %s: %s" % (key, value)) + from stable_baselines import A2C + logging.debug("[ai] A2C imported in %.2fs" % (time.time() - start)) - return a2c + start = time.time() + from stable_baselines.common.policies import MlpLstmPolicy + logging.debug("[ai] MlpLstmPolicy imported in %.2fs" % (time.time() - start)) + + start = time.time() + from stable_baselines.common.vec_env import DummyVecEnv + logging.debug("[ai] DummyVecEnv imported in %.2fs" % (time.time() - start)) + + start = time.time() + import pwnagotchi.ai.gym as wrappers + logging.debug("[ai] gym wrapper imported in %.2fs" % (time.time() - start)) + + env = wrappers.Environment(agent, epoch) + env = DummyVecEnv([lambda: env]) + + logging.info("[ai] creating model ...") + + start = time.time() + a2c = A2C(MlpLstmPolicy, env, **config['params']) + logging.debug("[ai] A2C crated in %.2fs" % (time.time() - start)) + + if from_disk and os.path.exists(config['path']): + logging.info("[ai] loading %s ..." % config['path']) + start = time.time() + a2c.load(config['path'], env) + logging.debug("[ai] A2C loaded in %.2fs" % (time.time() - start)) + else: + logging.info("[ai] model created:") + for key, value in config['params'].items(): + logging.info(" %s: %s" % (key, value)) + + return a2c + except Exception as e: + logging.exception("error while starting AI") + + return False From 8b00e0ae106740df4ce02fad912e487e3b3d9880 Mon Sep 17 00:00:00 2001 From: Simone Margaritelli Date: Fri, 25 Oct 2019 12:17:44 +0200 Subject: [PATCH 060/168] misc: small fix or general refactoring i did not bother commenting --- pwnagotchi/ai/__init__.py | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/pwnagotchi/ai/__init__.py b/pwnagotchi/ai/__init__.py index 4dbdc9b5..d2cf186d 100644 --- a/pwnagotchi/ai/__init__.py +++ b/pwnagotchi/ai/__init__.py @@ -16,6 +16,8 @@ def load(config, agent, epoch, from_disk=True): return False try: + begin = time.time() + logging.info("[ai] bootstrapping dependencies ...") start = time.time() @@ -53,8 +55,11 @@ def load(config, agent, epoch, from_disk=True): for key, value in config['params'].items(): logging.info(" %s: %s" % (key, value)) + logging.debug("[ai] total loading time is %.2fs" % (time.time() - begin)) + return a2c except Exception as e: logging.exception("error while starting AI") + logging.warning("[ai] AI not loaded!") return False From cb6365b9f21ddfefea8039b33c37ddc87c66ea66 Mon Sep 17 00:00:00 2001 From: Alex Muthmann Date: Fri, 25 Oct 2019 12:18:13 +0200 Subject: [PATCH 061/168] Add missing lcdhat --- pwnagotchi/defaults.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pwnagotchi/defaults.yml b/pwnagotchi/defaults.yml index f7634f1f..07862d97 100644 --- a/pwnagotchi/defaults.yml +++ b/pwnagotchi/defaults.yml @@ -200,7 +200,7 @@ ui: display: enabled: true rotation: 180 - # Possible options inkyphat/inky, papirus/papi, waveshare_1/ws_1 or waveshare_2/ws_2, oledhat, waveshare27inch + # Possible options inkyphat/inky, papirus/papi, waveshare_1/ws_1 or waveshare_2/ws_2, oledhat, lcdhat, waveshare27inch type: 'waveshare_2' # Possible options red/yellow/black (black used for monocromatic displays) color: 'black' From c6c2e0e7ce521d458b7119dc01dd29d54181fc55 Mon Sep 17 00:00:00 2001 From: Alex Muthmann Date: Fri, 25 Oct 2019 12:25:42 +0200 Subject: [PATCH 062/168] Update utils.py --- pwnagotchi/utils.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/pwnagotchi/utils.py b/pwnagotchi/utils.py index 1c430f4d..74ad7edf 100644 --- a/pwnagotchi/utils.py +++ b/pwnagotchi/utils.py @@ -85,6 +85,9 @@ def load_config(args): elif config['ui']['display']['type'] in ('ws_27inch', 'ws27inch', 'waveshare_27inch', 'waveshare27inch'): config['ui']['display']['type'] = 'waveshare27inch' + elif config['ui']['display']['type'] in ('lcdhat'): + config['ui']['display']['type'] = 'lcdhat' + else: print("unsupported display type %s" % config['ui']['display']['type']) exit(1) From 2c9f54567f965c0f282c652a78543eaaabd936eb Mon Sep 17 00:00:00 2001 From: Simone Margaritelli Date: Fri, 25 Oct 2019 13:21:08 +0200 Subject: [PATCH 063/168] misc: small fix or general refactoring i did not bother commenting --- bin/pwnagotchi | 4 ++-- pwnagotchi/ai/__init__.py | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/bin/pwnagotchi b/bin/pwnagotchi index 1d481379..9405fe8c 100755 --- a/bin/pwnagotchi +++ b/bin/pwnagotchi @@ -35,8 +35,6 @@ if __name__ == '__main__': config = utils.load_config(args) utils.setup_logging(args, config) - logging.debug("effective configuration:\n\n%s\n\n" % yaml.dump(config, default_flow_style=False)) - pwnagotchi.set_name(config['main']['name']) plugins.load(config) @@ -47,6 +45,8 @@ if __name__ == '__main__': logging.info("%s@%s (v%s)" % (pwnagotchi.name(), agent.fingerprint(), pwnagotchi.version)) + logging.debug("effective configuration:\n\n%s\n\n" % yaml.dump(config, default_flow_style=False)) + for _, plugin in plugins.loaded.items(): logging.debug("plugin '%s' v%s loaded from %s" % (plugin.__name__, plugin.__version__, plugin.__file__)) diff --git a/pwnagotchi/ai/__init__.py b/pwnagotchi/ai/__init__.py index d2cf186d..73ca4fca 100644 --- a/pwnagotchi/ai/__init__.py +++ b/pwnagotchi/ai/__init__.py @@ -43,7 +43,7 @@ def load(config, agent, epoch, from_disk=True): start = time.time() a2c = A2C(MlpLstmPolicy, env, **config['params']) - logging.debug("[ai] A2C crated in %.2fs" % (time.time() - start)) + logging.debug("[ai] A2C created in %.2fs" % (time.time() - start)) if from_disk and os.path.exists(config['path']): logging.info("[ai] loading %s ..." % config['path']) From 5f4ee26f99ee62939634bb52dff773e774ad75cb Mon Sep 17 00:00:00 2001 From: Simone Margaritelli Date: Fri, 25 Oct 2019 15:29:10 +0200 Subject: [PATCH 064/168] new: pwnagotchi folder can now be in /boot/ in which case will be moved to /etc/pwnagotchi --- pwnagotchi/utils.py | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/pwnagotchi/utils.py b/pwnagotchi/utils.py index 74ad7edf..cb9d2ae7 100644 --- a/pwnagotchi/utils.py +++ b/pwnagotchi/utils.py @@ -36,9 +36,13 @@ def load_config(args): # logging not configured here yet print("installing /boot/config.yml to %s ...", args.user_config) # https://stackoverflow.com/questions/42392600/oserror-errno-18-invalid-cross-device-link - shutil.move("/boot/config.yml", args.user_config) + shutil.rmtree('/etc/pwnagotchi', ignore_errors=True) + shutil.move('/boot/pwnagotchi', '/etc/', args.user_config) - # if not config is found, copy the defaults + if os.path.isdir('/boot/pwnagotchi'): + print("installing /boot/pwnagotchi to /etc/pwnagotchi ...") + + # if no config is found, copy the defaults if not os.path.exists(args.config): print("copying %s to %s ..." % (ref_defaults_file, args.config)) shutil.copy(ref_defaults_file, args.config) From 22afb563e3f6b248c8d535ef60e67efefd88200b Mon Sep 17 00:00:00 2001 From: Simone Margaritelli Date: Fri, 25 Oct 2019 16:01:53 +0200 Subject: [PATCH 065/168] misc: small fix or general refactoring i did not bother commenting --- pwnagotchi/utils.py | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/pwnagotchi/utils.py b/pwnagotchi/utils.py index cb9d2ae7..392d2fd7 100644 --- a/pwnagotchi/utils.py +++ b/pwnagotchi/utils.py @@ -36,13 +36,15 @@ def load_config(args): # logging not configured here yet print("installing /boot/config.yml to %s ...", args.user_config) # https://stackoverflow.com/questions/42392600/oserror-errno-18-invalid-cross-device-link - shutil.rmtree('/etc/pwnagotchi', ignore_errors=True) - shutil.move('/boot/pwnagotchi', '/etc/', args.user_config) + shutil.move("/boot/config.yml", args.user_config) + # check for an entire pwnagotchi folder on /boot/ if os.path.isdir('/boot/pwnagotchi'): print("installing /boot/pwnagotchi to /etc/pwnagotchi ...") + shutil.rmtree('/etc/pwnagotchi', ignore_errors=True) + shutil.move('/boot/pwnagotchi', '/etc/') - # if no config is found, copy the defaults + # if not config is found, copy the defaults if not os.path.exists(args.config): print("copying %s to %s ..." % (ref_defaults_file, args.config)) shutil.copy(ref_defaults_file, args.config) From 715e6965375d041aad4649071a26731bb541acfb Mon Sep 17 00:00:00 2001 From: Simone Margaritelli Date: Fri, 25 Oct 2019 16:37:45 +0200 Subject: [PATCH 066/168] fix: rebooting after setting hostname --- pwnagotchi/__init__.py | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/pwnagotchi/__init__.py b/pwnagotchi/__init__.py index 3bca6e31..f9ea149c 100644 --- a/pwnagotchi/__init__.py +++ b/pwnagotchi/__init__.py @@ -4,6 +4,7 @@ import logging import time import re import pwnagotchi.ui.view as view +import pwnagotchi version = '1.1.0b' @@ -40,10 +41,7 @@ def set_name(new_name): fp.write(patched) os.system("hostname '%s'" % new_name) - _name = new_name - logging.info("restarting avahi ...") - os.system("service avahi-daemon restart") - logging.info("hostname set") + pwnagotchi.reboot() def name(): @@ -58,6 +56,7 @@ def uptime(): with open('/proc/uptime') as fp: return int(fp.read().split('.')[0]) + def mem_usage(): with open('/proc/meminfo') as fp: for line in fp: @@ -73,10 +72,11 @@ def mem_usage(): if line.startswith("Cached:"): kb_main_cached = int(line.split()[1]) kb_mem_used = kb_mem_total - kb_mem_free - kb_main_cached - kb_main_buffers - return round(kb_mem_used/kb_mem_total,1) + return round(kb_mem_used / kb_mem_total, 1) return 0 + def cpu_load(): with open('/proc/stat', 'rt') as fp: for line in fp: From 047f0d5d632edcbef5291079cffd565200e88815 Mon Sep 17 00:00:00 2001 From: Simone Margaritelli Date: Fri, 25 Oct 2019 16:43:14 +0200 Subject: [PATCH 067/168] misc: bump bettercap to 2.26 --- builder/pwnagotchi.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/builder/pwnagotchi.yml b/builder/pwnagotchi.yml index 5893e17e..17f12815 100644 --- a/builder/pwnagotchi.yml +++ b/builder/pwnagotchi.yml @@ -33,7 +33,7 @@ - ifup@wlan0.service packages: bettercap: - url: "https://github.com/bettercap/bettercap/releases/download/v2.25/bettercap_linux_armv6l_2.25.zip" + url: "https://github.com/bettercap/bettercap/releases/download/v2.26/bettercap_linux_armhf_v2.26.zip" ui: "https://github.com/bettercap/ui/releases/download/v1.3.0/ui.zip" pwngrid: url: "https://github.com/evilsocket/pwngrid/releases/download/v1.10.1/pwngrid_linux_armhf_v1.10.1.zip" From 39a6ae1be56740704438d8facae00229590185ec Mon Sep 17 00:00:00 2001 From: Leon T Date: Fri, 25 Oct 2019 16:57:05 +0200 Subject: [PATCH 068/168] Created the paw-gps plugin v1.0.0 This plugin connects to PAW Server with the GPS hack and saves the GPS location when a handshake is detected --- pwnagotchi/plugins/default/paw-gps.py | 33 +++++++++++++++++++++++++++ 1 file changed, 33 insertions(+) create mode 100644 pwnagotchi/plugins/default/paw-gps.py diff --git a/pwnagotchi/plugins/default/paw-gps.py b/pwnagotchi/plugins/default/paw-gps.py new file mode 100644 index 00000000..02c827e6 --- /dev/null +++ b/pwnagotchi/plugins/default/paw-gps.py @@ -0,0 +1,33 @@ +__author__ = 'leont' +__version__ = '1.0.0' +__name__ = 'pawgps' +__license__ = 'GPL3' +__description__ = 'Saves GPS coordinates whenever an handshake is captured. The GPS data is get from PAW on android ' + +''' +You need an bluetooth connection to your android phone which is running PAW server with the GPS "hack" from Systemic: +https://raw.githubusercontent.com/systemik/pwnagotchi-bt-tether/master/GPS-via-PAW +''' + +import logging +import json +import requests + +OPTIONS = dict() + + +def on_loaded(): + logging.info("PAW-GPS loaded") + if 'ip' not in OPTIONS or ('ip' in OPTIONS and OPTIONS['ip'] is None): + logging.info("PAW-GPS: No IP Address in the config file is defined, it uses the default (192.168.44.1)") + +def on_handshake(agent, filename, access_point, client_station): + if 'ip' not in OPTIONS or ('ip' in OPTIONS and OPTIONS['ip'] is None): + ip = "192.168.44.1" + + gps = requests.get('http://' + ip + '/gps.xhtml') + gps_filename = filename.replace('.pcap', '.gps.json') + + logging.info("saving GPS to %s (%s)" % (gps_filename, gps)) + with open(gps_filename, 'w+t') as f: + f.write(gps.text) From 5dc780a88f199fd4456e25868c8db463f64a4cc2 Mon Sep 17 00:00:00 2001 From: Leon T Date: Fri, 25 Oct 2019 17:02:15 +0200 Subject: [PATCH 069/168] Edited defaut.yml to add the pawgps plugin --- pwnagotchi/defaults.yml | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/pwnagotchi/defaults.yml b/pwnagotchi/defaults.yml index 07862d97..e08906be 100644 --- a/pwnagotchi/defaults.yml +++ b/pwnagotchi/defaults.yml @@ -79,6 +79,10 @@ main: memtemp: # Display memory usage, cpu load and cpu temperature on screen enabled: false orientation: horizontal # horizontal/vertical + pawgps: + enabled: false + #The IP Address of your phone with Paw Server running, default (option is empty) is 192.168.44.1 + ip: '' # monitor interface to use iface: mon0 # command to run to bring the mon interface up in case it's not up already From 4aa29f1b799662d9641dbf60439aa237197c4224 Mon Sep 17 00:00:00 2001 From: Simone Margaritelli Date: Fri, 25 Oct 2019 19:15:55 +0200 Subject: [PATCH 070/168] misc: small fix or general refactoring i did not bother commenting --- pwnagotchi/plugins/default/auto-update.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/pwnagotchi/plugins/default/auto-update.py b/pwnagotchi/plugins/default/auto-update.py index c3595059..2ed389c9 100644 --- a/pwnagotchi/plugins/default/auto-update.py +++ b/pwnagotchi/plugins/default/auto-update.py @@ -176,7 +176,8 @@ def on_internet_available(agent): for repo, local_version, is_native, svc_name in to_check: info = check(local_version, repo, is_native) if info['url'] is not None: - logging.warning("update for %s available: %s" % (repo, info['url'])) + logging.warning( + "update for %s available (local version is '%s'): %s" % (repo, info['current'], info['url'])) info['service'] = svc_name to_install.append(info) From c0252c9830521300e6deb12f5a629f11460b9dbb Mon Sep 17 00:00:00 2001 From: Simone Margaritelli Date: Fri, 25 Oct 2019 19:32:11 +0200 Subject: [PATCH 071/168] fix: safer call to webhook --- pwnagotchi/plugins/__init__.py | 14 +++++++++++++- pwnagotchi/ui/web.py | 3 +-- 2 files changed, 14 insertions(+), 3 deletions(-) diff --git a/pwnagotchi/plugins/__init__.py b/pwnagotchi/plugins/__init__.py index d0805311..cb58323d 100644 --- a/pwnagotchi/plugins/__init__.py +++ b/pwnagotchi/plugins/__init__.py @@ -16,7 +16,19 @@ def on(event_name, *args, **kwargs): cb_name = 'on_%s' % event_name for plugin_name, plugin in loaded.items(): if cb_name in plugin.__dict__: - # print("calling %s %s(%s)" %(cb_name, args, kwargs)) + try: + plugin.__dict__[cb_name](*args, **kwargs) + except Exception as e: + logging.error("error while running %s.%s : %s" % (plugin_name, cb_name, e)) + logging.error(e, exc_info=True) + + +def one(plugin_name, event_name, *args, **kwargs): + global loaded + if plugin_name in loaded: + plugin = loaded[plugin_name] + cb_name = 'on_%s' % event_name + if cb_name in plugin.__dict__: try: plugin.__dict__[cb_name](*args, **kwargs) except Exception as e: diff --git a/pwnagotchi/ui/web.py b/pwnagotchi/ui/web.py index 82af2693..e5432717 100644 --- a/pwnagotchi/ui/web.py +++ b/pwnagotchi/ui/web.py @@ -166,8 +166,7 @@ class Handler(BaseHTTPRequestHandler): if plugin_from_path: plugin_name = plugin_from_path.groups()[0] right_path = plugin_from_path.groups()[1] if len(plugin_from_path.groups()) == 2 else None - if plugin_name in plugins.loaded and hasattr(plugins.loaded[plugin_name], 'on_webhook'): - plugins.loaded[plugin_name].on_webhook(self, right_path) + plugins.one(plugin_name, 'webhook', right_path) else: self.send_response(404) From 31c1d742e0218bf4aa142bf5de2b77ebb13a062c Mon Sep 17 00:00:00 2001 From: Simone Margaritelli Date: Fri, 25 Oct 2019 19:34:31 +0200 Subject: [PATCH 072/168] fix: webui /shutdown is now on POST --- pwnagotchi/ui/web.py | 15 +++++++++++---- 1 file changed, 11 insertions(+), 4 deletions(-) diff --git a/pwnagotchi/ui/web.py b/pwnagotchi/ui/web.py index e5432717..60fa2559 100644 --- a/pwnagotchi/ui/web.py +++ b/pwnagotchi/ui/web.py @@ -52,7 +52,7 @@ INDEX = """

-
+
@@ -149,24 +149,31 @@ class Handler(BaseHTTPRequestHandler): return True - # main entry point of the http server + def do_POST(self): + if not self._is_allowed(): + return + if self.path.startswith('/shutdown'): + self._shutdown() + else: + self.send_response(404) + def do_GET(self): if not self._is_allowed(): return if self.path == '/': self._index() - elif self.path.startswith('/shutdown'): - self._shutdown() elif self.path.startswith('/ui'): self._image() + elif self.path.startswith('/plugins'): plugin_from_path = re.match(r'\/plugins\/([^\/]+)(\/.*)?', self.path) if plugin_from_path: plugin_name = plugin_from_path.groups()[0] right_path = plugin_from_path.groups()[1] if len(plugin_from_path.groups()) == 2 else None plugins.one(plugin_name, 'webhook', right_path) + else: self.send_response(404) From cf146a54ee82bcc4b0f72c154216b653105b2ef7 Mon Sep 17 00:00:00 2001 From: Simone Margaritelli Date: Fri, 25 Oct 2019 19:36:23 +0200 Subject: [PATCH 073/168] misc: small fix or general refactoring i did not bother commenting --- pwnagotchi/ui/web.py | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/pwnagotchi/ui/web.py b/pwnagotchi/ui/web.py index 60fa2559..57f63890 100644 --- a/pwnagotchi/ui/web.py +++ b/pwnagotchi/ui/web.py @@ -130,11 +130,6 @@ class Handler(BaseHTTPRequestHandler): except: pass - def do_OPTIONS(self): - self.send_response(200) - self._send_cors_headers() - self.end_headers() - # check the Origin header vs CORS def _is_allowed(self): origin = self.headers.get('origin') @@ -149,6 +144,11 @@ class Handler(BaseHTTPRequestHandler): return True + def do_OPTIONS(self): + self.send_response(200) + self._send_cors_headers() + self.end_headers() + def do_POST(self): if not self._is_allowed(): return From 3f785ee06a265d58ebc03d9dfae1a048a201dd39 Mon Sep 17 00:00:00 2001 From: Simone Margaritelli Date: Fri, 25 Oct 2019 19:38:23 +0200 Subject: [PATCH 074/168] misc: small fix or general refactoring i did not bother commenting --- pwnagotchi/ui/web.py | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/pwnagotchi/ui/web.py b/pwnagotchi/ui/web.py index 57f63890..8a142301 100644 --- a/pwnagotchi/ui/web.py +++ b/pwnagotchi/ui/web.py @@ -168,10 +168,11 @@ class Handler(BaseHTTPRequestHandler): self._image() elif self.path.startswith('/plugins'): - plugin_from_path = re.match(r'\/plugins\/([^\/]+)(\/.*)?', self.path) - if plugin_from_path: - plugin_name = plugin_from_path.groups()[0] - right_path = plugin_from_path.groups()[1] if len(plugin_from_path.groups()) == 2 else None + matches = re.match(r'\/plugins\/([^\/]+)(\/.*)?', self.path) + if matches: + groups = matches.groups() + plugin_name = groups[0] + right_path = groups[1] if len(groups) == 2 else None plugins.one(plugin_name, 'webhook', right_path) else: From 24ae443ee904e2fb7870e056874bf792574c98ac Mon Sep 17 00:00:00 2001 From: SpiderDead Date: Fri, 25 Oct 2019 23:20:01 +0200 Subject: [PATCH 075/168] Updated libraries to V4.0 for the 2.7" display Signed-off-by: Mike van der Vrugt --- .../ui/hw/libs/waveshare/v27inch/epd2in7.py | 464 +++++++++++++----- .../ui/hw/libs/waveshare/v27inch/epdconfig.py | 157 ++++-- 2 files changed, 461 insertions(+), 160 deletions(-) diff --git a/pwnagotchi/ui/hw/libs/waveshare/v27inch/epd2in7.py b/pwnagotchi/ui/hw/libs/waveshare/v27inch/epd2in7.py index 01e84736..20f16657 100644 --- a/pwnagotchi/ui/hw/libs/waveshare/v27inch/epd2in7.py +++ b/pwnagotchi/ui/hw/libs/waveshare/v27inch/epd2in7.py @@ -1,33 +1,13 @@ -# /***************************************************************************** -# * | File : EPD_1in54.py +# ***************************************************************************** +# * | File : epd2in7.py # * | Author : Waveshare team # * | Function : Electronic paper driver # * | Info : # *---------------- -# * | This version: V3.0 -# * | Date : 2018-11-06 -# * | Info : python2 demo -# * 1.Remove: -# digital_write(self, pin, value) -# digital_read(self, pin) -# delay_ms(self, delaytime) -# set_lut(self, lut) -# self.lut = self.lut_full_update -# * 2.Change: -# display_frame -> TurnOnDisplay -# set_memory_area -> SetWindow -# set_memory_pointer -> SetCursor -# get_frame_buffer -> getbuffer -# set_frame_memory -> display -# * 3.How to use -# epd = epd2in7.EPD() -# epd.init(epd.lut_full_update) -# image = Image.new('1', (epd1in54.EPD_WIDTH, epd1in54.EPD_HEIGHT), 255) -# ... -# drawing ...... -# ... -# epd.display(getbuffer(image)) -# ******************************************************************************/ +# * | This version: V4.0 +# * | Date : 2019-06-20 +# # | Info : python demo +# ----------------------------------------------------------------------------- # Permission is hereby granted, free of charge, to any person obtaining a copy # of this software and associated documnetation files (the "Software"), to deal # in the Software without restriction, including without limitation the rights @@ -47,64 +27,31 @@ # THE SOFTWARE. # - +import logging from . import epdconfig -from PIL import Image -import RPi.GPIO as GPIO # Display resolution EPD_WIDTH = 176 EPD_HEIGHT = 264 -# EPD2IN7 commands -PANEL_SETTING = 0x00 -POWER_SETTING = 0x01 -POWER_OFF = 0x02 -POWER_OFF_SEQUENCE_SETTING = 0x03 -POWER_ON = 0x04 -POWER_ON_MEASURE = 0x05 -BOOSTER_SOFT_START = 0x06 -DEEP_SLEEP = 0x07 -DATA_START_TRANSMISSION_1 = 0x10 -DATA_STOP = 0x11 -DISPLAY_REFRESH = 0x12 -DATA_START_TRANSMISSION_2 = 0x13 -PARTIAL_DATA_START_TRANSMISSION_1 = 0x14 -PARTIAL_DATA_START_TRANSMISSION_2 = 0x15 -PARTIAL_DISPLAY_REFRESH = 0x16 -LUT_FOR_VCOM = 0x20 -LUT_WHITE_TO_WHITE = 0x21 -LUT_BLACK_TO_WHITE = 0x22 -LUT_WHITE_TO_BLACK = 0x23 -LUT_BLACK_TO_BLACK = 0x24 -PLL_CONTROL = 0x30 -TEMPERATURE_SENSOR_COMMAND = 0x40 -TEMPERATURE_SENSOR_CALIBRATION = 0x41 -TEMPERATURE_SENSOR_WRITE = 0x42 -TEMPERATURE_SENSOR_READ = 0x43 -VCOM_AND_DATA_INTERVAL_SETTING = 0x50 -LOW_POWER_DETECTION = 0x51 -TCON_SETTING = 0x60 -TCON_RESOLUTION = 0x61 -SOURCE_AND_GATE_START_SETTING = 0x62 -GET_STATUS = 0x71 -AUTO_MEASURE_VCOM = 0x80 -VCOM_VALUE = 0x81 -VCM_DC_SETTING_REGISTER = 0x82 -PROGRAM_MODE = 0xA0 -ACTIVE_PROGRAM = 0xA1 -READ_OTP_DATA = 0xA2 - +GRAY1 = 0xff #white +GRAY2 = 0xC0 +GRAY3 = 0x80 #gray +GRAY4 = 0x00 #Blackest class EPD: def __init__(self): self.reset_pin = epdconfig.RST_PIN self.dc_pin = epdconfig.DC_PIN self.busy_pin = epdconfig.BUSY_PIN + self.cs_pin = epdconfig.CS_PIN self.width = EPD_WIDTH self.height = EPD_HEIGHT + self.GRAY1 = GRAY1 #white + self.GRAY2 = GRAY2 + self.GRAY3 = GRAY3 #gray + self.GRAY4 = GRAY4 #Blackest - lut_vcom_dc = [ - 0x00, 0x00, + lut_vcom_dc = [0x00, 0x00, 0x00, 0x08, 0x00, 0x00, 0x00, 0x02, 0x60, 0x28, 0x28, 0x00, 0x00, 0x01, 0x00, 0x14, 0x00, 0x00, 0x00, 0x01, @@ -148,147 +95,418 @@ class EPD: 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - ] + ] + ###################full screen update LUT###################### + #0~3 gray + gray_lut_vcom = [ + 0x00, 0x00, + 0x00, 0x0A, 0x00, 0x00, 0x00, 0x01, + 0x60, 0x14, 0x14, 0x00, 0x00, 0x01, + 0x00, 0x14, 0x00, 0x00, 0x00, 0x01, + 0x00, 0x13, 0x0A, 0x01, 0x00, 0x01, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + ] + #R21 + gray_lut_ww =[ + 0x40, 0x0A, 0x00, 0x00, 0x00, 0x01, + 0x90, 0x14, 0x14, 0x00, 0x00, 0x01, + 0x10, 0x14, 0x0A, 0x00, 0x00, 0x01, + 0xA0, 0x13, 0x01, 0x00, 0x00, 0x01, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + ] + #R22H r + gray_lut_bw =[ + 0x40, 0x0A, 0x00, 0x00, 0x00, 0x01, + 0x90, 0x14, 0x14, 0x00, 0x00, 0x01, + 0x00, 0x14, 0x0A, 0x00, 0x00, 0x01, + 0x99, 0x0C, 0x01, 0x03, 0x04, 0x01, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + ] + #R23H w + gray_lut_wb =[ + 0x40, 0x0A, 0x00, 0x00, 0x00, 0x01, + 0x90, 0x14, 0x14, 0x00, 0x00, 0x01, + 0x00, 0x14, 0x0A, 0x00, 0x00, 0x01, + 0x99, 0x0B, 0x04, 0x04, 0x01, 0x01, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + ] + #R24H b + gray_lut_bb =[ + 0x80, 0x0A, 0x00, 0x00, 0x00, 0x01, + 0x90, 0x14, 0x14, 0x00, 0x00, 0x01, + 0x20, 0x14, 0x0A, 0x00, 0x00, 0x01, + 0x50, 0x13, 0x01, 0x00, 0x00, 0x01, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + ] + # Hardware reset def reset(self): - epdconfig.digital_write(self.reset_pin, GPIO.HIGH) + epdconfig.digital_write(self.reset_pin, 1) epdconfig.delay_ms(200) - epdconfig.digital_write(self.reset_pin, GPIO.LOW) # module reset - epdconfig.delay_ms(200) - epdconfig.digital_write(self.reset_pin, GPIO.HIGH) + epdconfig.digital_write(self.reset_pin, 0) + epdconfig.delay_ms(10) + epdconfig.digital_write(self.reset_pin, 1) epdconfig.delay_ms(200) def send_command(self, command): - epdconfig.digital_write(self.dc_pin, GPIO.LOW) + epdconfig.digital_write(self.dc_pin, 0) + epdconfig.digital_write(self.cs_pin, 0) epdconfig.spi_writebyte([command]) + epdconfig.digital_write(self.cs_pin, 1) def send_data(self, data): - epdconfig.digital_write(self.dc_pin, GPIO.HIGH) + epdconfig.digital_write(self.dc_pin, 1) + epdconfig.digital_write(self.cs_pin, 0) epdconfig.spi_writebyte([data]) + epdconfig.digital_write(self.cs_pin, 1) - def wait_until_idle(self): - while(epdconfig.digital_read(self.busy_pin) == 0): # 0: idle, 1: busy - self.send_command(0x71) - epdconfig.delay_ms(100) + def ReadBusy(self): + logging.debug("e-Paper busy") + while(epdconfig.digital_read(self.busy_pin) == 0): # 0: idle, 1: busy + epdconfig.delay_ms(200) + logging.debug("e-Paper busy release") def set_lut(self): - self.send_command(LUT_FOR_VCOM) # vcom + self.send_command(0x20) # vcom for count in range(0, 44): self.send_data(self.lut_vcom_dc[count]) - self.send_command(LUT_WHITE_TO_WHITE) # ww -- + self.send_command(0x21) # ww -- for count in range(0, 42): self.send_data(self.lut_ww[count]) - self.send_command(LUT_BLACK_TO_WHITE) # bw r + self.send_command(0x22) # bw r for count in range(0, 42): self.send_data(self.lut_bw[count]) - self.send_command(LUT_WHITE_TO_BLACK) # wb w + self.send_command(0x23) # wb w for count in range(0, 42): self.send_data(self.lut_bb[count]) - self.send_command(LUT_BLACK_TO_BLACK) # bb b + self.send_command(0x24) # bb b for count in range(0, 42): self.send_data(self.lut_wb[count]) + def gray_SetLut(self): + self.send_command(0x20) + for count in range(0, 44): #vcom + self.send_data(self.gray_lut_vcom[count]) + + self.send_command(0x21) #red not use + for count in range(0, 42): + self.send_data(self.gray_lut_ww[count]) + + self.send_command(0x22) #bw r + for count in range(0, 42): + self.send_data(self.gray_lut_bw[count]) + + self.send_command(0x23) #wb w + for count in range(0, 42): + self.send_data(self.gray_lut_wb[count]) + + self.send_command(0x24) #bb b + for count in range(0, 42): + self.send_data(self.gray_lut_bb[count]) + + self.send_command(0x25) #vcom + for count in range(0, 42): + self.send_data(self.gray_lut_ww[count]) + def init(self): if (epdconfig.module_init() != 0): return -1 + # EPD hardware init start self.reset() - self.send_command(POWER_SETTING) - self.send_data(0x03) # VDS_EN, VDG_EN - self.send_data(0x00) # VCOM_HV, VGHL_LV[1], VGHL_LV[0] - self.send_data(0x2b) # VDH - self.send_data(0x2b) # VDL - self.send_data(0x09) # VDHR - self.send_command(BOOSTER_SOFT_START) + + self.send_command(0x01) # POWER_SETTING + self.send_data(0x03) # VDS_EN, VDG_EN + self.send_data(0x00) # VCOM_HV, VGHL_LV[1], VGHL_LV[0] + self.send_data(0x2b) # VDH + self.send_data(0x2b) # VDL + self.send_data(0x09) # VDHR + + self.send_command(0x06) # BOOSTER_SOFT_START self.send_data(0x07) self.send_data(0x07) self.send_data(0x17) + # Power optimization self.send_command(0xF8) self.send_data(0x60) self.send_data(0xA5) + # Power optimization self.send_command(0xF8) self.send_data(0x89) self.send_data(0xA5) + # Power optimization self.send_command(0xF8) self.send_data(0x90) self.send_data(0x00) + # Power optimization self.send_command(0xF8) self.send_data(0x93) self.send_data(0x2A) + # Power optimization self.send_command(0xF8) self.send_data(0xA0) self.send_data(0xA5) + # Power optimization self.send_command(0xF8) self.send_data(0xA1) self.send_data(0x00) + # Power optimization self.send_command(0xF8) self.send_data(0x73) self.send_data(0x41) - self.send_command(PARTIAL_DISPLAY_REFRESH) + + self.send_command(0x16) # PARTIAL_DISPLAY_REFRESH self.send_data(0x00) - self.send_command(POWER_ON) - self.wait_until_idle() + self.send_command(0x04) # POWER_ON + self.ReadBusy() - self.send_command(PANEL_SETTING) - self.send_data(0xAF) # KW-BF KWR-AF BWROTP 0f - self.send_command(PLL_CONTROL) - self.send_data(0x3A) # 3A 100HZ 29 150Hz 39 200HZ 31 171HZ - self.send_command(VCM_DC_SETTING_REGISTER) + self.send_command(0x00) # PANEL_SETTING + self.send_data(0xAF) # KW-BF KWR-AF BWROTP 0f + + self.send_command(0x30) # PLL_CONTROL + self.send_data(0x3A) # 3A 100HZ 29 150Hz 39 200HZ 31 171HZ + + self.send_command(0x82) # VCM_DC_SETTING_REGISTER self.send_data(0x12) self.set_lut() - # EPD hardware init end return 0 + def Init_4Gray(self): + if (epdconfig.module_init() != 0): + return -1 + self.reset() + + self.send_command(0x01) #POWER SETTING + self.send_data (0x03) + self.send_data (0x00) + self.send_data (0x2b) + self.send_data (0x2b) + + + self.send_command(0x06) #booster soft start + self.send_data (0x07) #A + self.send_data (0x07) #B + self.send_data (0x17) #C + + self.send_command(0xF8) #boost?? + self.send_data (0x60) + self.send_data (0xA5) + + self.send_command(0xF8) #boost?? + self.send_data (0x89) + self.send_data (0xA5) + + self.send_command(0xF8) #boost?? + self.send_data (0x90) + self.send_data (0x00) + + self.send_command(0xF8) #boost?? + self.send_data (0x93) + self.send_data (0x2A) + + self.send_command(0xF8) #boost?? + self.send_data (0xa0) + self.send_data (0xa5) + + self.send_command(0xF8) #boost?? + self.send_data (0xa1) + self.send_data (0x00) + + self.send_command(0xF8) #boost?? + self.send_data (0x73) + self.send_data (0x41) + + self.send_command(0x16) + self.send_data(0x00) + + self.send_command(0x04) + self.ReadBusy() + + self.send_command(0x00) #panel setting + self.send_data(0xbf) #KW-BF KWR-AF BWROTP 0f + + self.send_command(0x30) #PLL setting + self.send_data (0x90) #100hz + + self.send_command(0x61) #resolution setting + self.send_data (0x00) #176 + self.send_data (0xb0) + self.send_data (0x01) #264 + self.send_data (0x08) + + self.send_command(0x82) #vcom_DC setting + self.send_data (0x12) + + self.send_command(0X50) #VCOM AND DATA INTERVAL SETTING + self.send_data(0x97) + def getbuffer(self, image): - # print "bufsiz = ",(self.width/8) * self.height - buf = [0xFF] * ((self.width//8) * self.height) + # logging.debug("bufsiz = ",int(self.width/8) * self.height) + buf = [0xFF] * (int(self.width/8) * self.height) image_monocolor = image.convert('1') imwidth, imheight = image_monocolor.size pixels = image_monocolor.load() - # print "imwidth = %d, imheight = %d",imwidth,imheight + # logging.debug("imwidth = %d, imheight = %d",imwidth,imheight) if(imwidth == self.width and imheight == self.height): - print("Vertical") + logging.debug("Vertical") for y in range(imheight): for x in range(imwidth): # Set the bits for the column of pixels at the current position. if pixels[x, y] == 0: - buf[(x + y * self.width) // 8] &= ~(0x80 >> (x % 8)) + buf[int((x + y * self.width) / 8)] &= ~(0x80 >> (x % 8)) elif(imwidth == self.height and imheight == self.width): - print("Horizontal") + logging.debug("Horizontal") for y in range(imheight): for x in range(imwidth): newx = y newy = self.height - x - 1 if pixels[x, y] == 0: - buf[(newx + newy*self.width) // 8] &= ~(0x80 >> (y % 8)) + buf[int((newx + newy*self.width) / 8)] &= ~(0x80 >> (y % 8)) return buf - + + def getbuffer_4Gray(self, image): + # logging.debug("bufsiz = ",int(self.width/8) * self.height) + buf = [0xFF] * (int(self.width / 4) * self.height) + image_monocolor = image.convert('L') + imwidth, imheight = image_monocolor.size + pixels = image_monocolor.load() + i=0 + # logging.debug("imwidth = %d, imheight = %d",imwidth,imheight) + if(imwidth == self.width and imheight == self.height): + logging.debug("Vertical") + for y in range(imheight): + for x in range(imwidth): + # Set the bits for the column of pixels at the current position. + if(pixels[x, y] == 0xC0): + pixels[x, y] = 0x80 + elif (pixels[x, y] == 0x80): + pixels[x, y] = 0x40 + i= i+1 + if(i%4 == 0): + buf[int((x + (y * self.width))/4)] = ((pixels[x-3, y]&0xc0) | (pixels[x-2, y]&0xc0)>>2 | (pixels[x-1, y]&0xc0)>>4 | (pixels[x, y]&0xc0)>>6) + + elif(imwidth == self.height and imheight == self.width): + logging.debug("Horizontal") + for x in range(imwidth): + for y in range(imheight): + newx = y + newy = x + if(pixels[x, y] == 0xC0): + pixels[x, y] = 0x80 + elif (pixels[x, y] == 0x80): + pixels[x, y] = 0x40 + i= i+1 + if(i%4 == 0): + buf[int((newx + (newy * self.width))/4)] = ((pixels[x, y-3]&0xc0) | (pixels[x, y-2]&0xc0)>>2 | (pixels[x, y-1]&0xc0)>>4 | (pixels[x, y]&0xc0)>>6) + return buf + def display(self, image): - self.send_command(DATA_START_TRANSMISSION_1) - for i in range(0, self.width * self.height // 8): + self.send_command(0x10) + for i in range(0, int(self.width * self.height / 8)): self.send_data(0xFF) - self.send_command(DATA_START_TRANSMISSION_2) - for i in range(0, self.width * self.height // 8): + self.send_command(0x13) + for i in range(0, int(self.width * self.height / 8)): self.send_data(image[i]) - self.send_command(DISPLAY_REFRESH) - self.wait_until_idle() + self.send_command(0x12) + self.ReadBusy() + + def display_4Gray(self, image): + self.send_command(0x10) + for i in range(0, 5808): #5808*4 46464 + temp3=0 + for j in range(0, 2): + temp1 = image[i*2+j] + for k in range(0, 2): + temp2 = temp1&0xC0 + if(temp2 == 0xC0): + temp3 |= 0x01#white + elif(temp2 == 0x00): + temp3 |= 0x00 #black + elif(temp2 == 0x80): + temp3 |= 0x01 #gray1 + else: #0x40 + temp3 |= 0x00 #gray2 + temp3 <<= 1 + + temp1 <<= 2 + temp2 = temp1&0xC0 + if(temp2 == 0xC0): #white + temp3 |= 0x01 + elif(temp2 == 0x00): #black + temp3 |= 0x00 + elif(temp2 == 0x80): + temp3 |= 0x01 #gray1 + else : #0x40 + temp3 |= 0x00 #gray2 + if(j!=1 or k!=1): + temp3 <<= 1 + temp1 <<= 2 + self.send_data(temp3) + + self.send_command(0x13) + for i in range(0, 5808): #5808*4 46464 + temp3=0 + for j in range(0, 2): + temp1 = image[i*2+j] + for k in range(0, 2): + temp2 = temp1&0xC0 + if(temp2 == 0xC0): + temp3 |= 0x01#white + elif(temp2 == 0x00): + temp3 |= 0x00 #black + elif(temp2 == 0x80): + temp3 |= 0x00 #gray1 + else: #0x40 + temp3 |= 0x01 #gray2 + temp3 <<= 1 + + temp1 <<= 2 + temp2 = temp1&0xC0 + if(temp2 == 0xC0): #white + temp3 |= 0x01 + elif(temp2 == 0x00): #black + temp3 |= 0x00 + elif(temp2 == 0x80): + temp3 |= 0x00 #gray1 + else: #0x40 + temp3 |= 0x01 #gray2 + if(j!=1 or k!=1): + temp3 <<= 1 + temp1 <<= 2 + self.send_data(temp3) + + self.gray_SetLut() + self.send_command(0x12) + epdconfig.delay_ms(200) + self.ReadBusy() + # pass def Clear(self, color): - self.send_command(DATA_START_TRANSMISSION_1) - for i in range(0, self.width * self.height // 8): + self.send_command(0x10) + for i in range(0, int(self.width * self.height / 8)): self.send_data(0xFF) - self.send_command(DATA_START_TRANSMISSION_2) - for i in range(0, self.width * self.height // 8): + self.send_command(0x13) + for i in range(0, int(self.width * self.height / 8)): self.send_data(0xFF) - self.send_command(DISPLAY_REFRESH) - self.wait_until_idle() + self.send_command(0x12) + self.ReadBusy() def sleep(self): self.send_command(0X50) @@ -296,5 +514,7 @@ class EPD: self.send_command(0X02) self.send_command(0X07) self.send_data(0xA5) + + epdconfig.module_exit() ### END OF FILE ### diff --git a/pwnagotchi/ui/hw/libs/waveshare/v27inch/epdconfig.py b/pwnagotchi/ui/hw/libs/waveshare/v27inch/epdconfig.py index 78ff6479..861f43da 100644 --- a/pwnagotchi/ui/hw/libs/waveshare/v27inch/epdconfig.py +++ b/pwnagotchi/ui/hw/libs/waveshare/v27inch/epdconfig.py @@ -1,19 +1,13 @@ # /***************************************************************************** -# * | File : EPD_1in54.py +# * | File : epdconfig.py # * | Author : Waveshare team # * | Function : Hardware underlying interface # * | Info : # *---------------- -# * | This version: V2.0 -# * | Date : 2018-11-01 +# * | This version: V1.0 +# * | Date : 2019-06-21 # * | Info : -# * 1.Remove: -# digital_write(self, pin, value) -# digital_read(self, pin) -# delay_ms(self, delaytime) -# set_lut(self, lut) -# self.lut = self.lut_full_update -# ******************************************************************************/ +# ****************************************************************************** # Permission is hereby granted, free of charge, to any person obtaining a copy # of this software and associated documnetation files (the "Software"), to deal # in the Software without restriction, including without limitation the rights @@ -33,41 +27,128 @@ # THE SOFTWARE. # - -import spidev -import RPi.GPIO as GPIO +import os +import logging +import sys import time -# Pin definition -RST_PIN = 17 -DC_PIN = 25 -CS_PIN = 8 -BUSY_PIN = 24 -# SPI device, bus = 0, device = 0 -SPI = spidev.SpiDev(0, 0) +class RaspberryPi: + # Pin definition + RST_PIN = 17 + DC_PIN = 25 + CS_PIN = 8 + BUSY_PIN = 24 -def digital_write(pin, value): - GPIO.output(pin, value) + def __init__(self): + import spidev + import RPi.GPIO -def digital_read(pin): - return GPIO.input(BUSY_PIN) + self.GPIO = RPi.GPIO -def delay_ms(delaytime): - time.sleep(delaytime / 1000.0) + # SPI device, bus = 0, device = 0 + self.SPI = spidev.SpiDev(0, 0) -def spi_writebyte(data): - SPI.writebytes(data) + def digital_write(self, pin, value): + self.GPIO.output(pin, value) + + def digital_read(self, pin): + return self.GPIO.input(pin) + + def delay_ms(self, delaytime): + time.sleep(delaytime / 1000.0) + + def spi_writebyte(self, data): + self.SPI.writebytes(data) + + def module_init(self): + self.GPIO.setmode(self.GPIO.BCM) + self.GPIO.setwarnings(False) + self.GPIO.setup(self.RST_PIN, self.GPIO.OUT) + self.GPIO.setup(self.DC_PIN, self.GPIO.OUT) + self.GPIO.setup(self.CS_PIN, self.GPIO.OUT) + self.GPIO.setup(self.BUSY_PIN, self.GPIO.IN) + self.SPI.max_speed_hz = 4000000 + self.SPI.mode = 0b00 + return 0 + + def module_exit(self): + logging.debug("spi end") + self.SPI.close() + + logging.debug("close 5V, Module enters 0 power consumption ...") + self.GPIO.output(self.RST_PIN, 0) + self.GPIO.output(self.DC_PIN, 0) + + self.GPIO.cleanup() + + +class JetsonNano: + # Pin definition + RST_PIN = 17 + DC_PIN = 25 + CS_PIN = 8 + BUSY_PIN = 24 + + def __init__(self): + import ctypes + find_dirs = [ + os.path.dirname(os.path.realpath(__file__)), + '/usr/local/lib', + '/usr/lib', + ] + self.SPI = None + for find_dir in find_dirs: + so_filename = os.path.join(find_dir, 'sysfs_software_spi.so') + if os.path.exists(so_filename): + self.SPI = ctypes.cdll.LoadLibrary(so_filename) + break + if self.SPI is None: + raise RuntimeError('Cannot find sysfs_software_spi.so') + + import Jetson.GPIO + self.GPIO = Jetson.GPIO + + def digital_write(self, pin, value): + self.GPIO.output(pin, value) + + def digital_read(self, pin): + return self.GPIO.input(self.BUSY_PIN) + + def delay_ms(self, delaytime): + time.sleep(delaytime / 1000.0) + + def spi_writebyte(self, data): + self.SPI.SYSFS_software_spi_transfer(data[0]) + + def module_init(self): + self.GPIO.setmode(self.GPIO.BCM) + self.GPIO.setwarnings(False) + self.GPIO.setup(self.RST_PIN, self.GPIO.OUT) + self.GPIO.setup(self.DC_PIN, self.GPIO.OUT) + self.GPIO.setup(self.CS_PIN, self.GPIO.OUT) + self.GPIO.setup(self.BUSY_PIN, self.GPIO.IN) + self.SPI.SYSFS_software_spi_begin() + return 0 + + def module_exit(self): + logging.debug("spi end") + self.SPI.SYSFS_software_spi_end() + + logging.debug("close 5V, Module enters 0 power consumption ...") + self.GPIO.output(self.RST_PIN, 0) + self.GPIO.output(self.DC_PIN, 0) + + self.GPIO.cleanup() + + +if os.path.exists('/sys/bus/platform/drivers/gpiomem-bcm2835'): + implementation = RaspberryPi() +else: + implementation = JetsonNano() + +for func in [x for x in dir(implementation) if not x.startswith('_')]: + setattr(sys.modules[__name__], func, getattr(implementation, func)) -def module_init(): - GPIO.setmode(GPIO.BCM) - GPIO.setwarnings(False) - GPIO.setup(RST_PIN, GPIO.OUT) - GPIO.setup(DC_PIN, GPIO.OUT) - GPIO.setup(CS_PIN, GPIO.OUT) - GPIO.setup(BUSY_PIN, GPIO.IN) - SPI.max_speed_hz = 2000000 - SPI.mode = 0b00 - return 0; ### END OF FILE ### From beb2b83f361b109ed155e8a7d53e02fa6c874586 Mon Sep 17 00:00:00 2001 From: PhyberApex Date: Sat, 26 Oct 2019 02:12:37 +0200 Subject: [PATCH 076/168] Minor text fixes There is no plural of information. --- pwnagotchi/plugins/default/wigle.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pwnagotchi/plugins/default/wigle.py b/pwnagotchi/plugins/default/wigle.py index 1ce072dc..462cdab4 100644 --- a/pwnagotchi/plugins/default/wigle.py +++ b/pwnagotchi/plugins/default/wigle.py @@ -155,7 +155,7 @@ def on_internet_available(agent): continue if gps_data['Latitude'] == 0 and gps_data['Longitude'] == 0: - logging.warning("WIGLE: Not enough gps-informations for %s. Trying again next time.", gps_file) + logging.warning("WIGLE: Not enough gps-information for %s. Trying again next time.", gps_file) SKIP.append(gps_file) continue @@ -167,7 +167,7 @@ def on_internet_available(agent): WifiInfo.CHANNEL, WifiInfo.RSSI]) except FieldNotFoundError: - logging.error("WIGLE: Could not extract all informations. Skip %s", gps_file) + logging.error("WIGLE: Could not extract all information. Skip %s", gps_file) SKIP.append(gps_file) continue except Scapy_Exception as sc_e: From 0a33f9c0af67206db0a38c88c5735948140917e1 Mon Sep 17 00:00:00 2001 From: Simone Margaritelli Date: Sat, 26 Oct 2019 10:31:02 +0200 Subject: [PATCH 077/168] new: bump bettercap version to 2.26.1 --- builder/pwnagotchi.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/builder/pwnagotchi.yml b/builder/pwnagotchi.yml index 17f12815..fa332895 100644 --- a/builder/pwnagotchi.yml +++ b/builder/pwnagotchi.yml @@ -33,7 +33,7 @@ - ifup@wlan0.service packages: bettercap: - url: "https://github.com/bettercap/bettercap/releases/download/v2.26/bettercap_linux_armhf_v2.26.zip" + url: "https://github.com/bettercap/bettercap/releases/download/v2.26.1/bettercap_linux_armhf_v2.26.1.zip" ui: "https://github.com/bettercap/ui/releases/download/v1.3.0/ui.zip" pwngrid: url: "https://github.com/evilsocket/pwngrid/releases/download/v1.10.1/pwngrid_linux_armhf_v1.10.1.zip" From 99f6758aaef4d9331e8fcb00846755884ac1b7e3 Mon Sep 17 00:00:00 2001 From: Simone Margaritelli Date: Sat, 26 Oct 2019 14:14:43 +0200 Subject: [PATCH 078/168] releasing v1.1.0RC0 --- pwnagotchi/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pwnagotchi/__init__.py b/pwnagotchi/__init__.py index f9ea149c..3f11d4cb 100644 --- a/pwnagotchi/__init__.py +++ b/pwnagotchi/__init__.py @@ -6,7 +6,7 @@ import re import pwnagotchi.ui.view as view import pwnagotchi -version = '1.1.0b' +version = '1.1.0RC0' _name = None From 5643f9ae70ea51e38bb6afe63ac0c29872058435 Mon Sep 17 00:00:00 2001 From: python273 Date: Sat, 26 Oct 2019 17:07:47 +0300 Subject: [PATCH 079/168] Set CORS headers only if set in config --- pwnagotchi/defaults.yml | 2 +- pwnagotchi/ui/web.py | 33 +++++++++++++++++---------------- pwnagotchi/utils.py | 6 +++--- 3 files changed, 21 insertions(+), 20 deletions(-) diff --git a/pwnagotchi/defaults.yml b/pwnagotchi/defaults.yml index e08906be..ca0c74ca 100644 --- a/pwnagotchi/defaults.yml +++ b/pwnagotchi/defaults.yml @@ -211,7 +211,7 @@ ui: video: enabled: true address: '0.0.0.0' - origin: '*' + origin: null port: 8080 # command to be executed when a new png frame is available # for instance, to use with framebuffer based displays: diff --git a/pwnagotchi/ui/web.py b/pwnagotchi/ui/web.py index 8a142301..716a2c0b 100644 --- a/pwnagotchi/ui/web.py +++ b/pwnagotchi/ui/web.py @@ -75,7 +75,7 @@ SHUTDOWN = """ class Handler(BaseHTTPRequestHandler): - AllowedOrigin = '*' + AllowedOrigin = None # CORS headers are not sent # suppress internal logging def log_message(self, format, *args): @@ -88,12 +88,13 @@ class Handler(BaseHTTPRequestHandler): self.send_header("X-XSS-Protection", "1; mode=block") self.send_header("Referrer-Policy", "same-origin") # cors - self.send_header("Access-Control-Allow-Origin", Handler.AllowedOrigin) - self.send_header('Access-Control-Allow-Credentials', 'true') - self.send_header("Access-Control-Allow-Methods", "GET, POST, PUT, DELETE, OPTIONS") - self.send_header("Access-Control-Allow-Headers", - "Accept, Content-Type, Content-Length, Accept-Encoding, X-CSRF-Token, Authorization") - self.send_header("Vary", "Origin") + if Handler.AllowedOrigin: + self.send_header("Access-Control-Allow-Origin", Handler.AllowedOrigin) + self.send_header('Access-Control-Allow-Credentials', 'true') + self.send_header("Access-Control-Allow-Methods", "GET, POST, PUT, DELETE, OPTIONS") + self.send_header("Access-Control-Allow-Headers", + "Accept, Content-Type, Content-Length, Accept-Encoding, X-CSRF-Token, Authorization") + self.send_header("Vary", "Origin") # just render some html in a 200 response def _html(self, html): @@ -132,15 +133,18 @@ class Handler(BaseHTTPRequestHandler): # check the Origin header vs CORS def _is_allowed(self): + if not Handler.AllowedOrigin or Handler.AllowedOrigin == '*': + return True + + # TODO: FIX doesn't work with GET requests same-origin origin = self.headers.get('origin') - if not origin and Handler.AllowedOrigin != '*': + if not origin: logging.warning("request with no Origin header from %s" % self.address_string()) return False - if Handler.AllowedOrigin != '*': - if origin != Handler.AllowedOrigin: - logging.warning("request with blocked Origin from %s: %s" % (self.address_string(), origin)) - return False + if origin != Handler.AllowedOrigin: + logging.warning("request with blocked Origin from %s: %s" % (self.address_string(), origin)) + return False return True @@ -186,11 +190,8 @@ class Server(object): self._address = config['video']['address'] self._httpd = None - if 'origin' in config['video'] and config['video']['origin'] != '*': + if 'origin' in config['video']: Handler.AllowedOrigin = config['video']['origin'] - else: - logging.warning("THE WEB UI IS RUNNING WITH ALLOWED ORIGIN SET TO *, READ WHY YOU SHOULD CHANGE IT HERE " + - "https://developer.mozilla.org/it/docs/Web/HTTP/CORS") if self._enabled: _thread.start_new_thread(self._http_serve, ()) diff --git a/pwnagotchi/utils.py b/pwnagotchi/utils.py index 392d2fd7..25aab13a 100644 --- a/pwnagotchi/utils.py +++ b/pwnagotchi/utils.py @@ -79,7 +79,7 @@ def load_config(args): elif config['ui']['display']['type'] in ('papirus', 'papi'): config['ui']['display']['type'] = 'papirus' - elif config['ui']['display']['type'] in ('oledhat'): + elif config['ui']['display']['type'] in ('oledhat',): config['ui']['display']['type'] = 'oledhat' elif config['ui']['display']['type'] in ('ws_1', 'ws1', 'waveshare_1', 'waveshare1'): @@ -91,9 +91,9 @@ def load_config(args): elif config['ui']['display']['type'] in ('ws_27inch', 'ws27inch', 'waveshare_27inch', 'waveshare27inch'): config['ui']['display']['type'] = 'waveshare27inch' - elif config['ui']['display']['type'] in ('lcdhat'): + elif config['ui']['display']['type'] in ('lcdhat',): config['ui']['display']['type'] = 'lcdhat' - + else: print("unsupported display type %s" % config['ui']['display']['type']) exit(1) From 063cc736893f000d80a83034cf1fe66af23fae62 Mon Sep 17 00:00:00 2001 From: Simone Margaritelli Date: Sat, 26 Oct 2019 16:39:49 +0200 Subject: [PATCH 080/168] misc: small fix or general refactoring i did not bother commenting --- pwnagotchi/ui/view.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pwnagotchi/ui/view.py b/pwnagotchi/ui/view.py index e7fb80c1..d529eca7 100644 --- a/pwnagotchi/ui/view.py +++ b/pwnagotchi/ui/view.py @@ -216,7 +216,7 @@ class View(object): face = random.choice((faces.MOTIVATED, faces.FRIEND, faces.HAPPY)) # normal friend, neutral-positive else: - face = random.choice((faces.EXCITED, faces.HAPPY)) + face = random.choice((faces.EXCITED, faces.HAPPY, faces.SMART)) self.set('face', face) self.set('status', self._voice.on_new_peer(peer)) From 546c7fe3974e9417fad6409386dcfc884988b6e8 Mon Sep 17 00:00:00 2001 From: Simone Margaritelli Date: Sat, 26 Oct 2019 17:15:42 +0200 Subject: [PATCH 081/168] fix: chown backup file (fixes #409) --- scripts/backup.sh | 2 ++ 1 file changed, 2 insertions(+) diff --git a/scripts/backup.sh b/scripts/backup.sh index 71c4f612..9d19d8ee 100755 --- a/scripts/backup.sh +++ b/scripts/backup.sh @@ -33,6 +33,8 @@ for file in "${FILES_TO_BACKUP[@]}"; do ssh pi@$UNIT_HOSTNAME "sudo cp -r $file /tmp/backup$dir" > /dev/null done +ssh pi@$UNIT_HOSTNAME "sudo chown pi:pi -R /tmp/backup" > /dev/null + echo "@ pulling from $UNIT_HOSTNAME ..." rm -rf /tmp/backup From ea51ab70146bf82f059f5918e903ba26fee41fcd Mon Sep 17 00:00:00 2001 From: Edward Medvedev Date: Sat, 26 Oct 2019 18:36:42 +0300 Subject: [PATCH 082/168] Optimize plugin imports Signed-off-by: Edward Medvedev --- pwnagotchi/plugins/default/AircrackOnly.py | 1 - pwnagotchi/plugins/default/grid.py | 8 ++------ pwnagotchi/plugins/default/paw-gps.py | 1 - 3 files changed, 2 insertions(+), 8 deletions(-) diff --git a/pwnagotchi/plugins/default/AircrackOnly.py b/pwnagotchi/plugins/default/AircrackOnly.py index a8402097..e6860810 100644 --- a/pwnagotchi/plugins/default/AircrackOnly.py +++ b/pwnagotchi/plugins/default/AircrackOnly.py @@ -12,7 +12,6 @@ Aircrack-ng needed, to install: import logging import subprocess import string -import re import os OPTIONS = dict() diff --git a/pwnagotchi/plugins/default/grid.py b/pwnagotchi/plugins/default/grid.py index e0c6aa96..c15b4658 100644 --- a/pwnagotchi/plugins/default/grid.py +++ b/pwnagotchi/plugins/default/grid.py @@ -11,14 +11,10 @@ import time import glob import pwnagotchi.grid as grid -import pwnagotchi.utils as utils -from pwnagotchi.ui.components import LabeledValue -from pwnagotchi.ui.view import BLACK -import pwnagotchi.ui.fonts as fonts -from pwnagotchi.utils import WifiInfo, extract_from_pcap +from pwnagotchi.utils import StatusFile, WifiInfo, extract_from_pcap OPTIONS = dict() -REPORT = utils.StatusFile('/root/.api-report.json', data_format='json') +REPORT = StatusFile('/root/.api-report.json', data_format='json') UNREAD_MESSAGES = 0 TOTAL_MESSAGES = 0 diff --git a/pwnagotchi/plugins/default/paw-gps.py b/pwnagotchi/plugins/default/paw-gps.py index 02c827e6..a5268c42 100644 --- a/pwnagotchi/plugins/default/paw-gps.py +++ b/pwnagotchi/plugins/default/paw-gps.py @@ -10,7 +10,6 @@ https://raw.githubusercontent.com/systemik/pwnagotchi-bt-tether/master/GPS-via-P ''' import logging -import json import requests OPTIONS = dict() From d984ea8a76150746f00ca14eceb154bf47d71292 Mon Sep 17 00:00:00 2001 From: Justin Richards Date: Sat, 26 Oct 2019 12:52:07 -0500 Subject: [PATCH 083/168] adding support for dfrobot 2.13in epaper display Signed-off-by: Justin Richards --- pwnagotchi/ui/display.py | 3 + pwnagotchi/ui/hw/__init__.py | 3 + pwnagotchi/ui/hw/dfrobot.py | 43 ++ pwnagotchi/ui/hw/libs/dfrobot/LICENSE | 504 ++++++++++++++++++ pwnagotchi/ui/hw/libs/dfrobot/__init__.py | 0 pwnagotchi/ui/hw/libs/dfrobot/dfrobot.py | 66 +++ .../ui/hw/libs/dfrobot/dfrobot_epaper.py | 208 ++++++++ pwnagotchi/ui/hw/libs/dfrobot/gpio.py | 64 +++ pwnagotchi/ui/hw/libs/dfrobot/spi.py | 21 + pwnagotchi/utils.py | 3 + 10 files changed, 915 insertions(+) create mode 100644 pwnagotchi/ui/hw/dfrobot.py create mode 100644 pwnagotchi/ui/hw/libs/dfrobot/LICENSE create mode 100644 pwnagotchi/ui/hw/libs/dfrobot/__init__.py create mode 100644 pwnagotchi/ui/hw/libs/dfrobot/dfrobot.py create mode 100644 pwnagotchi/ui/hw/libs/dfrobot/dfrobot_epaper.py create mode 100644 pwnagotchi/ui/hw/libs/dfrobot/gpio.py create mode 100644 pwnagotchi/ui/hw/libs/dfrobot/spi.py diff --git a/pwnagotchi/ui/display.py b/pwnagotchi/ui/display.py index c28b4d75..d218044a 100644 --- a/pwnagotchi/ui/display.py +++ b/pwnagotchi/ui/display.py @@ -48,6 +48,9 @@ class Display(View): def is_lcdhat(self): return self._implementation.name == 'lcdhat' + def is_dfrobot(self): + return self._implementation.name == 'dfrobot' + def is_waveshare_any(self): return self.is_waveshare_v1() or self.is_waveshare_v2() diff --git a/pwnagotchi/ui/hw/__init__.py b/pwnagotchi/ui/hw/__init__.py index 1e0bf31a..66e50e43 100644 --- a/pwnagotchi/ui/hw/__init__.py +++ b/pwnagotchi/ui/hw/__init__.py @@ -2,6 +2,7 @@ from pwnagotchi.ui.hw.inky import Inky from pwnagotchi.ui.hw.papirus import Papirus from pwnagotchi.ui.hw.oledhat import OledHat from pwnagotchi.ui.hw.lcdhat import LcdHat +from pwnagotchi.ui.hw.dfrobot import DFRobot from pwnagotchi.ui.hw.waveshare1 import WaveshareV1 from pwnagotchi.ui.hw.waveshare2 import WaveshareV2 from pwnagotchi.ui.hw.waveshare27inch import Waveshare27inch @@ -21,6 +22,8 @@ def display_for(config): if config['ui']['display']['type'] == 'lcdhat': return LcdHat(config) + if config['ui']['display']['type'] == 'dfrobot': + return DFRobot(config) elif config['ui']['display']['type'] == 'waveshare_1': return WaveshareV1(config) diff --git a/pwnagotchi/ui/hw/dfrobot.py b/pwnagotchi/ui/hw/dfrobot.py new file mode 100644 index 00000000..b79c7faa --- /dev/null +++ b/pwnagotchi/ui/hw/dfrobot.py @@ -0,0 +1,43 @@ +import logging + +import pwnagotchi.ui.fonts as fonts +from pwnagotchi.ui.hw.base import DisplayImpl + +class DFRobot(DisplayImpl): + def __init__(self, config): + super(DFRobot, self).__init__(config, 'dfrobot') + self._display = None + + def layout(self): + fonts.setup(10, 9, 10, 35) + self._layout['width'] = 250 + self._layout['height'] = 122 + self._layout['face'] = (0, 40) + self._layout['name'] = (5, 20) + self._layout['channel'] = (0, 0) + self._layout['aps'] = (28, 0) + self._layout['uptime'] = (185, 0) + self._layout['line1'] = [0, 14, 250, 14] + self._layout['line2'] = [0, 108, 250, 108] + self._layout['friend_face'] = (0, 92) + self._layout['friend_name'] = (40, 94) + self._layout['shakes'] = (0, 109) + self._layout['mode'] = (225, 109) + self._layout['status'] = { + 'pos': (125, 20), + 'font': fonts.Medium, + 'max': 20 + } + return self._layout + + def initialize(self): + logging.info("initializing dfrobot display") + from pwnagotchi.ui.hw.libs.dfrobot.dfrobot import DFRobot + self._display = DFRobot() + + def render(self, canvas): + buf = self._display.getbuffer(canvas) + self._display.display(buf) + + def clear(self): + self._display.Clear(0xFF) \ No newline at end of file diff --git a/pwnagotchi/ui/hw/libs/dfrobot/LICENSE b/pwnagotchi/ui/hw/libs/dfrobot/LICENSE new file mode 100644 index 00000000..8000a6fa --- /dev/null +++ b/pwnagotchi/ui/hw/libs/dfrobot/LICENSE @@ -0,0 +1,504 @@ + GNU LESSER GENERAL PUBLIC LICENSE + Version 2.1, February 1999 + + Copyright (C) 1991, 1999 Free Software Foundation, Inc. + 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + Everyone is permitted to copy and distribute verbatim copies + of this license document, but changing it is not allowed. + +[This is the first released version of the Lesser GPL. It also counts + as the successor of the GNU Library Public License, version 2, hence + the version number 2.1.] + + Preamble + + The licenses for most software are designed to take away your +freedom to share and change it. By contrast, the GNU General Public +Licenses are intended to guarantee your freedom to share and change +free software--to make sure the software is free for all its users. + + This license, the Lesser General Public License, applies to some +specially designated software packages--typically libraries--of the +Free Software Foundation and other authors who decide to use it. You +can use it too, but we suggest you first think carefully about whether +this license or the ordinary General Public License is the better +strategy to use in any particular case, based on the explanations below. + + When we speak of free software, we are referring to freedom of use, +not price. Our General Public Licenses are designed to make sure that +you have the freedom to distribute copies of free software (and charge +for this service if you wish); that you receive source code or can get +it if you want it; that you can change the software and use pieces of +it in new free programs; and that you are informed that you can do +these things. + + To protect your rights, we need to make restrictions that forbid +distributors to deny you these rights or to ask you to surrender these +rights. These restrictions translate to certain responsibilities for +you if you distribute copies of the library or if you modify it. + + For example, if you distribute copies of the library, whether gratis +or for a fee, you must give the recipients all the rights that we gave +you. You must make sure that they, too, receive or can get the source +code. If you link other code with the library, you must provide +complete object files to the recipients, so that they can relink them +with the library after making changes to the library and recompiling +it. And you must show them these terms so they know their rights. + + We protect your rights with a two-step method: (1) we copyright the +library, and (2) we offer you this license, which gives you legal +permission to copy, distribute and/or modify the library. + + To protect each distributor, we want to make it very clear that +there is no warranty for the free library. Also, if the library is +modified by someone else and passed on, the recipients should know +that what they have is not the original version, so that the original +author's reputation will not be affected by problems that might be +introduced by others. + + Finally, software patents pose a constant threat to the existence of +any free program. We wish to make sure that a company cannot +effectively restrict the users of a free program by obtaining a +restrictive license from a patent holder. Therefore, we insist that +any patent license obtained for a version of the library must be +consistent with the full freedom of use specified in this license. + + Most GNU software, including some libraries, is covered by the +ordinary GNU General Public License. This license, the GNU Lesser +General Public License, applies to certain designated libraries, and +is quite different from the ordinary General Public License. We use +this license for certain libraries in order to permit linking those +libraries into non-free programs. + + When a program is linked with a library, whether statically or using +a shared library, the combination of the two is legally speaking a +combined work, a derivative of the original library. The ordinary +General Public License therefore permits such linking only if the +entire combination fits its criteria of freedom. The Lesser General +Public License permits more lax criteria for linking other code with +the library. + + We call this license the "Lesser" General Public License because it +does Less to protect the user's freedom than the ordinary General +Public License. It also provides other free software developers Less +of an advantage over competing non-free programs. These disadvantages +are the reason we use the ordinary General Public License for many +libraries. However, the Lesser license provides advantages in certain +special circumstances. + + For example, on rare occasions, there may be a special need to +encourage the widest possible use of a certain library, so that it becomes +a de-facto standard. To achieve this, non-free programs must be +allowed to use the library. A more frequent case is that a free +library does the same job as widely used non-free libraries. In this +case, there is little to gain by limiting the free library to free +software only, so we use the Lesser General Public License. + + In other cases, permission to use a particular library in non-free +programs enables a greater number of people to use a large body of +free software. For example, permission to use the GNU C Library in +non-free programs enables many more people to use the whole GNU +operating system, as well as its variant, the GNU/Linux operating +system. + + Although the Lesser General Public License is Less protective of the +users' freedom, it does ensure that the user of a program that is +linked with the Library has the freedom and the wherewithal to run +that program using a modified version of the Library. + + The precise terms and conditions for copying, distribution and +modification follow. Pay close attention to the difference between a +"work based on the library" and a "work that uses the library". The +former contains code derived from the library, whereas the latter must +be combined with the library in order to run. + + GNU LESSER GENERAL PUBLIC LICENSE + TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION + + 0. This License Agreement applies to any software library or other +program which contains a notice placed by the copyright holder or +other authorized party saying it may be distributed under the terms of +this Lesser General Public License (also called "this License"). +Each licensee is addressed as "you". + + A "library" means a collection of software functions and/or data +prepared so as to be conveniently linked with application programs +(which use some of those functions and data) to form executables. + + The "Library", below, refers to any such software library or work +which has been distributed under these terms. A "work based on the +Library" means either the Library or any derivative work under +copyright law: that is to say, a work containing the Library or a +portion of it, either verbatim or with modifications and/or translated +straightforwardly into another language. (Hereinafter, translation is +included without limitation in the term "modification".) + + "Source code" for a work means the preferred form of the work for +making modifications to it. For a library, complete source code means +all the source code for all modules it contains, plus any associated +interface definition files, plus the scripts used to control compilation +and installation of the library. + + Activities other than copying, distribution and modification are not +covered by this License; they are outside its scope. The act of +running a program using the Library is not restricted, and output from +such a program is covered only if its contents constitute a work based +on the Library (independent of the use of the Library in a tool for +writing it). Whether that is true depends on what the Library does +and what the program that uses the Library does. + + 1. You may copy and distribute verbatim copies of the Library's +complete source code as you receive it, in any medium, provided that +you conspicuously and appropriately publish on each copy an +appropriate copyright notice and disclaimer of warranty; keep intact +all the notices that refer to this License and to the absence of any +warranty; and distribute a copy of this License along with the +Library. + + You may charge a fee for the physical act of transferring a copy, +and you may at your option offer warranty protection in exchange for a +fee. + + 2. You may modify your copy or copies of the Library or any portion +of it, thus forming a work based on the Library, and copy and +distribute such modifications or work under the terms of Section 1 +above, provided that you also meet all of these conditions: + + a) The modified work must itself be a software library. + + b) You must cause the files modified to carry prominent notices + stating that you changed the files and the date of any change. + + c) You must cause the whole of the work to be licensed at no + charge to all third parties under the terms of this License. + + d) If a facility in the modified Library refers to a function or a + table of data to be supplied by an application program that uses + the facility, other than as an argument passed when the facility + is invoked, then you must make a good faith effort to ensure that, + in the event an application does not supply such function or + table, the facility still operates, and performs whatever part of + its purpose remains meaningful. + + (For example, a function in a library to compute square roots has + a purpose that is entirely well-defined independent of the + application. Therefore, Subsection 2d requires that any + application-supplied function or table used by this function must + be optional: if the application does not supply it, the square + root function must still compute square roots.) + +These requirements apply to the modified work as a whole. If +identifiable sections of that work are not derived from the Library, +and can be reasonably considered independent and separate works in +themselves, then this License, and its terms, do not apply to those +sections when you distribute them as separate works. But when you +distribute the same sections as part of a whole which is a work based +on the Library, the distribution of the whole must be on the terms of +this License, whose permissions for other licensees extend to the +entire whole, and thus to each and every part regardless of who wrote +it. + +Thus, it is not the intent of this section to claim rights or contest +your rights to work written entirely by you; rather, the intent is to +exercise the right to control the distribution of derivative or +collective works based on the Library. + +In addition, mere aggregation of another work not based on the Library +with the Library (or with a work based on the Library) on a volume of +a storage or distribution medium does not bring the other work under +the scope of this License. + + 3. You may opt to apply the terms of the ordinary GNU General Public +License instead of this License to a given copy of the Library. To do +this, you must alter all the notices that refer to this License, so +that they refer to the ordinary GNU General Public License, version 2, +instead of to this License. (If a newer version than version 2 of the +ordinary GNU General Public License has appeared, then you can specify +that version instead if you wish.) Do not make any other change in +these notices. + + Once this change is made in a given copy, it is irreversible for +that copy, so the ordinary GNU General Public License applies to all +subsequent copies and derivative works made from that copy. + + This option is useful when you wish to copy part of the code of +the Library into a program that is not a library. + + 4. You may copy and distribute the Library (or a portion or +derivative of it, under Section 2) in object code or executable form +under the terms of Sections 1 and 2 above provided that you accompany +it with the complete corresponding machine-readable source code, which +must be distributed under the terms of Sections 1 and 2 above on a +medium customarily used for software interchange. + + If distribution of object code is made by offering access to copy +from a designated place, then offering equivalent access to copy the +source code from the same place satisfies the requirement to +distribute the source code, even though third parties are not +compelled to copy the source along with the object code. + + 5. A program that contains no derivative of any portion of the +Library, but is designed to work with the Library by being compiled or +linked with it, is called a "work that uses the Library". Such a +work, in isolation, is not a derivative work of the Library, and +therefore falls outside the scope of this License. + + However, linking a "work that uses the Library" with the Library +creates an executable that is a derivative of the Library (because it +contains portions of the Library), rather than a "work that uses the +library". The executable is therefore covered by this License. +Section 6 states terms for distribution of such executables. + + When a "work that uses the Library" uses material from a header file +that is part of the Library, the object code for the work may be a +derivative work of the Library even though the source code is not. +Whether this is true is especially significant if the work can be +linked without the Library, or if the work is itself a library. The +threshold for this to be true is not precisely defined by law. + + If such an object file uses only numerical parameters, data +structure layouts and accessors, and small macros and small inline +functions (ten lines or less in length), then the use of the object +file is unrestricted, regardless of whether it is legally a derivative +work. (Executables containing this object code plus portions of the +Library will still fall under Section 6.) + + Otherwise, if the work is a derivative of the Library, you may +distribute the object code for the work under the terms of Section 6. +Any executables containing that work also fall under Section 6, +whether or not they are linked directly with the Library itself. + + 6. As an exception to the Sections above, you may also combine or +link a "work that uses the Library" with the Library to produce a +work containing portions of the Library, and distribute that work +under terms of your choice, provided that the terms permit +modification of the work for the customer's own use and reverse +engineering for debugging such modifications. + + You must give prominent notice with each copy of the work that the +Library is used in it and that the Library and its use are covered by +this License. You must supply a copy of this License. If the work +during execution displays copyright notices, you must include the +copyright notice for the Library among them, as well as a reference +directing the user to the copy of this License. Also, you must do one +of these things: + + a) Accompany the work with the complete corresponding + machine-readable source code for the Library including whatever + changes were used in the work (which must be distributed under + Sections 1 and 2 above); and, if the work is an executable linked + with the Library, with the complete machine-readable "work that + uses the Library", as object code and/or source code, so that the + user can modify the Library and then relink to produce a modified + executable containing the modified Library. (It is understood + that the user who changes the contents of definitions files in the + Library will not necessarily be able to recompile the application + to use the modified definitions.) + + b) Use a suitable shared library mechanism for linking with the + Library. A suitable mechanism is one that (1) uses at run time a + copy of the library already present on the user's computer system, + rather than copying library functions into the executable, and (2) + will operate properly with a modified version of the library, if + the user installs one, as long as the modified version is + interface-compatible with the version that the work was made with. + + c) Accompany the work with a written offer, valid for at + least three years, to give the same user the materials + specified in Subsection 6a, above, for a charge no more + than the cost of performing this distribution. + + d) If distribution of the work is made by offering access to copy + from a designated place, offer equivalent access to copy the above + specified materials from the same place. + + e) Verify that the user has already received a copy of these + materials or that you have already sent this user a copy. + + For an executable, the required form of the "work that uses the +Library" must include any data and utility programs needed for +reproducing the executable from it. However, as a special exception, +the materials to be distributed need not include anything that is +normally distributed (in either source or binary form) with the major +components (compiler, kernel, and so on) of the operating system on +which the executable runs, unless that component itself accompanies +the executable. + + It may happen that this requirement contradicts the license +restrictions of other proprietary libraries that do not normally +accompany the operating system. Such a contradiction means you cannot +use both them and the Library together in an executable that you +distribute. + + 7. You may place library facilities that are a work based on the +Library side-by-side in a single library together with other library +facilities not covered by this License, and distribute such a combined +library, provided that the separate distribution of the work based on +the Library and of the other library facilities is otherwise +permitted, and provided that you do these two things: + + a) Accompany the combined library with a copy of the same work + based on the Library, uncombined with any other library + facilities. This must be distributed under the terms of the + Sections above. + + b) Give prominent notice with the combined library of the fact + that part of it is a work based on the Library, and explaining + where to find the accompanying uncombined form of the same work. + + 8. You may not copy, modify, sublicense, link with, or distribute +the Library except as expressly provided under this License. Any +attempt otherwise to copy, modify, sublicense, link with, or +distribute the Library is void, and will automatically terminate your +rights under this License. However, parties who have received copies, +or rights, from you under this License will not have their licenses +terminated so long as such parties remain in full compliance. + + 9. You are not required to accept this License, since you have not +signed it. However, nothing else grants you permission to modify or +distribute the Library or its derivative works. These actions are +prohibited by law if you do not accept this License. Therefore, by +modifying or distributing the Library (or any work based on the +Library), you indicate your acceptance of this License to do so, and +all its terms and conditions for copying, distributing or modifying +the Library or works based on it. + + 10. Each time you redistribute the Library (or any work based on the +Library), the recipient automatically receives a license from the +original licensor to copy, distribute, link with or modify the Library +subject to these terms and conditions. You may not impose any further +restrictions on the recipients' exercise of the rights granted herein. +You are not responsible for enforcing compliance by third parties with +this License. + + 11. If, as a consequence of a court judgment or allegation of patent +infringement or for any other reason (not limited to patent issues), +conditions are imposed on you (whether by court order, agreement or +otherwise) that contradict the conditions of this License, they do not +excuse you from the conditions of this License. If you cannot +distribute so as to satisfy simultaneously your obligations under this +License and any other pertinent obligations, then as a consequence you +may not distribute the Library at all. For example, if a patent +license would not permit royalty-free redistribution of the Library by +all those who receive copies directly or indirectly through you, then +the only way you could satisfy both it and this License would be to +refrain entirely from distribution of the Library. + +If any portion of this section is held invalid or unenforceable under any +particular circumstance, the balance of the section is intended to apply, +and the section as a whole is intended to apply in other circumstances. + +It is not the purpose of this section to induce you to infringe any +patents or other property right claims or to contest validity of any +such claims; this section has the sole purpose of protecting the +integrity of the free software distribution system which is +implemented by public license practices. Many people have made +generous contributions to the wide range of software distributed +through that system in reliance on consistent application of that +system; it is up to the author/donor to decide if he or she is willing +to distribute software through any other system and a licensee cannot +impose that choice. + +This section is intended to make thoroughly clear what is believed to +be a consequence of the rest of this License. + + 12. If the distribution and/or use of the Library is restricted in +certain countries either by patents or by copyrighted interfaces, the +original copyright holder who places the Library under this License may add +an explicit geographical distribution limitation excluding those countries, +so that distribution is permitted only in or among countries not thus +excluded. In such case, this License incorporates the limitation as if +written in the body of this License. + + 13. The Free Software Foundation may publish revised and/or new +versions of the Lesser General Public License from time to time. +Such new versions will be similar in spirit to the present version, +but may differ in detail to address new problems or concerns. + +Each version is given a distinguishing version number. If the Library +specifies a version number of this License which applies to it and +"any later version", you have the option of following the terms and +conditions either of that version or of any later version published by +the Free Software Foundation. If the Library does not specify a +license version number, you may choose any version ever published by +the Free Software Foundation. + + 14. If you wish to incorporate parts of the Library into other free +programs whose distribution conditions are incompatible with these, +write to the author to ask for permission. For software which is +copyrighted by the Free Software Foundation, write to the Free +Software Foundation; we sometimes make exceptions for this. Our +decision will be guided by the two goals of preserving the free status +of all derivatives of our free software and of promoting the sharing +and reuse of software generally. + + NO WARRANTY + + 15. BECAUSE THE LIBRARY IS LICENSED FREE OF CHARGE, THERE IS NO +WARRANTY FOR THE LIBRARY, TO THE EXTENT PERMITTED BY APPLICABLE LAW. +EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR +OTHER PARTIES PROVIDE THE LIBRARY "AS IS" WITHOUT WARRANTY OF ANY +KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR +PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE +LIBRARY IS WITH YOU. SHOULD THE LIBRARY PROVE DEFECTIVE, YOU ASSUME +THE COST OF ALL NECESSARY SERVICING, REPAIR OR CORRECTION. + + 16. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN +WRITING WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY +AND/OR REDISTRIBUTE THE LIBRARY AS PERMITTED ABOVE, BE LIABLE TO YOU +FOR DAMAGES, INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR +CONSEQUENTIAL DAMAGES ARISING OUT OF THE USE OR INABILITY TO USE THE +LIBRARY (INCLUDING BUT NOT LIMITED TO LOSS OF DATA OR DATA BEING +RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD PARTIES OR A +FAILURE OF THE LIBRARY TO OPERATE WITH ANY OTHER SOFTWARE), EVEN IF +SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH +DAMAGES. + + END OF TERMS AND CONDITIONS + + How to Apply These Terms to Your New Libraries + + If you develop a new library, and you want it to be of the greatest +possible use to the public, we recommend making it free software that +everyone can redistribute and change. You can do so by permitting +redistribution under these terms (or, alternatively, under the terms of the +ordinary General Public License). + + To apply these terms, attach the following notices to the library. It is +safest to attach them to the start of each source file to most effectively +convey the exclusion of warranty; and each file should have at least the +"copyright" line and a pointer to where the full notice is found. + + + Copyright (C) + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) any later version. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with this library; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 + USA + +Also add information on how to contact you by electronic and paper mail. + +You should also get your employer (if you work as a programmer) or your +school, if any, to sign a "copyright disclaimer" for the library, if +necessary. Here is a sample; alter the names: + + Yoyodyne, Inc., hereby disclaims all copyright interest in the + library `Frob' (a library for tweaking knobs) written by James Random + Hacker. + + , 1 April 1990 + Ty Coon, President of Vice + +That's all there is to it! diff --git a/pwnagotchi/ui/hw/libs/dfrobot/__init__.py b/pwnagotchi/ui/hw/libs/dfrobot/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/pwnagotchi/ui/hw/libs/dfrobot/dfrobot.py b/pwnagotchi/ui/hw/libs/dfrobot/dfrobot.py new file mode 100644 index 00000000..02ebb8d6 --- /dev/null +++ b/pwnagotchi/ui/hw/libs/dfrobot/dfrobot.py @@ -0,0 +1,66 @@ +# DFRobot display support + +import logging +from . import dfrobot_epaper + +#Resolution of display +WIDTH = 250 +HEIGHT = 122 + +RASPBERRY_SPI_BUS = 0 +RASPBERRY_SPI_DEV = 0 +RASPBERRY_PIN_CS = 27 +RASPBERRY_PIN_CD = 17 +RASPBERRY_PIN_BUSY = 4 + +class DFRobot: + def __init__(self): + self._display = dfrobot_epaper.DFRobot_Epaper_SPI(RASPBERRY_SPI_BUS, RASPBERRY_SPI_DEV, RASPBERRY_PIN_CS, RASPBERRY_PIN_CD, RASPBERRY_PIN_BUSY) + self._display.begin() + self.clear(0xFF) + self.FULL = self._display.FULL + self.PART = self._display.PART + + def getbuffer(self, image): + if HEIGHT % 8 == 0: + linewidth = HEIGHT // 8 + else: + linewidth = HEIGHT // 8 + 1 + + buf = [0xFF] * (linewidth * WIDTH) + image_monocolor = image.convert('1') + imwidth, imheight = image_monocolor.size + pixels = image_monocolor.load() + + if (imwidth == HEIGHT and imheight == WIDTH): + for y in range(imheight): + for x in range(imwidth): + if pixels[x,y] == 0: + x = imwidth - x + buf[x // 8 + y * linewidth] &= ~(0x80 >> (x % 8)) + elif (imwidth == WIDTH and imheight == HEIGHT): + for y in range(imheight): + for x in range(imwidth): + newx = y + newy = WIDTH - x - 1 + if pixels[x,y] == 0: + newy = imwidth - newy - 1 + buf[newx // 8 + newy * linewidth] &= ~(0x80 >> (y % 8)) + return buf + + def flush(self, type): + self._display.flush(type) + + def display(self, buf): + self._display.setBuffer(buf) + self.flush(self._display.PART) + + def clear(self, color): + if HEIGHT % 8 == 0: + linewidth = HEIGHT // 8 + else: + linewidth = HEIGHT // 8 + 1 + + buf = [color] * (linewidth * WIDTH) + self._display.setBuffer(buf) + self.flush(self._display.FULL) diff --git a/pwnagotchi/ui/hw/libs/dfrobot/dfrobot_epaper.py b/pwnagotchi/ui/hw/libs/dfrobot/dfrobot_epaper.py new file mode 100644 index 00000000..d207a45d --- /dev/null +++ b/pwnagotchi/ui/hw/libs/dfrobot/dfrobot_epaper.py @@ -0,0 +1,208 @@ +# -*- coding:utf-8 -*- + +import time + +import sys +sys.path.append("..") + + +try: + from .spi import SPI + from .gpio import GPIO +except: + print("unknow platform") + exit() + +CONFIG_IL0376F = { + +} + +CONFIG_IL3895 = { + +} + +class DFRobot_Epaper: + + XDOT = 128 + YDOT = 250 + + FULL = True + PART = False + + def __init__(self, width = 250, height = 122): + # length = width * height // 8 + length = 4000 + self._displayBuffer = bytearray(length) + i = 0 + while i < length: + self._displayBuffer[i] = 0xff + i = i + 1 + + self._isBusy = False + self._busyExitEdge = GPIO.RISING + + def _busyCB(self, channel): + self._isBusy = False + + def setBusyExitEdge(self, edge): + if edge != GPIO.HIGH and edge != GPIO.LOW: + return + self._busyEdge = edge + + def begin(self): + pass + # self.setBusyCB(self._busyCB) + # self._powerOn() + + def setBuffer(self, buffer): + self._displayBuffer = buffer + + def pixel(self, x, y, color): + if x < 0 or x >= self._width: + return + if y < 0 or y >= self._height: + return + x = int(x) + y = int(y) + m = int(x * 16 + (y + 1) / 8) + sy = int((y + 1) % 8) + if color == self.WHITE: + if sy != 0: + self._displayBuffer[m] = self._displayBuffer[m] | int(pow(2, 8 - sy)) + else: + self._displayBuffer[m - 1] = self._displayBuffer[m - 1] | 1 + elif color == self.BLACK: + if sy != 0: + self._displayBuffer[m] = self._displayBuffer[m] & (0xff - int(pow(2, 8 - sy))) + else: + self._displayBuffer[m - 1] = self._displayBuffer[m - 1] & 0xfe + + def _setWindow(self, x, y): + hres = y // 8 + hres = hres << 3 + vres_h = x >> 8 + vres_l = x & 0xff + self.writeCmdAndData(0x61, [hres, vres_h, vres_l]) + + def _initLut(self, mode): + if mode == self.FULL: + self.writeCmdAndData(0x32, [0x22,0x55,0xAA,0x55,0xAA,0x55,0xAA, + 0x11,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x1E,0x1E,0x1E,0x1E,0x1E, + 0x1E,0x1E,0x1E,0x01,0x00,0x00,0x00,0x00]) + elif mode == self.PART: + self.writeCmdAndData(0x32, [0x18,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x0F,0x01,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00]) + + def _setRamData(self, xStart, xEnd, yStart, yStart1, yEnd, yEnd1): + self.writeCmdAndData(0x44, [xStart, xEnd]) + self.writeCmdAndData(0x45, [yStart, yStart1, yEnd, yEnd1]) + + def _setRamPointer(self, x, y, y1): + self.writeCmdAndData(0x4e, [x]) + self.writeCmdAndData(0x4f, [y, y1]) + + def _init(self): + self.writeCmdAndData(0x01, [(self.YDOT - 1) % 256, (self.YDOT - 1) // 256, 0x00]) + self.writeCmdAndData(0x0c, [0xd7, 0xd6, 0x9d]) + self.writeCmdAndData(0x2c, [0xa8]) + self.writeCmdAndData(0x3a, [0x1a]) + self.writeCmdAndData(0x3b, [0x08]) + self.writeCmdAndData(0x11, [0x01]) + self._setRamData(0x00, (self.XDOT - 1) // 8, (self.YDOT - 1) % 256, (self.YDOT - 1) // 256, 0x00, 0x00) + self._setRamPointer(0x00, (self.YDOT - 1) % 256, (self.YDOT - 1) // 256) + + def _writeDisRam(self, sizeX, sizeY): + if sizeX % 8 != 0: + sizeX = sizeX + (8 - sizeX % 8) + sizeX = sizeX // 8 + self.writeCmdAndData(0x24, self._displayBuffer[0: sizeX * sizeY]) + + def _updateDis(self, mode): + if mode == self.FULL: + self.writeCmdAndData(0x22, [0xc7]) + elif mode == self.PART: + self.writeCmdAndData(0x22, [0x04]) + else: + return + self.writeCmdAndData(0x20, []) + self.writeCmdAndData(0xff, []) + + def _waitBusyExit(self): + temp = 0 + while self.readBusy() != False: + time.sleep(0.01) + temp = temp + 1 + if (temp % 200) == 0: + print("waitBusyExit") + + def _powerOn(self): + self.writeCmdAndData(0x22, [0xc0]) + self.writeCmdAndData(0x20, []) + + def _powerOff(self): + self.writeCmdAndData(0x12, []) + self.writeCmdAndData(0x82, [0x00]) + self.writeCmdAndData(0x01, [0x02, 0x00, 0x00, 0x00, 0x00]) + self.writeCmdAndData(0x02, []) + + def _disPart(self, xStart, xEnd, yStart, yEnd): + self._setRamData(xStart // 8, xEnd // 8, yEnd % 256, yEnd // 256, yStart % 256, yStart // 256) + self._setRamPointer(xStart // 8, yEnd % 256, yEnd // 256) + self._writeDisRam(xEnd - xStart, yEnd - yStart + 1) + self._updateDis(self.PART) + + def flush(self, mode): + if mode != self.FULL and mode != self.PART: + return + self._init() + self._initLut(mode) + self._powerOn() + if mode == self.PART: + self._disPart(0, self.XDOT - 1, 0, self.YDOT - 1) + else: + self._setRamPointer(0x00, (self.YDOT - 1) % 256, (self.YDOT - 1) // 256) + self._writeDisRam(self.XDOT, self.YDOT) + self._updateDis(mode) + + def startDrawBitmapFile(self, x, y): + self._bitmapFileStartX = x + self._bitmapFileStartY = y + + def bitmapFileHelper(self, buf): + for i in range(len(buf) // 3): + addr = i * 3 + if buf[addr] == 0x00 and buf[addr + 1] == 0x00 and buf[addr + 2] == 0x00: + self.pixel(self._bitmapFileStartX, self._bitmapFileStartY, self.BLACK) + else: + self.pixel(self._bitmapFileStartX, self._bitmapFileStartY, self.WHITE) + self._bitmapFileStartX += 1 + + def endDrawBitmapFile(self): + self.flush(self.PART) + +class DFRobot_Epaper_SPI(DFRobot_Epaper): + + def __init__(self, bus, dev, cs, cd, busy): + DFRobot_Epaper.__init__(self) + self._spi = SPI(bus, dev) + self._cs = GPIO(cs, GPIO.OUT) + self._cd = GPIO(cd, GPIO.OUT) + self._busy = GPIO(busy, GPIO.IN) + + def writeCmdAndData(self, cmd, data = []): + self._waitBusyExit() + self._cs.setOut(GPIO.LOW) + self._cd.setOut(GPIO.LOW) + self._spi.transfer([cmd]) + self._cd.setOut(GPIO.HIGH) + self._spi.transfer(data) + self._cs.setOut(GPIO.HIGH) + + def readBusy(self): + return self._busy.read() + + def setBusyCB(self, cb): + self._busy.setInterrupt(self._busyExitEdge, cb) diff --git a/pwnagotchi/ui/hw/libs/dfrobot/gpio.py b/pwnagotchi/ui/hw/libs/dfrobot/gpio.py new file mode 100644 index 00000000..3a6a597f --- /dev/null +++ b/pwnagotchi/ui/hw/libs/dfrobot/gpio.py @@ -0,0 +1,64 @@ +# -*- coding:utf-8 -*- + +import time +import RPi.GPIO as RPIGPIO + +RPIGPIO.setmode(RPIGPIO.BCM) +RPIGPIO.setwarnings(False) + +class GPIO: + + HIGH = RPIGPIO.HIGH + LOW = RPIGPIO.LOW + + OUT = RPIGPIO.OUT + IN = RPIGPIO.IN + + RISING = RPIGPIO.RISING + FALLING = RPIGPIO.FALLING + BOTH = RPIGPIO.BOTH + + def __init__(self, pin, mode, defaultOut = HIGH): + self._pin = pin + self._fInt = None + self._intDone = True + self._intMode = None + if mode == self.OUT: + RPIGPIO.setup(pin, mode) + if defaultOut == self.HIGH: + RPIGPIO.output(pin, defaultOut) + else: + RPIGPIO.output(pin, self.LOW) + else: + RPIGPIO.setup(pin, self.IN, pull_up_down = RPIGPIO.PUD_UP) + + def setOut(self, level): + if level: + RPIGPIO.output(self._pin, self.HIGH) + else: + RPIGPIO.output(self._pin, self.LOW) + + def _intCB(self, status): + if self._intDone: + self._intDone = False + time.sleep(0.02) + if self._intMode == self.BOTH: + self._fInt() + elif self._intMode == self.RISING and self.read() == self.HIGH: + self._fInt() + elif self._intMode == self.FALLING and self.read() == self.LOW: + self._fInt() + self._intDone = True + + def setInterrupt(self, mode, cb): + if mode != self.RISING and mode != self.FALLING and mode != self.BOTH: + return + self._intMode = mode + RPIGPIO.add_event_detect(self._pin, mode, self._intCB) + self._fInt = cb + + def read(self): + return RPIGPIO.input(self._pin) + + def cleanup(self): + RPIGPIO.cleanup() diff --git a/pwnagotchi/ui/hw/libs/dfrobot/spi.py b/pwnagotchi/ui/hw/libs/dfrobot/spi.py new file mode 100644 index 00000000..58529abb --- /dev/null +++ b/pwnagotchi/ui/hw/libs/dfrobot/spi.py @@ -0,0 +1,21 @@ +# -*- coding:utf-8 -*- + +import spidev + +class SPI: + + MODE_1 = 1 + MODE_2 = 2 + MODE_3 = 3 + MODE_4 = 4 + + def __init__(self, bus, dev, speed = 3900000, mode = MODE_4): + self._bus = spidev.SpiDev() + self._bus.open(bus, dev) + self._bus.no_cs = True + self._bus.max_speed_hz = speed + + def transfer(self, buf): + if len(buf): + return self._bus.xfer(buf) + return [] diff --git a/pwnagotchi/utils.py b/pwnagotchi/utils.py index 25aab13a..968abd4f 100644 --- a/pwnagotchi/utils.py +++ b/pwnagotchi/utils.py @@ -94,6 +94,9 @@ def load_config(args): elif config['ui']['display']['type'] in ('lcdhat',): config['ui']['display']['type'] = 'lcdhat' + elif config['ui']['display']['type'] in ('dfrobot', 'df'): + config['ui']['display']['type'] = 'dfrobot' + else: print("unsupported display type %s" % config['ui']['display']['type']) exit(1) From 88f7384f52db9c391ec27a0759a70f328aaff920 Mon Sep 17 00:00:00 2001 From: Justin Richards Date: Sat, 26 Oct 2019 12:55:23 -0500 Subject: [PATCH 084/168] adding to list of available options Signed-off-by: Justin Richards --- pwnagotchi/defaults.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pwnagotchi/defaults.yml b/pwnagotchi/defaults.yml index ca0c74ca..b9c49c5d 100644 --- a/pwnagotchi/defaults.yml +++ b/pwnagotchi/defaults.yml @@ -204,7 +204,7 @@ ui: display: enabled: true rotation: 180 - # Possible options inkyphat/inky, papirus/papi, waveshare_1/ws_1 or waveshare_2/ws_2, oledhat, lcdhat, waveshare27inch + # Possible options inkyphat/inky, papirus/papi, waveshare_1/ws_1 or waveshare_2/ws_2, oledhat, lcdhat, waveshare27inch, dfrobot/df type: 'waveshare_2' # Possible options red/yellow/black (black used for monocromatic displays) color: 'black' From db7fc74b4a3722cb517834411bbb7e62da97198f Mon Sep 17 00:00:00 2001 From: georgikoemdzhiev Date: Sat, 26 Oct 2019 19:01:02 +0100 Subject: [PATCH 085/168] Added bulgarian language WiP --- pwnagotchi/locale/bg/LC_MESSAGES/voice.po | 225 ++++++++++++++++++++++ 1 file changed, 225 insertions(+) create mode 100644 pwnagotchi/locale/bg/LC_MESSAGES/voice.po diff --git a/pwnagotchi/locale/bg/LC_MESSAGES/voice.po b/pwnagotchi/locale/bg/LC_MESSAGES/voice.po new file mode 100644 index 00000000..fb5afe7b --- /dev/null +++ b/pwnagotchi/locale/bg/LC_MESSAGES/voice.po @@ -0,0 +1,225 @@ +# 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 , YEAR. +# +#, fuzzy +msgid "" +msgstr "" +"Project-Id-Version: PACKAGE VERSION\n" +"Report-Msgid-Bugs-To: \n" +"POT-Creation-Date: 2019-10-23 20:56+0200\n" +"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" +"Last-Translator: FULL NAME \n" +"Language-Team: LANGUAGE \n" +"Language: \n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=CHARSET\n" +"Content-Transfer-Encoding: 8bit\n" + +msgid "ZzzzZZzzzzZzzz" +msgstr "" + +msgid "Hi, I'm Pwnagotchi! Starting ..." +msgstr "Здравей, аз съм Pwnagotchi! Стартиране ..." + +msgid "New day, new hunt, new pwns!" +msgstr "Нов ден, нов лов, нови жертви!" + +msgid "Hack the Planet!" +msgstr "Хакни планетата!" + +msgid "AI ready." +msgstr "AI готов." + +msgid "The neural network is ready." +msgstr "Невронната мрежа е готова." + +msgid "Generating keys, do not turn off ..." +msgstr "Генериране на ключове, не изключвай ..." + +#, python-brace-format +msgid "Hey, channel {channel} is free! Your AP will say thanks." +msgstr "Здравей, канал {channel} е свободен! твоя AP ще каже благодаря." + +msgid "I'm bored ..." +msgstr "Скучно ми е ..." + +msgid "Let's go for a walk!" +msgstr "Хайда да се поразходим!" + +msgid "This is the best day of my life!" +msgstr "Това е най-добрият ден в живота ми!" + +msgid "Shitty day :/" +msgstr "Тъп ден :/" + +msgid "I'm extremely bored ..." +msgstr "Супер много ми е скучно ..." + +msgid "I'm very sad ..." +msgstr "Много съм тъжен ..." + +msgid "I'm sad" +msgstr "Тъжен съм" + +msgid "I'm living the life!" +msgstr "Живота ми е фантастичен!" + +msgid "I pwn therefore I am." +msgstr "Аз живея за да хаквам." + +msgid "So many networks!!!" +msgstr "Толкова много мрежи!!!" + +msgid "I'm having so much fun!" +msgstr "Толкова много се забавлявам!" + +msgid "My crime is that of curiosity ..." +msgstr "Моето престъпление е това че съм любопитен ..." + +#, python-brace-format +msgid "Hello {name}! Nice to meet you." +msgstr "Здравей {name}! Приятно ми е да се запознаем." + +#, python-brace-format +msgid "Unit {name} is nearby!" +msgstr "Устройство {name} е наблизо!" + +#, python-brace-format +msgid "Uhm ... goodbye {name}" +msgstr "Ами ... довиждане {name}" + +#, python-brace-format +msgid "{name} is gone ..." +msgstr "" + +#, python-brace-format +msgid "Whoops ... {name} is gone." +msgstr "" + +#, python-brace-format +msgid "{name} missed!" +msgstr "" + +msgid "Missed!" +msgstr "" + +msgid "Good friends are a blessing!" +msgstr "" + +msgid "I love my friends!" +msgstr "" + +msgid "Nobody wants to play with me ..." +msgstr "" + +msgid "I feel so alone ..." +msgstr "" + +msgid "Where's everybody?!" +msgstr "" + +#, python-brace-format +msgid "Napping for {secs}s ..." +msgstr "" + +msgid "Zzzzz" +msgstr "" + +#, python-brace-format +msgid "ZzzZzzz ({secs}s)" +msgstr "" + +msgid "Good night." +msgstr "" + +msgid "Zzz" +msgstr "" + +#, python-brace-format +msgid "Waiting for {secs}s ..." +msgstr "" + +#, python-brace-format +msgid "Looking around ({secs}s)" +msgstr "" + +#, python-brace-format +msgid "Hey {what} let's be friends!" +msgstr "" + +#, python-brace-format +msgid "Associating to {what}" +msgstr "" + +#, python-brace-format +msgid "Yo {what}!" +msgstr "" + +#, python-brace-format +msgid "Just decided that {mac} needs no WiFi!" +msgstr "" + +#, python-brace-format +msgid "Deauthenticating {mac}" +msgstr "" + +#, python-brace-format +msgid "Kickbanning {mac}!" +msgstr "" + +#, python-brace-format +msgid "Cool, we got {num} new handshake{plural}!" +msgstr "" + +#, python-brace-format +msgid "You have {count} new message{plural}!" +msgstr "" + +msgid "Ops, something went wrong ... Rebooting ..." +msgstr "" + +#, python-brace-format +msgid "Kicked {num} stations\n" +msgstr "" + +#, python-brace-format +msgid "Made {num} new friends\n" +msgstr "" + +#, python-brace-format +msgid "Got {num} handshakes\n" +msgstr "" + +msgid "Met 1 peer" +msgstr "" + +#, python-brace-format +msgid "Met {num} peers" +msgstr "" + +#, 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 "" + +msgid "hours" +msgstr "" + +msgid "minutes" +msgstr "" + +msgid "seconds" +msgstr "" + +msgid "hour" +msgstr "" + +msgid "minute" +msgstr "" + +msgid "second" +msgstr "" From d6228b133b8fd2085ce4b0d342636a15bd19272e Mon Sep 17 00:00:00 2001 From: georgikoemdzhiev Date: Sat, 26 Oct 2019 19:47:47 +0100 Subject: [PATCH 086/168] Completed bulgarian translation --- pwnagotchi/locale/bg/LC_MESSAGES/voice.po | 75 ++++++++++++----------- 1 file changed, 38 insertions(+), 37 deletions(-) diff --git a/pwnagotchi/locale/bg/LC_MESSAGES/voice.po b/pwnagotchi/locale/bg/LC_MESSAGES/voice.po index fb5afe7b..5b74c18e 100644 --- a/pwnagotchi/locale/bg/LC_MESSAGES/voice.po +++ b/pwnagotchi/locale/bg/LC_MESSAGES/voice.po @@ -92,134 +92,135 @@ msgstr "Ами ... довиждане {name}" #, python-brace-format msgid "{name} is gone ..." -msgstr "" +msgstr "{name} изчезна ..." #, python-brace-format msgid "Whoops ... {name} is gone." -msgstr "" +msgstr "Упс ... {name} изчезна." #, python-brace-format msgid "{name} missed!" -msgstr "" +msgstr "{name} загубен!" msgid "Missed!" -msgstr "" +msgstr "Загубен!" msgid "Good friends are a blessing!" -msgstr "" +msgstr "Добрите приятели са благословия!" msgid "I love my friends!" -msgstr "" +msgstr "Обичам приятелите си!" msgid "Nobody wants to play with me ..." -msgstr "" +msgstr "Никой не иска да си играе с мен ..." msgid "I feel so alone ..." -msgstr "" +msgstr "Чувствам се толкова самотен ..." msgid "Where's everybody?!" -msgstr "" +msgstr "Къде са всички?!" #, python-brace-format msgid "Napping for {secs}s ..." -msgstr "" +msgstr "Заспивам за {secs} секунди ..." msgid "Zzzzz" -msgstr "" +msgstr "Zzzzz" #, python-brace-format msgid "ZzzZzzz ({secs}s)" -msgstr "" +msgstr "ZzzZzzz ({secs}s)" msgid "Good night." -msgstr "" +msgstr "Лека нощ." msgid "Zzz" -msgstr "" +msgstr "Zzz" #, python-brace-format msgid "Waiting for {secs}s ..." -msgstr "" +msgstr "Чакам {secs} секунди ..." #, python-brace-format msgid "Looking around ({secs}s)" -msgstr "" +msgstr "Оглеждам се ({secs}секунди)" #, python-brace-format msgid "Hey {what} let's be friends!" -msgstr "" +msgstr "Хей {what} нека станем приятели!" #, python-brace-format msgid "Associating to {what}" -msgstr "" +msgstr "Свръзване с {what}" #, python-brace-format msgid "Yo {what}!" -msgstr "" +msgstr "Ей {what}!" #, python-brace-format msgid "Just decided that {mac} needs no WiFi!" -msgstr "" +msgstr "Реших, че {mac} не се нуждае от WiFi!" #, python-brace-format msgid "Deauthenticating {mac}" -msgstr "" +msgstr "Неудостоверяване на {mac}" #, python-brace-format msgid "Kickbanning {mac}!" -msgstr "" +msgstr "Ритам и прогонвам {mac}!" #, python-brace-format msgid "Cool, we got {num} new handshake{plural}!" -msgstr "" +msgstr "Супер, имаме {num} нови handshake{plural}!" #, python-brace-format msgid "You have {count} new message{plural}!" -msgstr "" +msgstr "Имате {count} нови съобщения!" msgid "Ops, something went wrong ... Rebooting ..." -msgstr "" +msgstr "Упс, нещо се обърка ... Рестартиране ..." #, python-brace-format msgid "Kicked {num} stations\n" -msgstr "" +msgstr "Отхвърлих {num} станции\n" #, python-brace-format msgid "Made {num} new friends\n" -msgstr "" +msgstr "Направих {num} нови приятели\n" #, python-brace-format msgid "Got {num} handshakes\n" -msgstr "" +msgstr "Имам {num} handshakes\n" msgid "Met 1 peer" -msgstr "" +msgstr "Срещнах 1 връстник" #, python-brace-format msgid "Met {num} peers" -msgstr "" +msgstr "Срещнах {num} връстници" #, 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 "" +msgstr "Аз pwn-вах за {duration} и отхвърлих {deauthed} clients! Също така срещнах {associated} нови приятели и изядох {handshakes} handshakes! #pwnagotchi +#pwnlog #pwnlife #hacktheplanet #skynet" msgid "hours" -msgstr "" +msgstr "часове" msgid "minutes" -msgstr "" +msgstr "минути" msgid "seconds" -msgstr "" +msgstr "секунди" msgid "hour" -msgstr "" +msgstr "час" msgid "minute" -msgstr "" +msgstr "минута" msgid "second" -msgstr "" +msgstr "секунда" From 3463740fd9b8f66328369911836f0063d7e0357b Mon Sep 17 00:00:00 2001 From: georgikoemdzhiev Date: Sat, 26 Oct 2019 19:51:37 +0100 Subject: [PATCH 087/168] Small fixes --- pwnagotchi/locale/bg/LC_MESSAGES/voice.po | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pwnagotchi/locale/bg/LC_MESSAGES/voice.po b/pwnagotchi/locale/bg/LC_MESSAGES/voice.po index 5b74c18e..ac3b2fe0 100644 --- a/pwnagotchi/locale/bg/LC_MESSAGES/voice.po +++ b/pwnagotchi/locale/bg/LC_MESSAGES/voice.po @@ -24,7 +24,7 @@ msgid "Hi, I'm Pwnagotchi! Starting ..." msgstr "Здравей, аз съм Pwnagotchi! Стартиране ..." msgid "New day, new hunt, new pwns!" -msgstr "Нов ден, нов лов, нови жертви!" +msgstr "Нов ден, нов лов, нови pwns!" msgid "Hack the Planet!" msgstr "Хакни планетата!" @@ -67,7 +67,7 @@ msgid "I'm living the life!" msgstr "Живота ми е фантастичен!" msgid "I pwn therefore I am." -msgstr "Аз живея за да хаквам." +msgstr "Аз живея за да pwn-вам." msgid "So many networks!!!" msgstr "Толкова много мрежи!!!" From 1522470fa5138b2be734c71a0b89c9b9d393898e Mon Sep 17 00:00:00 2001 From: georgikoemdzhiev Date: Sat, 26 Oct 2019 19:55:14 +0100 Subject: [PATCH 088/168] Fixed compile --- pwnagotchi/locale/bg/LC_MESSAGES/voice.po | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pwnagotchi/locale/bg/LC_MESSAGES/voice.po b/pwnagotchi/locale/bg/LC_MESSAGES/voice.po index ac3b2fe0..6562a7e9 100644 --- a/pwnagotchi/locale/bg/LC_MESSAGES/voice.po +++ b/pwnagotchi/locale/bg/LC_MESSAGES/voice.po @@ -204,8 +204,8 @@ 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 "Аз pwn-вах за {duration} и отхвърлих {deauthed} clients! Също така срещнах {associated} нови приятели и изядох {handshakes} handshakes! #pwnagotchi -#pwnlog #pwnlife #hacktheplanet #skynet" +msgstr "Аз pwn-вах за {duration} и отхвърлих {deauthed} clients! Също така срещнах {associated} нови приятели и изядох {handshakes} handshakes! #pwnagotchi " +"#pwnlog #pwnlife #hacktheplanet #skynet" msgid "hours" msgstr "часове" From a44c3258773e8070aa3e6cd01beda42228576298 Mon Sep 17 00:00:00 2001 From: georgikoemdzhiev Date: Sat, 26 Oct 2019 20:07:59 +0100 Subject: [PATCH 089/168] Added meta data --- pwnagotchi/locale/bg/LC_MESSAGES/voice.mo | Bin 0 -> 5520 bytes pwnagotchi/locale/bg/LC_MESSAGES/voice.po | 18 +++++++++--------- 2 files changed, 9 insertions(+), 9 deletions(-) create mode 100644 pwnagotchi/locale/bg/LC_MESSAGES/voice.mo diff --git a/pwnagotchi/locale/bg/LC_MESSAGES/voice.mo b/pwnagotchi/locale/bg/LC_MESSAGES/voice.mo new file mode 100644 index 0000000000000000000000000000000000000000..4147a333f56b01419a807a998cd47c2065f2b7cd GIT binary patch literal 5520 zcmbuBZHygN9mWref{Unts36L@R%t7H7b-zJ{XJ*q-n%W} z7pI+lX6ByrKhOX9znt^C%P)M?aBbqghWlR^7&8w3_O1NkTK6_%-T|%$uLN%b*Mmdg z`@k6dD0m3e{2zmtfUkfW|7-9);A`Nu;D11kyY}tI+zM_2E!Y6B03QP{2Y(1^{%P=1 z@R#8G!9V2qcksQuUv`l({5RL}N9*)~%ita0b>Kfh*(+R}@!be&-gfW<;1@yJZGt`E zx4?IUPk~G|FM%F|(1MpB&h=m~D4w4K<;Ntbar;0EJ`PH+r5s-cW$%xm z#{V0<8oV45;!)(!$G}@bh2rzzW#9yegeCzm1o!3dUjdQCJP3;SH}dyK^7kh}>F^x* zF7RdWstb(yS^oZeg68}J6_-oinfbK^9OQil+@yJ+bp8t{z8B*3hrw&X?cnX;&EO$$ z3j7IpC%BG9Zvg{vEqDZ!f4=|~hd+UR;Khi)5xfQL1*@R^{w`?2AAzrduYlrt3No_y zS8yG84TC=p4uYCL18xAH1`FV;pvM0h#DuvDQO3aa;0@rHLCt#+ybb&fxEZ{XAj$7N zpyuBP-UxmhydC^0s5*EH$t6G5f_lFjl&*82)_(#-IAEQc_xGaw zSv9LmTFc*0aZ6vtUVat1^H#x=F`wlAAUEY<-olMLu78NDF4f9MxaGUzubfi+r8QE! zOL0*wrAPbPz{AblpWv20(nR$mjrzHD5dvfNkY9=;>DRfC#KlfJ=~91C?kX?amtrUV z)^o4nrW~?sV93^euiWf2192Rc0xt=|DVs!gZnokj^Ja4tReSBMZ>OTf&V`NIybb+X zTk*njT=Ay;xtVIC?p5atW{dANl8PTDK`ApbSMy5qW~(3i_3Xp6-;8^0IkF+Pl14qW z(d4A<>+3UHqo`~r>w%9|>(zbh*@>zj$8afRKZe0nCDFh(iw=Xh$87UT)0VaE&Z-ys zNx^LMt5sYGy_!E?u%kiAmrFI@Pi!-4^znN$chFY-WNmCG{Pyyi-0Za_?1z5U&gBpD zHi#MG`vtovYSit(PCFY^t2XwUtn7u;F;;_KJG8cDcg}_$a+NAU!Hy?h-AO7phwP+} z%{a1NH41&_`jD+gdwpALb_opGnb}bO)cwf_F@~&HV=|T}cr9nsc=NxM)cu-YZJzhN z;_X!sS*F$~RqSLVEV$X#zpqe6rc0*%<>td7J|6REDs(Ybrz?UuIm)?V5PHN$kyM8*yUGekmwJj>xdS@{omnax0ANJ;7Z; z!EA^91R<7lZb0@)4!I3%Gj~aNfZZ7sq@qE1A zjCf@n>eNZ@YL6K~hz)ke_v`L48==?Oj0AD)mkVa3X-oB><}1@2@o3VP8ucKGgQV$# zKI+ZPbfS^1n0-govPags(rkoD_Jn$I!Hh-|QMn0kLZd1`FW!QrLU7#d9WzvA91*ID zjL#B7J6n%eF$dP%yj5uFzLcXn&SJ zd5_su;n}&T^irTOgqvi4mXW(FHMi;%4Pl}wB~`*^cNnPd^{nZk@6{)o1#^!VIA$(8 zSLtz&N{6cUmDKY74#rlZXeM^XyN0Hy!alR7-R%lIHpoD-cdkTWlPq6rbRutRRja#q z-#)|5|9z`Muh)I1LU(X~Vp9vkM&joX8wf^WIfvMs%fdw>|5VEpVrMpwV z5{1g^w>t+mZy(q?Xzv~z8z0&++B4?QMD?UN5>Ex?;%6FDadB7FZ+mv`D31Ai1I;OJ zp~m{{p22~!;>eETmI1qMNB_vs_4n==9qbwQ;-t8%?uBvHOQL$ey=(XIupJ#38MK=Q zM+Sz5?;O~&Wo&SK{C0+hQw?M&?()4_za1VJ-MU*$HVqHoSq=K4`c!ue3`T}V2D`L3 z^xfFAISLbUtGKH<1D}NMbpyE=gtw{L)?+{U^yX~?W8;Inx+6tw(ytc>!%|cxIs5Ic z69H#LcCGZK^i1pi^h~b9NkhL%3fqzBUDtaBh+Nk&?SJ8LlcXx2PT`ki!9>$di%OWYX; z_j|gWo@#xw^^jX<5ei}lj}x6AMERtPfBFccjyW6mYt&iyc$mc((^HUa9bm!q7|%zY z`v-V(^jn7uCVi4o%Nl@Bt%IoYMCNSnYPZSunDlupEHiM4MVvWtzdt>y8&i*`$DJ1o zm^_&mUcsi{@9<#>i*=?$^Eit_ji(csJqx81a-F#=uy{VR;f@o|dq>hq>SRh+Ugzlf z3{VZnoXu0Kc4Ss$*#&9FeVlbp7c3s|rFB?4bnBo*=m;mqiX`mL@sxFrrYA5; zDy^Kejgv%DXFq?t@zs`|zCemBIByikl}reRok7-R{_<7dlyu5hIA)=K20BZmh31?e zXpPgPC4_hwLeO8r&>~(*;!JV6dr{V%$VbxU)&p2u9T!Khll%&olAW^YM2;o8)7UB91fA8R0^Zs=vVZcEI5U+L`?nl>y$}LS= zeN=I@)EmwZ59RAB7zxxInAWYW|Nj7!{s2N>mk+&Gs^#Yw*tnb^3a3fAmzBc`37+Ri z8YV6|$4IE0v`bK2x1i@8xv{b_S z42gA$ux7mSGRTib${i&dL>Ru_&{$~`&Is~KKezmw>J5`4jP8bMgU$NABTN%Zm(L%O zTXefZdz9Q#!P7h8axBlsr*J~8kE#%*MU5u?)D=lmY@;#4yPZa*tcWm14ML6GwVtz1 z_)clF1TOP2*WJ}@c0y$8YAv!>@H7+_Brz7;2H{>^Yj3|2(CjpacDT!glkJ@Zi>~YE zUD&1OOHN!7RY7)$J%@`2oJna(8Mva>mZQ3n73$5VNL}+A`7= z>8akObD?%qMGfkw`R8^T$mK)GFm�>FH5As|>4q>KAP%CyPkb-XGZ%+a#o2)*!nr zi1i+Jt;ji~X=GZSoGPU3q-E0QT)0K*q;r|ABmb?ltac4;*JrT#0yMOptl0k%&hMcR zv6Q-yXKx@o9%9-Or_j`ez{#8+_GDX0mF=0h{it?aw_7sYZQ+Ch#`%F1DV#HyXC2AG zCdbDmodGz4zUje~MMz7NfwQq_B$3*Qg1!1>$tX2CHGA~j*Cq3Y++8xaf6+-`Rhj)4 DgEEgw literal 0 HcmV?d00001 diff --git a/pwnagotchi/locale/bg/LC_MESSAGES/voice.po b/pwnagotchi/locale/bg/LC_MESSAGES/voice.po index 6562a7e9..e6d14313 100644 --- a/pwnagotchi/locale/bg/LC_MESSAGES/voice.po +++ b/pwnagotchi/locale/bg/LC_MESSAGES/voice.po @@ -1,20 +1,20 @@ -# SOME DESCRIPTIVE TITLE. -# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER +# pwnagotchi. +# Copyright (C) 2019 # This file is distributed under the same license as the PACKAGE package. -# FIRST AUTHOR , YEAR. +# FIRST AUTHOR Georgi Koemdzhiev , 2019. # #, fuzzy msgid "" msgstr "" -"Project-Id-Version: PACKAGE VERSION\n" +"Project-Id-Version: 0.0.1\n" "Report-Msgid-Bugs-To: \n" "POT-Creation-Date: 2019-10-23 20:56+0200\n" -"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" -"Last-Translator: FULL NAME \n" -"Language-Team: LANGUAGE \n" -"Language: \n" +"PO-Revision-Date: 2019-10-23 20:56+0200\n" +"Last-Translator: Georgi Koemdzhiev \n" +"Language-Team: \n" +"Language: bulgarian\n" "MIME-Version: 1.0\n" -"Content-Type: text/plain; charset=CHARSET\n" +"Content-Type: text/plain; charset=utf-8\n" "Content-Transfer-Encoding: 8bit\n" msgid "ZzzzZZzzzzZzzz" From d2af951b34ea2c9bd1c2a5c9d2f5b88320484246 Mon Sep 17 00:00:00 2001 From: georgikoemdzhiev Date: Sat, 26 Oct 2019 20:09:39 +0100 Subject: [PATCH 090/168] Generated mo file --- pwnagotchi/locale/bg/LC_MESSAGES/voice.mo | Bin 5520 -> 5527 bytes 1 file changed, 0 insertions(+), 0 deletions(-) diff --git a/pwnagotchi/locale/bg/LC_MESSAGES/voice.mo b/pwnagotchi/locale/bg/LC_MESSAGES/voice.mo index 4147a333f56b01419a807a998cd47c2065f2b7cd..52947470f7ebbbf6d2327a685de34f595e4125b1 100644 GIT binary patch delta 649 zcmX}oK}%Fo6u|Lg!x+w#rm3h%T&XOa-g|dO$Mh+K5K=I%L}(TGtmm5{XU1mUD7YE5 zFHpFMRtdFeDY|x%AVG@=grHq3an-Jn3h{q+aP_z%CiLL()HPs-p9}{U7 zk;Ax1FaE|6Y`Y?oz${MUGLGXeUcsVmmfs51N9$x^~?$WNGrK?qlpGp2tpHeg7kJIjU`K zCpV5_1GSV+j#^1=r3O_>yXoPCPH7#R@+zSUG>l}m4m#>mi)rAm0aFm zQscQWT&%c`6Xe5ObR-Ms{}N2TiA0`DIQ3 delta 657 zcmYMwy=zlZ7{KATR;^9!mu7q?IE|A8&!Rz_zlrns2v6f@ zwDCViIwI7N*fEg+XYd@}Mw|Eymv9#|I6k`n^9{5GuHkvShberEXYjz-{zu|y?`5=` zy@hsw2gvf|)m09*;Ro!+-^g(C2M^)ExX57~MVm0uHQsdrS(@msYZ&8x1&`y+C%e6U zLu)#-s+Wx_o1mC0@W;+{32aaEGvRbVaTtAn%T*#Y<7q+*YQY&n<>)}?j<*G~T zo~QEKH!5j-o%7NpD((}^w+FY|${U771h@bO~ dQ?9hywPwRr^V<)jQ+=5g9TZLJ#k=U2{(l<(V*3C9 From d5007385f3778435e538edbee1977045ee333153 Mon Sep 17 00:00:00 2001 From: Ben Lebherz Date: Sun, 27 Oct 2019 01:35:13 +0200 Subject: [PATCH 091/168] add username option, .bashrc to backup files, implement shellcheck hints --- scripts/backup.sh | 22 +++++++++++++--------- 1 file changed, 13 insertions(+), 9 deletions(-) diff --git a/scripts/backup.sh b/scripts/backup.sh index 9d19d8ee..348061cc 100755 --- a/scripts/backup.sh +++ b/scripts/backup.sh @@ -4,44 +4,48 @@ UNIT_HOSTNAME=${1:-10.0.0.2} # output backup zip file OUTPUT=${2:-pwnagotchi-backup.zip} +# username to use for ssh +USERNAME=${3:-pi} # what to backup FILES_TO_BACKUP=( /root/brain.nn /root/brain.json /root/.api-report.json + /root/.bashrc /root/handshakes /root/peers /etc/pwnagotchi/ /var/log/pwnagotchi.log + /home/pi/.bashrc ) -ping -c 1 $UNIT_HOSTNAME >/dev/null || { - echo "@ unit $UNIT_HOSTNAME can't be reached, make sure it's connected and a static IP assigned to the USB interface." +ping -c 1 "${UNIT_HOSTNAME}" >/dev/null || { + echo "@ unit ${UNIT_HOSTNAME} can't be reached, make sure it's connected and a static IP assigned to the USB interface." exit 1 } echo "@ backing up $UNIT_HOSTNAME to $OUTPUT ..." -ssh pi@$UNIT_HOSTNAME "sudo rm -rf /tmp/backup && sudo rm -rf /tmp/backup.zip" > /dev/null +ssh "${USERNAME}@${UNIT_HOSTNAME}" "sudo rm -rf /tmp/backup && sudo rm -rf /tmp/backup.zip" > /dev/null for file in "${FILES_TO_BACKUP[@]}"; do - dir=$(dirname $file) + dir=$(dirname "$file") echo "@ copying $file to /tmp/backup$dir" - ssh pi@$UNIT_HOSTNAME "mkdir -p /tmp/backup$dir" > /dev/null - ssh pi@$UNIT_HOSTNAME "sudo cp -r $file /tmp/backup$dir" > /dev/null + ssh "${USERNAME}@${UNIT_HOSTNAME}" "mkdir -p /tmp/backup${dir}" > /dev/null + ssh "${USERNAME}@${UNIT_HOSTNAME}" "sudo cp -r ${file} /tmp/backup${dir}" > /dev/null done -ssh pi@$UNIT_HOSTNAME "sudo chown pi:pi -R /tmp/backup" > /dev/null +ssh "${USERNAME}@${UNIT_HOSTNAME}" "sudo chown ${USERNAME}:${USERNAME} -R /tmp/backup" > /dev/null echo "@ pulling from $UNIT_HOSTNAME ..." rm -rf /tmp/backup -scp -rC pi@$UNIT_HOSTNAME:/tmp/backup /tmp/ +scp -rC "${USERNAME}@${UNIT_HOSTNAME}":/tmp/backup /tmp/ echo "@ compressing ..." -zip -r -9 -q $OUTPUT /tmp/backup +zip -r -9 -q $"OUTPUT" /tmp/backup rm -rf /tmp/backup From 69b3fabba1a1e063f12cd4d28aee36af5dd689cc Mon Sep 17 00:00:00 2001 From: PhyberApex Date: Sun, 27 Oct 2019 02:02:34 +0200 Subject: [PATCH 092/168] Fix for #421 Missed giving the argument to actually be able to write the response. --- pwnagotchi/ui/web.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pwnagotchi/ui/web.py b/pwnagotchi/ui/web.py index 716a2c0b..8376ce4f 100644 --- a/pwnagotchi/ui/web.py +++ b/pwnagotchi/ui/web.py @@ -177,7 +177,7 @@ class Handler(BaseHTTPRequestHandler): groups = matches.groups() plugin_name = groups[0] right_path = groups[1] if len(groups) == 2 else None - plugins.one(plugin_name, 'webhook', right_path) + plugins.one(plugin_name, 'webhook', self, right_path) else: self.send_response(404) From d3d4df2500243d1c4e266f95627d387198ee219c Mon Sep 17 00:00:00 2001 From: Josh Soref Date: Sat, 26 Oct 2019 22:03:31 -0400 Subject: [PATCH 093/168] spelling: available --- pwnagotchi/plugins/default/auto-backup.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pwnagotchi/plugins/default/auto-backup.py b/pwnagotchi/plugins/default/auto-backup.py index b5fba2d1..7fa76440 100644 --- a/pwnagotchi/plugins/default/auto-backup.py +++ b/pwnagotchi/plugins/default/auto-backup.py @@ -2,7 +2,7 @@ __author__ = '33197631+dadav@users.noreply.github.com' __version__ = '1.0.0' __name__ = 'auto-backup' __license__ = 'GPL3' -__description__ = 'This plugin backups files when internet is availaible.' +__description__ = 'This plugin backups files when internet is available.' from pwnagotchi.utils import StatusFile import logging From 8a87bbeb9032ffcbeb4e1e3aac49e9fa4968c2c5 Mon Sep 17 00:00:00 2001 From: Josh Soref Date: Sat, 26 Oct 2019 22:13:18 -0400 Subject: [PATCH 094/168] spelling: delete --- pwnagotchi/plugins/default/AircrackOnly.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pwnagotchi/plugins/default/AircrackOnly.py b/pwnagotchi/plugins/default/AircrackOnly.py index e6860810..237f8c46 100644 --- a/pwnagotchi/plugins/default/AircrackOnly.py +++ b/pwnagotchi/plugins/default/AircrackOnly.py @@ -28,7 +28,7 @@ def on_handshake(agent, filename, access_point, client_station): if result: logging.info("[AircrackOnly] contains handshake") else: - todetele = 1 + todelete = 1 if todelete == 0: result = subprocess.run(('/usr/bin/aircrack-ng '+ filename +' | grep "PMKID" | awk \'{print $2}\''),shell=True, stdout=subprocess.PIPE) @@ -36,7 +36,7 @@ def on_handshake(agent, filename, access_point, client_station): if result: logging.info("[AircrackOnly] contains PMKID") else: - todetele = 1 + todelete = 1 if todelete == 1: os.remove(filename) From 479f9cf79db8b13333cf6e8ec0cc4d92caf6aad7 Mon Sep 17 00:00:00 2001 From: Josh Soref Date: Sat, 26 Oct 2019 22:05:29 -0400 Subject: [PATCH 095/168] spelling: display --- pwnagotchi/ui/hw/libs/papirus/epd.py | 2 +- pwnagotchi/ui/hw/libs/waveshare/lcdhat/ST7789.py | 2 +- pwnagotchi/ui/hw/libs/waveshare/oledhat/SH1106.py | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/pwnagotchi/ui/hw/libs/papirus/epd.py b/pwnagotchi/ui/hw/libs/papirus/epd.py index 11764d99..009ed9e0 100644 --- a/pwnagotchi/ui/hw/libs/papirus/epd.py +++ b/pwnagotchi/ui/hw/libs/papirus/epd.py @@ -173,7 +173,7 @@ to use: # attempt grayscale conversion, and then to single bit # better to do this before calling this if the image is to - # be dispayed several times + # be displayed several times if image.mode != "1": image = ImageOps.grayscale(image).convert("1", dither=Image.FLOYDSTEINBERG) diff --git a/pwnagotchi/ui/hw/libs/waveshare/lcdhat/ST7789.py b/pwnagotchi/ui/hw/libs/waveshare/lcdhat/ST7789.py index 88409ecc..3864d798 100644 --- a/pwnagotchi/ui/hw/libs/waveshare/lcdhat/ST7789.py +++ b/pwnagotchi/ui/hw/libs/waveshare/lcdhat/ST7789.py @@ -35,7 +35,7 @@ class ST7789(object): self._spi.writebytes([val]) def Init(self): - """Initialize dispaly""" + """Initialize display""" self.reset() self.command(0x36) diff --git a/pwnagotchi/ui/hw/libs/waveshare/oledhat/SH1106.py b/pwnagotchi/ui/hw/libs/waveshare/oledhat/SH1106.py index a89716d8..d206f704 100644 --- a/pwnagotchi/ui/hw/libs/waveshare/oledhat/SH1106.py +++ b/pwnagotchi/ui/hw/libs/waveshare/oledhat/SH1106.py @@ -34,7 +34,7 @@ class SH1106(object): def Init(self): if (config.module_init() != 0): return -1 - """Initialize dispaly""" + """Initialize display""" self.reset() self.command(0xAE);#--turn off oled panel self.command(0x02);#---set low column address From 13b73a64c838f521afc5d4c8b8d72b3af3b31ddb Mon Sep 17 00:00:00 2001 From: Josh Soref Date: Sat, 26 Oct 2019 22:05:42 -0400 Subject: [PATCH 096/168] spelling: documentation --- pwnagotchi/ui/hw/libs/waveshare/oledhat/config.py | 2 +- pwnagotchi/ui/hw/libs/waveshare/v1/epd2in13.py | 2 +- pwnagotchi/ui/hw/libs/waveshare/v1/epd2in13bc.py | 2 +- pwnagotchi/ui/hw/libs/waveshare/v1/epdconfig.py | 2 +- pwnagotchi/ui/hw/libs/waveshare/v2/waveshare.py | 2 +- pwnagotchi/ui/hw/libs/waveshare/v27inch/epd2in7.py | 2 +- pwnagotchi/ui/hw/libs/waveshare/v27inch/epdconfig.py | 2 +- 7 files changed, 7 insertions(+), 7 deletions(-) diff --git a/pwnagotchi/ui/hw/libs/waveshare/oledhat/config.py b/pwnagotchi/ui/hw/libs/waveshare/oledhat/config.py index 1eaf68e5..07da6c24 100644 --- a/pwnagotchi/ui/hw/libs/waveshare/oledhat/config.py +++ b/pwnagotchi/ui/hw/libs/waveshare/oledhat/config.py @@ -9,7 +9,7 @@ # * | Info : # ******************************************************************************/ # Permission is hereby granted, free of charge, to any person obtaining a copy -# of this software and associated documnetation files (the "Software"), to deal +# 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 diff --git a/pwnagotchi/ui/hw/libs/waveshare/v1/epd2in13.py b/pwnagotchi/ui/hw/libs/waveshare/v1/epd2in13.py index 93685e18..ade7bb8d 100644 --- a/pwnagotchi/ui/hw/libs/waveshare/v1/epd2in13.py +++ b/pwnagotchi/ui/hw/libs/waveshare/v1/epd2in13.py @@ -9,7 +9,7 @@ # # | Info : python demo # ----------------------------------------------------------------------------- # Permission is hereby granted, free of charge, to any person obtaining a copy -# of this software and associated documnetation files (the "Software"), to deal +# 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 diff --git a/pwnagotchi/ui/hw/libs/waveshare/v1/epd2in13bc.py b/pwnagotchi/ui/hw/libs/waveshare/v1/epd2in13bc.py index f17f0af0..c829ba3b 100644 --- a/pwnagotchi/ui/hw/libs/waveshare/v1/epd2in13bc.py +++ b/pwnagotchi/ui/hw/libs/waveshare/v1/epd2in13bc.py @@ -9,7 +9,7 @@ # # | Info : python demo # ----------------------------------------------------------------------------- # Permission is hereby granted, free of charge, to any person obtaining a copy -# of this software and associated documnetation files (the "Software"), to deal +# 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 diff --git a/pwnagotchi/ui/hw/libs/waveshare/v1/epdconfig.py b/pwnagotchi/ui/hw/libs/waveshare/v1/epdconfig.py index 76d8ca9d..debf0028 100644 --- a/pwnagotchi/ui/hw/libs/waveshare/v1/epdconfig.py +++ b/pwnagotchi/ui/hw/libs/waveshare/v1/epdconfig.py @@ -9,7 +9,7 @@ # * | Info : # ****************************************************************************** # Permission is hereby granted, free of charge, to any person obtaining a copy -# of this software and associated documnetation files (the "Software"), to deal +# 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 diff --git a/pwnagotchi/ui/hw/libs/waveshare/v2/waveshare.py b/pwnagotchi/ui/hw/libs/waveshare/v2/waveshare.py index 6c3ed04a..a90909a0 100644 --- a/pwnagotchi/ui/hw/libs/waveshare/v2/waveshare.py +++ b/pwnagotchi/ui/hw/libs/waveshare/v2/waveshare.py @@ -27,7 +27,7 @@ # epd.display(getbuffer(image)) # ******************************************************************************// # Permission is hereby granted, free of charge, to any person obtaining a copy -# of this software and associated documnetation files (the "Software"), to deal +# 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 diff --git a/pwnagotchi/ui/hw/libs/waveshare/v27inch/epd2in7.py b/pwnagotchi/ui/hw/libs/waveshare/v27inch/epd2in7.py index 20f16657..3247b3c7 100644 --- a/pwnagotchi/ui/hw/libs/waveshare/v27inch/epd2in7.py +++ b/pwnagotchi/ui/hw/libs/waveshare/v27inch/epd2in7.py @@ -9,7 +9,7 @@ # # | Info : python demo # ----------------------------------------------------------------------------- # Permission is hereby granted, free of charge, to any person obtaining a copy -# of this software and associated documnetation files (the "Software"), to deal +# 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 diff --git a/pwnagotchi/ui/hw/libs/waveshare/v27inch/epdconfig.py b/pwnagotchi/ui/hw/libs/waveshare/v27inch/epdconfig.py index 861f43da..eff576b4 100644 --- a/pwnagotchi/ui/hw/libs/waveshare/v27inch/epdconfig.py +++ b/pwnagotchi/ui/hw/libs/waveshare/v27inch/epdconfig.py @@ -9,7 +9,7 @@ # * | Info : # ****************************************************************************** # Permission is hereby granted, free of charge, to any person obtaining a copy -# of this software and associated documnetation files (the "Software"), to deal +# 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 From 3bc887631bd7a4a97c7814db43ffdeb50a5263e3 Mon Sep 17 00:00:00 2001 From: Josh Soref Date: Sat, 26 Oct 2019 22:07:34 -0400 Subject: [PATCH 097/168] spelling: functions --- pwnagotchi/plugins/default/ups_lite.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pwnagotchi/plugins/default/ups_lite.py b/pwnagotchi/plugins/default/ups_lite.py index d9a37d28..5c0d9d7b 100644 --- a/pwnagotchi/plugins/default/ups_lite.py +++ b/pwnagotchi/plugins/default/ups_lite.py @@ -1,6 +1,6 @@ # Based on UPS Lite v1.1 from https://github.com/xenDE # -# funtions for get UPS status - needs enable "i2c" in raspi-config +# functions for get UPS status - needs enable "i2c" in raspi-config # # https://github.com/linshuqin329/UPS-Lite # From 28c28b8b0cceb76016fc72d4bf9418b49df7db06 Mon Sep 17 00:00:00 2001 From: Josh Soref Date: Sat, 26 Oct 2019 22:07:47 -0400 Subject: [PATCH 098/168] spelling: furnished --- pwnagotchi/ui/hw/libs/waveshare/oledhat/config.py | 2 +- pwnagotchi/ui/hw/libs/waveshare/v1/epd2in13.py | 2 +- pwnagotchi/ui/hw/libs/waveshare/v1/epd2in13bc.py | 2 +- pwnagotchi/ui/hw/libs/waveshare/v1/epdconfig.py | 2 +- pwnagotchi/ui/hw/libs/waveshare/v2/waveshare.py | 2 +- pwnagotchi/ui/hw/libs/waveshare/v27inch/epd2in7.py | 2 +- pwnagotchi/ui/hw/libs/waveshare/v27inch/epdconfig.py | 2 +- 7 files changed, 7 insertions(+), 7 deletions(-) diff --git a/pwnagotchi/ui/hw/libs/waveshare/oledhat/config.py b/pwnagotchi/ui/hw/libs/waveshare/oledhat/config.py index 07da6c24..6f2355e5 100644 --- a/pwnagotchi/ui/hw/libs/waveshare/oledhat/config.py +++ b/pwnagotchi/ui/hw/libs/waveshare/oledhat/config.py @@ -13,7 +13,7 @@ # in the Software without restriction, including without limitation the rights # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell # copies of the Software, and to permit persons to whom the Software is -# furished to do so, subject to the following conditions: +# 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. diff --git a/pwnagotchi/ui/hw/libs/waveshare/v1/epd2in13.py b/pwnagotchi/ui/hw/libs/waveshare/v1/epd2in13.py index ade7bb8d..7913c0ef 100644 --- a/pwnagotchi/ui/hw/libs/waveshare/v1/epd2in13.py +++ b/pwnagotchi/ui/hw/libs/waveshare/v1/epd2in13.py @@ -13,7 +13,7 @@ # in the Software without restriction, including without limitation the rights # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell # copies of the Software, and to permit persons to whom the Software is -# furished to do so, subject to the following conditions: +# 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. diff --git a/pwnagotchi/ui/hw/libs/waveshare/v1/epd2in13bc.py b/pwnagotchi/ui/hw/libs/waveshare/v1/epd2in13bc.py index c829ba3b..7974617d 100644 --- a/pwnagotchi/ui/hw/libs/waveshare/v1/epd2in13bc.py +++ b/pwnagotchi/ui/hw/libs/waveshare/v1/epd2in13bc.py @@ -13,7 +13,7 @@ # in the Software without restriction, including without limitation the rights # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell # copies of the Software, and to permit persons to whom the Software is -# furished to do so, subject to the following conditions: +# 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. diff --git a/pwnagotchi/ui/hw/libs/waveshare/v1/epdconfig.py b/pwnagotchi/ui/hw/libs/waveshare/v1/epdconfig.py index debf0028..728f5cf8 100644 --- a/pwnagotchi/ui/hw/libs/waveshare/v1/epdconfig.py +++ b/pwnagotchi/ui/hw/libs/waveshare/v1/epdconfig.py @@ -13,7 +13,7 @@ # in the Software without restriction, including without limitation the rights # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell # copies of the Software, and to permit persons to whom the Software is -# furished to do so, subject to the following conditions: +# 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. diff --git a/pwnagotchi/ui/hw/libs/waveshare/v2/waveshare.py b/pwnagotchi/ui/hw/libs/waveshare/v2/waveshare.py index a90909a0..a9faac84 100644 --- a/pwnagotchi/ui/hw/libs/waveshare/v2/waveshare.py +++ b/pwnagotchi/ui/hw/libs/waveshare/v2/waveshare.py @@ -31,7 +31,7 @@ # in the Software without restriction, including without limitation the rights # to use, copy, modify, merge, publish, distribute, sublicense, and//or sell # copies of the Software, and to permit persons to whom the Software is -# furished to do so, subject to the following conditions: +# 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. diff --git a/pwnagotchi/ui/hw/libs/waveshare/v27inch/epd2in7.py b/pwnagotchi/ui/hw/libs/waveshare/v27inch/epd2in7.py index 3247b3c7..4bb2245a 100644 --- a/pwnagotchi/ui/hw/libs/waveshare/v27inch/epd2in7.py +++ b/pwnagotchi/ui/hw/libs/waveshare/v27inch/epd2in7.py @@ -13,7 +13,7 @@ # in the Software without restriction, including without limitation the rights # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell # copies of the Software, and to permit persons to whom the Software is -# furished to do so, subject to the following conditions: +# 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. diff --git a/pwnagotchi/ui/hw/libs/waveshare/v27inch/epdconfig.py b/pwnagotchi/ui/hw/libs/waveshare/v27inch/epdconfig.py index eff576b4..efbc319e 100644 --- a/pwnagotchi/ui/hw/libs/waveshare/v27inch/epdconfig.py +++ b/pwnagotchi/ui/hw/libs/waveshare/v27inch/epdconfig.py @@ -13,7 +13,7 @@ # in the Software without restriction, including without limitation the rights # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell # copies of the Software, and to permit persons to whom the Software is -# furished to do so, subject to the following conditions: +# 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. From a2c3df5c99007e3624e550cc0a0dc132c61782b1 Mon Sep 17 00:00:00 2001 From: Josh Soref Date: Sat, 26 Oct 2019 22:08:06 -0400 Subject: [PATCH 099/168] spelling: going --- pwnagotchi/ui/view.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pwnagotchi/ui/view.py b/pwnagotchi/ui/view.py index d529eca7..94a34e1b 100644 --- a/pwnagotchi/ui/view.py +++ b/pwnagotchi/ui/view.py @@ -238,7 +238,7 @@ class View(object): part = secs / 10.0 for step in range(0, 10): - # if we weren't in a normal state before goin + # if we weren't in a normal state before going # to sleep, keep that face and status on for # a while, otherwise the sleep animation will # always override any minor state change before it From 1c2ff20ab4b4f0471676c368293c939eeb6be124 Mon Sep 17 00:00:00 2001 From: Josh Soref Date: Sat, 26 Oct 2019 22:08:50 -0400 Subject: [PATCH 100/168] spelling: lifting --- scripts/win_connection_share.ps1 | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/scripts/win_connection_share.ps1 b/scripts/win_connection_share.ps1 index c7c4ece8..6c07c47a 100644 --- a/scripts/win_connection_share.ps1 +++ b/scripts/win_connection_share.ps1 @@ -51,10 +51,10 @@ Param ( Function Create-HNetObjects { <# .SYNOPSIS - A helper function that does the heavy lifiting with NetCfg.HNetShare + A helper function that does the heavy lifting with NetCfg.HNetShare .DESCRIPTION - A helper function that does the heavy lifiting with NetCfg.HNetShare. This returns a PSObject containing the `INetSharingConfigurationForINetConnection` info of 2 Adapters. + A helper function that does the heavy lifting with NetCfg.HNetShare. This returns a PSObject containing the `INetSharingConfigurationForINetConnection` info of 2 Adapters. .PARAMETER InternetAdaptor The output of Get-NetAdaptor filtered down to the 'main' uplink interface. From ecfc5f90633cb6e49ea3d7f96ef337e664d7832d Mon Sep 17 00:00:00 2001 From: Josh Soref Date: Sat, 26 Oct 2019 22:09:19 -0400 Subject: [PATCH 101/168] spelling: occurred --- pwnagotchi/plugins/default/unfiltered_example.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pwnagotchi/plugins/default/unfiltered_example.py b/pwnagotchi/plugins/default/unfiltered_example.py index c883b7c0..9fe1e496 100644 --- a/pwnagotchi/plugins/default/unfiltered_example.py +++ b/pwnagotchi/plugins/default/unfiltered_example.py @@ -13,7 +13,7 @@ OPTIONS = dict() def on_loaded(): logging.warning("%s plugin loaded" % __name__) -# called when AP list is ready, before whitelist filtering has occured +# called when AP list is ready, before whitelist filtering has occurred def on_unfiltered_ap_list(agent,aps): logging.info("Unfiltered AP list to follow") for ap in aps: From 102a061814e3e65e8656931ffd848fe90adfaa2c Mon Sep 17 00:00:00 2001 From: Josh Soref Date: Sat, 26 Oct 2019 22:09:37 -0400 Subject: [PATCH 102/168] spelling: please --- pwnagotchi/plugins/default/bt-tether.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pwnagotchi/plugins/default/bt-tether.py b/pwnagotchi/plugins/default/bt-tether.py index 2a7e595f..dbbcebe1 100644 --- a/pwnagotchi/plugins/default/bt-tether.py +++ b/pwnagotchi/plugins/default/bt-tether.py @@ -423,7 +423,7 @@ def on_loaded(): for opt in ['share_internet', 'mac', 'ip', 'netmask', 'interval']: if opt not in OPTIONS or (opt in OPTIONS and OPTIONS[opt] is None): - logging.error("BT-TET: Pleace specify the %s in your config.yml.", opt) + logging.error("BT-TET: Please specify the %s in your config.yml.", opt) return # ensure bluetooth is running From 5a16819febe68e478f1af9e8b87481758e407621 Mon Sep 17 00:00:00 2001 From: Josh Soref Date: Sat, 26 Oct 2019 22:09:57 -0400 Subject: [PATCH 103/168] spelling: pwnagotchi --- pwnagotchi/plugins/default/screen_refresh.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pwnagotchi/plugins/default/screen_refresh.py b/pwnagotchi/plugins/default/screen_refresh.py index 94173ff2..3c7047fa 100644 --- a/pwnagotchi/plugins/default/screen_refresh.py +++ b/pwnagotchi/plugins/default/screen_refresh.py @@ -1,4 +1,4 @@ -__author__ = 'pwnagotcchi [at] rossmarks [dot] uk' +__author__ = 'pwnagotchi [at] rossmarks [dot] uk' __version__ = '1.0.0' __name__ = 'screen_refresh' __license__ = 'GPL3' From 9888e1fb39de08ecba3035c3a47fa90a659c8e07 Mon Sep 17 00:00:00 2001 From: Josh Soref Date: Sat, 26 Oct 2019 22:12:28 -0400 Subject: [PATCH 104/168] spelling: stopping --- pwnagotchi/plugins/default/bt-tether.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pwnagotchi/plugins/default/bt-tether.py b/pwnagotchi/plugins/default/bt-tether.py index dbbcebe1..509519cd 100644 --- a/pwnagotchi/plugins/default/bt-tether.py +++ b/pwnagotchi/plugins/default/bt-tether.py @@ -236,7 +236,7 @@ class BTNap: timeout -= 1 try: - logging.debug("BT-TETHER: Stoping Discovery ...") + logging.debug("BT-TETHER: Stopping Discovery ...") bt_dev.StopDiscovery() except Exception as bt_ex: logging.error(bt_ex) From 0b495ebd1354d30e87b591bba3aa176f2faf8853 Mon Sep 17 00:00:00 2001 From: Josh Soref Date: Sat, 26 Oct 2019 22:12:45 -0400 Subject: [PATCH 105/168] spelling: successfully --- pwnagotchi/plugins/default/auto-backup.py | 2 +- pwnagotchi/plugins/default/onlinehashcrack.py | 2 +- pwnagotchi/plugins/default/wigle.py | 2 +- pwnagotchi/plugins/default/wpa-sec.py | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/pwnagotchi/plugins/default/auto-backup.py b/pwnagotchi/plugins/default/auto-backup.py index 7fa76440..c235f787 100644 --- a/pwnagotchi/plugins/default/auto-backup.py +++ b/pwnagotchi/plugins/default/auto-backup.py @@ -30,7 +30,7 @@ def on_loaded(): return READY = True - logging.info("AUTO-BACKUP: Successfuly loaded.") + logging.info("AUTO-BACKUP: Successfully loaded.") def on_internet_available(agent): diff --git a/pwnagotchi/plugins/default/onlinehashcrack.py b/pwnagotchi/plugins/default/onlinehashcrack.py index dbf8a4ec..4f51ed7f 100644 --- a/pwnagotchi/plugins/default/onlinehashcrack.py +++ b/pwnagotchi/plugins/default/onlinehashcrack.py @@ -74,7 +74,7 @@ def on_internet_available(agent): _upload_to_ohc(handshake) reported.append(handshake) REPORT.update(data={'reported': reported}) - logging.info(f"OHC: Successfuly uploaded {handshake}") + logging.info(f"OHC: Successfully uploaded {handshake}") except requests.exceptions.RequestException as req_e: SKIP.append(handshake) logging.error("OHC: %s", req_e) diff --git a/pwnagotchi/plugins/default/wigle.py b/pwnagotchi/plugins/default/wigle.py index 462cdab4..ad2b8a56 100644 --- a/pwnagotchi/plugins/default/wigle.py +++ b/pwnagotchi/plugins/default/wigle.py @@ -186,7 +186,7 @@ def on_internet_available(agent): _send_to_wigle(csv_entries, OPTIONS['api_key']) reported += no_err_entries REPORT.update(data={'reported': reported}) - logging.info("WIGLE: Successfuly uploaded %d files", len(no_err_entries)) + logging.info("WIGLE: Successfully uploaded %d files", len(no_err_entries)) except requests.exceptions.RequestException as re_e: SKIP += no_err_entries logging.error("WIGLE: Got an exception while uploading %s", re_e) diff --git a/pwnagotchi/plugins/default/wpa-sec.py b/pwnagotchi/plugins/default/wpa-sec.py index 6bf99940..1addc330 100644 --- a/pwnagotchi/plugins/default/wpa-sec.py +++ b/pwnagotchi/plugins/default/wpa-sec.py @@ -77,7 +77,7 @@ def on_internet_available(agent): _upload_to_wpasec(handshake) reported.append(handshake) REPORT.update(data={'reported': reported}) - logging.info("WPA_SEC: Successfuly uploaded %s", handshake) + logging.info("WPA_SEC: Successfully uploaded %s", handshake) except requests.exceptions.RequestException as req_e: SKIP.append(handshake) logging.error("WPA_SEC: %s", req_e) From 8a687b723b447a1466341099466603b1b82a4e20 Mon Sep 17 00:00:00 2001 From: Josh Soref Date: Sat, 26 Oct 2019 22:13:42 -0400 Subject: [PATCH 106/168] spelling: transfer --- pwnagotchi/ui/hw/libs/papirus/epd.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pwnagotchi/ui/hw/libs/papirus/epd.py b/pwnagotchi/ui/hw/libs/papirus/epd.py index 009ed9e0..aed46059 100644 --- a/pwnagotchi/ui/hw/libs/papirus/epd.py +++ b/pwnagotchi/ui/hw/libs/papirus/epd.py @@ -47,7 +47,7 @@ to use: image = Image.new('1', epd.size, 0) # draw on image epd.clear() # clear the panel - epd.display(image) # tranfer image data + epd.display(image) # transfer image data epd.update() # refresh the panel image - not needed if auto=true """ From 5ae7695229ddb04b3523cdeccc8f466a0413d391 Mon Sep 17 00:00:00 2001 From: Josh Soref Date: Sat, 26 Oct 2019 22:13:59 -0400 Subject: [PATCH 107/168] spelling: unknown --- pwnagotchi/ui/hw/libs/dfrobot/dfrobot_epaper.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pwnagotchi/ui/hw/libs/dfrobot/dfrobot_epaper.py b/pwnagotchi/ui/hw/libs/dfrobot/dfrobot_epaper.py index d207a45d..8182bee1 100644 --- a/pwnagotchi/ui/hw/libs/dfrobot/dfrobot_epaper.py +++ b/pwnagotchi/ui/hw/libs/dfrobot/dfrobot_epaper.py @@ -10,7 +10,7 @@ try: from .spi import SPI from .gpio import GPIO except: - print("unknow platform") + print("unknown platform") exit() CONFIG_IL0376F = { From b93bcd07d4eb91b8f191a373589166c1a3a6e7af Mon Sep 17 00:00:00 2001 From: Josh Soref Date: Sat, 26 Oct 2019 22:14:15 -0400 Subject: [PATCH 108/168] spelling: uploads --- pwnagotchi/plugins/default/onlinehashcrack.py | 2 +- pwnagotchi/plugins/default/wigle.py | 2 +- pwnagotchi/plugins/default/wpa-sec.py | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/pwnagotchi/plugins/default/onlinehashcrack.py b/pwnagotchi/plugins/default/onlinehashcrack.py index 4f51ed7f..47f881a0 100644 --- a/pwnagotchi/plugins/default/onlinehashcrack.py +++ b/pwnagotchi/plugins/default/onlinehashcrack.py @@ -2,7 +2,7 @@ __author__ = '33197631+dadav@users.noreply.github.com' __version__ = '2.0.0' __name__ = 'onlinehashcrack' __license__ = 'GPL3' -__description__ = 'This plugin automatically uploades handshakes to https://onlinehashcrack.com' +__description__ = 'This plugin automatically uploads handshakes to https://onlinehashcrack.com' import os import logging diff --git a/pwnagotchi/plugins/default/wigle.py b/pwnagotchi/plugins/default/wigle.py index ad2b8a56..5437132b 100644 --- a/pwnagotchi/plugins/default/wigle.py +++ b/pwnagotchi/plugins/default/wigle.py @@ -2,7 +2,7 @@ __author__ = '33197631+dadav@users.noreply.github.com' __version__ = '2.0.0' __name__ = 'wigle' __license__ = 'GPL3' -__description__ = 'This plugin automatically uploades collected wifis to wigle.net' +__description__ = 'This plugin automatically uploads collected wifis to wigle.net' import os import logging diff --git a/pwnagotchi/plugins/default/wpa-sec.py b/pwnagotchi/plugins/default/wpa-sec.py index 1addc330..5b13f315 100644 --- a/pwnagotchi/plugins/default/wpa-sec.py +++ b/pwnagotchi/plugins/default/wpa-sec.py @@ -2,7 +2,7 @@ __author__ = '33197631+dadav@users.noreply.github.com' __version__ = '2.0.1' __name__ = 'wpa-sec' __license__ = 'GPL3' -__description__ = 'This plugin automatically uploades handshakes to https://wpa-sec.stanev.org' +__description__ = 'This plugin automatically uploads handshakes to https://wpa-sec.stanev.org' import os import logging From b66c86b31ada8ccf6ddaff7d4c6f6ae6ce7398cd Mon Sep 17 00:00:00 2001 From: Ed Medvedev Date: Sun, 27 Oct 2019 07:47:22 +0200 Subject: [PATCH 109/168] Fix the AircrackOnly plugin AircrackOnly plugin wasn't working because of a typo: marking files for deletion referenced a wrong variable. --- pwnagotchi/plugins/default/AircrackOnly.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/pwnagotchi/plugins/default/AircrackOnly.py b/pwnagotchi/plugins/default/AircrackOnly.py index e6860810..b6d20286 100644 --- a/pwnagotchi/plugins/default/AircrackOnly.py +++ b/pwnagotchi/plugins/default/AircrackOnly.py @@ -1,5 +1,5 @@ __author__ = 'pwnagotchi [at] rossmarks [dot] uk' -__version__ = '1.0.0' +__version__ = '1.0.1' __name__ = 'AircrackOnly' __license__ = 'GPL3' __description__ = 'confirm pcap contains handshake/PMKID or delete it' @@ -28,7 +28,7 @@ def on_handshake(agent, filename, access_point, client_station): if result: logging.info("[AircrackOnly] contains handshake") else: - todetele = 1 + todelete = 1 if todelete == 0: result = subprocess.run(('/usr/bin/aircrack-ng '+ filename +' | grep "PMKID" | awk \'{print $2}\''),shell=True, stdout=subprocess.PIPE) @@ -36,11 +36,11 @@ def on_handshake(agent, filename, access_point, client_station): if result: logging.info("[AircrackOnly] contains PMKID") else: - todetele = 1 + todelete = 1 if todelete == 1: os.remove(filename) - set_text("uncrackable pcap") + set_text("Removed an uncrackable pcap") display.update(force=True) text_to_set = ""; From 16e310e37792f87481ab281d3e97a92781254557 Mon Sep 17 00:00:00 2001 From: Justin Richards Date: Sun, 27 Oct 2019 02:18:42 -0500 Subject: [PATCH 110/168] Adding fast display update for waveshare 3-color Signed-off-by: Justin Richards --- .../ui/hw/libs/waveshare/v1/epd2in13bcFAST.py | 359 ++++++++++++++++++ pwnagotchi/ui/hw/waveshare1.py | 17 +- 2 files changed, 370 insertions(+), 6 deletions(-) create mode 100644 pwnagotchi/ui/hw/libs/waveshare/v1/epd2in13bcFAST.py diff --git a/pwnagotchi/ui/hw/libs/waveshare/v1/epd2in13bcFAST.py b/pwnagotchi/ui/hw/libs/waveshare/v1/epd2in13bcFAST.py new file mode 100644 index 00000000..b7704543 --- /dev/null +++ b/pwnagotchi/ui/hw/libs/waveshare/v1/epd2in13bcFAST.py @@ -0,0 +1,359 @@ +# ***************************************************************************** +# * | File : epd2in13d.py +# * | Author : Waveshare team +# * | Function : Electronic paper driver +# * | Info : +# *---------------- +# * | This version: V4.0 +# * | Date : 2019-06-20 +# # | Info : python demo +# ----------------------------------------------------------------------------- +# Permission is hereby granted, free of charge, to any person obtaining a copy +# of this software and associated documnetation files (the "Software"), to deal +# in the Software without restriction, including without limitation the rights +# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +# copies of the Software, and to permit persons to whom the Software is +# furished to do so, subject to the following conditions: +# +# The above copyright notice and this permission notice shall be included in +# all copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +# FITNESS OR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +# LIABILITY WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +# THE SOFTWARE. +# + + +# ============================================================================= +# THIS FILE HAS BEEN MODIFIED FROM ORIGINAL, IT HAS BEEN MODIFIED TO RUN THE +# THREE COLOR WAVESHARE 2.13IN DISPLAY AT A MUCH, MUCH FASTER RATE THAN NORMAL +# AND IT COULD DAMAGE YOUR DISPLAY. THERE IS NO WARRANTY INCLUDED AND YOU USE +# THIS CODE AT YOUR OWN RISK. WE ARE NOT RESPONSIBLE FOR ANYTHING THAT HAPPENS +# INCLUDING BUT NOT LIMITED TO: DESTRUCTION OF YOUR DISPLAY, PI, HOUSE, CAR, +# SPACE-TIME-CONTINUUM, OR IF THE CODE KILLS YOUR CAT. IF YOU AREN'T WILLING TO +# TAKE THESE RISKS, PLEASE DO NOT USE THIS CODE. +# ============================================================================= + + +import logging +from . import epdconfig +from PIL import Image +import RPi.GPIO as GPIO + +# Display resolution +EPD_WIDTH = 104 +EPD_HEIGHT = 212 + +class EPD: + def __init__(self): + self.reset_pin = epdconfig.RST_PIN + self.dc_pin = epdconfig.DC_PIN + self.busy_pin = epdconfig.BUSY_PIN + self.cs_pin = epdconfig.CS_PIN + self.width = EPD_WIDTH + self.height = EPD_HEIGHT + + lut_vcomDC = [ + 0x00, 0x08, 0x00, 0x00, 0x00, 0x02, + 0x60, 0x28, 0x28, 0x00, 0x00, 0x01, + 0x00, 0x14, 0x00, 0x00, 0x00, 0x01, + 0x00, 0x12, 0x12, 0x00, 0x00, 0x01, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, + ] + + lut_ww = [ + 0x40, 0x08, 0x00, 0x00, 0x00, 0x02, + 0x90, 0x28, 0x28, 0x00, 0x00, 0x01, + 0x40, 0x14, 0x00, 0x00, 0x00, 0x01, + 0xA0, 0x12, 0x12, 0x00, 0x00, 0x01, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + ] + + lut_bw = [ + 0x40, 0x17, 0x00, 0x00, 0x00, 0x02, + 0x90, 0x0F, 0x0F, 0x00, 0x00, 0x03, + 0x40, 0x0A, 0x01, 0x00, 0x00, 0x01, + 0xA0, 0x0E, 0x0E, 0x00, 0x00, 0x02, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + ] + + lut_wb = [ + 0x80, 0x08, 0x00, 0x00, 0x00, 0x02, + 0x90, 0x28, 0x28, 0x00, 0x00, 0x01, + 0x80, 0x14, 0x00, 0x00, 0x00, 0x01, + 0x50, 0x12, 0x12, 0x00, 0x00, 0x01, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + ] + + lut_bb = [ + 0x80, 0x08, 0x00, 0x00, 0x00, 0x02, + 0x90, 0x28, 0x28, 0x00, 0x00, 0x00, + 0x80, 0x14, 0x00, 0x00, 0x00, 0x01, + 0x50, 0x12, 0x12, 0x00, 0x00, 0x01, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + ] + + lut_vcom1 = [ + 0xA0, 0x10, 0x10, 0x00, 0x00, 0x02, + 0x00, 0x10, 0x10, 0x00, 0x00, 0x01, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, + ] + + lut_ww1 = [ + 0x50, 0x01, 0x01, 0x00, 0x00, 0x01, + 0xA0, 0x42, 0x42, 0x00, 0x00, 0x01, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + ] + + lut_bw1 = [ + 0x50, 0x01, 0x01, 0x00, 0x00, 0x01, + 0xA0, 0x42, 0x42, 0x00, 0x00, 0x01, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + ] + + lut_wb1 = [ + 0xA0, 0x01, 0x01, 0x00, 0x00, 0x01, + 0x50, 0x42, 0x42, 0x00, 0x00, 0x01, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + ] + + lut_bb1 = [ + 0xA0, 0x01, 0x01, 0x00, 0x00, 0x01, + 0x50, 0x42, 0x42, 0x00, 0x00, 0x01, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + ] + + + # Hardware reset + def reset(self): + epdconfig.digital_write(self.reset_pin, 1) + epdconfig.delay_ms(200) + epdconfig.digital_write(self.reset_pin, 0) + epdconfig.delay_ms(10) + epdconfig.digital_write(self.reset_pin, 1) + epdconfig.delay_ms(200) + + def send_command(self, command): + epdconfig.digital_write(self.dc_pin, 0) + epdconfig.digital_write(self.cs_pin, 0) + epdconfig.spi_writebyte([command]) + epdconfig.digital_write(self.cs_pin, 1) + + def send_data(self, data): + epdconfig.digital_write(self.dc_pin, 1) + epdconfig.digital_write(self.cs_pin, 0) + epdconfig.spi_writebyte([data]) + epdconfig.digital_write(self.cs_pin, 1) + + def ReadBusy(self): + logging.debug("e-Paper busy") + while(epdconfig.digital_read(self.busy_pin) == 0): # 0: idle, 1: busy + self.send_command(0x71) + epdconfig.delay_ms(100) + logging.debug("e-Paper busy release") + + def TurnOnDisplay(self): + self.send_command(0x12) + epdconfig.delay_ms(10) + self.ReadBusy() + + def init(self): + if (epdconfig.module_init() != 0): + return -1 + # EPD hardware init start + self.reset() + + self.send_command(0x01) # POWER SETTING + self.send_data(0x03) + self.send_data(0x00) + self.send_data(0x2b) + self.send_data(0x2b) + self.send_data(0x03) + + self.send_command(0x06) # boost soft start + self.send_data(0x17) # A + self.send_data(0x17) # B + self.send_data(0x17) # C + + self.send_command(0x04) + self.ReadBusy() + + self.send_command(0x00) # panel setting + self.send_data(0xbf) # LUT from OTP,128x296 + self.send_data(0x0d) # VCOM to 0V fast + + self.send_command(0x30) # PLL setting + self.send_data(0x21) # 3a 100HZ 29 150Hz 39 200HZ 31 171HZ + + self.send_command(0x61) # resolution setting + self.send_data(self.width) + self.send_data((self.height >> 8) & 0xff) + self.send_data(self.height& 0xff) + + self.send_command(0x82) # vcom_DC setting + self.send_data(0x28) + return 0 + + def SetFullReg(self): + self.send_command(0x82) + self.send_data(0x00) + self.send_command(0X50) + self.send_data(0x97) +# self.send_command(0x00) # panel setting +# self.send_data(0x9f) # LUT from OTP,128x296 + + def SetPartReg(self): +# self.send_command(0x00) # panel setting +# self.send_data(0xbf) # LUT from OTP,128x296 + self.send_command(0x82) + self.send_data(0x03) + self.send_command(0X50) + self.send_data(0x47) + + self.send_command(0x20) # vcom + for count in range(0, 44): + self.send_data(self.lut_vcom1[count]) + self.send_command(0x21) # ww -- + for count in range(0, 42): + self.send_data(self.lut_ww1[count]) + self.send_command(0x22) # bw r + for count in range(0, 42): + self.send_data(self.lut_bw1[count]) + self.send_command(0x23) # wb w + for count in range(0, 42): + self.send_data(self.lut_wb1[count]) + self.send_command(0x24) # bb b + for count in range(0, 42): + self.send_data(self.lut_bb1[count]) + + def getbuffer(self, image): + # logging.debug("bufsiz = ",int(self.width/8) * self.height) + buf = [0xFF] * (int(self.width/8) * self.height) + image_monocolor = image.convert('1') + imwidth, imheight = image_monocolor.size + pixels = image_monocolor.load() + # logging.debug("imwidth = %d, imheight = %d",imwidth,imheight) + if(imwidth == self.width and imheight == self.height): + logging.debug("Vertical") + for y in range(imheight): + for x in range(imwidth): + # Set the bits for the column of pixels at the current position. + if pixels[x, y] == 0: + buf[int((x + y * self.width) / 8)] &= ~(0x80 >> (x % 8)) + elif(imwidth == self.height and imheight == self.width): + logging.debug("Horizontal") + for y in range(imheight): + for x in range(imwidth): + newx = y + newy = self.height - x - 1 + if pixels[x, y] == 0: + buf[int((newx + newy*self.width) / 8)] &= ~(0x80 >> (y % 8)) + return buf + + def display(self, image): + if (Image == None): + return + + self.send_command(0x10) + for i in range(0, int(self.width * self.height / 8)): + self.send_data(0x00) + epdconfig.delay_ms(10) + + self.send_command(0x13) + for i in range(0, int(self.width * self.height / 8)): + self.send_data(image[i]) + epdconfig.delay_ms(10) + + self.SetFullReg() + self.TurnOnDisplay() + + def DisplayPartial(self, image): + if (Image == None): + return + + self.SetPartReg() + self.send_command(0x91) + self.send_command(0x90) + self.send_data(0) + self.send_data(self.width - 1) + + self.send_data(0) + self.send_data(0) + self.send_data(int(self.height / 256)) + self.send_data(self.height % 256 - 1) + self.send_data(0x28) + + self.send_command(0x10) + for i in range(0, int(self.width * self.height / 8)): + self.send_data(image[i]) + epdconfig.delay_ms(10) + + self.send_command(0x13) + for i in range(0, int(self.width * self.height / 8)): + self.send_data(~image[i]) + epdconfig.delay_ms(10) + + self.TurnOnDisplay() + + def Clear(self): + self.send_command(0x10) + for i in range(0, int(self.width * self.height / 8)): + self.send_data(0x00) + epdconfig.delay_ms(10) + + self.send_command(0x13) + for i in range(0, int(self.width * self.height / 8)): + self.send_data(0x00) + epdconfig.delay_ms(10) + + self.SetFullReg() + self.TurnOnDisplay() + + def sleep(self): + self.send_command(0X50) + self.send_data(0xf7) + self.send_command(0X02) # power off + self.send_command(0X07) # deep sleep + self.send_data(0xA5) + + epdconfig.module_exit() + +### END OF FILE ### diff --git a/pwnagotchi/ui/hw/waveshare1.py b/pwnagotchi/ui/hw/waveshare1.py index 47e55e19..72557e85 100644 --- a/pwnagotchi/ui/hw/waveshare1.py +++ b/pwnagotchi/ui/hw/waveshare1.py @@ -60,7 +60,14 @@ class WaveshareV1(DisplayImpl): self._display.init(self._display.lut_full_update) self._display.Clear(0xFF) self._display.init(self._display.lut_partial_update) - + elif self.config['color'] == 'fastAndFurious': + logging.info("initializing waveshare v1 3-color display in FAST MODE") + logging.info("THIS MAY BE POTENTIALLY DANGEROUS. NO WARRANTY IS PROVIDED") + logging.info("USE THIS DISPLAY IN THIS MODE AT YOUR OWN RISK") + from pwnagotchi.ui.hw.libs.waveshare.v1.epd2in13bcFAST import EPD + self._display = EPD() + self._display.init() + self._display.Clear(0xFF) else: logging.info("initializing waveshare v1 display 3-color mode") from pwnagotchi.ui.hw.libs.waveshare.v1.epd2in13bc import EPD @@ -72,13 +79,11 @@ class WaveshareV1(DisplayImpl): if self.config['color'] == 'black': buf = self._display.getbuffer(canvas) self._display.display(buf) + elif self.config['color'] == 'fastAndFurious': + buf_black = self._display.getbuffer(canvas) + self._display.DisplayPartial(buf_black) else: buf_black = self._display.getbuffer(canvas) - # emptyImage = Image.new('1', (self._display.height, self._display.width), 255) - # buf_color = self._display.getbuffer(emptyImage) - # self._display.display(buf_black,buf_color) - # Custom display function that only handles black - # Was included in epd2in13bc.py self._display.displayBlack(buf_black) def clear(self): From f1bc2d945b5bdfb4eea20a607528921a7957ac3c Mon Sep 17 00:00:00 2001 From: Justin Richards Date: Sun, 27 Oct 2019 02:29:09 -0500 Subject: [PATCH 111/168] Adding fastAndFurious and warning info Signed-off-by: Justin Richards --- pwnagotchi/defaults.yml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/pwnagotchi/defaults.yml b/pwnagotchi/defaults.yml index ca0c74ca..76ca14c9 100644 --- a/pwnagotchi/defaults.yml +++ b/pwnagotchi/defaults.yml @@ -207,6 +207,8 @@ ui: # Possible options inkyphat/inky, papirus/papi, waveshare_1/ws_1 or waveshare_2/ws_2, oledhat, lcdhat, waveshare27inch type: 'waveshare_2' # Possible options red/yellow/black (black used for monocromatic displays) + # Waveshare tri-color 2.13in display can be over-driven with color set as 'fastAndFurious' + # THIS IS POTENTIALLY DANGEROUS. DO NOT USE UNLESS YOU UNDERSTAND THAT IT COULD KILL YOUR DISPLAY color: 'black' video: enabled: true From 723e1892e4f7ccc7c806fe1e3bd6828a9ceb1446 Mon Sep 17 00:00:00 2001 From: Justin Richards Date: Sun, 27 Oct 2019 02:53:26 -0500 Subject: [PATCH 112/168] Fixed bug. Not sure how that got changed back Signed-off-by: Justin Richards --- pwnagotchi/ui/hw/waveshare1.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pwnagotchi/ui/hw/waveshare1.py b/pwnagotchi/ui/hw/waveshare1.py index 72557e85..40ecc3f4 100644 --- a/pwnagotchi/ui/hw/waveshare1.py +++ b/pwnagotchi/ui/hw/waveshare1.py @@ -67,7 +67,7 @@ class WaveshareV1(DisplayImpl): from pwnagotchi.ui.hw.libs.waveshare.v1.epd2in13bcFAST import EPD self._display = EPD() self._display.init() - self._display.Clear(0xFF) + self._display.Clear() else: logging.info("initializing waveshare v1 display 3-color mode") from pwnagotchi.ui.hw.libs.waveshare.v1.epd2in13bc import EPD From 8cb3e1c8d5b49471879b0cf0378950844cbf913c Mon Sep 17 00:00:00 2001 From: strasharo Date: Sun, 27 Oct 2019 10:38:28 +0200 Subject: [PATCH 113/168] Don't disable display output for DVI as well Some people are attaching DVI displays through an adapter. --- builder/pwnagotchi.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/builder/pwnagotchi.yml b/builder/pwnagotchi.yml index fa332895..daa90706 100644 --- a/builder/pwnagotchi.yml +++ b/builder/pwnagotchi.yml @@ -371,7 +371,7 @@ path: /etc/rc.local insertbefore: "exit 0" block: | - if ! /opt/vc/bin/tvservice -s | grep HDMI; then + if ! /opt/vc/bin/tvservice -s | egrep 'HDMI|DVI'; then /opt/vc/bin/tvservice -o fi From 5e1486be9383716e32d4b09e238e0cfccd294cf2 Mon Sep 17 00:00:00 2001 From: georgikoemdzhiev Date: Sat, 26 Oct 2019 19:55:14 +0100 Subject: [PATCH 114/168] Fixed compile Signed-off-by: georgikoemdzhiev --- pwnagotchi/locale/bg/LC_MESSAGES/voice.po | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/pwnagotchi/locale/bg/LC_MESSAGES/voice.po b/pwnagotchi/locale/bg/LC_MESSAGES/voice.po index e6d14313..6fd372ac 100644 --- a/pwnagotchi/locale/bg/LC_MESSAGES/voice.po +++ b/pwnagotchi/locale/bg/LC_MESSAGES/voice.po @@ -1,9 +1,9 @@ -# pwnagotchi. +# pwnagotchi voice data. # Copyright (C) 2019 -# This file is distributed under the same license as the PACKAGE package. -# FIRST AUTHOR Georgi Koemdzhiev , 2019. +# This file is distributed under the same license as the pwnagotchi package. +# FIRST AUTHOR , 2019. # -#, fuzzy +#, msgid "" msgstr "" "Project-Id-Version: 0.0.1\n" From f4fa2597810125b5e5e1af4ce4c913d4ac8ed913 Mon Sep 17 00:00:00 2001 From: Ronan Gaillard Date: Sun, 27 Oct 2019 16:02:00 +0100 Subject: [PATCH 115/168] Add support for Waveshare 1.54 inch screen --- pwnagotchi/ui/display.py | 5 +- pwnagotchi/ui/hw/__init__.py | 6 +- .../hw/libs/waveshare/v154inch/epd1in54b.py | 219 ++++++++++++++++++ .../hw/libs/waveshare/v154inch/epdconfig.py | 154 ++++++++++++ pwnagotchi/ui/hw/waveshare154inch.py | 47 ++++ pwnagotchi/utils.py | 3 + 6 files changed, 432 insertions(+), 2 deletions(-) create mode 100644 pwnagotchi/ui/hw/libs/waveshare/v154inch/epd1in54b.py create mode 100644 pwnagotchi/ui/hw/libs/waveshare/v154inch/epdconfig.py create mode 100644 pwnagotchi/ui/hw/waveshare154inch.py diff --git a/pwnagotchi/ui/display.py b/pwnagotchi/ui/display.py index d218044a..b0ff106c 100644 --- a/pwnagotchi/ui/display.py +++ b/pwnagotchi/ui/display.py @@ -49,7 +49,10 @@ class Display(View): return self._implementation.name == 'lcdhat' def is_dfrobot(self): - return self._implementation.name == 'dfrobot' + return self._implementation.name == 'dfrobot' + + def is_waveshare154inch(self): + return self._implementation.name == 'waveshare154inch' def is_waveshare_any(self): return self.is_waveshare_v1() or self.is_waveshare_v2() diff --git a/pwnagotchi/ui/hw/__init__.py b/pwnagotchi/ui/hw/__init__.py index 66e50e43..3be215fd 100644 --- a/pwnagotchi/ui/hw/__init__.py +++ b/pwnagotchi/ui/hw/__init__.py @@ -6,6 +6,7 @@ from pwnagotchi.ui.hw.dfrobot import DFRobot from pwnagotchi.ui.hw.waveshare1 import WaveshareV1 from pwnagotchi.ui.hw.waveshare2 import WaveshareV2 from pwnagotchi.ui.hw.waveshare27inch import Waveshare27inch +from pwnagotchi.ui.hw.waveshare154inch import Waveshare154inch def display_for(config): @@ -32,4 +33,7 @@ def display_for(config): return WaveshareV2(config) elif config['ui']['display']['type'] == 'waveshare27inch': - return Waveshare27inch(config) \ No newline at end of file + return Waveshare27inch(config) + + elif config['ui']['display']['type'] == 'waveshare154inch': + return Waveshare154inch(config) diff --git a/pwnagotchi/ui/hw/libs/waveshare/v154inch/epd1in54b.py b/pwnagotchi/ui/hw/libs/waveshare/v154inch/epd1in54b.py new file mode 100644 index 00000000..7b241c9d --- /dev/null +++ b/pwnagotchi/ui/hw/libs/waveshare/v154inch/epd1in54b.py @@ -0,0 +1,219 @@ +# ***************************************************************************** +# * | File : epd1in54b.py +# * | Author : Waveshare team +# * | Function : Electronic paper driver +# * | Info : +# *---------------- +# * | This version: V4.0 +# * | Date : 2019-06-20 +# # | Info : python demo +# ----------------------------------------------------------------------------- +# Permission is hereby granted, free of charge, to any person obtaining a copy +# of this software and associated documnetation files (the "Software"), to deal +# in the Software without restriction, including without limitation the rights +# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +# copies of the Software, and to permit persons to whom the Software is +# furished to do so, subject to the following conditions: +# +# The above copyright notice and this permission notice shall be included in +# all copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +# FITNESS OR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +# LIABILITY WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +# THE SOFTWARE. +# + +import logging +from . import epdconfig + +# Display resolution +EPD_WIDTH = 200 +EPD_HEIGHT = 200 + +class EPD: + def __init__(self): + self.reset_pin = epdconfig.RST_PIN + self.dc_pin = epdconfig.DC_PIN + self.busy_pin = epdconfig.BUSY_PIN + self.cs_pin = epdconfig.CS_PIN + self.width = EPD_WIDTH + self.height = EPD_HEIGHT + + lut_vcom0 = [0x0E, 0x14, 0x01, 0x0A, 0x06, 0x04, 0x0A, 0x0A, 0x0F, 0x03, 0x03, 0x0C, 0x06, 0x0A, 0x00] + lut_w = [0x0E, 0x14, 0x01, 0x0A, 0x46, 0x04, 0x8A, 0x4A, 0x0F, 0x83, 0x43, 0x0C, 0x86, 0x0A, 0x04] + lut_b = [0x0E, 0x14, 0x01, 0x8A, 0x06, 0x04, 0x8A, 0x4A, 0x0F, 0x83, 0x43, 0x0C, 0x06, 0x4A, 0x04] + lut_g1 = [0x8E, 0x94, 0x01, 0x8A, 0x06, 0x04, 0x8A, 0x4A, 0x0F, 0x83, 0x43, 0x0C, 0x06, 0x0A, 0x04] + lut_g2 = [0x8E, 0x94, 0x01, 0x8A, 0x06, 0x04, 0x8A, 0x4A, 0x0F, 0x83, 0x43, 0x0C, 0x06, 0x0A, 0x04] + lut_vcom1 = [0x03, 0x1D, 0x01, 0x01, 0x08, 0x23, 0x37, 0x37, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00] + lut_red0 = [0x83, 0x5D, 0x01, 0x81, 0x48, 0x23, 0x77, 0x77, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00] + lut_red1 = [0x03, 0x1D, 0x01, 0x01, 0x08, 0x23, 0x37, 0x37, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00] + + # Hardware reset + def reset(self): + epdconfig.digital_write(self.reset_pin, 1) + epdconfig.delay_ms(200) + epdconfig.digital_write(self.reset_pin, 0) # module reset + epdconfig.delay_ms(10) + epdconfig.digital_write(self.reset_pin, 1) + epdconfig.delay_ms(200) + + def send_command(self, command): + epdconfig.digital_write(self.dc_pin, 0) + epdconfig.digital_write(self.cs_pin, 0) + epdconfig.spi_writebyte([command]) + epdconfig.digital_write(self.cs_pin, 1) + + def send_data(self, data): + epdconfig.digital_write(self.dc_pin, 1) + epdconfig.digital_write(self.cs_pin, 0) + epdconfig.spi_writebyte([data]) + epdconfig.digital_write(self.cs_pin, 1) + + def ReadBusy(self): + logging.debug("e-Paper busy") + while(epdconfig.digital_read(self.busy_pin) == 0): + epdconfig.delay_ms(100) + logging.debug("e-Paper busy release") + + def set_lut_bw(self): + self.send_command(0x20) # vcom + for count in range(0, 15): + self.send_data(self.lut_vcom0[count]) + self.send_command(0x21) # ww -- + for count in range(0, 15): + self.send_data(self.lut_w[count]) + self.send_command(0x22) # bw r + for count in range(0, 15): + self.send_data(self.lut_b[count]) + self.send_command(0x23) # wb w + for count in range(0, 15): + self.send_data(self.lut_g1[count]) + self.send_command(0x24) # bb b + for count in range(0, 15): + self.send_data(self.lut_g2[count]) + + def set_lut_red(self): + self.send_command(0x25) + for count in range(0, 15): + self.send_data(self.lut_vcom1[count]) + self.send_command(0x26) + for count in range(0, 15): + self.send_data(self.lut_red0[count]) + self.send_command(0x27) + for count in range(0, 15): + self.send_data(self.lut_red1[count]) + + def init(self): + if (epdconfig.module_init() != 0): + return -1 + # EPD hardware init start + self.reset() + + self.send_command(0x01) # POWER_SETTING + self.send_data(0x07) + self.send_data(0x00) + self.send_data(0x08) + self.send_data(0x00) + self.send_command(0x06) # BOOSTER_SOFT_START + self.send_data(0x07) + self.send_data(0x07) + self.send_data(0x07) + self.send_command(0x04) # POWER_ON + + self.ReadBusy() + + self.send_command(0X00) # PANEL_SETTING + self.send_data(0xCF) + self.send_command(0X50) # VCOM_AND_DATA_INTERVAL_SETTING + self.send_data(0x17) + self.send_command(0x30) # PLL_CONTROL + self.send_data(0x39) + self.send_command(0x61) # TCON_RESOLUTION set x and y + self.send_data(0xC8) + self.send_data(0x00) + self.send_data(0xC8) + self.send_command(0x82) # VCM_DC_SETTING_REGISTER + self.send_data(0x0E) + + self.set_lut_bw() + self.set_lut_red() + return 0 + + def getbuffer(self, image): + buf = [0xFF] * int(self.width * self.height / 8) + # Set buffer to value of Python Imaging Library image. + # Image must be in mode 1. + image_monocolor = image.convert('1') + imwidth, imheight = image_monocolor.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)) + + pixels = image_monocolor.load() + for y in range(self.height): + for x in range(self.width): + # Set the bits for the column of pixels at the current position. + if pixels[x, y] == 0: + buf[int((x + y * self.width) / 8)] &= ~(0x80 >> (x % 8)) + return buf + + def display(self, blackimage, redimage): + # send black data + if (blackimage != None): + self.send_command(0x10) # DATA_START_TRANSMISSION_1 + for i in range(0, int(self.width * self.height / 8)): + temp = 0x00 + for bit in range(0, 4): + if (blackimage[i] & (0x80 >> bit) != 0): + temp |= 0xC0 >> (bit * 2) + self.send_data(temp) + temp = 0x00 + for bit in range(4, 8): + if (blackimage[i] & (0x80 >> bit) != 0): + temp |= 0xC0 >> ((bit - 4) * 2) + self.send_data(temp) + + # send red data + if (redimage != None): + self.send_command(0x13) # DATA_START_TRANSMISSION_2 + for i in range(0, int(self.width * self.height / 8)): + self.send_data(redimage[i]) + + self.send_command(0x12) # DISPLAY_REFRESH + self.ReadBusy() + + def Clear(self): + self.send_command(0x10) # DATA_START_TRANSMISSION_1 + for i in range(0, int(self.width * self.height / 8)): + self.send_data(0xFF) + self.send_data(0xFF) + + self.send_command(0x13) # DATA_START_TRANSMISSION_2 + for i in range(0, int(self.width * self.height / 8)): + self.send_data(0xFF) + + self.send_command(0x12) # DISPLAY_REFRESH + self.ReadBusy() + + def sleep(self): + self.send_command(0x50) # VCOM_AND_DATA_INTERVAL_SETTING + self.send_data(0x17) + self.send_command(0x82) # to solve Vcom drop + self.send_data(0x00) + self.send_command(0x01) # power setting + self.send_data(0x02) # gate switch to external + self.send_data(0x00) + self.send_data(0x00) + self.send_data(0x00) + self.ReadBusy() + + self.send_command(0x02) # power off + + epdconfig.module_exit() + +### END OF FILE ### + diff --git a/pwnagotchi/ui/hw/libs/waveshare/v154inch/epdconfig.py b/pwnagotchi/ui/hw/libs/waveshare/v154inch/epdconfig.py new file mode 100644 index 00000000..861f43da --- /dev/null +++ b/pwnagotchi/ui/hw/libs/waveshare/v154inch/epdconfig.py @@ -0,0 +1,154 @@ +# /***************************************************************************** +# * | File : epdconfig.py +# * | Author : Waveshare team +# * | Function : Hardware underlying interface +# * | Info : +# *---------------- +# * | This version: V1.0 +# * | Date : 2019-06-21 +# * | Info : +# ****************************************************************************** +# Permission is hereby granted, free of charge, to any person obtaining a copy +# of this software and associated documnetation files (the "Software"), to deal +# in the Software without restriction, including without limitation the rights +# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +# copies of the Software, and to permit persons to whom the Software is +# furished to do so, subject to the following conditions: +# +# The above copyright notice and this permission notice shall be included in +# all copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +# FITNESS OR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +# LIABILITY WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +# THE SOFTWARE. +# + +import os +import logging +import sys +import time + + +class RaspberryPi: + # Pin definition + RST_PIN = 17 + DC_PIN = 25 + CS_PIN = 8 + BUSY_PIN = 24 + + def __init__(self): + import spidev + import RPi.GPIO + + self.GPIO = RPi.GPIO + + # SPI device, bus = 0, device = 0 + self.SPI = spidev.SpiDev(0, 0) + + def digital_write(self, pin, value): + self.GPIO.output(pin, value) + + def digital_read(self, pin): + return self.GPIO.input(pin) + + def delay_ms(self, delaytime): + time.sleep(delaytime / 1000.0) + + def spi_writebyte(self, data): + self.SPI.writebytes(data) + + def module_init(self): + self.GPIO.setmode(self.GPIO.BCM) + self.GPIO.setwarnings(False) + self.GPIO.setup(self.RST_PIN, self.GPIO.OUT) + self.GPIO.setup(self.DC_PIN, self.GPIO.OUT) + self.GPIO.setup(self.CS_PIN, self.GPIO.OUT) + self.GPIO.setup(self.BUSY_PIN, self.GPIO.IN) + self.SPI.max_speed_hz = 4000000 + self.SPI.mode = 0b00 + return 0 + + def module_exit(self): + logging.debug("spi end") + self.SPI.close() + + logging.debug("close 5V, Module enters 0 power consumption ...") + self.GPIO.output(self.RST_PIN, 0) + self.GPIO.output(self.DC_PIN, 0) + + self.GPIO.cleanup() + + +class JetsonNano: + # Pin definition + RST_PIN = 17 + DC_PIN = 25 + CS_PIN = 8 + BUSY_PIN = 24 + + def __init__(self): + import ctypes + find_dirs = [ + os.path.dirname(os.path.realpath(__file__)), + '/usr/local/lib', + '/usr/lib', + ] + self.SPI = None + for find_dir in find_dirs: + so_filename = os.path.join(find_dir, 'sysfs_software_spi.so') + if os.path.exists(so_filename): + self.SPI = ctypes.cdll.LoadLibrary(so_filename) + break + if self.SPI is None: + raise RuntimeError('Cannot find sysfs_software_spi.so') + + import Jetson.GPIO + self.GPIO = Jetson.GPIO + + def digital_write(self, pin, value): + self.GPIO.output(pin, value) + + def digital_read(self, pin): + return self.GPIO.input(self.BUSY_PIN) + + def delay_ms(self, delaytime): + time.sleep(delaytime / 1000.0) + + def spi_writebyte(self, data): + self.SPI.SYSFS_software_spi_transfer(data[0]) + + def module_init(self): + self.GPIO.setmode(self.GPIO.BCM) + self.GPIO.setwarnings(False) + self.GPIO.setup(self.RST_PIN, self.GPIO.OUT) + self.GPIO.setup(self.DC_PIN, self.GPIO.OUT) + self.GPIO.setup(self.CS_PIN, self.GPIO.OUT) + self.GPIO.setup(self.BUSY_PIN, self.GPIO.IN) + self.SPI.SYSFS_software_spi_begin() + return 0 + + def module_exit(self): + logging.debug("spi end") + self.SPI.SYSFS_software_spi_end() + + logging.debug("close 5V, Module enters 0 power consumption ...") + self.GPIO.output(self.RST_PIN, 0) + self.GPIO.output(self.DC_PIN, 0) + + self.GPIO.cleanup() + + +if os.path.exists('/sys/bus/platform/drivers/gpiomem-bcm2835'): + implementation = RaspberryPi() +else: + implementation = JetsonNano() + +for func in [x for x in dir(implementation) if not x.startswith('_')]: + setattr(sys.modules[__name__], func, getattr(implementation, func)) + + +### END OF FILE ### diff --git a/pwnagotchi/ui/hw/waveshare154inch.py b/pwnagotchi/ui/hw/waveshare154inch.py new file mode 100644 index 00000000..fc9cfa7d --- /dev/null +++ b/pwnagotchi/ui/hw/waveshare154inch.py @@ -0,0 +1,47 @@ +import logging + +import pwnagotchi.ui.fonts as fonts +from pwnagotchi.ui.hw.base import DisplayImpl + + +class Waveshare154inch(DisplayImpl): + def __init__(self, config): + super(Waveshare154inch, self).__init__(config, 'waveshare_1_54inch') + self._display = None + + def layout(self): + fonts.setup(10, 9, 10, 35) + self._layout['width'] = 200 + self._layout['height'] = 200 + self._layout['face'] = (0, 40) + self._layout['name'] = (5, 20) + self._layout['channel'] = (0, 0) + self._layout['aps'] = (28, 0) + self._layout['uptime'] = (135, 0) + self._layout['line1'] = [0, 14, 200, 14] + self._layout['line2'] = [0, 186, 200, 186] + self._layout['friend_face'] = (0, 92) + self._layout['friend_name'] = (40, 94) + self._layout['shakes'] = (0, 187) + self._layout['mode'] = (170, 187) + self._layout['status'] = { + 'pos': (5, 90), + 'font': fonts.Medium, + 'max': 20 + } + return self._layout + + def initialize(self): + logging.info("initializing waveshare v154 display") + from pwnagotchi.ui.hw.libs.waveshare.v154inch.epd1in54b import EPD + self._display = EPD() + self._display.init() + self._display.Clear() + + def render(self, canvas): + buf = self._display.getbuffer(canvas) + self._display.display(buf, None) + + def clear(self): + pass + #self._display.Clear() diff --git a/pwnagotchi/utils.py b/pwnagotchi/utils.py index 968abd4f..c474ae2f 100644 --- a/pwnagotchi/utils.py +++ b/pwnagotchi/utils.py @@ -97,6 +97,9 @@ def load_config(args): elif config['ui']['display']['type'] in ('dfrobot', 'df'): config['ui']['display']['type'] = 'dfrobot' + elif config['ui']['display']['type'] in ('ws_154inch', 'ws154inch', 'waveshare_154inch', 'waveshare154inch'): + config['ui']['display']['type'] = 'waveshare154inch' + else: print("unsupported display type %s" % config['ui']['display']['type']) exit(1) From 65a3553521a5831d3e9ecc0e77087a2774dbadf9 Mon Sep 17 00:00:00 2001 From: Dispsylala Date: Sun, 27 Oct 2019 15:43:42 +0000 Subject: [PATCH 116/168] Remove reference to VideoHandler, added reference to Peer::first_encounter() --- scripts/preview.py | 29 ++++++++++++++++++++++++++++- 1 file changed, 28 insertions(+), 1 deletion(-) diff --git a/scripts/preview.py b/scripts/preview.py index e0ef7c41..c338db7f 100755 --- a/scripts/preview.py +++ b/scripts/preview.py @@ -9,7 +9,7 @@ sys.path.insert(0, '../')) import pwnagotchi.ui.faces as faces -from pwnagotchi.ui.display import Display, VideoHandler +from pwnagotchi.ui.display import Display from PIL import Image @@ -50,6 +50,10 @@ class DummyPeer: def pwnd_total(): return 100 + @staticmethod + def first_encounter(): + return 1 + @staticmethod def face(): return faces.FRIEND @@ -108,6 +112,29 @@ def main(): enabled: true address: "0.0.0.0" port: 8080 + + faces: + look_r: '( ⚆_⚆)' + look_l: '(☉_☉ )' + look_r_happy: '( ◕‿◕)' + look_l_happy: '(◕‿◕ )' + sleep: '(⇀‿‿↼)' + sleep2: '(≖‿‿≖)' + awake: '(◕‿‿◕)' + bored: '(-__-)' + intense: '(°▃▃°)' + cool: '(⌐■_■)' + happy: '(•‿‿•)' + excited: '(ᵔ◡◡ᵔ)' + grateful: '(^‿‿^)' + motivated: '(☼‿‿☼)' + demotivated: '(≖__≖)' + smart: '(✜‿‿✜)' + lonely: '(ب__ب)' + sad: '(╥☁╥ )' + friend: '(♥‿‿♥)' + broken: '(☓‿‿☓)' + debug: '(#__#)' ''' list_of_displays = list() From 5119bf4326570b662aa10f96680e42fa986e54fe Mon Sep 17 00:00:00 2001 From: Ben Lebherz Date: Sun, 27 Oct 2019 17:59:06 +0100 Subject: [PATCH 117/168] quote shifting --- scripts/backup.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/scripts/backup.sh b/scripts/backup.sh index 348061cc..85b53e0c 100755 --- a/scripts/backup.sh +++ b/scripts/backup.sh @@ -46,6 +46,6 @@ scp -rC "${USERNAME}@${UNIT_HOSTNAME}":/tmp/backup /tmp/ echo "@ compressing ..." -zip -r -9 -q $"OUTPUT" /tmp/backup +zip -r -9 -q "$OUTPUT" /tmp/backup rm -rf /tmp/backup From bfe0ea7623f61aec7ec1e59c21ca67759c009057 Mon Sep 17 00:00:00 2001 From: Dispsylala Date: Sun, 27 Oct 2019 19:51:48 +0000 Subject: [PATCH 118/168] Makes Inky Fast mode opt-in by selecting 'fastAndFurious' Displays warning if selected. Otherwise uses original InkyPHAT library --- pwnagotchi/ui/hw/inky.py | 22 ++++++++++++++++------ 1 file changed, 16 insertions(+), 6 deletions(-) diff --git a/pwnagotchi/ui/hw/inky.py b/pwnagotchi/ui/hw/inky.py index 39170798..7ae3327b 100644 --- a/pwnagotchi/ui/hw/inky.py +++ b/pwnagotchi/ui/hw/inky.py @@ -33,15 +33,25 @@ class Inky(DisplayImpl): def initialize(self): logging.info("initializing inky display") - from pwnagotchi.ui.hw.libs.inkyphat.inkyphatfast import InkyPHATFast - self._display = InkyPHATFast(self.config['color']) - self._display.set_border(InkyPHATFast.BLACK) + + if self.config['color'] == 'fastAndFurious': + logging.info("Initializing Inky in 2-color FAST MODE") + logging.info("THIS MAY BE POTENTIALLY DANGEROUS. NO WARRANTY IS PROVIDED") + logging.info("USE THIS DISPLAY IN THIS MODE AT YOUR OWN RISK") + + from pwnagotchi.ui.hw.libs.inkyphat.inkyphatfast import InkyPHATFast + self._display = InkyPHATFast('black') + self._display.set_border(InkyPHATFast.BLACK) + else: + from inky import InkyPHAT + self._display = InkyPHAT(self.config['color']) + self._display.set_border(InkyPHAT.BLACK) def render(self, canvas): - if self.config['color'] != 'mono': - display_colors = 3 - else: + if self.config['color'] == 'black' or self.config['color'] == 'fastAndFurious': display_colors = 2 + else: + display_colors = 3 img_buffer = canvas.convert('RGB').convert('P', palette=1, colors=display_colors) if self.config['color'] == 'red': From 786564ebb86f21aa90261f3d9d19d9a8a11a2f02 Mon Sep 17 00:00:00 2001 From: Nikhil Jha Date: Sun, 27 Oct 2019 12:47:15 -0700 Subject: [PATCH 119/168] add waveshare 213d display Signed-off-by: Nikhil Jha --- pwnagotchi/ui/display.py | 3 + pwnagotchi/ui/hw/__init__.py | 4 + .../ui/hw/libs/waveshare/v213d/epd2in13d.py | 358 ++++++++++++++++++ .../ui/hw/libs/waveshare/v213d/epdconfig.py | 154 ++++++++ pwnagotchi/ui/hw/waveshare213d.py | 69 ++++ pwnagotchi/utils.py | 3 + 6 files changed, 591 insertions(+) create mode 100644 pwnagotchi/ui/hw/libs/waveshare/v213d/epd2in13d.py create mode 100644 pwnagotchi/ui/hw/libs/waveshare/v213d/epdconfig.py create mode 100644 pwnagotchi/ui/hw/waveshare213d.py diff --git a/pwnagotchi/ui/display.py b/pwnagotchi/ui/display.py index b0ff106c..25aed672 100644 --- a/pwnagotchi/ui/display.py +++ b/pwnagotchi/ui/display.py @@ -54,6 +54,9 @@ class Display(View): def is_waveshare154inch(self): return self._implementation.name == 'waveshare154inch' + def is_waveshare213d(self): + return self._implementation.name == 'waveshare213d' + def is_waveshare_any(self): return self.is_waveshare_v1() or self.is_waveshare_v2() diff --git a/pwnagotchi/ui/hw/__init__.py b/pwnagotchi/ui/hw/__init__.py index 3be215fd..b7db3bd8 100644 --- a/pwnagotchi/ui/hw/__init__.py +++ b/pwnagotchi/ui/hw/__init__.py @@ -7,6 +7,7 @@ from pwnagotchi.ui.hw.waveshare1 import WaveshareV1 from pwnagotchi.ui.hw.waveshare2 import WaveshareV2 from pwnagotchi.ui.hw.waveshare27inch import Waveshare27inch from pwnagotchi.ui.hw.waveshare154inch import Waveshare154inch +from pwnagotchi.ui.hw.waveshare213d import Waveshare213d def display_for(config): @@ -37,3 +38,6 @@ def display_for(config): elif config['ui']['display']['type'] == 'waveshare154inch': return Waveshare154inch(config) + + elif config['ui']['display']['type'] == 'waveshare213d': + return Waveshare213d(config) \ No newline at end of file diff --git a/pwnagotchi/ui/hw/libs/waveshare/v213d/epd2in13d.py b/pwnagotchi/ui/hw/libs/waveshare/v213d/epd2in13d.py new file mode 100644 index 00000000..de02f869 --- /dev/null +++ b/pwnagotchi/ui/hw/libs/waveshare/v213d/epd2in13d.py @@ -0,0 +1,358 @@ +# ***************************************************************************** +# * | File : epd2in13d.py +# * | Author : Waveshare team +# * | Function : Electronic paper driver +# * | Info : +# *---------------- +# * | This version: V4.0 +# * | Date : 2019-06-20 +# # | Info : python demo +# ----------------------------------------------------------------------------- +# Permission is hereby granted, free of charge, to any person obtaining a copy +# of this software and associated documnetation files (the "Software"), to deal +# in the Software without restriction, including without limitation the rights +# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +# copies of the Software, and to permit persons to whom the Software is +# furished to do so, subject to the following conditions: +# +# The above copyright notice and this permission notice shall be included in +# all copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +# FITNESS OR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +# LIABILITY WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +# THE SOFTWARE. +# + + +import logging +from . import epdconfig +from PIL import Image +import RPi.GPIO as GPIO + +# Display resolution +EPD_WIDTH = 104 +EPD_HEIGHT = 212 + +class EPD: + def __init__(self): + self.reset_pin = epdconfig.RST_PIN + self.dc_pin = epdconfig.DC_PIN + self.busy_pin = epdconfig.BUSY_PIN + self.cs_pin = epdconfig.CS_PIN + self.width = EPD_WIDTH + self.height = EPD_HEIGHT + + lut_vcomDC = [ + 0x00, 0x08, 0x00, 0x00, 0x00, 0x02, + 0x60, 0x28, 0x28, 0x00, 0x00, 0x01, + 0x00, 0x14, 0x00, 0x00, 0x00, 0x01, + 0x00, 0x12, 0x12, 0x00, 0x00, 0x01, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, + ] + + lut_ww = [ + 0x40, 0x08, 0x00, 0x00, 0x00, 0x02, + 0x90, 0x28, 0x28, 0x00, 0x00, 0x01, + 0x40, 0x14, 0x00, 0x00, 0x00, 0x01, + 0xA0, 0x12, 0x12, 0x00, 0x00, 0x01, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + ] + + lut_bw = [ + 0x40, 0x17, 0x00, 0x00, 0x00, 0x02, + 0x90, 0x0F, 0x0F, 0x00, 0x00, 0x03, + 0x40, 0x0A, 0x01, 0x00, 0x00, 0x01, + 0xA0, 0x0E, 0x0E, 0x00, 0x00, 0x02, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + ] + + lut_wb = [ + 0x80, 0x08, 0x00, 0x00, 0x00, 0x02, + 0x90, 0x28, 0x28, 0x00, 0x00, 0x01, + 0x80, 0x14, 0x00, 0x00, 0x00, 0x01, + 0x50, 0x12, 0x12, 0x00, 0x00, 0x01, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + ] + + lut_bb = [ + 0x80, 0x08, 0x00, 0x00, 0x00, 0x02, + 0x90, 0x28, 0x28, 0x00, 0x00, 0x01, + 0x80, 0x14, 0x00, 0x00, 0x00, 0x01, + 0x50, 0x12, 0x12, 0x00, 0x00, 0x01, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + ] + + lut_vcom1 = [ + 0x00, 0x19, 0x01, 0x00, 0x00, 0x01, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, + ] + + lut_ww1 = [ + 0x00, 0x19, 0x01, 0x00, 0x00, 0x01, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + ] + + lut_bw1 = [ + 0x80, 0x19, 0x01, 0x00, 0x00, 0x01, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + ] + + lut_wb1 = [ + 0x40, 0x19, 0x01, 0x00, 0x00, 0x01, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + ] + + lut_bb1 = [ + 0x00, 0x19, 0x01, 0x00, 0x00, 0x01, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + ] + + # Hardware reset + def reset(self): + epdconfig.digital_write(self.reset_pin, 1) + epdconfig.delay_ms(200) + epdconfig.digital_write(self.reset_pin, 0) + epdconfig.delay_ms(10) + epdconfig.digital_write(self.reset_pin, 1) + epdconfig.delay_ms(200) + + def send_command(self, command): + epdconfig.digital_write(self.dc_pin, 0) + epdconfig.digital_write(self.cs_pin, 0) + epdconfig.spi_writebyte([command]) + epdconfig.digital_write(self.cs_pin, 1) + + def send_data(self, data): + epdconfig.digital_write(self.dc_pin, 1) + epdconfig.digital_write(self.cs_pin, 0) + epdconfig.spi_writebyte([data]) + epdconfig.digital_write(self.cs_pin, 1) + + def ReadBusy(self): + logging.debug("e-Paper busy") + while(epdconfig.digital_read(self.busy_pin) == 0): # 0: idle, 1: busy + self.send_command(0x71) + epdconfig.delay_ms(100) + logging.debug("e-Paper busy release") + + def TurnOnDisplay(self): + self.send_command(0x12) + epdconfig.delay_ms(10) + self.ReadBusy() + + def init(self): + if (epdconfig.module_init() != 0): + return -1 + # EPD hardware init start + self.reset() + + self.send_command(0x01) # POWER SETTING + self.send_data(0x03) + self.send_data(0x00) + self.send_data(0x2b) + self.send_data(0x2b) + self.send_data(0x03) + + self.send_command(0x06) # boost soft start + self.send_data(0x17) # A + self.send_data(0x17) # B + self.send_data(0x17) # C + + self.send_command(0x04) + self.ReadBusy() + + self.send_command(0x00) # panel setting + self.send_data(0xbf) # LUT from OTP,128x296 + self.send_data(0x0d) # VCOM to 0V fast + + self.send_command(0x30) # PLL setting + self.send_data(0x3a) # 3a 100HZ 29 150Hz 39 200HZ 31 171HZ + + self.send_command(0x61) # resolution setting + self.send_data(self.width) + self.send_data((self.height >> 8) & 0xff) + self.send_data(self.height& 0xff) + + self.send_command(0x82) # vcom_DC setting + self.send_data(0x28) + return 0 + + def SetFullReg(self): + self.send_command(0x82) + self.send_data(0x00) + self.send_command(0X50) + self.send_data(0x97) + + self.send_command(0x20) # vcom + for count in range(0, 44): + self.send_data(self.lut_vcomDC[count]) + self.send_command(0x21) # ww -- + for count in range(0, 42): + self.send_data(self.lut_ww[count]) + self.send_command(0x22) # bw r + for count in range(0, 42): + self.send_data(self.lut_bw[count]) + self.send_command(0x23) # wb w + for count in range(0, 42): + self.send_data(self.lut_wb[count]) + self.send_command(0x24) # bb b + for count in range(0, 42): + self.send_data(self.lut_bb[count]) + + def SetPartReg(self): + self.send_command(0x82) + self.send_data(0x03) + self.send_command(0X50) + self.send_data(0x47) + + self.send_command(0x20) # vcom + for count in range(0, 44): + self.send_data(self.lut_vcom1[count]) + self.send_command(0x21) # ww -- + for count in range(0, 42): + self.send_data(self.lut_ww1[count]) + self.send_command(0x22) # bw r + for count in range(0, 42): + self.send_data(self.lut_bw1[count]) + self.send_command(0x23) # wb w + for count in range(0, 42): + self.send_data(self.lut_wb1[count]) + self.send_command(0x24) # bb b + for count in range(0, 42): + self.send_data(self.lut_bb1[count]) + + def getbuffer(self, image): + # logging.debug("bufsiz = ",int(self.width/8) * self.height) + buf = [0xFF] * (int(self.width/8) * self.height) + image_monocolor = image.convert('1') + imwidth, imheight = image_monocolor.size + pixels = image_monocolor.load() + # logging.debug("imwidth = %d, imheight = %d",imwidth,imheight) + if(imwidth == self.width and imheight == self.height): + logging.debug("Vertical") + for y in range(imheight): + for x in range(imwidth): + # Set the bits for the column of pixels at the current position. + if pixels[x, y] == 0: + buf[int((x + y * self.width) / 8)] &= ~(0x80 >> (x % 8)) + elif(imwidth == self.height and imheight == self.width): + logging.debug("Horizontal") + for y in range(imheight): + for x in range(imwidth): + newx = y + newy = self.height - x - 1 + if pixels[x, y] == 0: + buf[int((newx + newy*self.width) / 8)] &= ~(0x80 >> (y % 8)) + return buf + + def display(self, image): + if (Image == None): + return + + self.send_command(0x10) + for i in range(0, int(self.width * self.height / 8)): + self.send_data(0x00) + epdconfig.delay_ms(10) + + self.send_command(0x13) + for i in range(0, int(self.width * self.height / 8)): + self.send_data(image[i]) + epdconfig.delay_ms(10) + + self.SetFullReg() + self.TurnOnDisplay() + + def DisplayPartial(self, image): + if (Image == None): + return + + self.SetPartReg() + self.send_command(0x91) + self.send_command(0x90) + self.send_data(0) + self.send_data(self.width - 1) + + self.send_data(0) + self.send_data(0) + self.send_data(int(self.height / 256)) + self.send_data(self.height % 256 - 1) + self.send_data(0x28) + + self.send_command(0x10) + for i in range(0, int(self.width * self.height / 8)): + self.send_data(image[i]) + epdconfig.delay_ms(10) + + self.send_command(0x13) + for i in range(0, int(self.width * self.height / 8)): + self.send_data(~image[i]) + epdconfig.delay_ms(10) + + self.TurnOnDisplay() + + def Clear(self, color): + self.send_command(0x10) + for i in range(0, int(self.width * self.height / 8)): + self.send_data(0x00) + epdconfig.delay_ms(10) + + self.send_command(0x13) + for i in range(0, int(self.width * self.height / 8)): + self.send_data(0xFF) + epdconfig.delay_ms(10) + + self.SetFullReg() + self.TurnOnDisplay() + + def sleep(self): + self.send_command(0X50) + self.send_data(0xf7) + self.send_command(0X02) # power off + self.send_command(0X07) # deep sleep + self.send_data(0xA5) + + epdconfig.module_exit() + +### END OF FILE ### + diff --git a/pwnagotchi/ui/hw/libs/waveshare/v213d/epdconfig.py b/pwnagotchi/ui/hw/libs/waveshare/v213d/epdconfig.py new file mode 100644 index 00000000..861f43da --- /dev/null +++ b/pwnagotchi/ui/hw/libs/waveshare/v213d/epdconfig.py @@ -0,0 +1,154 @@ +# /***************************************************************************** +# * | File : epdconfig.py +# * | Author : Waveshare team +# * | Function : Hardware underlying interface +# * | Info : +# *---------------- +# * | This version: V1.0 +# * | Date : 2019-06-21 +# * | Info : +# ****************************************************************************** +# Permission is hereby granted, free of charge, to any person obtaining a copy +# of this software and associated documnetation files (the "Software"), to deal +# in the Software without restriction, including without limitation the rights +# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +# copies of the Software, and to permit persons to whom the Software is +# furished to do so, subject to the following conditions: +# +# The above copyright notice and this permission notice shall be included in +# all copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +# FITNESS OR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +# LIABILITY WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +# THE SOFTWARE. +# + +import os +import logging +import sys +import time + + +class RaspberryPi: + # Pin definition + RST_PIN = 17 + DC_PIN = 25 + CS_PIN = 8 + BUSY_PIN = 24 + + def __init__(self): + import spidev + import RPi.GPIO + + self.GPIO = RPi.GPIO + + # SPI device, bus = 0, device = 0 + self.SPI = spidev.SpiDev(0, 0) + + def digital_write(self, pin, value): + self.GPIO.output(pin, value) + + def digital_read(self, pin): + return self.GPIO.input(pin) + + def delay_ms(self, delaytime): + time.sleep(delaytime / 1000.0) + + def spi_writebyte(self, data): + self.SPI.writebytes(data) + + def module_init(self): + self.GPIO.setmode(self.GPIO.BCM) + self.GPIO.setwarnings(False) + self.GPIO.setup(self.RST_PIN, self.GPIO.OUT) + self.GPIO.setup(self.DC_PIN, self.GPIO.OUT) + self.GPIO.setup(self.CS_PIN, self.GPIO.OUT) + self.GPIO.setup(self.BUSY_PIN, self.GPIO.IN) + self.SPI.max_speed_hz = 4000000 + self.SPI.mode = 0b00 + return 0 + + def module_exit(self): + logging.debug("spi end") + self.SPI.close() + + logging.debug("close 5V, Module enters 0 power consumption ...") + self.GPIO.output(self.RST_PIN, 0) + self.GPIO.output(self.DC_PIN, 0) + + self.GPIO.cleanup() + + +class JetsonNano: + # Pin definition + RST_PIN = 17 + DC_PIN = 25 + CS_PIN = 8 + BUSY_PIN = 24 + + def __init__(self): + import ctypes + find_dirs = [ + os.path.dirname(os.path.realpath(__file__)), + '/usr/local/lib', + '/usr/lib', + ] + self.SPI = None + for find_dir in find_dirs: + so_filename = os.path.join(find_dir, 'sysfs_software_spi.so') + if os.path.exists(so_filename): + self.SPI = ctypes.cdll.LoadLibrary(so_filename) + break + if self.SPI is None: + raise RuntimeError('Cannot find sysfs_software_spi.so') + + import Jetson.GPIO + self.GPIO = Jetson.GPIO + + def digital_write(self, pin, value): + self.GPIO.output(pin, value) + + def digital_read(self, pin): + return self.GPIO.input(self.BUSY_PIN) + + def delay_ms(self, delaytime): + time.sleep(delaytime / 1000.0) + + def spi_writebyte(self, data): + self.SPI.SYSFS_software_spi_transfer(data[0]) + + def module_init(self): + self.GPIO.setmode(self.GPIO.BCM) + self.GPIO.setwarnings(False) + self.GPIO.setup(self.RST_PIN, self.GPIO.OUT) + self.GPIO.setup(self.DC_PIN, self.GPIO.OUT) + self.GPIO.setup(self.CS_PIN, self.GPIO.OUT) + self.GPIO.setup(self.BUSY_PIN, self.GPIO.IN) + self.SPI.SYSFS_software_spi_begin() + return 0 + + def module_exit(self): + logging.debug("spi end") + self.SPI.SYSFS_software_spi_end() + + logging.debug("close 5V, Module enters 0 power consumption ...") + self.GPIO.output(self.RST_PIN, 0) + self.GPIO.output(self.DC_PIN, 0) + + self.GPIO.cleanup() + + +if os.path.exists('/sys/bus/platform/drivers/gpiomem-bcm2835'): + implementation = RaspberryPi() +else: + implementation = JetsonNano() + +for func in [x for x in dir(implementation) if not x.startswith('_')]: + setattr(sys.modules[__name__], func, getattr(implementation, func)) + + +### END OF FILE ### diff --git a/pwnagotchi/ui/hw/waveshare213d.py b/pwnagotchi/ui/hw/waveshare213d.py new file mode 100644 index 00000000..fe036118 --- /dev/null +++ b/pwnagotchi/ui/hw/waveshare213d.py @@ -0,0 +1,69 @@ +import logging + +import pwnagotchi.ui.fonts as fonts +from pwnagotchi.ui.hw.base import DisplayImpl + + +class Waveshare213d(DisplayImpl): + def __init__(self, config): + super(Waveshare213d, self).__init__(config, 'waveshare213d') + self._display = None + + def layout(self): + if self.config['color'] == 'black': + fonts.setup(10, 9, 10, 35) + self._layout['width'] = 250 + self._layout['height'] = 122 + self._layout['face'] = (0, 40) + self._layout['name'] = (5, 20) + self._layout['channel'] = (0, 0) + self._layout['aps'] = (28, 0) + self._layout['uptime'] = (185, 0) + self._layout['line1'] = [0, 14, 250, 14] + self._layout['line2'] = [0, 108, 250, 108] + self._layout['friend_face'] = (0, 92) + self._layout['friend_name'] = (40, 94) + self._layout['shakes'] = (0, 109) + self._layout['mode'] = (225, 109) + self._layout['status'] = { + 'pos': (125, 20), + 'font': fonts.Medium, + 'max': 20 + } + else: + fonts.setup(10, 8, 10, 25) + self._layout['width'] = 212 + self._layout['height'] = 104 + self._layout['face'] = (0, 26) + self._layout['name'] = (5, 15) + self._layout['channel'] = (0, 0) + self._layout['aps'] = (28, 0) + self._layout['status'] = (91, 15) + self._layout['uptime'] = (147, 0) + self._layout['line1'] = [0, 12, 212, 12] + self._layout['line2'] = [0, 92, 212, 92] + self._layout['friend_face'] = (0, 76) + self._layout['friend_name'] = (40, 78) + self._layout['shakes'] = (0, 93) + self._layout['mode'] = (187, 93) + self._layout['status'] = { + 'pos': (125, 20), + 'font': fonts.Medium, + 'max': 14 + } + return self._layout + + def initialize(self): + logging.info("initializing waveshare 213d display") + from pwnagotchi.ui.hw.libs.waveshare.v213d.epd2in13d import EPD + self._display = EPD() + self._display.init(self._display.FULL_UPDATE) + self._display.Clear(0xff) + self._display.init(self._display.PART_UPDATE) + + def render(self, canvas): + buf = self._display.getbuffer(canvas) + self._display.displayPartial(buf) + + def clear(self): + self._display.Clear(0xff) diff --git a/pwnagotchi/utils.py b/pwnagotchi/utils.py index c474ae2f..86da38e5 100644 --- a/pwnagotchi/utils.py +++ b/pwnagotchi/utils.py @@ -100,6 +100,9 @@ def load_config(args): elif config['ui']['display']['type'] in ('ws_154inch', 'ws154inch', 'waveshare_154inch', 'waveshare154inch'): config['ui']['display']['type'] = 'waveshare154inch' + elif config['ui']['display']['type'] in ('ws_213d', 'ws213d', 'waveshare_213d', 'waveshare213d'): + config['ui']['display']['type'] = 'waveshare213d' + else: print("unsupported display type %s" % config['ui']['display']['type']) exit(1) From caec8370503a304a631886a906554cfca00817c2 Mon Sep 17 00:00:00 2001 From: Dispsylala Date: Sun, 27 Oct 2019 19:51:48 +0000 Subject: [PATCH 120/168] Makes Inky Fast mode opt-in by selecting 'fastAndFurious' Displays warning if selected. Otherwise uses original InkyPHAT library --- pwnagotchi/ui/hw/inky.py | 22 ++++++++++++++++------ 1 file changed, 16 insertions(+), 6 deletions(-) diff --git a/pwnagotchi/ui/hw/inky.py b/pwnagotchi/ui/hw/inky.py index 39170798..7ae3327b 100644 --- a/pwnagotchi/ui/hw/inky.py +++ b/pwnagotchi/ui/hw/inky.py @@ -33,15 +33,25 @@ class Inky(DisplayImpl): def initialize(self): logging.info("initializing inky display") - from pwnagotchi.ui.hw.libs.inkyphat.inkyphatfast import InkyPHATFast - self._display = InkyPHATFast(self.config['color']) - self._display.set_border(InkyPHATFast.BLACK) + + if self.config['color'] == 'fastAndFurious': + logging.info("Initializing Inky in 2-color FAST MODE") + logging.info("THIS MAY BE POTENTIALLY DANGEROUS. NO WARRANTY IS PROVIDED") + logging.info("USE THIS DISPLAY IN THIS MODE AT YOUR OWN RISK") + + from pwnagotchi.ui.hw.libs.inkyphat.inkyphatfast import InkyPHATFast + self._display = InkyPHATFast('black') + self._display.set_border(InkyPHATFast.BLACK) + else: + from inky import InkyPHAT + self._display = InkyPHAT(self.config['color']) + self._display.set_border(InkyPHAT.BLACK) def render(self, canvas): - if self.config['color'] != 'mono': - display_colors = 3 - else: + if self.config['color'] == 'black' or self.config['color'] == 'fastAndFurious': display_colors = 2 + else: + display_colors = 3 img_buffer = canvas.convert('RGB').convert('P', palette=1, colors=display_colors) if self.config['color'] == 'red': From dfcc2f6bf28528bcb659c1deece7140999c9c90a Mon Sep 17 00:00:00 2001 From: Justin Richards Date: Sun, 27 Oct 2019 15:48:14 -0500 Subject: [PATCH 121/168] Adding original screen clearing LUT to help prevent burn-in Signed-off-by: Justin Richards --- .../ui/hw/libs/waveshare/v1/epd2in13bcFAST.py | 91 +++++++++++-------- 1 file changed, 55 insertions(+), 36 deletions(-) diff --git a/pwnagotchi/ui/hw/libs/waveshare/v1/epd2in13bcFAST.py b/pwnagotchi/ui/hw/libs/waveshare/v1/epd2in13bcFAST.py index b7704543..5804bdfb 100644 --- a/pwnagotchi/ui/hw/libs/waveshare/v1/epd2in13bcFAST.py +++ b/pwnagotchi/ui/hw/libs/waveshare/v1/epd2in13bcFAST.py @@ -58,10 +58,10 @@ class EPD: self.height = EPD_HEIGHT lut_vcomDC = [ - 0x00, 0x08, 0x00, 0x00, 0x00, 0x02, - 0x60, 0x28, 0x28, 0x00, 0x00, 0x01, - 0x00, 0x14, 0x00, 0x00, 0x00, 0x01, - 0x00, 0x12, 0x12, 0x00, 0x00, 0x01, + 0x99, 0x20, 0x20, 0x20, 0x20, 0x06, + 0x00, 0x20, 0x20, 0x00, 0x00, 0x02, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, @@ -69,44 +69,46 @@ class EPD: ] lut_ww = [ - 0x40, 0x08, 0x00, 0x00, 0x00, 0x02, - 0x90, 0x28, 0x28, 0x00, 0x00, 0x01, - 0x40, 0x14, 0x00, 0x00, 0x00, 0x01, - 0xA0, 0x12, 0x12, 0x00, 0x00, 0x01, + 0x66, 0x06, 0x06, 0x06, 0x06, 0x04, + 0xAA, 0x32, 0x32, 0x00, 0x00, 0x02, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, ] lut_bw = [ - 0x40, 0x17, 0x00, 0x00, 0x00, 0x02, - 0x90, 0x0F, 0x0F, 0x00, 0x00, 0x03, - 0x40, 0x0A, 0x01, 0x00, 0x00, 0x01, - 0xA0, 0x0E, 0x0E, 0x00, 0x00, 0x02, + 0x66, 0x06, 0x06, 0x06, 0x06, 0x04, + 0xAA, 0x32, 0x32, 0x00, 0x00, 0x02, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, ] lut_wb = [ - 0x80, 0x08, 0x00, 0x00, 0x00, 0x02, - 0x90, 0x28, 0x28, 0x00, 0x00, 0x01, - 0x80, 0x14, 0x00, 0x00, 0x00, 0x01, - 0x50, 0x12, 0x12, 0x00, 0x00, 0x01, + 0x99, 0x06, 0x06, 0x06, 0x06, 0x04, + 0x55, 0x42, 0x42, 0x00, 0x00, 0x02, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, ] lut_bb = [ - 0x80, 0x08, 0x00, 0x00, 0x00, 0x02, - 0x90, 0x28, 0x28, 0x00, 0x00, 0x00, - 0x80, 0x14, 0x00, 0x00, 0x00, 0x01, - 0x50, 0x12, 0x12, 0x00, 0x00, 0x01, + 0x99, 0x06, 0x06, 0x06, 0x06, 0x04, + 0x55, 0x42, 0x42, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - ] + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + ] lut_vcom1 = [ 0xA0, 0x10, 0x10, 0x00, 0x00, 0x02, @@ -233,16 +235,32 @@ class EPD: return 0 def SetFullReg(self): - self.send_command(0x82) - self.send_data(0x00) - self.send_command(0X50) - self.send_data(0x97) -# self.send_command(0x00) # panel setting -# self.send_data(0x9f) # LUT from OTP,128x296 + self.send_command(0x00) # panel setting + self.send_data(0x9f) # LUT from OTP,128x296 + #self.send_command(0x82) + #self.send_data(0x00) + #self.send_command(0X50) + #self.send_data(0x97) + + #self.send_command(0x20) # vcom + #for count in range(0, 44): + # self.send_data(self.lut_vcomDC[count]) + #self.send_command(0x21) # ww -- + #for count in range(0, 42): + # self.send_data(self.lut_ww[count]) + #self.send_command(0x22) # bw r + #for count in range(0, 42): + # self.send_data(self.lut_bw[count]) + #self.send_command(0x23) # wb w + #for count in range(0, 42): + # self.send_data(self.lut_wb[count]) + #self.send_command(0x24) # bb b + #for count in range(0, 42): + # self.send_data(self.lut_bb[count]) def SetPartReg(self): -# self.send_command(0x00) # panel setting -# self.send_data(0xbf) # LUT from OTP,128x296 + self.send_command(0x00) # panel setting + self.send_data(0xbf) # LUT from OTP,128x296 self.send_command(0x82) self.send_data(0x03) self.send_command(0X50) @@ -336,16 +354,17 @@ class EPD: def Clear(self): self.send_command(0x10) for i in range(0, int(self.width * self.height / 8)): - self.send_data(0x00) - epdconfig.delay_ms(10) - + self.send_data(0xFF) + self.send_command(0x92) + self.send_command(0x13) for i in range(0, int(self.width * self.height / 8)): - self.send_data(0x00) - epdconfig.delay_ms(10) - + self.send_data(0xFF) + self.send_command(0x92) + self.SetFullReg() - self.TurnOnDisplay() + self.send_command(0x12) # REFRESH + self.ReadBusy() def sleep(self): self.send_command(0X50) From 4814e10940cbc6c4bf3ba2f9cf2fae63a121ed4c Mon Sep 17 00:00:00 2001 From: Nikhil Jha Date: Sun, 27 Oct 2019 14:14:42 -0700 Subject: [PATCH 122/168] attempt to fix init for 213d Signed-off-by: Nikhil Jha --- pwnagotchi/ui/hw/waveshare213d.py | 43 +++++++++++++++++++++++-------- 1 file changed, 32 insertions(+), 11 deletions(-) diff --git a/pwnagotchi/ui/hw/waveshare213d.py b/pwnagotchi/ui/hw/waveshare213d.py index fe036118..0d9dfcc1 100644 --- a/pwnagotchi/ui/hw/waveshare213d.py +++ b/pwnagotchi/ui/hw/waveshare213d.py @@ -38,7 +38,6 @@ class Waveshare213d(DisplayImpl): self._layout['name'] = (5, 15) self._layout['channel'] = (0, 0) self._layout['aps'] = (28, 0) - self._layout['status'] = (91, 15) self._layout['uptime'] = (147, 0) self._layout['line1'] = [0, 12, 212, 12] self._layout['line2'] = [0, 92, 212, 92] @@ -47,23 +46,45 @@ class Waveshare213d(DisplayImpl): self._layout['shakes'] = (0, 93) self._layout['mode'] = (187, 93) self._layout['status'] = { - 'pos': (125, 20), + 'pos': (91, 15), 'font': fonts.Medium, - 'max': 14 + 'max': 20 } return self._layout def initialize(self): - logging.info("initializing waveshare 213d display") - from pwnagotchi.ui.hw.libs.waveshare.v213d.epd2in13d import EPD - self._display = EPD() - self._display.init(self._display.FULL_UPDATE) - self._display.Clear(0xff) - self._display.init(self._display.PART_UPDATE) + if self.config['color'] == 'black': + logging.info("initializing waveshare v1 display in monochromatic mode") + from pwnagotchi.ui.hw.libs.waveshare.v1.epd2in13 import EPD + self._display = EPD() + self._display.init(self._display.lut_full_update) + self._display.Clear(0xFF) + self._display.init(self._display.lut_partial_update) + elif self.config['color'] == 'fastAndFurious': + logging.info("initializing waveshare v1 3-color display in FAST MODE") + logging.info("THIS MAY BE POTENTIALLY DANGEROUS. NO WARRANTY IS PROVIDED") + logging.info("USE THIS DISPLAY IN THIS MODE AT YOUR OWN RISK") + from pwnagotchi.ui.hw.libs.waveshare.v1.epd2in13bcFAST import EPD + self._display = EPD() + self._display.init() + self._display.Clear() + else: + logging.info("initializing waveshare v1 display 3-color mode") + from pwnagotchi.ui.hw.libs.waveshare.v1.epd2in13bc import EPD + self._display = EPD() + self._display.init() + self._display.Clear() def render(self, canvas): - buf = self._display.getbuffer(canvas) - self._display.displayPartial(buf) + if self.config['color'] == 'black': + buf = self._display.getbuffer(canvas) + self._display.display(buf) + elif self.config['color'] == 'fastAndFurious': + buf_black = self._display.getbuffer(canvas) + self._display.DisplayPartial(buf_black) + else: + buf_black = self._display.getbuffer(canvas) + self._display.displayBlack(buf_black) def clear(self): self._display.Clear(0xff) From 4d5bfc2adf630e42fdbd223c8febe0501af58fdb Mon Sep 17 00:00:00 2001 From: Nikhil Jha Date: Sun, 27 Oct 2019 14:20:54 -0700 Subject: [PATCH 123/168] maybe actually fix now Signed-off-by: Nikhil Jha --- .../ui/hw/libs/waveshare/v213d/epd2in13d.py | 2 +- pwnagotchi/ui/hw/waveshare213d.py | 40 +++++-------------- 2 files changed, 10 insertions(+), 32 deletions(-) diff --git a/pwnagotchi/ui/hw/libs/waveshare/v213d/epd2in13d.py b/pwnagotchi/ui/hw/libs/waveshare/v213d/epd2in13d.py index de02f869..81199c78 100644 --- a/pwnagotchi/ui/hw/libs/waveshare/v213d/epd2in13d.py +++ b/pwnagotchi/ui/hw/libs/waveshare/v213d/epd2in13d.py @@ -331,7 +331,7 @@ class EPD: self.TurnOnDisplay() - def Clear(self, color): + def Clear(self): self.send_command(0x10) for i in range(0, int(self.width * self.height / 8)): self.send_data(0x00) diff --git a/pwnagotchi/ui/hw/waveshare213d.py b/pwnagotchi/ui/hw/waveshare213d.py index 0d9dfcc1..a00d5481 100644 --- a/pwnagotchi/ui/hw/waveshare213d.py +++ b/pwnagotchi/ui/hw/waveshare213d.py @@ -53,38 +53,16 @@ class Waveshare213d(DisplayImpl): return self._layout def initialize(self): - if self.config['color'] == 'black': - logging.info("initializing waveshare v1 display in monochromatic mode") - from pwnagotchi.ui.hw.libs.waveshare.v1.epd2in13 import EPD - self._display = EPD() - self._display.init(self._display.lut_full_update) - self._display.Clear(0xFF) - self._display.init(self._display.lut_partial_update) - elif self.config['color'] == 'fastAndFurious': - logging.info("initializing waveshare v1 3-color display in FAST MODE") - logging.info("THIS MAY BE POTENTIALLY DANGEROUS. NO WARRANTY IS PROVIDED") - logging.info("USE THIS DISPLAY IN THIS MODE AT YOUR OWN RISK") - from pwnagotchi.ui.hw.libs.waveshare.v1.epd2in13bcFAST import EPD - self._display = EPD() - self._display.init() - self._display.Clear() - else: - logging.info("initializing waveshare v1 display 3-color mode") - from pwnagotchi.ui.hw.libs.waveshare.v1.epd2in13bc import EPD - self._display = EPD() - self._display.init() - self._display.Clear() + logging.info("initializing waveshare 213d display") + from pwnagotchi.ui.hw.libs.waveshare.v213d.epd2in13d import EPD + self._display = EPD() + self._display.init() + self._display.Clear() def render(self, canvas): - if self.config['color'] == 'black': - buf = self._display.getbuffer(canvas) - self._display.display(buf) - elif self.config['color'] == 'fastAndFurious': - buf_black = self._display.getbuffer(canvas) - self._display.DisplayPartial(buf_black) - else: - buf_black = self._display.getbuffer(canvas) - self._display.displayBlack(buf_black) + buf = self._display.getbuffer(canvas) + self._display.display(buf) def clear(self): - self._display.Clear(0xff) + pass + #self._display.Clear() From c0434b7ddef0f729baabc8a2851b1b79fd156d6d Mon Sep 17 00:00:00 2001 From: Dinh Bao Dang Date: Sun, 27 Oct 2019 22:29:16 +0100 Subject: [PATCH 124/168] add waveshare154inch config possibility to defaults.yml, since ... support was introduced in #434 --- pwnagotchi/defaults.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pwnagotchi/defaults.yml b/pwnagotchi/defaults.yml index 80161740..bf6380ea 100644 --- a/pwnagotchi/defaults.yml +++ b/pwnagotchi/defaults.yml @@ -204,7 +204,7 @@ ui: display: enabled: true rotation: 180 - # Possible options inkyphat/inky, papirus/papi, waveshare_1/ws_1 or waveshare_2/ws_2, oledhat, lcdhat, waveshare27inch, dfrobot/df + # Possible options inkyphat/inky, papirus/papi, waveshare_1/ws_1 or waveshare_2/ws_2, oledhat, lcdhat, waveshare154inch, waveshare27inch, dfrobot/df type: 'waveshare_2' # Possible options red/yellow/black (black used for monocromatic displays) # Waveshare tri-color 2.13in display can be over-driven with color set as 'fastAndFurious' From 7fd31e14f900353b9c49ea6fef434e9698a54bec Mon Sep 17 00:00:00 2001 From: Nikhil Jha Date: Sun, 27 Oct 2019 14:32:23 -0700 Subject: [PATCH 125/168] fix resolution Signed-off-by: Nikhil Jha --- pwnagotchi/ui/hw/waveshare213d.py | 63 +++++++++++-------------------- 1 file changed, 21 insertions(+), 42 deletions(-) diff --git a/pwnagotchi/ui/hw/waveshare213d.py b/pwnagotchi/ui/hw/waveshare213d.py index a00d5481..9aa8ff82 100644 --- a/pwnagotchi/ui/hw/waveshare213d.py +++ b/pwnagotchi/ui/hw/waveshare213d.py @@ -10,46 +10,25 @@ class Waveshare213d(DisplayImpl): self._display = None def layout(self): - if self.config['color'] == 'black': - fonts.setup(10, 9, 10, 35) - self._layout['width'] = 250 - self._layout['height'] = 122 - self._layout['face'] = (0, 40) - self._layout['name'] = (5, 20) - self._layout['channel'] = (0, 0) - self._layout['aps'] = (28, 0) - self._layout['uptime'] = (185, 0) - self._layout['line1'] = [0, 14, 250, 14] - self._layout['line2'] = [0, 108, 250, 108] - self._layout['friend_face'] = (0, 92) - self._layout['friend_name'] = (40, 94) - self._layout['shakes'] = (0, 109) - self._layout['mode'] = (225, 109) - self._layout['status'] = { - 'pos': (125, 20), - 'font': fonts.Medium, - 'max': 20 - } - else: - fonts.setup(10, 8, 10, 25) - self._layout['width'] = 212 - self._layout['height'] = 104 - self._layout['face'] = (0, 26) - self._layout['name'] = (5, 15) - self._layout['channel'] = (0, 0) - self._layout['aps'] = (28, 0) - self._layout['uptime'] = (147, 0) - self._layout['line1'] = [0, 12, 212, 12] - self._layout['line2'] = [0, 92, 212, 92] - self._layout['friend_face'] = (0, 76) - self._layout['friend_name'] = (40, 78) - self._layout['shakes'] = (0, 93) - self._layout['mode'] = (187, 93) - self._layout['status'] = { - 'pos': (91, 15), - 'font': fonts.Medium, - 'max': 20 - } + fonts.setup(10, 8, 10, 25) + self._layout['width'] = 212 + self._layout['height'] = 104 + self._layout['face'] = (0, 26) + self._layout['name'] = (5, 15) + self._layout['channel'] = (0, 0) + self._layout['aps'] = (28, 0) + self._layout['uptime'] = (147, 0) + self._layout['line1'] = [0, 12, 212, 12] + self._layout['line2'] = [0, 92, 212, 92] + self._layout['friend_face'] = (0, 76) + self._layout['friend_name'] = (40, 78) + self._layout['shakes'] = (0, 93) + self._layout['mode'] = (187, 93) + self._layout['status'] = { + 'pos': (91, 15), + 'font': fonts.Medium, + 'max': 20 + } return self._layout def initialize(self): @@ -64,5 +43,5 @@ class Waveshare213d(DisplayImpl): self._display.display(buf) def clear(self): - pass - #self._display.Clear() + #pass + self._display.Clear() From d3c6194e0fdc985748b558b76fd1734d2e35c288 Mon Sep 17 00:00:00 2001 From: Justin Richards Date: Sun, 27 Oct 2019 19:11:36 -0500 Subject: [PATCH 126/168] adding gpio plugin Signed-off-by: Justin Richards --- pwnagotchi/plugins/default/gpio_buttons.py | 38 ++++++++++++++++++++++ 1 file changed, 38 insertions(+) create mode 100644 pwnagotchi/plugins/default/gpio_buttons.py diff --git a/pwnagotchi/plugins/default/gpio_buttons.py b/pwnagotchi/plugins/default/gpio_buttons.py new file mode 100644 index 00000000..d9860d18 --- /dev/null +++ b/pwnagotchi/plugins/default/gpio_buttons.py @@ -0,0 +1,38 @@ +__author__ = 'ratmandu@gmail.com' +__version__ = '1.0.0' +__name__ = 'gpio_buttons' +__license__ = 'GPL3' +__description__ = 'GPIO Button support plugin' + +import logging +import RPi.GPIO as GPIO +import subprocess + +running = False +OPTIONS = dict() +GPIOs = {} +COMMANDs = None + +def runCommand(channel): + command = GPIOs[channel] + logging.info(f"Button Pressed! Running command: {command}") + process = subprocess.Popen(command, shell=True, stdin=None, stdout=open("/dev/null", "w"), stderr=None, executable="/bin/bash") + process.wait() + + +def on_loaded(): + logging.info("GPIO Button plugin loaded.") + + #get list of GPIOs + gpios = OPTIONS['gpios'] + + #set gpio numbering + GPIO.setmode(GPIO.BCM) + + for i in gpios: + gpio = list(i)[0] + command = i[gpio] + GPIOs[gpio] = command + GPIO.setup(gpio, GPIO.IN, GPIO.PUD_UP) + GPIO.add_event_detect(gpio, GPIO.FALLING, callback=runCommand, bouncetime=300) + logging.info("Added command: %s to GPIO #%d", command, gpio) From 92773a2b37331f228f0c1ef657607134ed2256b4 Mon Sep 17 00:00:00 2001 From: Justin Richards Date: Sun, 27 Oct 2019 19:20:43 -0500 Subject: [PATCH 127/168] adding to defaults.yml and shortening debounce a little bit Signed-off-by: Justin Richards --- pwnagotchi/defaults.yml | 6 ++++++ pwnagotchi/plugins/default/gpio_buttons.py | 2 +- 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/pwnagotchi/defaults.yml b/pwnagotchi/defaults.yml index 80161740..eb389aca 100644 --- a/pwnagotchi/defaults.yml +++ b/pwnagotchi/defaults.yml @@ -83,6 +83,12 @@ main: enabled: false #The IP Address of your phone with Paw Server running, default (option is empty) is 192.168.44.1 ip: '' + gpio_buttons: + enabled: false + #The following is a list of the GPIO number for your button, and the command you want to run when it is pressed + gpios: + - 20: 'sudo touch /root/.pwnagotchi-auto && sudo systemctl restart pwnagotchi' + - 21: 'shutdown -h now' # monitor interface to use iface: mon0 # command to run to bring the mon interface up in case it's not up already diff --git a/pwnagotchi/plugins/default/gpio_buttons.py b/pwnagotchi/plugins/default/gpio_buttons.py index d9860d18..c0f6ceb1 100644 --- a/pwnagotchi/plugins/default/gpio_buttons.py +++ b/pwnagotchi/plugins/default/gpio_buttons.py @@ -34,5 +34,5 @@ def on_loaded(): command = i[gpio] GPIOs[gpio] = command GPIO.setup(gpio, GPIO.IN, GPIO.PUD_UP) - GPIO.add_event_detect(gpio, GPIO.FALLING, callback=runCommand, bouncetime=300) + GPIO.add_event_detect(gpio, GPIO.FALLING, callback=runCommand, bouncetime=250) logging.info("Added command: %s to GPIO #%d", command, gpio) From 7676b55b87e3a4c7d5199b8d1742920f3c573db7 Mon Sep 17 00:00:00 2001 From: Justin Richards Date: Sun, 27 Oct 2019 19:36:29 -0500 Subject: [PATCH 128/168] Revert "Adding original screen clearing LUT to help prevent burn-in" This reverts commit dfcc2f6bf28528bcb659c1deece7140999c9c90a. --- .../ui/hw/libs/waveshare/v1/epd2in13bcFAST.py | 91 ++++++++----------- 1 file changed, 36 insertions(+), 55 deletions(-) diff --git a/pwnagotchi/ui/hw/libs/waveshare/v1/epd2in13bcFAST.py b/pwnagotchi/ui/hw/libs/waveshare/v1/epd2in13bcFAST.py index 5804bdfb..b7704543 100644 --- a/pwnagotchi/ui/hw/libs/waveshare/v1/epd2in13bcFAST.py +++ b/pwnagotchi/ui/hw/libs/waveshare/v1/epd2in13bcFAST.py @@ -58,10 +58,10 @@ class EPD: self.height = EPD_HEIGHT lut_vcomDC = [ - 0x99, 0x20, 0x20, 0x20, 0x20, 0x06, - 0x00, 0x20, 0x20, 0x00, 0x00, 0x02, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x08, 0x00, 0x00, 0x00, 0x02, + 0x60, 0x28, 0x28, 0x00, 0x00, 0x01, + 0x00, 0x14, 0x00, 0x00, 0x00, 0x01, + 0x00, 0x12, 0x12, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, @@ -69,46 +69,44 @@ class EPD: ] lut_ww = [ - 0x66, 0x06, 0x06, 0x06, 0x06, 0x04, - 0xAA, 0x32, 0x32, 0x00, 0x00, 0x02, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x40, 0x08, 0x00, 0x00, 0x00, 0x02, + 0x90, 0x28, 0x28, 0x00, 0x00, 0x01, + 0x40, 0x14, 0x00, 0x00, 0x00, 0x01, + 0xA0, 0x12, 0x12, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, ] lut_bw = [ - 0x66, 0x06, 0x06, 0x06, 0x06, 0x04, - 0xAA, 0x32, 0x32, 0x00, 0x00, 0x02, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x40, 0x17, 0x00, 0x00, 0x00, 0x02, + 0x90, 0x0F, 0x0F, 0x00, 0x00, 0x03, + 0x40, 0x0A, 0x01, 0x00, 0x00, 0x01, + 0xA0, 0x0E, 0x0E, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, ] lut_wb = [ - 0x99, 0x06, 0x06, 0x06, 0x06, 0x04, - 0x55, 0x42, 0x42, 0x00, 0x00, 0x02, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x80, 0x08, 0x00, 0x00, 0x00, 0x02, + 0x90, 0x28, 0x28, 0x00, 0x00, 0x01, + 0x80, 0x14, 0x00, 0x00, 0x00, 0x01, + 0x50, 0x12, 0x12, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, ] lut_bb = [ - 0x99, 0x06, 0x06, 0x06, 0x06, 0x04, - 0x55, 0x42, 0x42, 0x00, 0x00, 0x02, + 0x80, 0x08, 0x00, 0x00, 0x00, 0x02, + 0x90, 0x28, 0x28, 0x00, 0x00, 0x00, + 0x80, 0x14, 0x00, 0x00, 0x00, 0x01, + 0x50, 0x12, 0x12, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - ] + ] lut_vcom1 = [ 0xA0, 0x10, 0x10, 0x00, 0x00, 0x02, @@ -235,32 +233,16 @@ class EPD: return 0 def SetFullReg(self): - self.send_command(0x00) # panel setting - self.send_data(0x9f) # LUT from OTP,128x296 - #self.send_command(0x82) - #self.send_data(0x00) - #self.send_command(0X50) - #self.send_data(0x97) - - #self.send_command(0x20) # vcom - #for count in range(0, 44): - # self.send_data(self.lut_vcomDC[count]) - #self.send_command(0x21) # ww -- - #for count in range(0, 42): - # self.send_data(self.lut_ww[count]) - #self.send_command(0x22) # bw r - #for count in range(0, 42): - # self.send_data(self.lut_bw[count]) - #self.send_command(0x23) # wb w - #for count in range(0, 42): - # self.send_data(self.lut_wb[count]) - #self.send_command(0x24) # bb b - #for count in range(0, 42): - # self.send_data(self.lut_bb[count]) + self.send_command(0x82) + self.send_data(0x00) + self.send_command(0X50) + self.send_data(0x97) +# self.send_command(0x00) # panel setting +# self.send_data(0x9f) # LUT from OTP,128x296 def SetPartReg(self): - self.send_command(0x00) # panel setting - self.send_data(0xbf) # LUT from OTP,128x296 +# self.send_command(0x00) # panel setting +# self.send_data(0xbf) # LUT from OTP,128x296 self.send_command(0x82) self.send_data(0x03) self.send_command(0X50) @@ -354,17 +336,16 @@ class EPD: def Clear(self): self.send_command(0x10) for i in range(0, int(self.width * self.height / 8)): - self.send_data(0xFF) - self.send_command(0x92) - + self.send_data(0x00) + epdconfig.delay_ms(10) + self.send_command(0x13) for i in range(0, int(self.width * self.height / 8)): - self.send_data(0xFF) - self.send_command(0x92) - + self.send_data(0x00) + epdconfig.delay_ms(10) + self.SetFullReg() - self.send_command(0x12) # REFRESH - self.ReadBusy() + self.TurnOnDisplay() def sleep(self): self.send_command(0X50) From 0ad6e887acc55f46e08a9e20122d37ae8d076718 Mon Sep 17 00:00:00 2001 From: Ciara Brennan Date: Mon, 28 Oct 2019 11:49:24 +0000 Subject: [PATCH 129/168] check zip is installed first Signed-off-by: Ciara Brennan --- scripts/backup.sh | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/scripts/backup.sh b/scripts/backup.sh index 85b53e0c..281a2bb4 100755 --- a/scripts/backup.sh +++ b/scripts/backup.sh @@ -19,6 +19,11 @@ FILES_TO_BACKUP=( /home/pi/.bashrc ) +if ! type "zip" >/dev/null 2>&1; then + echo "This script requires zip, please resolve and try again" + exit 1 +fi + ping -c 1 "${UNIT_HOSTNAME}" >/dev/null || { echo "@ unit ${UNIT_HOSTNAME} can't be reached, make sure it's connected and a static IP assigned to the USB interface." exit 1 From 3b1d90baefa3daec734e8d21ecd59d4e66bc38e6 Mon Sep 17 00:00:00 2001 From: Alex Muthmann Date: Mon, 28 Oct 2019 12:52:14 +0100 Subject: [PATCH 130/168] Fix _display.clear() There is a typo (uppercase / lowercase) which prevents the display from initializing. --- pwnagotchi/ui/hw/lcdhat.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pwnagotchi/ui/hw/lcdhat.py b/pwnagotchi/ui/hw/lcdhat.py index 75779a33..92e45a7b 100644 --- a/pwnagotchi/ui/hw/lcdhat.py +++ b/pwnagotchi/ui/hw/lcdhat.py @@ -37,7 +37,7 @@ class LcdHat(DisplayImpl): from pwnagotchi.ui.hw.libs.waveshare.lcdhat.epd import EPD self._display = EPD() self._display.init() - self._display.Clear() + self._display.clear() def render(self, canvas): self._display.display(canvas) From 18a41f3e292f41dfd0150e068c031ec04348646c Mon Sep 17 00:00:00 2001 From: Lorenzo Milesi Date: Tue, 29 Oct 2019 08:32:55 +0100 Subject: [PATCH 131/168] Fix eth0 connection check for MANU mode Issue #460 - Originally noted by @ZeroCool-Dade https://github.com/evilsocket/pwnagotchi/commit/a78a4b0b3e5cfbdfea481e140e6c02e0bdb1f72d#commitcomment-35683998 Signed-off-by: Lorenzo Milesi --- builder/pwnagotchi.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/builder/pwnagotchi.yml b/builder/pwnagotchi.yml index daa90706..31dd7e9e 100644 --- a/builder/pwnagotchi.yml +++ b/builder/pwnagotchi.yml @@ -304,7 +304,7 @@ # blink 10 times to signal ready state /usr/bin/bootblink 10 & # start a detached screen session with bettercap - if [[ $(ifconfig | grep usb0 | grep RUNNING) ]] || [[ $(cat /sys/class/net/eth0/carrier) ]]; then + if [[ $(ifconfig | grep usb0 | grep RUNNING) ]] || [[ !$(grep '1' /sys/class/net/eth0/carrier) ]]; then # if override file exists, go into auto mode if [ -f /root/.pwnagotchi-auto ]; then rm /root/.pwnagotchi-auto @@ -323,7 +323,7 @@ content: | #!/usr/bin/env bash /usr/bin/monstart - if [[ $(ifconfig | grep usb0 | grep RUNNING) ]] || [[ $(cat /sys/class/net/eth0/carrier) ]]; then + if [[ $(ifconfig | grep usb0 | grep RUNNING) ]] || [[ !$(grep '1' /sys/class/net/eth0/carrier) ]]; then # if override file exists, go into auto mode if [ -f /root/.pwnagotchi-auto ]; then /usr/bin/bettercap -no-colors -caplet pwnagotchi-auto -iface mon0 From bcdbf41bb8a3c753f1992b99c6fea4d2935b3423 Mon Sep 17 00:00:00 2001 From: Dispsylala Date: Tue, 29 Oct 2019 22:38:41 +0000 Subject: [PATCH 132/168] Added exception handling to config.yml parsing/merging --- pwnagotchi/utils.py | 16 ++++++++++------ 1 file changed, 10 insertions(+), 6 deletions(-) diff --git a/pwnagotchi/utils.py b/pwnagotchi/utils.py index c474ae2f..7c374e30 100644 --- a/pwnagotchi/utils.py +++ b/pwnagotchi/utils.py @@ -65,12 +65,16 @@ def load_config(args): config = yaml.safe_load(fp) # load the user config - if os.path.exists(args.user_config): - with open(args.user_config) as fp: - user_config = yaml.safe_load(fp) - # if the file is empty, safe_load will return None and merge_config will boom. - if user_config: - config = merge_config(user_config, config) + try: + if os.path.exists(args.user_config): + with open(args.user_config) as fp: + user_config = yaml.safe_load(fp) + # if the file is empty, safe_load will return None and merge_config will boom. + if user_config: + config = merge_config(user_config, config) + except yaml.YAMLError as ex: + print("There was an error processing the configuration file:\n%s " % ex) + exit(1) # the very first step is to normalize the display name so we don't need dozens of if/elif around if config['ui']['display']['type'] in ('inky', 'inkyphat'): From 5255e5fd137297a8e41ce14076f4b226324b73c3 Mon Sep 17 00:00:00 2001 From: Dispsylala Date: Wed, 30 Oct 2019 01:05:58 +0000 Subject: [PATCH 133/168] Fix default setting to be an array, otherwise the iterator works over characters, not strings. --- scripts/preview.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/scripts/preview.py b/scripts/preview.py index c338db7f..719d1fb8 100755 --- a/scripts/preview.py +++ b/scripts/preview.py @@ -87,8 +87,7 @@ def append_images(images, horizontal=True, xmargin=0, ymargin=0): def main(): parser = argparse.ArgumentParser(description="This program emulates\ the pwnagotchi display") - parser.add_argument('--displays', help="Which displays to use.", nargs="+", - default="waveshare_2") + parser.add_argument('--displays', help="Which displays to use.", nargs="+", default=["waveshare_2"]) parser.add_argument('--lang', help="Language to use", default="en") parser.add_argument('--output', help="Path to output image (PNG)", default="preview.png") From cb09648ba17293f695837e8e04ec3895e5f620df Mon Sep 17 00:00:00 2001 From: Simone Margaritelli Date: Wed, 30 Oct 2019 14:11:14 +0100 Subject: [PATCH 134/168] misc: small fix or general refactoring i did not bother commenting --- pwnagotchi/log.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/pwnagotchi/log.py b/pwnagotchi/log.py index dda28e0a..7177cc03 100644 --- a/pwnagotchi/log.py +++ b/pwnagotchi/log.py @@ -137,7 +137,7 @@ class LastSession(object): 'channel': 1, 'rssi': int(rssi), 'identity': pubkey, - 'advertisement':{ + 'advertisement': { 'name': name, 'pwnd_tot': int(pwnd_tot) }}) @@ -171,7 +171,7 @@ class LastSession(object): if skip: logging.debug("skipping parsing of the last session logs ...") else: - logging.debug("parsing last session logs ...") + logging.debug("reading last session logs ...") lines = [] @@ -193,6 +193,8 @@ class LastSession(object): self.last_session_id = hashlib.md5(lines[0].encode()).hexdigest() self.last_saved_session_id = self._get_last_saved_session_id() + logging.debug("parsing last session logs (%d lines) ..." % len(lines)) + self._parse_stats() self.parsed = True From 9d19fb8e7ab991eda02470ad0846b295e4a2ae7c Mon Sep 17 00:00:00 2001 From: Simone Margaritelli Date: Wed, 30 Oct 2019 14:25:21 +0100 Subject: [PATCH 135/168] misc: small fix or general refactoring i did not bother commenting --- bin/pwnagotchi | 2 +- pwnagotchi/log.py | 11 ++++++++++- pwnagotchi/ui/view.py | 5 +++++ pwnagotchi/voice.py | 6 ++++++ 4 files changed, 22 insertions(+), 2 deletions(-) diff --git a/bin/pwnagotchi b/bin/pwnagotchi index 9405fe8c..82e47122 100755 --- a/bin/pwnagotchi +++ b/bin/pwnagotchi @@ -57,7 +57,7 @@ if __name__ == '__main__': elif args.do_manual: logging.info("entering manual mode ...") - agent.last_session.parse(args.skip_session) + agent.last_session.parse(agent.view(), args.skip_session) if not args.skip_session: logging.info( "the last session lasted %s (%d completed epochs, trained for %d), average reward:%s (min:%s max:%s)" % ( diff --git a/pwnagotchi/log.py b/pwnagotchi/log.py index 7177cc03..8ba77d5a 100644 --- a/pwnagotchi/log.py +++ b/pwnagotchi/log.py @@ -167,12 +167,14 @@ class LastSession(object): self.duration_human = ', '.join(self.duration_human) self.avg_reward /= (self.epochs if self.epochs else 1) - def parse(self, skip=False): + def parse(self, ui, skip=False): if skip: logging.debug("skipping parsing of the last session logs ...") else: logging.debug("reading last session logs ...") + ui.on_reading_logs() + lines = [] if os.path.exists(self.path): @@ -184,11 +186,18 @@ class LastSession(object): lines.append(line) if LastSession.START_TOKEN in line: break + + lines_so_far = len(lines) + if lines_so_far % 100 == 0: + ui.on_reading_logs(lines_so_far) + lines.reverse() if len(lines) == 0: lines.append("Initial Session"); + ui.on_reading_logs() + self.last_session = lines self.last_session_id = hashlib.md5(lines[0].encode()).hexdigest() self.last_saved_session_id = self._get_last_saved_session_id() diff --git a/pwnagotchi/ui/view.py b/pwnagotchi/ui/view.py index 94a34e1b..3a4358e1 100644 --- a/pwnagotchi/ui/view.py +++ b/pwnagotchi/ui/view.py @@ -233,6 +233,11 @@ class View(object): self.set('status', self._voice.on_free_channel(channel)) self.update() + def on_reading_logs(self, lines_so_far=0): + self.set('face', faces.SMART) + self.set('status', self._voice.on_reading_logs(lines_so_far)) + self.update() + def wait(self, secs, sleeping=True): was_normal = self.is_normal() part = secs / 10.0 diff --git a/pwnagotchi/voice.py b/pwnagotchi/voice.py index c832f192..0d06b6c3 100644 --- a/pwnagotchi/voice.py +++ b/pwnagotchi/voice.py @@ -43,6 +43,12 @@ class Voice: def on_free_channel(self, channel): return self._('Hey, channel {channel} is free! Your AP will say thanks.').format(channel=channel) + def on_reading_logs(self, lines_so_far=0): + if lines_so_far == 0: + return self._('Reading last session logs ...') + else: + return self._('Read {lines_so_far} log lines so far ...').format(lines_so_far=lines_so_far) + def on_bored(self): return random.choice([ self._('I\'m bored ...'), From 667c64832f3d00b46e9ccbbb330bf9ecbc2e3c52 Mon Sep 17 00:00:00 2001 From: Simone Margaritelli Date: Wed, 30 Oct 2019 15:47:36 +0100 Subject: [PATCH 136/168] releasing v1.1.0 --- pwnagotchi/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pwnagotchi/__init__.py b/pwnagotchi/__init__.py index 3f11d4cb..3c946b64 100644 --- a/pwnagotchi/__init__.py +++ b/pwnagotchi/__init__.py @@ -6,7 +6,7 @@ import re import pwnagotchi.ui.view as view import pwnagotchi -version = '1.1.0RC0' +version = '1.1.0' _name = None From 78a036ed1a1f0f0cfbb6281a1f4c32b313e2061b Mon Sep 17 00:00:00 2001 From: Simone Margaritelli Date: Wed, 30 Oct 2019 16:49:20 +0100 Subject: [PATCH 137/168] fix: fixed Slack invite link (fixes #466) --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index c03021e2..cb8235f1 100644 --- a/README.md +++ b/README.md @@ -32,7 +32,7 @@ https://www.pwnagotchi.ai   | Official Links ---------|------- -Slack | [pwnagotchi.slack.com](https://pwnagotchi.herokuapp.com) +Slack | [pwnagotchi.slack.com](https://invite.pwnagotchi.ai/) Twitter | [@pwnagotchi](https://twitter.com/pwnagotchi) Subreddit | [r/pwnagotchi](https://www.reddit.com/r/pwnagotchi/) Website | [pwnagotchi.ai](https://pwnagotchi.ai/) From 965416483df3e08185e8f23c59466b24505cf8d4 Mon Sep 17 00:00:00 2001 From: Simone Margaritelli Date: Wed, 30 Oct 2019 17:37:17 +0100 Subject: [PATCH 138/168] fix: more robust version parsing in auto-update (fixes #469) --- pwnagotchi/plugins/default/auto-update.py | 17 +++++++++++++---- 1 file changed, 13 insertions(+), 4 deletions(-) diff --git a/pwnagotchi/plugins/default/auto-update.py b/pwnagotchi/plugins/default/auto-update.py index 2ed389c9..e31210a6 100644 --- a/pwnagotchi/plugins/default/auto-update.py +++ b/pwnagotchi/plugins/default/auto-update.py @@ -1,10 +1,11 @@ __author__ = 'evilsocket@gmail.com' -__version__ = '1.1.0' +__version__ = '1.1.1' __name__ = 'auto-update' __license__ = 'GPL3' __description__ = 'This plugin checks when updates are available and applies them when internet is available.' import os +import re import logging import subprocess import requests @@ -147,6 +148,15 @@ def install(display, update): return True +def parse_version(cmd): + out = subprocess.getoutput(cmd) + for part in out.split(' '): + part = part.replace('v', '').strip() + if re.search(r'^\d+\.\d+\.\d+.*$', part): + return part + raise Exception('could not parse version from "%s": output=\n%s' % (cmd, out)) + + def on_internet_available(agent): global STATUS @@ -167,9 +177,8 @@ def on_internet_available(agent): to_install = [] to_check = [ - ('bettercap/bettercap', subprocess.getoutput('bettercap -version').split(' ')[1].replace('v', ''), - True, 'bettercap'), - ('evilsocket/pwngrid', subprocess.getoutput('pwngrid -version').replace('v', ''), True, 'pwngrid-peer'), + ('bettercap/bettercap', parse_version('bettercap -version'), True, 'bettercap'), + ('evilsocket/pwngrid', parse_version('pwngrid -version'), True, 'pwngrid-peer'), ('evilsocket/pwnagotchi', pwnagotchi.version, False, 'pwnagotchi') ] From 1600d8cbd14a0d3343c3d925da86b99d5a6ac296 Mon Sep 17 00:00:00 2001 From: Simone Margaritelli Date: Wed, 30 Oct 2019 17:50:56 +0100 Subject: [PATCH 139/168] fix: skipping open access points (fixes #463) --- pwnagotchi/agent.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/pwnagotchi/agent.py b/pwnagotchi/agent.py index 5b7c6ecd..e1cfd151 100644 --- a/pwnagotchi/agent.py +++ b/pwnagotchi/agent.py @@ -170,7 +170,9 @@ class Agent(Client, Automata, AsyncAdvertiser, AsyncTrainer): s = self.session() plugins.on("unfiltered_ap_list", self, s['wifi']['aps']) for ap in s['wifi']['aps']: - if ap['hostname'] not in whitelist: + if ap['encryption'] == '' or ap['encryption'] == 'OPEN': + continue + elif ap['hostname'] not in whitelist: if self._filter_included(ap): aps.append(ap) except Exception as e: From be414e57b308565b47a40a76d92fbb1a4b5e426c Mon Sep 17 00:00:00 2001 From: Simone Margaritelli Date: Wed, 30 Oct 2019 19:24:12 +0100 Subject: [PATCH 140/168] fix: builder now uses files provisioners and auto-update installs project data (ref #470) --- .../data/etc/systemd/system/bettercap.service | 14 ++ .../etc/systemd/system/pwnagotchi.service | 15 ++ .../etc/systemd/system/pwngrid-peer.service | 15 ++ builder/data/usr/bin/bettercap-launcher | 12 ++ builder/data/usr/bin/bootblink | 10 ++ builder/data/usr/bin/monstart | 2 + builder/data/usr/bin/monstop | 2 + builder/data/usr/bin/pwnagotchi-launcher | 15 ++ builder/pwnagotchi.json | 60 ++++++-- builder/pwnagotchi.yml | 138 +----------------- pwnagotchi/plugins/default/auto-update.py | 12 ++ 11 files changed, 149 insertions(+), 146 deletions(-) create mode 100644 builder/data/etc/systemd/system/bettercap.service create mode 100644 builder/data/etc/systemd/system/pwnagotchi.service create mode 100644 builder/data/etc/systemd/system/pwngrid-peer.service create mode 100755 builder/data/usr/bin/bettercap-launcher create mode 100755 builder/data/usr/bin/bootblink create mode 100755 builder/data/usr/bin/monstart create mode 100755 builder/data/usr/bin/monstop create mode 100755 builder/data/usr/bin/pwnagotchi-launcher diff --git a/builder/data/etc/systemd/system/bettercap.service b/builder/data/etc/systemd/system/bettercap.service new file mode 100644 index 00000000..1ba1fc6f --- /dev/null +++ b/builder/data/etc/systemd/system/bettercap.service @@ -0,0 +1,14 @@ +[Unit] +Description=bettercap api.rest service. +Documentation=https://bettercap.org +Wants=network.target + +[Service] +Type=simple +PermissionsStartOnly=true +ExecStart=/usr/bin/bettercap-launcher +Restart=always +RestartSec=30 + +[Install] +WantedBy=multi-user.target \ No newline at end of file diff --git a/builder/data/etc/systemd/system/pwnagotchi.service b/builder/data/etc/systemd/system/pwnagotchi.service new file mode 100644 index 00000000..c0071f3e --- /dev/null +++ b/builder/data/etc/systemd/system/pwnagotchi.service @@ -0,0 +1,15 @@ +[Unit] +Description=pwnagotchi Deep Reinforcement Learning instrumenting bettercap for WiFI pwning. +Documentation=https://pwnagotchi.ai +Wants=network.target +After=pwngrid-peer.service + +[Service] +Type=simple +PermissionsStartOnly=true +ExecStart=/usr/bin/pwnagotchi-launcher +Restart=always +RestartSec=30 + +[Install] +WantedBy=multi-user.target \ No newline at end of file diff --git a/builder/data/etc/systemd/system/pwngrid-peer.service b/builder/data/etc/systemd/system/pwngrid-peer.service new file mode 100644 index 00000000..76ad4b6e --- /dev/null +++ b/builder/data/etc/systemd/system/pwngrid-peer.service @@ -0,0 +1,15 @@ +[Unit] +Description=pwngrid peer service. +Documentation=https://pwnagotchi.ai +Wants=network.target +After=bettercap.service + +[Service] +Type=simple +PermissionsStartOnly=true +ExecStart=/usr/bin/pwngrid -keys /etc/pwnagotchi -address 127.0.0.1:8666 -client-token /root/.api-enrollment.json -wait -log /var/log/pwngrid-peer.log -iface mon0 +Restart=always +RestartSec=30 + +[Install] +WantedBy=multi-user.target \ No newline at end of file diff --git a/builder/data/usr/bin/bettercap-launcher b/builder/data/usr/bin/bettercap-launcher new file mode 100755 index 00000000..29be33e6 --- /dev/null +++ b/builder/data/usr/bin/bettercap-launcher @@ -0,0 +1,12 @@ +#!/usr/bin/env bash +/usr/bin/monstart +if [[ $(ifconfig | grep usb0 | grep RUNNING) ]] || [[ !$(grep '1' /sys/class/net/eth0/carrier) ]]; then + # if override file exists, go into auto mode + if [ -f /root/.pwnagotchi-auto ]; then + /usr/bin/bettercap -no-colors -caplet pwnagotchi-auto -iface mon0 + else + /usr/bin/bettercap -no-colors -caplet pwnagotchi-manual -iface mon0 + fi +else + /usr/bin/bettercap -no-colors -caplet pwnagotchi-auto -iface mon0 +fi diff --git a/builder/data/usr/bin/bootblink b/builder/data/usr/bin/bootblink new file mode 100755 index 00000000..7a3bbfdd --- /dev/null +++ b/builder/data/usr/bin/bootblink @@ -0,0 +1,10 @@ +#!/usr/bin/env bash +for i in $(seq 1 "$1"); +do +echo 0 >/sys/class/leds/led0/brightness +sleep 0.3 +echo 1 >/sys/class/leds/led0/brightness +sleep 0.3 +done +echo 0 >/sys/class/leds/led0/brightness +sleep 0.3 \ No newline at end of file diff --git a/builder/data/usr/bin/monstart b/builder/data/usr/bin/monstart new file mode 100755 index 00000000..db86e4ef --- /dev/null +++ b/builder/data/usr/bin/monstart @@ -0,0 +1,2 @@ +#!/usr/bin/env bash +iw phy phy0 interface add mon0 type monitor && ifconfig mon0 up \ No newline at end of file diff --git a/builder/data/usr/bin/monstop b/builder/data/usr/bin/monstop new file mode 100755 index 00000000..f98cd2d1 --- /dev/null +++ b/builder/data/usr/bin/monstop @@ -0,0 +1,2 @@ +#!/usr/bin/env bash +ifconfig mon0 down && iw dev mon0 del \ No newline at end of file diff --git a/builder/data/usr/bin/pwnagotchi-launcher b/builder/data/usr/bin/pwnagotchi-launcher new file mode 100755 index 00000000..8042e9bd --- /dev/null +++ b/builder/data/usr/bin/pwnagotchi-launcher @@ -0,0 +1,15 @@ +#!/usr/bin/env bash +# blink 10 times to signal ready state +/usr/bin/bootblink 10 & +# start a detached screen session with bettercap +if [[ $(ifconfig | grep usb0 | grep RUNNING) ]] || [[ !$(grep '1' /sys/class/net/eth0/carrier) ]]; then + # if override file exists, go into auto mode + if [ -f /root/.pwnagotchi-auto ]; then + rm /root/.pwnagotchi-auto + /usr/local/bin/pwnagotchi + else + /usr/local/bin/pwnagotchi --manual + fi +else + /usr/local/bin/pwnagotchi +fi \ No newline at end of file diff --git a/builder/pwnagotchi.json b/builder/pwnagotchi.json index d7ec0ca5..ec08b60e 100644 --- a/builder/pwnagotchi.json +++ b/builder/pwnagotchi.json @@ -1,12 +1,14 @@ { - "builders": [{ - "name": "pwnagotchi", - "type": "arm-image", - "iso_url" : "https://downloads.raspberrypi.org/raspbian_lite/images/raspbian_lite-2019-07-12/2019-07-10-raspbian-buster-lite.zip", - "iso_checksum_type":"sha256", - "iso_checksum":"9e5cf24ce483bb96e7736ea75ca422e3560e7b455eee63dd28f66fa1825db70e", - "last_partition_extra_size" : 3221225472 - }], + "builders": [ + { + "name": "pwnagotchi", + "type": "arm-image", + "iso_url": "https://downloads.raspberrypi.org/raspbian_lite/images/raspbian_lite-2019-07-12/2019-07-10-raspbian-buster-lite.zip", + "iso_checksum_type": "sha256", + "iso_checksum": "9e5cf24ce483bb96e7736ea75ca422e3560e7b455eee63dd28f66fa1825db70e", + "last_partition_extra_size": 3221225472 + } + ], "provisioners": [ { "type": "shell", @@ -18,7 +20,7 @@ ] }, { - "type":"ansible-local", + "type": "ansible-local", "playbook_file": "pwnagotchi.yml", "command": "ANSIBLE_FORCE_COLOR=1 PYTHONUNBUFFERED=1 PWN_VERSION={{user `pwn_version`}} PWN_HOSTNAME={{user `pwn_hostname`}} ansible-playbook" }, @@ -27,6 +29,46 @@ "inline": [ "sed -i 's/^#\\(.+\\)/\\1/g' /etc/ld.so.preload" ] + }, + { + "type": "file", + "source": "data/usr/bin/bettercap-launcher", + "destination": "/usr/bin/bettercap-launcher" + }, + { + "type": "file", + "source": "data/usr/bin/pwnagotchi-launcher", + "destination": "/usr/bin/pwnagotchi-launcher" + }, + { + "type": "file", + "source": "data/usr/bin/monstop", + "destination": "/usr/bin/monstop" + }, + { + "type": "file", + "source": "data/usr/bin/bootblink", + "destination": "/usr/bin/bootblink" + }, + { + "type": "file", + "source": "data/usr/bin/monstart", + "destination": "/usr/bin/monstart" + }, + { + "type": "file", + "source": "data/etc/systemd/system/pwngrid-peer.service", + "destination": "/etc/systemd/system/pwngrid-peer.service" + }, + { + "type": "file", + "source": "data/etc/systemd/system/pwnagotchi.service", + "destination": "/etc/systemd/system/pwnagotchi.service" + }, + { + "type": "file", + "source": "data/etc/systemd/system/bettercap.service", + "destination": "/etc/systemd/system/bettercap.service" } ] } diff --git a/builder/pwnagotchi.yml b/builder/pwnagotchi.yml index 31dd7e9e..41d2c30c 100644 --- a/builder/pwnagotchi.yml +++ b/builder/pwnagotchi.yml @@ -279,77 +279,6 @@ remote_src: yes mode: 0755 - - name: create bootblink script - copy: - dest: /usr/bin/bootblink - mode: 0755 - content: | - #!/usr/bin/env bash - for i in $(seq 1 "$1"); - do - echo 0 >/sys/class/leds/led0/brightness - sleep 0.3 - echo 1 >/sys/class/leds/led0/brightness - sleep 0.3 - done - echo 0 >/sys/class/leds/led0/brightness - sleep 0.3 - - - name: create pwnagotchi-launcher script - copy: - dest: /usr/bin/pwnagotchi-launcher - mode: 0755 - content: | - #!/usr/bin/env bash - # blink 10 times to signal ready state - /usr/bin/bootblink 10 & - # start a detached screen session with bettercap - if [[ $(ifconfig | grep usb0 | grep RUNNING) ]] || [[ !$(grep '1' /sys/class/net/eth0/carrier) ]]; then - # if override file exists, go into auto mode - if [ -f /root/.pwnagotchi-auto ]; then - rm /root/.pwnagotchi-auto - /usr/local/bin/pwnagotchi - else - /usr/local/bin/pwnagotchi --manual - fi - else - /usr/local/bin/pwnagotchi - fi - - - name: create bettercap-launcher script - copy: - dest: /usr/bin/bettercap-launcher - mode: 0755 - content: | - #!/usr/bin/env bash - /usr/bin/monstart - if [[ $(ifconfig | grep usb0 | grep RUNNING) ]] || [[ !$(grep '1' /sys/class/net/eth0/carrier) ]]; then - # if override file exists, go into auto mode - if [ -f /root/.pwnagotchi-auto ]; then - /usr/bin/bettercap -no-colors -caplet pwnagotchi-auto -iface mon0 - else - /usr/bin/bettercap -no-colors -caplet pwnagotchi-manual -iface mon0 - fi - else - /usr/bin/bettercap -no-colors -caplet pwnagotchi-auto -iface mon0 - fi - - - name: create monstart script - copy: - dest: /usr/bin/monstart - mode: 0755 - content: | - #!/usr/bin/env bash - iw phy phy0 interface add mon0 type monitor && ifconfig mon0 up - - - name: create monstop script - copy: - dest: /usr/bin/monstop - mode: 0755 - content: | - #!/usr/bin/env bash - ifconfig mon0 down && iw dev mon0 del - - name: create hdmion script copy: dest: /usr/bin/hdmion @@ -471,7 +400,7 @@ copy: dest: /etc/motd content: | - (◕‿‿◕) {{pwnagotchi.hostname}} (pwnagotchi-{{pwnagotchi.version}}) + (◕‿‿◕) {{pwnagotchi.hostname}} Hi! I'm a pwnagotchi, please take good care of me! Here are some basic things you need to know to raise me properly! @@ -507,71 +436,6 @@ apt: autoremove: yes - - name: add pwngrid-peer service to systemd - copy: - dest: /etc/systemd/system/pwngrid-peer.service - content: | - [Unit] - Description=pwngrid peer service. - Documentation=https://pwnagotchi.ai - Wants=network.target - - [Service] - Type=simple - PermissionsStartOnly=true - ExecStart=/usr/bin/pwngrid -keys /etc/pwnagotchi -address 127.0.0.1:8666 -client-token /root/.api-enrollment.json -wait -log /var/log/pwngrid-peer.log -iface mon0 - Restart=always - RestartSec=30 - - [Install] - WantedBy=multi-user.target - notify: - - reload systemd services - - - name: add bettercap service to systemd - copy: - dest: /etc/systemd/system/bettercap.service - content: | - [Unit] - Description=bettercap api.rest service. - Documentation=https://bettercap.org - Wants=network.target - After=pwngrid.service - - [Service] - Type=simple - PermissionsStartOnly=true - ExecStart=/usr/bin/bettercap-launcher - Restart=always - RestartSec=30 - - [Install] - WantedBy=multi-user.target - notify: - - reload systemd services - - - name: add pwnagotchi service to systemd - copy: - dest: /etc/systemd/system/pwnagotchi.service - content: | - [Unit] - Description=pwnagotchi Deep Reinforcement Learning instrumenting bettercap for WiFI pwning. - Documentation=https://pwnagotchi.ai - Wants=network.target - After=bettercap.service - - [Service] - Type=simple - PermissionsStartOnly=true - ExecStart=/usr/bin/pwnagotchi-launcher - Restart=always - RestartSec=30 - - [Install] - WantedBy=multi-user.target - notify: - - reload systemd services - - name: enable services systemd: name: "{{ item }}" diff --git a/pwnagotchi/plugins/default/auto-update.py b/pwnagotchi/plugins/default/auto-update.py index e31210a6..ba7f55c8 100644 --- a/pwnagotchi/plugins/default/auto-update.py +++ b/pwnagotchi/plugins/default/auto-update.py @@ -145,6 +145,18 @@ def install(display, update): os.system("cd %s && pip3 install ." % source_path) + data_path = os.path.join(source_path, "builder/data") + for source in glob.glob("%s/**" % data_path, recursive=True): + if os.path.isfile(source): + dest = source.replace(data_path, '') + dest_path = os.path.dirname(dest) + if not os.path.isdir(dest_path): + os.makedirs(dest_path) + logging.info("[update] installing %s to %s ..." % (source, dest)) + os.system("mv '%s' '%s'" % (source, dest)) + + os.system("systemctl daemon-reload") + return True From 3714899e956a344c3a5ca193671d85f2332bbda3 Mon Sep 17 00:00:00 2001 From: Jeremy O'Brien Date: Wed, 30 Oct 2019 15:33:01 -0400 Subject: [PATCH 141/168] fix: don't attempt to parse/upload pcaps with malformed filenames Signed-off-by: Jeremy O'Brien --- pwnagotchi/plugins/default/grid.py | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/pwnagotchi/plugins/default/grid.py b/pwnagotchi/plugins/default/grid.py index c15b4658..9ffd61f7 100644 --- a/pwnagotchi/plugins/default/grid.py +++ b/pwnagotchi/plugins/default/grid.py @@ -9,6 +9,7 @@ import os import logging import time import glob +import re import pwnagotchi.grid as grid from pwnagotchi.utils import StatusFile, WifiInfo, extract_from_pcap @@ -36,6 +37,10 @@ def parse_pcap(filename): # /root/handshakes/BSSID.pcap essid, bssid = '', net_id + mac_re = re.compile('[0-9a-fA-F]{12}') + if not mac_re.match(bssid): + return '', '' + it = iter(bssid) bssid = ':'.join([a + b for a, b in zip(it, it)]) From c7931450c3a71ccd044fdf0f9c874d4bd0cffb43 Mon Sep 17 00:00:00 2001 From: dadav <33197631+dadav@users.noreply.github.com> Date: Wed, 30 Oct 2019 22:28:12 +0100 Subject: [PATCH 142/168] change ordering --- builder/pwnagotchi.json | 22 +++++++++++----------- 1 file changed, 11 insertions(+), 11 deletions(-) diff --git a/builder/pwnagotchi.json b/builder/pwnagotchi.json index ec08b60e..7698f0b0 100644 --- a/builder/pwnagotchi.json +++ b/builder/pwnagotchi.json @@ -19,17 +19,6 @@ "apt-get install -y ansible" ] }, - { - "type": "ansible-local", - "playbook_file": "pwnagotchi.yml", - "command": "ANSIBLE_FORCE_COLOR=1 PYTHONUNBUFFERED=1 PWN_VERSION={{user `pwn_version`}} PWN_HOSTNAME={{user `pwn_hostname`}} ansible-playbook" - }, - { - "type": "shell", - "inline": [ - "sed -i 's/^#\\(.+\\)/\\1/g' /etc/ld.so.preload" - ] - }, { "type": "file", "source": "data/usr/bin/bettercap-launcher", @@ -69,6 +58,17 @@ "type": "file", "source": "data/etc/systemd/system/bettercap.service", "destination": "/etc/systemd/system/bettercap.service" + }, + { + "type": "ansible-local", + "playbook_file": "pwnagotchi.yml", + "command": "ANSIBLE_FORCE_COLOR=1 PYTHONUNBUFFERED=1 PWN_VERSION={{user `pwn_version`}} PWN_HOSTNAME={{user `pwn_hostname`}} ansible-playbook" + }, + { + "type": "shell", + "inline": [ + "sed -i 's/^#\\(.+\\)/\\1/g' /etc/ld.so.preload" + ] } ] } From 90386c7a648e118f561d98aef925038d0b595441 Mon Sep 17 00:00:00 2001 From: Alex Muthmann Date: Thu, 31 Oct 2019 08:49:58 +0100 Subject: [PATCH 143/168] Change memory splitting to have more memory available As most users won't use a big GUI, it should be sufficient to have 16MB assigned to the GPU and have some more for the system. --- builder/pwnagotchi.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/builder/pwnagotchi.yml b/builder/pwnagotchi.yml index 41d2c30c..10fda9a3 100644 --- a/builder/pwnagotchi.yml +++ b/builder/pwnagotchi.yml @@ -13,6 +13,7 @@ - "dtparam=spi=on" - "dtparam=i2c_arm=on" - "dtparam=i2c1=on" + - "gpu_mem=16" modules: - "i2c-dev" services: From 783ac615941d4ec155dde384ff1fe9a640fe1480 Mon Sep 17 00:00:00 2001 From: Simone Margaritelli Date: Thu, 31 Oct 2019 12:48:15 +0100 Subject: [PATCH 144/168] fix: the auto-update plugin now also installs launchers and service files via setup.py (closes #470) --- .../data/etc/network/interfaces.d/eth0-cfg | 2 + builder/data/etc/network/interfaces.d/lo-cfg | 2 + .../data/etc/network/interfaces.d/usb0-cfg | 7 +++ .../data/etc/network/interfaces.d/wlan0-cfg | 2 + builder/data/usr/bin/hdmioff | 2 + builder/data/usr/bin/hdmion | 2 + builder/pwnagotchi.json | 30 ++++++++++++ builder/pwnagotchi.yml | 49 ------------------- pwnagotchi/plugins/default/auto-update.py | 13 +---- setup.py | 26 +++++++++- 10 files changed, 73 insertions(+), 62 deletions(-) create mode 100644 builder/data/etc/network/interfaces.d/eth0-cfg create mode 100644 builder/data/etc/network/interfaces.d/lo-cfg create mode 100644 builder/data/etc/network/interfaces.d/usb0-cfg create mode 100644 builder/data/etc/network/interfaces.d/wlan0-cfg create mode 100755 builder/data/usr/bin/hdmioff create mode 100755 builder/data/usr/bin/hdmion diff --git a/builder/data/etc/network/interfaces.d/eth0-cfg b/builder/data/etc/network/interfaces.d/eth0-cfg new file mode 100644 index 00000000..2166051a --- /dev/null +++ b/builder/data/etc/network/interfaces.d/eth0-cfg @@ -0,0 +1,2 @@ +allow-hotplug eth0 +iface eth0 inet dhcp \ No newline at end of file diff --git a/builder/data/etc/network/interfaces.d/lo-cfg b/builder/data/etc/network/interfaces.d/lo-cfg new file mode 100644 index 00000000..18ff4764 --- /dev/null +++ b/builder/data/etc/network/interfaces.d/lo-cfg @@ -0,0 +1,2 @@ +auto lo +iface lo inet loopback \ No newline at end of file diff --git a/builder/data/etc/network/interfaces.d/usb0-cfg b/builder/data/etc/network/interfaces.d/usb0-cfg new file mode 100644 index 00000000..445ed905 --- /dev/null +++ b/builder/data/etc/network/interfaces.d/usb0-cfg @@ -0,0 +1,7 @@ +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 \ No newline at end of file diff --git a/builder/data/etc/network/interfaces.d/wlan0-cfg b/builder/data/etc/network/interfaces.d/wlan0-cfg new file mode 100644 index 00000000..f5425694 --- /dev/null +++ b/builder/data/etc/network/interfaces.d/wlan0-cfg @@ -0,0 +1,2 @@ +allow-hotplug wlan0 +iface wlan0 inet static \ No newline at end of file diff --git a/builder/data/usr/bin/hdmioff b/builder/data/usr/bin/hdmioff new file mode 100755 index 00000000..8f272bf7 --- /dev/null +++ b/builder/data/usr/bin/hdmioff @@ -0,0 +1,2 @@ +#!/usr/bin/env bash +sudo /opt/vc/bin/tvservice -o \ No newline at end of file diff --git a/builder/data/usr/bin/hdmion b/builder/data/usr/bin/hdmion new file mode 100755 index 00000000..3bd2188b --- /dev/null +++ b/builder/data/usr/bin/hdmion @@ -0,0 +1,2 @@ +#!/usr/bin/env bash +sudo /opt/vc/bin/tvservice -p \ No newline at end of file diff --git a/builder/pwnagotchi.json b/builder/pwnagotchi.json index 7698f0b0..56daeaf5 100644 --- a/builder/pwnagotchi.json +++ b/builder/pwnagotchi.json @@ -44,6 +44,36 @@ "source": "data/usr/bin/monstart", "destination": "/usr/bin/monstart" }, + { + "type": "file", + "source": "data/usr/bin/hdmion", + "destination": "/usr/bin/hdmion" + }, + { + "type": "file", + "source": "data/usr/bin/hdmioff", + "destination": "/usr/bin/hdmioff" + }, + { + "type": "file", + "source": "data/etc/network/interfaces.d/lo-cfg", + "destination": "/etc/network/interfaces.d/lo-cfg" + }, + { + "type": "file", + "source": "data/etc/network/interfaces.d/wlan0-cfg", + "destination": "/etc/network/interfaces.d/wlan0-cfg" + }, + { + "type": "file", + "source": "data/etc/network/interfaces.d/usb0-cfg", + "destination": "/etc/network/interfaces.d/usb0-cfg" + }, + { + "type": "file", + "source": "data/etc/network/interfaces.d/eth0-cfg", + "destination": "/etc/network/interfaces.d/eth0-cfg" + }, { "type": "file", "source": "data/etc/systemd/system/pwngrid-peer.service", diff --git a/builder/pwnagotchi.yml b/builder/pwnagotchi.yml index 10fda9a3..0164502e 100644 --- a/builder/pwnagotchi.yml +++ b/builder/pwnagotchi.yml @@ -280,22 +280,6 @@ remote_src: yes mode: 0755 - - name: create hdmion script - copy: - dest: /usr/bin/hdmion - mode: 0755 - content: | - #!/usr/bin/env bash - sudo /opt/vc/bin/tvservice -p - - - name: create hdmioff script - copy: - dest: /usr/bin/hdmioff - mode: 0755 - content: | - #!/usr/bin/env bash - sudo /opt/vc/bin/tvservice -o - - name: add HDMI powersave to rc.local blockinfile: path: /etc/rc.local @@ -329,39 +313,6 @@ # when: not user_config.stat.exists - - name: configure lo interface - copy: - dest: /etc/network/interfaces.d/lo-cfg - content: | - auto lo - iface lo inet loopback - - - name: configure wlan interface - copy: - dest: /etc/network/interfaces.d/wlan0-cfg - content: | - allow-hotplug wlan0 - iface wlan0 inet static - - - name: configure usb interface - copy: - dest: /etc/network/interfaces.d/usb0-cfg - content: | - 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 - - - name: configure eth0 interface (pi2/3/4) - copy: - dest: /etc/network/interfaces.d/eth0-cfg - content: | - allow-hotplug eth0 - iface eth0 inet dhcp - - name: enable ssh on boot file: path: /boot/ssh diff --git a/pwnagotchi/plugins/default/auto-update.py b/pwnagotchi/plugins/default/auto-update.py index ba7f55c8..33654902 100644 --- a/pwnagotchi/plugins/default/auto-update.py +++ b/pwnagotchi/plugins/default/auto-update.py @@ -143,20 +143,9 @@ def install(display, update): if not os.path.exists(source_path): source_path = "%s-%s" % (source_path, update['available']) + # setup.py is going to install data files for us os.system("cd %s && pip3 install ." % source_path) - data_path = os.path.join(source_path, "builder/data") - for source in glob.glob("%s/**" % data_path, recursive=True): - if os.path.isfile(source): - dest = source.replace(data_path, '') - dest_path = os.path.dirname(dest) - if not os.path.isdir(dest_path): - os.makedirs(dest_path) - logging.info("[update] installing %s to %s ..." % (source, dest)) - os.system("mv '%s' '%s'" % (source, dest)) - - os.system("systemctl daemon-reload") - return True diff --git a/setup.py b/setup.py index dab1f93d..fe58a736 100644 --- a/setup.py +++ b/setup.py @@ -1,7 +1,29 @@ #!/usr/bin/env python3 # -*- coding: utf-8 -*- from setuptools import setup, find_packages -import pwnagotchi +import os +import glob +import shutil + +setup_path = os.path.dirname(__file__) +data_path = os.path.join(setup_path, "builder/data") + +for source_filename in glob.glob("%s/**" % data_path, recursive=True): + if os.path.isfile(source_filename): + dest_filename = source_filename.replace(data_path, '') + dest_folder = os.path.dirname(dest_filename) + + print("installing %s to %s ..." % (source_filename, dest_filename)) + try: + if not os.path.isdir(dest_folder): + os.makedirs(dest_folder) + + shutil.copyfile(source_filename, dest_filename) + except Exception as e: + print("error installing %s: %s" % (source_filename, e)) + +# reload systemd units +os.system("systemctl daemon-reload") required = [] with open('requirements.txt') as fp: @@ -10,6 +32,8 @@ with open('requirements.txt') as fp: if line != "": required.append(line) +import pwnagotchi + setup(name='pwnagotchi', version=pwnagotchi.version, description='(⌐■_■) - Deep Reinforcement Learning instrumenting bettercap for WiFI pwning.', From 96c617e1521c28f5a4b0ee7f6acb6e6d15490fdb Mon Sep 17 00:00:00 2001 From: Simone Margaritelli Date: Thu, 31 Oct 2019 12:56:27 +0100 Subject: [PATCH 145/168] misc: small fix or general refactoring i did not bother commenting --- builder/data/usr/bin/bettercap-launcher | 3 +++ builder/data/usr/bin/pwnagotchi-launcher | 3 ++- 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/builder/data/usr/bin/bettercap-launcher b/builder/data/usr/bin/bettercap-launcher index 29be33e6..c3cdde27 100755 --- a/builder/data/usr/bin/bettercap-launcher +++ b/builder/data/usr/bin/bettercap-launcher @@ -1,5 +1,8 @@ #!/usr/bin/env bash +# start mon0 /usr/bin/monstart + +# if usb0 or eth0 are up if [[ $(ifconfig | grep usb0 | grep RUNNING) ]] || [[ !$(grep '1' /sys/class/net/eth0/carrier) ]]; then # if override file exists, go into auto mode if [ -f /root/.pwnagotchi-auto ]; then diff --git a/builder/data/usr/bin/pwnagotchi-launcher b/builder/data/usr/bin/pwnagotchi-launcher index 8042e9bd..079467e1 100755 --- a/builder/data/usr/bin/pwnagotchi-launcher +++ b/builder/data/usr/bin/pwnagotchi-launcher @@ -1,7 +1,8 @@ #!/usr/bin/env bash # blink 10 times to signal ready state /usr/bin/bootblink 10 & -# start a detached screen session with bettercap + +# if usb0 or eth0 are up if [[ $(ifconfig | grep usb0 | grep RUNNING) ]] || [[ !$(grep '1' /sys/class/net/eth0/carrier) ]]; then # if override file exists, go into auto mode if [ -f /root/.pwnagotchi-auto ]; then From 13064879e097f682c556d043e287a180c2cefaee Mon Sep 17 00:00:00 2001 From: Simone Margaritelli Date: Thu, 31 Oct 2019 14:14:13 +0100 Subject: [PATCH 146/168] fix: fixed, refactored and centralized launchers logic (closes #473) --- builder/data/usr/bin/bettercap-launcher | 18 +++----- builder/data/usr/bin/bootblink | 10 ----- builder/data/usr/bin/monstart | 3 +- builder/data/usr/bin/monstop | 3 +- builder/data/usr/bin/pwnagotchi-launcher | 19 +++------ builder/data/usr/bin/pwnlib | 54 ++++++++++++++++++++++++ builder/pwnagotchi.json | 10 ++--- 7 files changed, 77 insertions(+), 40 deletions(-) delete mode 100755 builder/data/usr/bin/bootblink create mode 100644 builder/data/usr/bin/pwnlib diff --git a/builder/data/usr/bin/bettercap-launcher b/builder/data/usr/bin/bettercap-launcher index c3cdde27..8b02ada6 100755 --- a/builder/data/usr/bin/bettercap-launcher +++ b/builder/data/usr/bin/bettercap-launcher @@ -1,15 +1,11 @@ #!/usr/bin/env bash -# start mon0 -/usr/bin/monstart +source /usr/bin/pwnlib -# if usb0 or eth0 are up -if [[ $(ifconfig | grep usb0 | grep RUNNING) ]] || [[ !$(grep '1' /sys/class/net/eth0/carrier) ]]; then - # if override file exists, go into auto mode - if [ -f /root/.pwnagotchi-auto ]; then - /usr/bin/bettercap -no-colors -caplet pwnagotchi-auto -iface mon0 - else - /usr/bin/bettercap -no-colors -caplet pwnagotchi-manual -iface mon0 - fi -else +# start mon0 +start_monitor_interface + +if is_auto_mode; then /usr/bin/bettercap -no-colors -caplet pwnagotchi-auto -iface mon0 +else + /usr/bin/bettercap -no-colors -caplet pwnagotchi-manual -iface mon0 fi diff --git a/builder/data/usr/bin/bootblink b/builder/data/usr/bin/bootblink deleted file mode 100755 index 7a3bbfdd..00000000 --- a/builder/data/usr/bin/bootblink +++ /dev/null @@ -1,10 +0,0 @@ -#!/usr/bin/env bash -for i in $(seq 1 "$1"); -do -echo 0 >/sys/class/leds/led0/brightness -sleep 0.3 -echo 1 >/sys/class/leds/led0/brightness -sleep 0.3 -done -echo 0 >/sys/class/leds/led0/brightness -sleep 0.3 \ No newline at end of file diff --git a/builder/data/usr/bin/monstart b/builder/data/usr/bin/monstart index db86e4ef..4c00a5c5 100755 --- a/builder/data/usr/bin/monstart +++ b/builder/data/usr/bin/monstart @@ -1,2 +1,3 @@ #!/usr/bin/env bash -iw phy phy0 interface add mon0 type monitor && ifconfig mon0 up \ No newline at end of file +source /usr/bin/pwnlib +start_monitor_interface diff --git a/builder/data/usr/bin/monstop b/builder/data/usr/bin/monstop index f98cd2d1..c90a3aef 100755 --- a/builder/data/usr/bin/monstop +++ b/builder/data/usr/bin/monstop @@ -1,2 +1,3 @@ #!/usr/bin/env bash -ifconfig mon0 down && iw dev mon0 del \ No newline at end of file +source /usr/bin/pwnlib +stop_monitor_interface \ No newline at end of file diff --git a/builder/data/usr/bin/pwnagotchi-launcher b/builder/data/usr/bin/pwnagotchi-launcher index 079467e1..78c37fe0 100755 --- a/builder/data/usr/bin/pwnagotchi-launcher +++ b/builder/data/usr/bin/pwnagotchi-launcher @@ -1,16 +1,11 @@ #!/usr/bin/env bash -# blink 10 times to signal ready state -/usr/bin/bootblink 10 & +source /usr/bin/pwnlib -# if usb0 or eth0 are up -if [[ $(ifconfig | grep usb0 | grep RUNNING) ]] || [[ !$(grep '1' /sys/class/net/eth0/carrier) ]]; then - # if override file exists, go into auto mode - if [ -f /root/.pwnagotchi-auto ]; then - rm /root/.pwnagotchi-auto - /usr/local/bin/pwnagotchi - else - /usr/local/bin/pwnagotchi --manual - fi -else +# blink 10 times to signal ready state +blink_led 10 & + +if is_auto_mode; then /usr/local/bin/pwnagotchi +else + /usr/local/bin/pwnagotchi --manual fi \ No newline at end of file diff --git a/builder/data/usr/bin/pwnlib b/builder/data/usr/bin/pwnlib new file mode 100644 index 00000000..811d90f5 --- /dev/null +++ b/builder/data/usr/bin/pwnlib @@ -0,0 +1,54 @@ +#!/usr/bin/env bash + +# well ... it blinks the led +blink_led() { + for i in $(seq 1 "$1"); do + echo 0 >/sys/class/leds/led0/brightness + sleep 0.3 + echo 1 >/sys/class/leds/led0/brightness + sleep 0.3 + done + echo 0 >/sys/class/leds/led0/brightness + sleep 0.3 +} + +# starts mon0 +start_monitor_interface() { + iw phy phy0 interface add mon0 type monitor && ifconfig mon0 up +} + +# stops mon0 +stop_monitor_interface() { + ifconfig mon0 down && iw dev mon0 del +} + +# returns 0 if the specificed network interface is up +is_interface_up() { + if grep -qi 'up' /sys/class/net/$1/operstate; then + return 0 + fi + return 1 +} + +# returns 0 if conditions for AUTO mode are met +is_auto_mode() { + # check override file first + if [ -f /root/.pwnagotchi-auto ]; then + # remove the override file if found + rm -rf /root/.pwnagotchi-auto + return 0 + fi + + # if usb0 is up, we're in MANU + if is_interface_up usb0; then + return 1 + fi + + # if eth0 is up (for other boards), we're in MANU + if is_interface_up eth0; then + return 1 + fi + + # no override, but none of the interfaces is up -> AUTO + return 0 +} diff --git a/builder/pwnagotchi.json b/builder/pwnagotchi.json index 56daeaf5..c86468f8 100644 --- a/builder/pwnagotchi.json +++ b/builder/pwnagotchi.json @@ -19,6 +19,11 @@ "apt-get install -y ansible" ] }, + { + "type": "file", + "source": "data/usr/bin/pwnlib", + "destination": "/usr/bin/pwnlib" + }, { "type": "file", "source": "data/usr/bin/bettercap-launcher", @@ -34,11 +39,6 @@ "source": "data/usr/bin/monstop", "destination": "/usr/bin/monstop" }, - { - "type": "file", - "source": "data/usr/bin/bootblink", - "destination": "/usr/bin/bootblink" - }, { "type": "file", "source": "data/usr/bin/monstart", From 4240d0587239d200c865dcbda9abf5ffb480e0d1 Mon Sep 17 00:00:00 2001 From: Simone Margaritelli Date: Thu, 31 Oct 2019 14:20:35 +0100 Subject: [PATCH 147/168] misc: small fix or general refactoring i did not bother commenting --- README.md | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/README.md b/README.md index cb8235f1..230b590a 100644 --- a/README.md +++ b/README.md @@ -1,14 +1,12 @@ # Pwnagotchi

-

Release Software License Contributors Travis - Slack + Slack follow on Twitter -

[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 WiFi 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/), From 64aad56fbaeb0fe316772319801f25f309019c45 Mon Sep 17 00:00:00 2001 From: Simone Margaritelli Date: Thu, 31 Oct 2019 14:47:51 +0100 Subject: [PATCH 148/168] =?UTF-8?q?releasing=20v=1B[3~1.1.1?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- pwnagotchi/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pwnagotchi/__init__.py b/pwnagotchi/__init__.py index 3c946b64..bc453f13 100644 --- a/pwnagotchi/__init__.py +++ b/pwnagotchi/__init__.py @@ -6,7 +6,7 @@ import re import pwnagotchi.ui.view as view import pwnagotchi -version = '1.1.0' +version = '[3~1.1.1' _name = None From 03067da00542d87c80a3df6c2f6ec3aa2705caf6 Mon Sep 17 00:00:00 2001 From: Simone Margaritelli Date: Thu, 31 Oct 2019 14:49:44 +0100 Subject: [PATCH 149/168] misc: small fix or general refactoring i did not bother commenting --- pwnagotchi/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pwnagotchi/__init__.py b/pwnagotchi/__init__.py index bc453f13..3c946b64 100644 --- a/pwnagotchi/__init__.py +++ b/pwnagotchi/__init__.py @@ -6,7 +6,7 @@ import re import pwnagotchi.ui.view as view import pwnagotchi -version = '[3~1.1.1' +version = '1.1.0' _name = None From 3c32bbb58216f757805bc351942b512d596aa391 Mon Sep 17 00:00:00 2001 From: Simone Margaritelli Date: Thu, 31 Oct 2019 14:50:03 +0100 Subject: [PATCH 150/168] releasing v1.1.1 --- pwnagotchi/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pwnagotchi/__init__.py b/pwnagotchi/__init__.py index 3c946b64..5474c073 100644 --- a/pwnagotchi/__init__.py +++ b/pwnagotchi/__init__.py @@ -6,7 +6,7 @@ import re import pwnagotchi.ui.view as view import pwnagotchi -version = '1.1.0' +version = '1.1.1' _name = None From 31d401e03b7773e0c806140622436662662dd500 Mon Sep 17 00:00:00 2001 From: Simone Margaritelli Date: Thu, 31 Oct 2019 15:39:59 +0100 Subject: [PATCH 151/168] fix: increased delay before shutting down to allow slower displays to update (closes #446) --- pwnagotchi/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pwnagotchi/__init__.py b/pwnagotchi/__init__.py index 5474c073..c0cac857 100644 --- a/pwnagotchi/__init__.py +++ b/pwnagotchi/__init__.py @@ -103,7 +103,7 @@ def shutdown(): if view.ROOT: view.ROOT.on_shutdown() # give it some time to refresh the ui - time.sleep(5) + time.sleep(10) os.system("sync") os.system("halt") From bd63f71a1d6e00968037a4200c19fbc757998d6f Mon Sep 17 00:00:00 2001 From: Simone Margaritelli Date: Thu, 31 Oct 2019 17:25:21 +0100 Subject: [PATCH 152/168] fix: +x to /usr/bin/* while creating the .img --- builder/data/usr/bin/pwnlib | 0 builder/pwnagotchi.json | 6 ++++++ 2 files changed, 6 insertions(+) mode change 100644 => 100755 builder/data/usr/bin/pwnlib diff --git a/builder/data/usr/bin/pwnlib b/builder/data/usr/bin/pwnlib old mode 100644 new mode 100755 diff --git a/builder/pwnagotchi.json b/builder/pwnagotchi.json index c86468f8..2591eacd 100644 --- a/builder/pwnagotchi.json +++ b/builder/pwnagotchi.json @@ -89,6 +89,12 @@ "source": "data/etc/systemd/system/bettercap.service", "destination": "/etc/systemd/system/bettercap.service" }, + { + "type": "shell", + "inline": [ + "chmod +x /usr/bin/*" + ] + }, { "type": "ansible-local", "playbook_file": "pwnagotchi.yml", From 8118a10a6a9d08bfec1a3d1d0c7d411fe69ac226 Mon Sep 17 00:00:00 2001 From: Simone Margaritelli Date: Thu, 31 Oct 2019 18:24:43 +0100 Subject: [PATCH 153/168] new: auto-update is now enabled by default --- pwnagotchi/defaults.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pwnagotchi/defaults.yml b/pwnagotchi/defaults.yml index 72a75205..bb78d8ad 100644 --- a/pwnagotchi/defaults.yml +++ b/pwnagotchi/defaults.yml @@ -21,9 +21,9 @@ main: - YourHomeNetworkHere auto-update: - enabled: false - interval: 12 # every 12 hours + enabled: true install: true # if false, it will only warn that updates are available, if true it will install them + interval: 1 # every 1 hour auto-backup: enabled: false From 3efa96b292dda53a0b9eba4fbed7e0e63db97cef Mon Sep 17 00:00:00 2001 From: Jeremy O'Brien Date: Thu, 31 Oct 2019 13:18:42 -0400 Subject: [PATCH 154/168] enhancement: Improve the backup script - Significantly decrease time it takes to save a backup - Remove host dependency on 'zip' binary - Preserve file attributes on backed-up files - Avoid copying files on the pi itself to /tmp Signed-off-by: Jeremy O'Brien --- scripts/backup.sh | 32 ++------------------------------ scripts/restore.sh | 16 ++++++++++++++++ 2 files changed, 18 insertions(+), 30 deletions(-) create mode 100755 scripts/restore.sh diff --git a/scripts/backup.sh b/scripts/backup.sh index 281a2bb4..6fa17849 100755 --- a/scripts/backup.sh +++ b/scripts/backup.sh @@ -3,7 +3,7 @@ # name of the ethernet gadget interface on the host UNIT_HOSTNAME=${1:-10.0.0.2} # output backup zip file -OUTPUT=${2:-pwnagotchi-backup.zip} +OUTPUT=${2:-pwnagotchi-backup.tgz} # username to use for ssh USERNAME=${3:-pi} # what to backup @@ -19,38 +19,10 @@ FILES_TO_BACKUP=( /home/pi/.bashrc ) -if ! type "zip" >/dev/null 2>&1; then - echo "This script requires zip, please resolve and try again" - exit 1 -fi - ping -c 1 "${UNIT_HOSTNAME}" >/dev/null || { echo "@ unit ${UNIT_HOSTNAME} can't be reached, make sure it's connected and a static IP assigned to the USB interface." exit 1 } echo "@ backing up $UNIT_HOSTNAME to $OUTPUT ..." - -ssh "${USERNAME}@${UNIT_HOSTNAME}" "sudo rm -rf /tmp/backup && sudo rm -rf /tmp/backup.zip" > /dev/null - -for file in "${FILES_TO_BACKUP[@]}"; do - dir=$(dirname "$file") - - echo "@ copying $file to /tmp/backup$dir" - - ssh "${USERNAME}@${UNIT_HOSTNAME}" "mkdir -p /tmp/backup${dir}" > /dev/null - ssh "${USERNAME}@${UNIT_HOSTNAME}" "sudo cp -r ${file} /tmp/backup${dir}" > /dev/null -done - -ssh "${USERNAME}@${UNIT_HOSTNAME}" "sudo chown ${USERNAME}:${USERNAME} -R /tmp/backup" > /dev/null - -echo "@ pulling from $UNIT_HOSTNAME ..." - -rm -rf /tmp/backup -scp -rC "${USERNAME}@${UNIT_HOSTNAME}":/tmp/backup /tmp/ - -echo "@ compressing ..." - -zip -r -9 -q "$OUTPUT" /tmp/backup -rm -rf /tmp/backup - +ssh "${USERNAME}@${UNIT_HOSTNAME}" "sudo tar cv ${FILES_TO_BACKUP[@]}" | gzip -9 > "$OUTPUT" diff --git a/scripts/restore.sh b/scripts/restore.sh new file mode 100755 index 00000000..ca60a80a --- /dev/null +++ b/scripts/restore.sh @@ -0,0 +1,16 @@ +#!/usr/bin/env bash + +# name of the ethernet gadget interface on the host +UNIT_HOSTNAME=${1:-10.0.0.2} +# output backup zip file +BACKUP=${2:-pwnagotchi-backup.tgz} +# username to use for ssh +USERNAME=${3:-pi} + +ping -c 1 "${UNIT_HOSTNAME}" >/dev/null || { + echo "@ unit ${UNIT_HOSTNAME} can't be reached, make sure it's connected and a static IP assigned to the USB interface." + exit 1 +} + +echo "@ restoring $BACKUP to $UNIT_HOSTNAME ..." +cat ${BACKUP} | ssh "${USERNAME}@${UNIT_HOSTNAME}" "sudo tar xzv -C /" From bfdaffa14bc383ed2d36967db58f9fbe5979113b Mon Sep 17 00:00:00 2001 From: dadav <33197631+dadav@users.noreply.github.com> Date: Fri, 1 Nov 2019 09:20:06 +0100 Subject: [PATCH 155/168] Add reference to network object --- pwnagotchi/plugins/default/bt-tether.py | 15 +++++++++------ 1 file changed, 9 insertions(+), 6 deletions(-) diff --git a/pwnagotchi/plugins/default/bt-tether.py b/pwnagotchi/plugins/default/bt-tether.py index 509519cd..2da90cf8 100644 --- a/pwnagotchi/plugins/default/bt-tether.py +++ b/pwnagotchi/plugins/default/bt-tether.py @@ -18,7 +18,7 @@ from pwnagotchi.utils import StatusFile READY = False INTERVAL = StatusFile('/root/.bt-tether') OPTIONS = dict() - +NETWORK = None class BTError(Exception): """ @@ -275,15 +275,15 @@ class BTNap: try: logging.debug('BT-TETHER: Connecting to nap network ...') net.Connect('nap') - return True + return net, True except dbus.exceptions.DBusException as err: if err.get_dbus_name() == 'org.bluez.Error.AlreadyConnected': - return True + return net, True connected = BTNap.prop_get(net, 'Connected') if not connected: - return False - return True + return None, False + return net, True class SystemdUnitWrapper: @@ -444,6 +444,7 @@ def on_ui_update(ui): if READY: global INTERVAL + global NETWORK if INTERVAL.newer_then_minutes(OPTIONS['interval']): return @@ -488,7 +489,9 @@ def on_ui_update(ui): if not btnap_iface.exists(): # connected and paired but not napping logging.debug('BT-TETHER: Try to connect to nap ...') - if BTNap.nap(dev_remote): + network, status = BTNap.nap(dev_remote) + NETWORK = network + if status: logging.info('BT-TETHER: Napping!') ui.set('bluetooth', 'C') time.sleep(5) From 1827ee564c017c549bee0c482868d3d8dcef115f Mon Sep 17 00:00:00 2001 From: dadav <33197631+dadav@users.noreply.github.com> Date: Fri, 1 Nov 2019 10:04:36 +0100 Subject: [PATCH 156/168] Add version option --- bin/pwnagotchi | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/bin/pwnagotchi b/bin/pwnagotchi index 82e47122..e1409245 100755 --- a/bin/pwnagotchi +++ b/bin/pwnagotchi @@ -31,7 +31,15 @@ if __name__ == '__main__': parser.add_argument('--debug', dest="debug", action="store_true", default=False, help="Enable debug logs.") + parser.add_argument('--version', dest="version", action="store_true", default=False, + help="Prints the version.") + args = parser.parse_args() + + if args.version: + print(pwnagotchi.version) + SystemExit(0) + config = utils.load_config(args) utils.setup_logging(args, config) From ae330dc0b5219100f5830194274644d511a57716 Mon Sep 17 00:00:00 2001 From: Simone Margaritelli Date: Fri, 1 Nov 2019 12:12:37 +0100 Subject: [PATCH 157/168] fix: don't reset network interfaces configuration if not needed (closes #483) --- setup.py | 42 ++++++++++++++++++++++++++++-------------- 1 file changed, 28 insertions(+), 14 deletions(-) diff --git a/setup.py b/setup.py index fe58a736..906dcfdf 100644 --- a/setup.py +++ b/setup.py @@ -5,25 +5,39 @@ import os import glob import shutil -setup_path = os.path.dirname(__file__) -data_path = os.path.join(setup_path, "builder/data") -for source_filename in glob.glob("%s/**" % data_path, recursive=True): - if os.path.isfile(source_filename): - dest_filename = source_filename.replace(data_path, '') +def install_file(source_filename, dest_filename): + # do not overwrite network configuration if it exists already + # https://github.com/evilsocket/pwnagotchi/issues/483 + if dest_filename.startswith('/etc/network/interfaces.d/') and os.path.exists(dest_filename): + print("%s exists, skipping ..." % dest_filename) + return + + print("installing %s to %s ..." % (source_filename, dest_filename)) + try: dest_folder = os.path.dirname(dest_filename) + if not os.path.isdir(dest_folder): + os.makedirs(dest_folder) - print("installing %s to %s ..." % (source_filename, dest_filename)) - try: - if not os.path.isdir(dest_folder): - os.makedirs(dest_folder) + shutil.copyfile(source_filename, dest_filename) + except Exception as e: + print("error installing %s: %s" % (source_filename, e)) - shutil.copyfile(source_filename, dest_filename) - except Exception as e: - print("error installing %s: %s" % (source_filename, e)) -# reload systemd units -os.system("systemctl daemon-reload") +def install_system_files(): + setup_path = os.path.dirname(__file__) + data_path = os.path.join(setup_path, "builder/data") + + for source_filename in glob.glob("%s/**" % data_path, recursive=True): + if os.path.isfile(source_filename): + dest_filename = source_filename.replace(data_path, '') + install_file(source_filename, dest_filename) + + # reload systemd units + os.system("systemctl daemon-reload") + + +install_system_files() required = [] with open('requirements.txt') as fp: From 2f948306ebeabf10050bb0c07a3254b420c30751 Mon Sep 17 00:00:00 2001 From: Simone Margaritelli Date: Fri, 1 Nov 2019 13:51:45 +0100 Subject: [PATCH 158/168] misc: refactored plugin system to use classes --- pwnagotchi/plugins/__init__.py | 64 +++-- pwnagotchi/plugins/default/AircrackOnly.py | 81 +++--- pwnagotchi/plugins/default/auto-backup.py | 74 +++-- pwnagotchi/plugins/default/auto-update.py | 57 ++-- pwnagotchi/plugins/default/bt-tether.py | 132 ++++----- pwnagotchi/plugins/default/example.py | 258 ++++++++---------- pwnagotchi/plugins/default/gpio_buttons.py | 64 ++--- pwnagotchi/plugins/default/gps.py | 65 +++-- pwnagotchi/plugins/default/grid.py | 171 ++++++------ pwnagotchi/plugins/default/memtemp.py | 60 ++-- pwnagotchi/plugins/default/net-pos.py | 222 ++++++++------- pwnagotchi/plugins/default/onlinehashcrack.py | 138 +++++----- pwnagotchi/plugins/default/paw-gps.py | 30 +- pwnagotchi/plugins/default/quickdic.py | 77 +++--- pwnagotchi/plugins/default/screen_refresh.py | 37 ++- pwnagotchi/plugins/default/twitter.py | 77 +++--- .../plugins/default/unfiltered_example.py | 22 -- pwnagotchi/plugins/default/ups_lite.py | 32 +-- pwnagotchi/plugins/default/wigle.py | 192 +++++++------ pwnagotchi/plugins/default/wpa-sec.py | 139 +++++----- 20 files changed, 943 insertions(+), 1049 deletions(-) delete mode 100644 pwnagotchi/plugins/default/unfiltered_example.py diff --git a/pwnagotchi/plugins/__init__.py b/pwnagotchi/plugins/__init__.py index cb58323d..a183294d 100644 --- a/pwnagotchi/plugins/__init__.py +++ b/pwnagotchi/plugins/__init__.py @@ -7,20 +7,20 @@ default_path = os.path.join(os.path.dirname(os.path.realpath(__file__)), "defaul loaded = {} -def dummy_callback(): - pass +class Plugin: + @classmethod + def __init_subclass__(cls, **kwargs): + super().__init_subclass__(**kwargs) + global loaded + plugin_name = cls.__module__.split('.')[0] + plugin_instance = cls() + logging.debug("loaded plugin %s as %s" % (plugin_name, plugin_instance)) + loaded[plugin_name] = plugin_instance def on(event_name, *args, **kwargs): - global loaded - cb_name = 'on_%s' % event_name for plugin_name, plugin in loaded.items(): - if cb_name in plugin.__dict__: - try: - plugin.__dict__[cb_name](*args, **kwargs) - except Exception as e: - logging.error("error while running %s.%s : %s" % (plugin_name, cb_name, e)) - logging.error(e, exc_info=True) + one(plugin_name, event_name, *args, **kwargs) def one(plugin_name, event_name, *args, **kwargs): @@ -28,15 +28,17 @@ def one(plugin_name, event_name, *args, **kwargs): if plugin_name in loaded: plugin = loaded[plugin_name] cb_name = 'on_%s' % event_name - if cb_name in plugin.__dict__: + callback = getattr(plugin, cb_name, None) + if callback is not None and callable(callback): try: - plugin.__dict__[cb_name](*args, **kwargs) + callback(*args, **kwargs) except Exception as e: logging.error("error while running %s.%s : %s" % (plugin_name, cb_name, e)) logging.error(e, exc_info=True) def load_from_file(filename): + logging.debug("loading %s" % filename) plugin_name = os.path.basename(filename.replace(".py", "")) spec = importlib.util.spec_from_file_location(plugin_name, filename) instance = importlib.util.module_from_spec(spec) @@ -46,19 +48,15 @@ def load_from_file(filename): def load_from_path(path, enabled=()): global loaded + logging.debug("loading plugins from %s - enabled: %s" % (path, enabled)) for filename in glob.glob(os.path.join(path, "*.py")): - try: - name, plugin = load_from_file(filename) - if name in loaded: - raise Exception("plugin %s already loaded from %s" % (name, plugin.__file__)) - elif name not in enabled: - # print("plugin %s is not enabled" % name) - pass - else: - loaded[name] = plugin - except Exception as e: - logging.warning("error while loading %s: %s" % (filename, e)) - logging.debug(e, exc_info=True) + plugin_name = os.path.basename(filename.replace(".py", "")) + if plugin_name in enabled: + try: + load_from_file(filename) + except Exception as e: + logging.warning("error while loading %s: %s" % (filename, e)) + logging.debug(e, exc_info=True) return loaded @@ -66,17 +64,17 @@ def load_from_path(path, enabled=()): def load(config): enabled = [name for name, options in config['main']['plugins'].items() if 'enabled' in options and options['enabled']] - custom_path = config['main']['custom_plugins'] if 'custom_plugins' in config['main'] else None + # load default plugins - loaded = load_from_path(default_path, enabled=enabled) - # set the options - for name, plugin in loaded.items(): - plugin.__dict__['OPTIONS'] = config['main']['plugins'][name] + load_from_path(default_path, enabled=enabled) + # load custom ones + custom_path = config['main']['custom_plugins'] if 'custom_plugins' in config['main'] else None if custom_path is not None: - loaded = load_from_path(custom_path, enabled=enabled) - # set the options - for name, plugin in loaded.items(): - plugin.__dict__['OPTIONS'] = config['main']['plugins'][name] + load_from_path(custom_path, enabled=enabled) + + # propagate options + for name, plugin in loaded.items(): + plugin.options = config['main']['plugins'][name] on('loaded') diff --git a/pwnagotchi/plugins/default/AircrackOnly.py b/pwnagotchi/plugins/default/AircrackOnly.py index b6d20286..4691e686 100644 --- a/pwnagotchi/plugins/default/AircrackOnly.py +++ b/pwnagotchi/plugins/default/AircrackOnly.py @@ -1,56 +1,57 @@ -__author__ = 'pwnagotchi [at] rossmarks [dot] uk' -__version__ = '1.0.1' -__name__ = 'AircrackOnly' -__license__ = 'GPL3' -__description__ = 'confirm pcap contains handshake/PMKID or delete it' - -''' -Aircrack-ng needed, to install: -> apt-get install aircrack-ng -''' +import pwnagotchi.plugins as plugins import logging import subprocess import string import os -OPTIONS = dict() +''' +Aircrack-ng needed, to install: +> apt-get install aircrack-ng +''' -def on_loaded(): - logging.info("aircrackonly plugin loaded") -def on_handshake(agent, filename, access_point, client_station): - display = agent._view - todelete = 0 +class AircrackOnly(plugins.Plugin): + __author__ = 'pwnagotchi [at] rossmarks [dot] uk' + __version__ = '1.0.1' + __license__ = 'GPL3' + __description__ = 'confirm pcap contains handshake/PMKID or delete it' - result = subprocess.run(('/usr/bin/aircrack-ng '+ filename +' | grep "1 handshake" | awk \'{print $2}\''),shell=True, stdout=subprocess.PIPE) - result = result.stdout.decode('utf-8').translate({ord(c) :None for c in string.whitespace}) - if result: - logging.info("[AircrackOnly] contains handshake") - else: - todelete = 1 + def __init__(self): + super().__init__(self) + self.text_to_set = "" - if todelete == 0: - result = subprocess.run(('/usr/bin/aircrack-ng '+ filename +' | grep "PMKID" | awk \'{print $2}\''),shell=True, stdout=subprocess.PIPE) - result = result.stdout.decode('utf-8').translate({ord(c) :None for c in string.whitespace}) + def on_loaded(self): + logging.info("aircrackonly plugin loaded") + + def on_handshake(self, agent, filename, access_point, client_station): + display = agent._view + todelete = 0 + + result = subprocess.run(('/usr/bin/aircrack-ng ' + filename + ' | grep "1 handshake" | awk \'{print $2}\''), + shell=True, stdout=subprocess.PIPE) + result = result.stdout.decode('utf-8').translate({ord(c): None for c in string.whitespace}) if result: - logging.info("[AircrackOnly] contains PMKID") + logging.info("[AircrackOnly] contains handshake") else: todelete = 1 - if todelete == 1: - os.remove(filename) - set_text("Removed an uncrackable pcap") - display.update(force=True) + if todelete == 0: + result = subprocess.run(('/usr/bin/aircrack-ng ' + filename + ' | grep "PMKID" | awk \'{print $2}\''), + shell=True, stdout=subprocess.PIPE) + result = result.stdout.decode('utf-8').translate({ord(c): None for c in string.whitespace}) + if result: + logging.info("[AircrackOnly] contains PMKID") + else: + todelete = 1 -text_to_set = ""; -def set_text(text): - global text_to_set - text_to_set = text + if todelete == 1: + os.remove(filename) + self.text_to_set = "Removed an uncrackable pcap" + display.update(force=True) -def on_ui_update(ui): - global text_to_set - if text_to_set: - ui.set('face', "(>.<)") - ui.set('status', text_to_set) - text_to_set = "" + def on_ui_update(self, ui): + if self.text_to_set: + ui.set('face', "(>.<)") + ui.set('status', self.text_to_set) + self.text_to_set = "" diff --git a/pwnagotchi/plugins/default/auto-backup.py b/pwnagotchi/plugins/default/auto-backup.py index c235f787..9b15efc0 100644 --- a/pwnagotchi/plugins/default/auto-backup.py +++ b/pwnagotchi/plugins/default/auto-backup.py @@ -1,49 +1,47 @@ -__author__ = '33197631+dadav@users.noreply.github.com' -__version__ = '1.0.0' -__name__ = 'auto-backup' -__license__ = 'GPL3' -__description__ = 'This plugin backups files when internet is available.' - +import pwnagotchi.plugins as plugins from pwnagotchi.utils import StatusFile import logging import os import subprocess -OPTIONS = dict() -READY = False -STATUS = StatusFile('/root/.auto-backup') +class AutoBackup(plugins.Plugin): + __author__ = '33197631+dadav@users.noreply.github.com' + __version__ = '1.0.0' + __license__ = 'GPL3' + __description__ = 'This plugin backups files when internet is available.' -def on_loaded(): - global READY + def __init__(self): + self.ready = False + self.status = StatusFile('/root/.auto-backup') - if 'files' not in OPTIONS or ('files' in OPTIONS and OPTIONS['files'] is None): - logging.error("AUTO-BACKUP: No files to backup.") - return - - if 'interval' not in OPTIONS or ('interval' in OPTIONS and OPTIONS['interval'] is None): - logging.error("AUTO-BACKUP: Interval is not set.") - return - - if 'commands' not in OPTIONS or ('commands' in OPTIONS and OPTIONS['commands'] is None): - logging.error("AUTO-BACKUP: No commands given.") - return - - READY = True - logging.info("AUTO-BACKUP: Successfully loaded.") - - -def on_internet_available(agent): - global STATUS - - if READY: - if STATUS.newer_then_days(OPTIONS['interval']): + def on_loaded(self): + if 'files' not in self.options or ('files' in self.options and self.options['files'] is None): + logging.error("AUTO-BACKUP: No files to backup.") return - + + if 'interval' not in self.options or ('interval' in self.options and self.options['interval'] is None): + logging.error("AUTO-BACKUP: Interval is not set.") + return + + if 'commands' not in self.options or ('commands' in self.options and self.options['commands'] is None): + logging.error("AUTO-BACKUP: No commands given.") + return + + self.ready = True + logging.info("AUTO-BACKUP: Successfully loaded.") + + def on_internet_available(self, agent): + if not self.ready: + return + + if self.status.newer_then_days(self.options['interval']): + return + # Only backup existing files to prevent errors - existing_files = list(filter(lambda f: os.path.exists(f), OPTIONS['files'])) + existing_files = list(filter(lambda f: os.path.exists(f), self.options['files'])) files_to_backup = " ".join(existing_files) - + try: display = agent.view() @@ -51,10 +49,10 @@ def on_internet_available(agent): display.set('status', 'Backing up ...') display.update() - for cmd in OPTIONS['commands']: + for cmd in self.options['commands']: logging.info(f"AUTO-BACKUP: Running {cmd.format(files=files_to_backup)}") process = subprocess.Popen(cmd.format(files=files_to_backup), shell=True, stdin=None, - stdout=open("/dev/null", "w"), stderr=None, executable="/bin/bash") + stdout=open("/dev/null", "w"), stderr=None, executable="/bin/bash") process.wait() if process.returncode > 0: raise OSError(f"Command failed (rc: {process.returncode})") @@ -62,7 +60,7 @@ def on_internet_available(agent): logging.info("AUTO-BACKUP: backup done") display.set('status', 'Backup done!') display.update() - STATUS.update() + self.status.update() except OSError as os_e: logging.info(f"AUTO-BACKUP: Error: {os_e}") display.set('status', 'Backup failed!') diff --git a/pwnagotchi/plugins/default/auto-update.py b/pwnagotchi/plugins/default/auto-update.py index 33654902..0a4b0ff0 100644 --- a/pwnagotchi/plugins/default/auto-update.py +++ b/pwnagotchi/plugins/default/auto-update.py @@ -1,9 +1,3 @@ -__author__ = 'evilsocket@gmail.com' -__version__ = '1.1.1' -__name__ = 'auto-update' -__license__ = 'GPL3' -__description__ = 'This plugin checks when updates are available and applies them when internet is available.' - import os import re import logging @@ -15,21 +9,9 @@ import glob import pkg_resources import pwnagotchi +import pwnagotchi.plugins as plugins from pwnagotchi.utils import StatusFile -OPTIONS = dict() -READY = False -STATUS = StatusFile('/root/.auto-update') - - -def on_loaded(): - global READY - if 'interval' not in OPTIONS or ('interval' in OPTIONS and OPTIONS['interval'] is None): - logging.error("[update] main.plugins.auto-update.interval is not set") - return - READY = True - logging.info("[update] plugin loaded.") - def check(version, repo, native=True): logging.debug("checking remote version for %s, local is %s" % (repo, version)) @@ -158,14 +140,32 @@ def parse_version(cmd): raise Exception('could not parse version from "%s": output=\n%s' % (cmd, out)) -def on_internet_available(agent): - global STATUS +class AutoUpdate(plugins.Plugin): + __author__ = 'evilsocket@gmail.com' + __version__ = '1.1.1' + __name__ = 'auto-update' + __license__ = 'GPL3' + __description__ = 'This plugin checks when updates are available and applies them when internet is available.' - logging.debug("[update] internet connectivity is available (ready %s)" % READY) + def __init__(self): + self.ready = False + self.status = StatusFile('/root/.auto-update') - if READY: - if STATUS.newer_then_hours(OPTIONS['interval']): - logging.debug("[update] last check happened less than %d hours ago" % OPTIONS['interval']) + def on_loaded(self): + if 'interval' not in self.options or ('interval' in self.options and self.options['interval'] is None): + logging.error("[update] main.plugins.auto-update.interval is not set") + return + self.ready = True + logging.info("[update] plugin loaded.") + + def on_internet_available(self, agent): + logging.debug("[update] internet connectivity is available (ready %s)" % self.ready) + + if not self.ready: + return + + if self.status.newer_then_hours(self.options['interval']): + logging.debug("[update] last check happened less than %d hours ago" % self.options['interval']) return logging.info("[update] checking for updates ...") @@ -187,7 +187,8 @@ def on_internet_available(agent): info = check(local_version, repo, is_native) if info['url'] is not None: logging.warning( - "update for %s available (local version is '%s'): %s" % (repo, info['current'], info['url'])) + "update for %s available (local version is '%s'): %s" % ( + repo, info['current'], info['url'])) info['service'] = svc_name to_install.append(info) @@ -195,7 +196,7 @@ def on_internet_available(agent): num_installed = 0 if num_updates > 0: - if OPTIONS['install']: + if self.options['install']: for update in to_install: if install(display, update): num_installed += 1 @@ -204,7 +205,7 @@ def on_internet_available(agent): logging.info("[update] done") - STATUS.update() + self.status.update() if num_installed > 0: display.update(force=True, new_data={'status': 'Rebooting ...'}) diff --git a/pwnagotchi/plugins/default/bt-tether.py b/pwnagotchi/plugins/default/bt-tether.py index 2da90cf8..7c89387f 100644 --- a/pwnagotchi/plugins/default/bt-tether.py +++ b/pwnagotchi/plugins/default/bt-tether.py @@ -1,9 +1,3 @@ -__author__ = '33197631+dadav@users.noreply.github.com' -__version__ = '1.0.0' -__name__ = 'bt-tether' -__license__ = 'GPL3' -__description__ = 'This makes the display reachable over bluetooth' - import os import time import re @@ -14,11 +8,8 @@ from pwnagotchi.ui.components import LabeledValue from pwnagotchi.ui.view import BLACK import pwnagotchi.ui.fonts as fonts from pwnagotchi.utils import StatusFile +import pwnagotchi.plugins as plugins -READY = False -INTERVAL = StatusFile('/root/.bt-tether') -OPTIONS = dict() -NETWORK = None class BTError(Exception): """ @@ -26,6 +17,7 @@ class BTError(Exception): """ pass + class BTNap: """ This class creates a bluetooth connection to the specified bt-mac @@ -41,7 +33,6 @@ class BTNap: def __init__(self, mac): self._mac = mac - @staticmethod def get_bus(): """ @@ -59,9 +50,9 @@ class BTNap: """ manager = getattr(BTNap.get_manager, 'cached_obj', None) if not manager: - manager = BTNap.get_manager.cached_obj = dbus.Interface( - BTNap.get_bus().get_object(BTNap.IFACE_BASE, '/'), - 'org.freedesktop.DBus.ObjectManager' ) + manager = BTNap.get_manager.cached_obj = dbus.Interface( + BTNap.get_bus().get_object(BTNap.IFACE_BASE, '/'), + 'org.freedesktop.DBus.ObjectManager') return manager @staticmethod @@ -82,7 +73,6 @@ class BTNap: iface = obj.dbus_interface return obj.Set(iface, k, v, dbus_interface=BTNap.IFACE_PROPS) - @staticmethod def find_adapter(pattern=None): """ @@ -98,14 +88,14 @@ class BTNap: """ bus, obj = BTNap.get_bus(), None for path, ifaces in objects.items(): - adapter = ifaces.get(BTNap.IFACE_ADAPTER) - if adapter is None: - continue - if not pattern or pattern == adapter['Address'] or path.endswith(pattern): - obj = bus.get_object(BTNap.IFACE_BASE, path) - yield dbus.Interface(obj, BTNap.IFACE_ADAPTER) + adapter = ifaces.get(BTNap.IFACE_ADAPTER) + if adapter is None: + continue + if not pattern or pattern == adapter['Address'] or path.endswith(pattern): + obj = bus.get_object(BTNap.IFACE_BASE, path) + yield dbus.Interface(obj, BTNap.IFACE_ADAPTER) if obj is None: - raise BTError('Bluetooth adapter not found') + raise BTError('Bluetooth adapter not found') @staticmethod def find_device(device_address, adapter_pattern=None): @@ -178,7 +168,6 @@ class BTNap: logging.debug("BT-TETHER: Device is not connected.") return None, False - def is_paired(self): """ Check if already connected @@ -198,7 +187,6 @@ class BTNap: logging.debug("BT-TETHER: Device is not paired.") return False - def wait_for_device(self, timeout=15): """ Wait for device @@ -227,7 +215,7 @@ class BTNap: try: dev_remote = BTNap.find_device(self._mac, bt_dev) logging.debug("BT-TETHER: Using remote device (addr: %s): %s", - BTNap.prop_get(dev_remote, 'Address'), dev_remote.object_path ) + BTNap.prop_get(dev_remote, 'Address'), dev_remote.object_path) break except BTError: logging.debug("BT-TETHER: Not found yet ...") @@ -259,7 +247,6 @@ class BTNap: pass return False - @staticmethod def nap(device): logging.debug('BT-TETHER: Trying to nap ...') @@ -267,7 +254,7 @@ class BTNap: try: logging.debug('BT-TETHER: Connecting to profile ...') device.ConnectProfile('nap') - except Exception: # raises exception, but still works + except Exception: # raises exception, but still works pass net = dbus.Interface(device, 'org.bluez.Network1') @@ -297,7 +284,7 @@ class SystemdUnitWrapper: @staticmethod def _action_on_unit(action, unit): process = subprocess.Popen(f"systemctl {action} {unit}", shell=True, stdin=None, - stdout=open("/dev/null", "w"), stderr=None, executable="/bin/bash") + stdout=open("/dev/null", "w"), stderr=None, executable="/bin/bash") process.wait() if process.returncode > 0: return False @@ -309,7 +296,7 @@ class SystemdUnitWrapper: Calls systemctl daemon-reload """ process = subprocess.Popen("systemctl daemon-reload", shell=True, stdin=None, - stdout=open("/dev/null", "w"), stderr=None, executable="/bin/bash") + stdout=open("/dev/null", "w"), stderr=None, executable="/bin/bash") process.wait() if process.returncode > 0: return False @@ -387,16 +374,15 @@ class IfaceWrapper: """ return open(f"{self.path}/operstate", 'r').read().rsplit('\n') == 'up' - def set_addr(self, addr): """ Set the netmask """ process = subprocess.Popen(f"ip addr add {addr} dev {self.iface}", shell=True, stdin=None, - stdout=open("/dev/null", "w"), stderr=None, executable="/bin/bash") + stdout=open("/dev/null", "w"), stderr=None, executable="/bin/bash") process.wait() - if process.returncode == 2 or process.returncode == 0: # 2 = already set + if process.returncode == 2 or process.returncode == 0: # 2 = already set return True return False @@ -404,7 +390,7 @@ class IfaceWrapper: @staticmethod def set_route(addr): process = subprocess.Popen(f"ip route replace default via {addr}", shell=True, stdin=None, - stdout=open("/dev/null", "w"), stderr=None, executable="/bin/bash") + stdout=open("/dev/null", "w"), stderr=None, executable="/bin/bash") process.wait() if process.returncode > 0: @@ -413,44 +399,47 @@ class IfaceWrapper: return True +class BTTether(plugins.Plugin): + __author__ = '33197631+dadav@users.noreply.github.com' + __version__ = '1.0.0' + __license__ = 'GPL3' + __description__ = 'This makes the display reachable over bluetooth' -def on_loaded(): - """ - Gets called when the plugin gets loaded - """ - global READY - global INTERVAL + def __init__(self): + self.ready = False + self.interval = StatusFile('/root/.bt-tether') + self.network = None - for opt in ['share_internet', 'mac', 'ip', 'netmask', 'interval']: - if opt not in OPTIONS or (opt in OPTIONS and OPTIONS[opt] is None): - logging.error("BT-TET: Please specify the %s in your config.yml.", opt) + def on_loaded(self): + for opt in ['share_internet', 'mac', 'ip', 'netmask', 'interval']: + if opt not in self.options or (opt in self.options and self.options[opt] is None): + logging.error("BT-TET: Please specify the %s in your config.yml.", opt) + return + + # ensure bluetooth is running + bt_unit = SystemdUnitWrapper('bluetooth.service') + if not bt_unit.is_active(): + if not bt_unit.start(): + logging.error("BT-TET: Can't start bluetooth.service") + return + + self.interval.update() + self.ready = True + + def on_ui_setup(self, ui): + ui.add_element('bluetooth', LabeledValue(color=BLACK, label='BT', value='-', position=(ui.width() / 2 - 15, 0), + label_font=fonts.Bold, text_font=fonts.Medium)) + + def on_ui_update(self, ui): + if not self.ready: return - # ensure bluetooth is running - bt_unit = SystemdUnitWrapper('bluetooth.service') - if not bt_unit.is_active(): - if not bt_unit.start(): - logging.error("BT-TET: Can't start bluetooth.service") + if self.interval.newer_then_minutes(self.options['interval']): return - INTERVAL.update() - READY = True + self.interval.update() - -def on_ui_update(ui): - """ - Try to connect to device - """ - - if READY: - global INTERVAL - global NETWORK - if INTERVAL.newer_then_minutes(OPTIONS['interval']): - return - - INTERVAL.update() - - bt = BTNap(OPTIONS['mac']) + bt = BTNap(self.options['mac']) logging.debug('BT-TETHER: Check if already connected and paired') dev_remote, connected = bt.is_connected() @@ -483,14 +472,13 @@ def on_ui_update(ui): else: logging.debug('BT-TETHER: Already paired.') - btnap_iface = IfaceWrapper('bnep0') logging.debug('BT-TETHER: Check interface') if not btnap_iface.exists(): # connected and paired but not napping logging.debug('BT-TETHER: Try to connect to nap ...') network, status = BTNap.nap(dev_remote) - NETWORK = network + self.network = network if status: logging.info('BT-TETHER: Napping!') ui.set('bluetooth', 'C') @@ -504,7 +492,7 @@ def on_ui_update(ui): logging.debug('BT-TETHER: Interface found') # check ip - addr = f"{OPTIONS['ip']}/{OPTIONS['netmask']}" + addr = f"{self.options['ip']}/{self.options['netmask']}" logging.debug('BT-TETHER: Try to set ADDR to interface') if not btnap_iface.set_addr(addr): @@ -515,9 +503,10 @@ def on_ui_update(ui): logging.debug('BT-TETHER: Set ADDR to interface') # change route if sharking - if OPTIONS['share_internet']: + if self.options['share_internet']: logging.debug('BT-TETHER: Set routing and change resolv.conf') - IfaceWrapper.set_route(".".join(OPTIONS['ip'].split('.')[:-1] + ['1'])) # im not proud about that + IfaceWrapper.set_route( + ".".join(self.options['ip'].split('.')[:-1] + ['1'])) # im not proud about that # fix resolv.conf; dns over https ftw! with open('/etc/resolv.conf', 'r+') as resolv: nameserver = resolv.read() @@ -530,8 +519,3 @@ def on_ui_update(ui): else: logging.error('BT-TETHER: bnep0 not found') ui.set('bluetooth', 'BE') - - -def on_ui_setup(ui): - ui.add_element('bluetooth', LabeledValue(color=BLACK, label='BT', value='-', position=(ui.width() / 2 - 15, 0), - label_font=fonts.Bold, text_font=fonts.Medium)) diff --git a/pwnagotchi/plugins/default/example.py b/pwnagotchi/plugins/default/example.py index 72087cfb..3aa5c7d8 100644 --- a/pwnagotchi/plugins/default/example.py +++ b/pwnagotchi/plugins/default/example.py @@ -1,182 +1,154 @@ -__author__ = 'evilsocket@gmail.com' -__version__ = '1.0.0' -__name__ = 'hello_world' -__license__ = 'GPL3' -__description__ = 'An example plugin for pwnagotchi that implements all the available callbacks.' - import logging +import pwnagotchi.plugins as plugins from pwnagotchi.ui.components import LabeledValue from pwnagotchi.ui.view import BLACK import pwnagotchi.ui.fonts as fonts -# Will be set with the options in config.yml config['main']['plugins'][__name__] -OPTIONS = dict() +class Example(plugins.Plugin): + __author__ = 'evilsocket@gmail.com' + __version__ = '1.0.0' + __license__ = 'GPL3' + __description__ = 'An example plugin for pwnagotchi that implements all the available callbacks.' -# called when :/plugins/ is opened -def on_webhook(response, path): - res = "Hook triggered" - response.send_response(200) - response.send_header('Content-type', 'text/html') - response.end_headers() + def __init__(self): + logging.debug("example plugin created") - try: - response.wfile.write(bytes(res, "utf-8")) - except Exception as ex: - logging.error(ex) + # called when the plugin is loaded + def on_loaded(self): + logging.warning("WARNING: this plugin should be disabled! options = " % self.options) -# called when the plugin is loaded -def on_loaded(): - logging.warning("WARNING: plugin %s should be disabled!" % __name__) + # called when :/plugins/ is opened + def on_webhook(self, response, path): + res = "Hook triggered" + response.send_response(200) + response.send_header('Content-type', 'text/html') + response.end_headers() + try: + response.wfile.write(bytes(res, "utf-8")) + except Exception as ex: + logging.error(ex) -# called in manual mode when there's internet connectivity -def on_internet_available(agent): - pass + # called in manual mode when there's internet connectivity + def on_internet_available(self, agent): + pass + # called to setup the ui elements + def on_ui_setup(self, ui): + # add custom UI elements + ui.add_element('ups', LabeledValue(color=BLACK, label='UPS', value='0%/0V', position=(ui.width() / 2 - 25, 0), + label_font=fonts.Bold, text_font=fonts.Medium)) -# called to setup the ui elements -def on_ui_setup(ui): - # add custom UI elements - ui.add_element('ups', LabeledValue(color=BLACK, label='UPS', value='0%/0V', position=(ui.width() / 2 - 25, 0), - label_font=fonts.Bold, text_font=fonts.Medium)) + # called when the ui is updated + def on_ui_update(self, ui): + # update those elements + some_voltage = 0.1 + some_capacity = 100.0 + ui.set('ups', "%4.2fV/%2i%%" % (some_voltage, some_capacity)) + # called when the hardware display setup is done, display is an hardware specific object + def on_display_setup(self, display): + pass -# called when the ui is updated -def on_ui_update(ui): - # update those elements - some_voltage = 0.1 - some_capacity = 100.0 + # called when everything is ready and the main loop is about to start + def on_ready(self, agent): + logging.info("unit is ready") + # you can run custom bettercap commands if you want + # agent.run('ble.recon on') + # or set a custom state + # agent.set_bored() - ui.set('ups', "%4.2fV/%2i%%" % (some_voltage, some_capacity)) + # called when the AI finished loading + def on_ai_ready(self, agent): + pass + # called when the AI finds a new set of parameters + def on_ai_policy(self, agent, policy): + pass -# called when the hardware display setup is done, display is an hardware specific object -def on_display_setup(display): - pass + # called when the AI starts training for a given number of epochs + def on_ai_training_start(self, agent, epochs): + pass + # called after the AI completed a training epoch + def on_ai_training_step(self, agent, _locals, _globals): + pass -# called when everything is ready and the main loop is about to start -def on_ready(agent): - logging.info("unit is ready") - # you can run custom bettercap commands if you want - # agent.run('ble.recon on') - # or set a custom state - # agent.set_bored() + # called when the AI has done training + def on_ai_training_end(self, agent): + pass + # called when the AI got the best reward so far + def on_ai_best_reward(self, agent, reward): + pass -# called when the AI finished loading -def on_ai_ready(agent): - pass + # called when the AI got the worst reward so far + def on_ai_worst_reward(self, agent, reward): + pass + # called when a non overlapping wifi channel is found to be free + def on_free_channel(self, agent, channel): + pass -# called when the AI finds a new set of parameters -def on_ai_policy(agent, policy): - pass + # called when the status is set to bored + def on_bored(self, agent): + pass + # called when the status is set to sad + def on_sad(self, agent): + pass -# called when the AI starts training for a given number of epochs -def on_ai_training_start(agent, epochs): - pass + # called when the status is set to excited + def on_excited(aself, gent): + pass + # called when the status is set to lonely + def on_lonely(self, agent): + pass -# called after the AI completed a training epoch -def on_ai_training_step(agent, _locals, _globals): - pass + # called when the agent is rebooting the board + def on_rebooting(self, agent): + pass + # called when the agent is waiting for t seconds + def on_wait(self, agent, t): + pass -# called when the AI has done training -def on_ai_training_end(agent): - pass + # called when the agent is sleeping for t seconds + def on_sleep(self, agent, t): + pass + # called when the agent refreshed its access points list + def on_wifi_update(self, agent, access_points): + pass -# called when the AI got the best reward so far -def on_ai_best_reward(agent, reward): - pass + # called when the agent is sending an association frame + def on_association(self, agent, access_point): + pass + # called when the agent is deauthenticating a client station from an AP + def on_deauthentication(self, agent, access_point, client_station): + pass -# called when the AI got the worst reward so far -def on_ai_worst_reward(agent, reward): - pass + # callend when the agent is tuning on a specific channel + def on_channel_hop(self, agent, channel): + pass + # called when a new handshake is captured, access_point and client_station are json objects + # if the agent could match the BSSIDs to the current list, otherwise they are just the strings of the BSSIDs + def on_handshake(self, agent, filename, access_point, client_station): + pass -# called when a non overlapping wifi channel is found to be free -def on_free_channel(agent, channel): - pass + # called when an epoch is over (where an epoch is a single loop of the main algorithm) + def on_epoch(self, agent, epoch, epoch_data): + pass + # called when a new peer is detected + def on_peer_detected(self, agent, peer): + pass -# called when the status is set to bored -def on_bored(agent): - pass - - -# called when the status is set to sad -def on_sad(agent): - pass - - -# called when the status is set to excited -def on_excited(agent): - pass - - -# called when the status is set to lonely -def on_lonely(agent): - pass - - -# called when the agent is rebooting the board -def on_rebooting(agent): - pass - - -# called when the agent is waiting for t seconds -def on_wait(agent, t): - pass - - -# called when the agent is sleeping for t seconds -def on_sleep(agent, t): - pass - - -# called when the agent refreshed its access points list -def on_wifi_update(agent, access_points): - pass - - -# called when the agent is sending an association frame -def on_association(agent, access_point): - pass - - -# callend when the agent is deauthenticating a client station from an AP -def on_deauthentication(agent, access_point, client_station): - pass - - -# callend when the agent is tuning on a specific channel -def on_channel_hop(agent, channel): - pass - - -# called when a new handshake is captured, access_point and client_station are json objects -# if the agent could match the BSSIDs to the current list, otherwise they are just the strings of the BSSIDs -def on_handshake(agent, filename, access_point, client_station): - pass - - -# called when an epoch is over (where an epoch is a single loop of the main algorithm) -def on_epoch(agent, epoch, epoch_data): - pass - - -# called when a new peer is detected -def on_peer_detected(agent, peer): - pass - - -# called when a known peer is lost -def on_peer_lost(agent, peer): - pass + # called when a known peer is lost + def on_peer_lost(self, agent, peer): + pass diff --git a/pwnagotchi/plugins/default/gpio_buttons.py b/pwnagotchi/plugins/default/gpio_buttons.py index c0f6ceb1..7d9adb87 100644 --- a/pwnagotchi/plugins/default/gpio_buttons.py +++ b/pwnagotchi/plugins/default/gpio_buttons.py @@ -1,38 +1,40 @@ -__author__ = 'ratmandu@gmail.com' -__version__ = '1.0.0' -__name__ = 'gpio_buttons' -__license__ = 'GPL3' -__description__ = 'GPIO Button support plugin' - import logging import RPi.GPIO as GPIO import subprocess - -running = False -OPTIONS = dict() -GPIOs = {} -COMMANDs = None - -def runCommand(channel): - command = GPIOs[channel] - logging.info(f"Button Pressed! Running command: {command}") - process = subprocess.Popen(command, shell=True, stdin=None, stdout=open("/dev/null", "w"), stderr=None, executable="/bin/bash") - process.wait() +import pwnagotchi.plugins as plugins -def on_loaded(): - logging.info("GPIO Button plugin loaded.") - - #get list of GPIOs - gpios = OPTIONS['gpios'] +class GPIOButtons(plugins.Plugin): + __author__ = 'ratmandu@gmail.com' + __version__ = '1.0.0' + __license__ = 'GPL3' + __description__ = 'GPIO Button support plugin' - #set gpio numbering - GPIO.setmode(GPIO.BCM) + def __init__(self): + self.running = False + self.ports = {} + self.commands = None - for i in gpios: - gpio = list(i)[0] - command = i[gpio] - GPIOs[gpio] = command - GPIO.setup(gpio, GPIO.IN, GPIO.PUD_UP) - GPIO.add_event_detect(gpio, GPIO.FALLING, callback=runCommand, bouncetime=250) - logging.info("Added command: %s to GPIO #%d", command, gpio) + def runCommand(self, channel): + command = self.ports[channel] + logging.info(f"Button Pressed! Running command: {command}") + process = subprocess.Popen(command, shell=True, stdin=None, stdout=open("/dev/null", "w"), stderr=None, + executable="/bin/bash") + process.wait() + + def on_loaded(self): + logging.info("GPIO Button plugin loaded.") + + # get list of GPIOs + gpios = self.options['gpios'] + + # set gpio numbering + GPIO.setmode(GPIO.BCM) + + for i in gpios: + gpio = list(i)[0] + command = i[gpio] + self.ports[gpio] = command + GPIO.setup(gpio, GPIO.IN, GPIO.PUD_UP) + GPIO.add_event_detect(gpio, GPIO.FALLING, callback=self.runCommand, bouncetime=250) + logging.info("Added command: %s to GPIO #%d", command, gpio) diff --git a/pwnagotchi/plugins/default/gps.py b/pwnagotchi/plugins/default/gps.py index 234df3a9..01945597 100644 --- a/pwnagotchi/plugins/default/gps.py +++ b/pwnagotchi/plugins/default/gps.py @@ -1,45 +1,42 @@ -__author__ = 'evilsocket@gmail.com' -__version__ = '1.0.0' -__name__ = 'gps' -__license__ = 'GPL3' -__description__ = 'Save GPS coordinates whenever an handshake is captured.' - import logging import json import os - -running = False -OPTIONS = dict() +import pwnagotchi.plugins as plugins -def on_loaded(): - logging.info("gps plugin loaded for %s" % OPTIONS['device']) +class GPS(plugins.Plugin): + __author__ = 'evilsocket@gmail.com' + __version__ = '1.0.0' + __license__ = 'GPL3' + __description__ = 'Save GPS coordinates whenever an handshake is captured.' + def __init__(self): + self.running = False -def on_ready(agent): - global running + def on_loaded(self): + logging.info("gps plugin loaded for %s" % self.options['device']) - if os.path.exists(OPTIONS['device']): - logging.info("enabling gps bettercap's module for %s" % OPTIONS['device']) - try: - agent.run('gps off') - except: - pass + def on_ready(self, agent): + if os.path.exists(self.options['device']): + logging.info("enabling gps bettercap's module for %s" % self.options['device']) + try: + agent.run('gps off') + except: + pass - agent.run('set gps.device %s' % OPTIONS['device']) - agent.run('set gps.speed %d' % OPTIONS['speed']) - agent.run('gps on') - running = True - else: - logging.warning("no GPS detected") + agent.run('set gps.device %s' % self.options['device']) + agent.run('set gps.speed %d' % self.options['speed']) + agent.run('gps on') + running = True + else: + logging.warning("no GPS detected") + def on_handshake(self, agent, filename, access_point, client_station): + if self.running: + info = agent.session() + gps = info['gps'] + gps_filename = filename.replace('.pcap', '.gps.json') -def on_handshake(agent, filename, access_point, client_station): - if running: - info = agent.session() - gps = info['gps'] - gps_filename = filename.replace('.pcap', '.gps.json') - - logging.info("saving GPS to %s (%s)" % (gps_filename, gps)) - with open(gps_filename, 'w+t') as fp: - json.dump(gps, fp) + logging.info("saving GPS to %s (%s)" % (gps_filename, gps)) + with open(gps_filename, 'w+t') as fp: + json.dump(gps, fp) diff --git a/pwnagotchi/plugins/default/grid.py b/pwnagotchi/plugins/default/grid.py index 9ffd61f7..58609394 100644 --- a/pwnagotchi/plugins/default/grid.py +++ b/pwnagotchi/plugins/default/grid.py @@ -1,10 +1,3 @@ -__author__ = 'evilsocket@gmail.com' -__version__ = '1.0.1' -__name__ = 'grid' -__license__ = 'GPL3' -__description__ = 'This plugin signals the unit cryptographic identity and list of pwned networks and list of pwned ' \ - 'networks to api.pwnagotchi.ai ' - import os import logging import time @@ -12,18 +5,9 @@ import glob import re import pwnagotchi.grid as grid +import pwnagotchi.plugins as plugins from pwnagotchi.utils import StatusFile, WifiInfo, extract_from_pcap -OPTIONS = dict() -REPORT = StatusFile('/root/.api-report.json', data_format='json') - -UNREAD_MESSAGES = 0 -TOTAL_MESSAGES = 0 - - -def on_loaded(): - logging.info("grid plugin loaded.") - def parse_pcap(filename): logging.info("grid: parsing %s ..." % filename) @@ -57,93 +41,100 @@ def parse_pcap(filename): return info[WifiInfo.ESSID], info[WifiInfo.BSSID] -def is_excluded(what): - for skip in OPTIONS['exclude']: - skip = skip.lower() - what = what.lower() - if skip in what or skip.replace(':', '') in what: - return True - return False +class Grid(plugins.Plugin): + __author__ = 'evilsocket@gmail.com' + __version__ = '1.0.1' + __license__ = 'GPL3' + __description__ = 'This plugin signals the unit cryptographic identity and list of pwned networks and list of pwned ' \ + 'networks to api.pwnagotchi.ai ' + def __init__(self): + self.options = dict() + self.report = StatusFile('/root/.api-report.json', data_format='json') -def set_reported(reported, net_id): - global REPORT - reported.append(net_id) - REPORT.update(data={'reported': reported}) + self.unread_messages = 0 + self.total_messages = 0 + def is_excluded(self, what): + for skip in self.options['exclude']: + skip = skip.lower() + what = what.lower() + if skip in what or skip.replace(':', '') in what: + return True + return False -def check_inbox(agent): - global REPORT, UNREAD_MESSAGES, TOTAL_MESSAGES + def on_loaded(self): + logging.info("grid plugin loaded.") - logging.debug("checking mailbox ...") + def set_reported(self, reported, net_id): + reported.append(net_id) + self.report.update(data={'reported': reported}) - messages = grid.inbox() - TOTAL_MESSAGES = len(messages) - UNREAD_MESSAGES = len([m for m in messages if m['seen_at'] is None]) + def check_inbox(self, agent): + logging.debug("checking mailbox ...") + messages = grid.inbox() + self.total_messages = len(messages) + self.unread_messages = len([m for m in messages if m['seen_at'] is None]) - if UNREAD_MESSAGES: - logging.debug("[grid] unread:%d total:%d" % (UNREAD_MESSAGES, TOTAL_MESSAGES)) - agent.view().on_unread_messages(UNREAD_MESSAGES, TOTAL_MESSAGES) + if self.unread_messages: + logging.debug("[grid] unread:%d total:%d" % (self.unread_messages, self.total_messages)) + agent.view().on_unread_messages(self.unread_messages, self.total_messages) + def check_handshakes(self, agent): + logging.debug("checking pcaps") -def check_handshakes(agent): - logging.debug("checking pcaps") + pcap_files = glob.glob(os.path.join(agent.config()['bettercap']['handshakes'], "*.pcap")) + num_networks = len(pcap_files) + reported = self.report.data_field_or('reported', default=[]) + num_reported = len(reported) + num_new = num_networks - num_reported - pcap_files = glob.glob(os.path.join(agent.config()['bettercap']['handshakes'], "*.pcap")) - num_networks = len(pcap_files) - reported = REPORT.data_field_or('reported', default=[]) - num_reported = len(reported) - num_new = num_networks - num_reported + if num_new > 0: + if self.options['report']: + logging.info("grid: %d new networks to report" % num_new) + logging.debug("self.options: %s" % self.options) + logging.debug(" exclude: %s" % self.options['exclude']) - if num_new > 0: - if OPTIONS['report']: - logging.info("grid: %d new networks to report" % num_new) - logging.debug("OPTIONS: %s" % OPTIONS) - logging.debug(" exclude: %s" % OPTIONS['exclude']) + for pcap_file in pcap_files: + net_id = os.path.basename(pcap_file).replace('.pcap', '') + if net_id not in reported: + if self.is_excluded(net_id): + logging.debug("skipping %s due to exclusion filter" % pcap_file) + self.set_reported(reported, net_id) + continue - for pcap_file in pcap_files: - net_id = os.path.basename(pcap_file).replace('.pcap', '') - if net_id not in reported: - if is_excluded(net_id): - logging.debug("skipping %s due to exclusion filter" % pcap_file) - set_reported(reported, net_id) - continue - - essid, bssid = parse_pcap(pcap_file) - if bssid: - if is_excluded(essid) or is_excluded(bssid): - logging.debug("not reporting %s due to exclusion filter" % pcap_file) - set_reported(reported, net_id) + essid, bssid = parse_pcap(pcap_file) + if bssid: + if self.is_excluded(essid) or self.is_excluded(bssid): + logging.debug("not reporting %s due to exclusion filter" % pcap_file) + self.set_reported(reported, net_id) + else: + if grid.report_ap(essid, bssid): + self.set_reported(reported, net_id) + time.sleep(1.5) else: - if grid.report_ap(essid, bssid): - set_reported(reported, net_id) - time.sleep(1.5) - else: - logging.warning("no bssid found?!") - else: - logging.debug("grid: reporting disabled") + logging.warning("no bssid found?!") + else: + logging.debug("grid: reporting disabled") + def on_internet_available(self, agent): + logging.debug("internet available") -def on_internet_available(agent): - global REPORT, UNREAD_MESSAGES, TOTAL_MESSAGES + try: + grid.update_data(agent.last_session) + except Exception as e: + logging.error("error connecting to the pwngrid-peer service: %s" % e) + logging.debug(e, exc_info=True) + return - logging.debug("internet available") + try: + self.check_inbox(agent) + except Exception as e: + logging.error("[grid] error while checking inbox: %s" % e) + logging.debug(e, exc_info=True) - try: - grid.update_data(agent.last_session) - except Exception as e: - logging.error("error connecting to the pwngrid-peer service: %s" % e) - logging.debug(e, exc_info=True) - return - - try: - check_inbox(agent) - except Exception as e: - logging.error("[grid] error while checking inbox: %s" % e) - logging.debug(e, exc_info=True) - - try: - check_handshakes(agent) - except Exception as e: - logging.error("[grid] error while checking pcaps: %s" % e) - logging.debug(e, exc_info=True) + try: + self.check_handshakes(agent) + except Exception as e: + logging.error("[grid] error while checking pcaps: %s" % e) + logging.debug(e, exc_info=True) diff --git a/pwnagotchi/plugins/default/memtemp.py b/pwnagotchi/plugins/default/memtemp.py index 3d6682b6..521aa655 100644 --- a/pwnagotchi/plugins/default/memtemp.py +++ b/pwnagotchi/plugins/default/memtemp.py @@ -17,48 +17,44 @@ # - Added horizontal and vertical orientation # ############################################################### - -__author__ = 'https://github.com/xenDE' -__version__ = '1.0.1' -__name__ = 'memtemp' -__license__ = 'GPL3' -__description__ = 'A plugin that will display memory/cpu usage and temperature' - from pwnagotchi.ui.components import LabeledValue from pwnagotchi.ui.view import BLACK import pwnagotchi.ui.fonts as fonts +import pwnagotchi.plugins as plugins import pwnagotchi import logging -OPTIONS = dict() +class MemTemp(plugins.Plugin): + __author__ = 'https://github.com/xenDE' + __version__ = '1.0.1' + __license__ = 'GPL3' + __description__ = 'A plugin that will display memory/cpu usage and temperature' -def on_loaded(): - logging.info("memtemp plugin loaded.") + def on_loaded(self): + logging.info("memtemp plugin loaded.") + def mem_usage(self): + return int(pwnagotchi.mem_usage() * 100) -def mem_usage(): - return int(pwnagotchi.mem_usage() * 100) + def cpu_load(self): + return int(pwnagotchi.cpu_load() * 100) + def on_ui_setup(self, ui): + if self.options['orientation'] == "horizontal": + ui.add_element('memtemp', LabeledValue(color=BLACK, label='', value='mem cpu temp\n - - -', + position=(ui.width() / 2 + 30, ui.height() / 2 + 15), + label_font=fonts.Small, text_font=fonts.Small)) + elif self.options['orientation'] == "vertical": + ui.add_element('memtemp', LabeledValue(color=BLACK, label='', value=' mem:-\n cpu:-\ntemp:-', + position=(ui.width() / 2 + 55, ui.height() / 2), + label_font=fonts.Small, text_font=fonts.Small)) -def cpu_load(): - return int(pwnagotchi.cpu_load() * 100) + def on_ui_update(self, ui): + if self.options['orientation'] == "horizontal": + ui.set('memtemp', + " mem cpu temp\n %s%% %s%% %sc" % (self.mem_usage(), self.cpu_load(), pwnagotchi.temperature())) - -def on_ui_setup(ui): - if OPTIONS['orientation'] == "horizontal": - ui.add_element('memtemp', LabeledValue(color=BLACK, label='', value='mem cpu temp\n - - -', - position=(ui.width() / 2 + 30, ui.height() / 2 + 15), - label_font=fonts.Small, text_font=fonts.Small)) - elif OPTIONS['orientation'] == "vertical": - ui.add_element('memtemp', LabeledValue(color=BLACK, label='', value=' mem:-\n cpu:-\ntemp:-', - position=(ui.width() / 2 + 55, ui.height() / 2), - label_font=fonts.Small, text_font=fonts.Small)) - - -def on_ui_update(ui): - if OPTIONS['orientation'] == "horizontal": - ui.set('memtemp', " mem cpu temp\n %s%% %s%% %sc" % (mem_usage(), cpu_load(), pwnagotchi.temperature())) - - elif OPTIONS['orientation'] == "vertical": - ui.set('memtemp', " mem:%s%%\n cpu:%s%%\ntemp:%sc" % (mem_usage(), cpu_load(), pwnagotchi.temperature())) + elif self.options['orientation'] == "vertical": + ui.set('memtemp', + " mem:%s%%\n cpu:%s%%\ntemp:%sc" % (self.mem_usage(), self.cpu_load(), pwnagotchi.temperature())) diff --git a/pwnagotchi/plugins/default/net-pos.py b/pwnagotchi/plugins/default/net-pos.py index d0c54ae0..3f1f4e5a 100644 --- a/pwnagotchi/plugins/default/net-pos.py +++ b/pwnagotchi/plugins/default/net-pos.py @@ -1,140 +1,134 @@ -__author__ = 'zenzen san' -__version__ = '2.0.0' -__name__ = 'net-pos' -__license__ = 'GPL3' -__description__ = """Saves a json file with the access points with more signal - whenever a handshake is captured. - When internet is available the files are converted in geo locations - using Mozilla LocationService """ - import logging import json import os import requests +import pwnagotchi.plugins as plugins from pwnagotchi.utils import StatusFile MOZILLA_API_URL = 'https://location.services.mozilla.com/v1/geolocate?key={api}' -REPORT = StatusFile('/root/.net_pos_saved', data_format='json') -SKIP = list() -READY = False -OPTIONS = dict() -def on_loaded(): - global READY +class NetPos(plugins.Plugin): + __author__ = 'zenzen san' + __version__ = '2.0.0' + __license__ = 'GPL3' + __description__ = """Saves a json file with the access points with more signal + whenever a handshake is captured. + When internet is available the files are converted in geo locations + using Mozilla LocationService """ - if 'api_key' not in OPTIONS or ('api_key' in OPTIONS and OPTIONS['api_key'] is None): - logging.error("NET-POS: api_key isn't set. Can't use mozilla's api.") - return + def __init__(self): + self.report = StatusFile('/root/.net_pos_saved', data_format='json') + self.skip = list() + self.ready = False - READY = True + def on_loaded(self): + if 'api_key' not in self.options or ('api_key' in self.options and self.options['api_key'] is None): + logging.error("NET-POS: api_key isn't set. Can't use mozilla's api.") + return - logging.info("net-pos plugin loaded.") + self.ready = True + logging.info("net-pos plugin loaded.") -def _append_saved(path): - to_save = list() - if isinstance(path, str): - to_save.append(path) - elif isinstance(path, list): - to_save += path - else: - raise TypeError("Expected list or str, got %s" % type(path)) + def _append_saved(self, path): + to_save = list() + if isinstance(path, str): + to_save.append(path) + elif isinstance(path, list): + to_save += path + else: + raise TypeError("Expected list or str, got %s" % type(path)) - with open('/root/.net_pos_saved', 'a') as saved_file: - for x in to_save: - saved_file.write(x + "\n") + with open('/root/.net_pos_saved', 'a') as saved_file: + for x in to_save: + saved_file.write(x + "\n") -def on_internet_available(agent): - global SKIP - global REPORT + def on_internet_available(self, agent): + if self.ready: + config = agent.config() + display = agent.view() + reported = self.report.data_field_or('reported', default=list()) + handshake_dir = config['bettercap']['handshakes'] - if READY: - config = agent.config() - display = agent.view() - reported = REPORT.data_field_or('reported', default=list()) - handshake_dir = config['bettercap']['handshakes'] + all_files = os.listdir(handshake_dir) + all_np_files = [os.path.join(handshake_dir, filename) + for filename in all_files + if filename.endswith('.net-pos.json')] + new_np_files = set(all_np_files) - set(reported) - set(self.skip) - all_files = os.listdir(handshake_dir) - all_np_files = [os.path.join(handshake_dir, filename) - for filename in all_files - if filename.endswith('.net-pos.json')] - new_np_files = set(all_np_files) - set(reported) - set(SKIP) - - if new_np_files: - logging.info("NET-POS: Found %d new net-pos files. Fetching positions ...", len(new_np_files)) - display.set('status', f"Found {len(new_np_files)} new net-pos files. Fetching positions ...") - display.update(force=True) - for idx, np_file in enumerate(new_np_files): - - geo_file = np_file.replace('.net-pos.json', '.geo.json') - if os.path.exists(geo_file): - # got already the position - reported.append(np_file) - REPORT.update(data={'reported': reported}) - continue - - try: - geo_data = _get_geo_data(np_file) # returns json obj - except requests.exceptions.RequestException as req_e: - logging.error("NET-POS: %s", req_e) - SKIP += np_file - continue - except json.JSONDecodeError as js_e: - logging.error("NET-POS: %s", js_e) - SKIP += np_file - continue - except OSError as os_e: - logging.error("NET-POS: %s", os_e) - SKIP += np_file - continue - - with open(geo_file, 'w+t') as sf: - json.dump(geo_data, sf) - - reported.append(np_file) - REPORT.update(data={'reported': reported}) - - display.set('status', f"Fetching positions ({idx+1}/{len(new_np_files)})") + if new_np_files: + logging.info("NET-POS: Found %d new net-pos files. Fetching positions ...", len(new_np_files)) + display.set('status', f"Found {len(new_np_files)} new net-pos files. Fetching positions ...") display.update(force=True) + for idx, np_file in enumerate(new_np_files): + geo_file = np_file.replace('.net-pos.json', '.geo.json') + if os.path.exists(geo_file): + # got already the position + reported.append(np_file) + self.report.update(data={'reported': reported}) + continue -def on_handshake(agent, filename, access_point, client_station): - netpos = _get_netpos(agent) - netpos_filename = filename.replace('.pcap', '.net-pos.json') - logging.info("NET-POS: Saving net-location to %s", netpos_filename) + try: + geo_data = self._get_geo_data(np_file) # returns json obj + except requests.exceptions.RequestException as req_e: + logging.error("NET-POS: %s", req_e) + self.skip += np_file + continue + except json.JSONDecodeError as js_e: + logging.error("NET-POS: %s", js_e) + self.skip += np_file + continue + except OSError as os_e: + logging.error("NET-POS: %s", os_e) + self.skip += np_file + continue - try: - with open(netpos_filename, 'w+t') as net_pos_file: - json.dump(netpos, net_pos_file) - except OSError as os_e: - logging.error("NET-POS: %s", os_e) + with open(geo_file, 'w+t') as sf: + json.dump(geo_data, sf) + reported.append(np_file) + self.report.update(data={'reported': reported}) -def _get_netpos(agent): - aps = agent.get_access_points() - netpos = dict() - netpos['wifiAccessPoints'] = list() - # 6 seems a good number to save a wifi networks location - for access_point in sorted(aps, key=lambda i: i['rssi'], reverse=True)[:6]: - netpos['wifiAccessPoints'].append({'macAddress': access_point['mac'], - 'signalStrength': access_point['rssi']}) - return netpos + display.set('status', f"Fetching positions ({idx + 1}/{len(new_np_files)})") + display.update(force=True) -def _get_geo_data(path, timeout=30): - geourl = MOZILLA_API_URL.format(api=OPTIONS['api_key']) + def on_handshake(self, agent, filename, access_point, client_station): + netpos = self._get_netpos(agent) + netpos_filename = filename.replace('.pcap', '.net-pos.json') + logging.info("NET-POS: Saving net-location to %s", netpos_filename) - try: - with open(path, "r") as json_file: - data = json.load(json_file) - except json.JSONDecodeError as js_e: - raise js_e - except OSError as os_e: - raise os_e + try: + with open(netpos_filename, 'w+t') as net_pos_file: + json.dump(netpos, net_pos_file) + except OSError as os_e: + logging.error("NET-POS: %s", os_e) - try: - result = requests.post(geourl, - json=data, - timeout=timeout) - return result.json() - except requests.exceptions.RequestException as req_e: - raise req_e + def _get_netpos(self, agent): + aps = agent.get_access_points() + netpos = dict() + netpos['wifiAccessPoints'] = list() + # 6 seems a good number to save a wifi networks location + for access_point in sorted(aps, key=lambda i: i['rssi'], reverse=True)[:6]: + netpos['wifiAccessPoints'].append({'macAddress': access_point['mac'], + 'signalStrength': access_point['rssi']}) + return netpos + + def _get_geo_data(self, path, timeout=30): + geourl = MOZILLA_API_URL.format(api=self.options['api_key']) + + try: + with open(path, "r") as json_file: + data = json.load(json_file) + except json.JSONDecodeError as js_e: + raise js_e + except OSError as os_e: + raise os_e + + try: + result = requests.post(geourl, + json=data, + timeout=timeout) + return result.json() + except requests.exceptions.RequestException as req_e: + raise req_e diff --git a/pwnagotchi/plugins/default/onlinehashcrack.py b/pwnagotchi/plugins/default/onlinehashcrack.py index 47f881a0..7a02a300 100644 --- a/pwnagotchi/plugins/default/onlinehashcrack.py +++ b/pwnagotchi/plugins/default/onlinehashcrack.py @@ -1,86 +1,82 @@ -__author__ = '33197631+dadav@users.noreply.github.com' -__version__ = '2.0.0' -__name__ = 'onlinehashcrack' -__license__ = 'GPL3' -__description__ = 'This plugin automatically uploads handshakes to https://onlinehashcrack.com' - import os import logging import requests from pwnagotchi.utils import StatusFile - -READY = False -REPORT = StatusFile('/root/.ohc_uploads', data_format='json') -SKIP = list() -OPTIONS = dict() +import pwnagotchi.plugins as plugins -def on_loaded(): - """ - Gets called when the plugin gets loaded - """ - global READY +class OnlineHashCrack(plugins.Plugin): + __author__ = '33197631+dadav@users.noreply.github.com' + __version__ = '2.0.0' + __license__ = 'GPL3' + __description__ = 'This plugin automatically uploads handshakes to https://onlinehashcrack.com' - if 'email' not in OPTIONS or ('email' in OPTIONS and OPTIONS['email'] is None): - logging.error("OHC: Email isn't set. Can't upload to onlinehashcrack.com") - return + def __init__(self): + self.ready = False + self.report = StatusFile('/root/.ohc_uploads', data_format='json') + self.skip = list() - READY = True + def on_loaded(self): + """ + Gets called when the plugin gets loaded + """ + if 'email' not in self.options or ('email' in self.options and self.options['email'] is None): + logging.error("OHC: Email isn't set. Can't upload to onlinehashcrack.com") + return + self.ready = True -def _upload_to_ohc(path, timeout=30): - """ - Uploads the file to onlinehashcrack.com - """ - with open(path, 'rb') as file_to_upload: - data = {'email': OPTIONS['email']} - payload = {'file': file_to_upload} + def _upload_to_ohc(self, path, timeout=30): + """ + Uploads the file to onlinehashcrack.com + """ + with open(path, 'rb') as file_to_upload: + data = {'email': self.options['email']} + payload = {'file': file_to_upload} - try: - result = requests.post('https://api.onlinehashcrack.com', - data=data, - files=payload, - timeout=timeout) - if 'already been sent' in result.text: - logging.warning(f"{path} was already uploaded.") - except requests.exceptions.RequestException as e: - logging.error(f"OHC: Got an exception while uploading {path} -> {e}") - raise e + try: + result = requests.post('https://api.onlinehashcrack.com', + data=data, + files=payload, + timeout=timeout) + if 'already been sent' in result.text: + logging.warning(f"{path} was already uploaded.") + except requests.exceptions.RequestException as e: + logging.error(f"OHC: Got an exception while uploading {path} -> {e}") + raise e + def on_internet_available(self, agent): + """ + Called in manual mode when there's internet connectivity + """ + if self.ready: + display = agent.view() + config = agent.config() + reported = self.report.data_field_or('reported', default=list()) -def on_internet_available(agent): - """ - Called in manual mode when there's internet connectivity - """ - global REPORT - global SKIP - if READY: - display = agent.view() - config = agent.config() - reported = REPORT.data_field_or('reported', default=list()) + handshake_dir = config['bettercap']['handshakes'] + handshake_filenames = os.listdir(handshake_dir) + handshake_paths = [os.path.join(handshake_dir, filename) for filename in handshake_filenames if + filename.endswith('.pcap')] + handshake_new = set(handshake_paths) - set(reported) - set(self.skip) - handshake_dir = config['bettercap']['handshakes'] - handshake_filenames = os.listdir(handshake_dir) - handshake_paths = [os.path.join(handshake_dir, filename) for filename in handshake_filenames if filename.endswith('.pcap')] - handshake_new = set(handshake_paths) - set(reported) - set(SKIP) - - if handshake_new: - logging.info("OHC: Internet connectivity detected. Uploading new handshakes to onelinehashcrack.com") - - for idx, handshake in enumerate(handshake_new): - display.set('status', f"Uploading handshake to onlinehashcrack.com ({idx + 1}/{len(handshake_new)})") - display.update(force=True) - try: - _upload_to_ohc(handshake) - reported.append(handshake) - REPORT.update(data={'reported': reported}) - logging.info(f"OHC: Successfully uploaded {handshake}") - except requests.exceptions.RequestException as req_e: - SKIP.append(handshake) - logging.error("OHC: %s", req_e) - continue - except OSError as os_e: - SKIP.append(handshake) - logging.error("OHC: %s", os_e) - continue + if handshake_new: + logging.info("OHC: Internet connectivity detected. Uploading new handshakes to onelinehashcrack.com") + for idx, handshake in enumerate(handshake_new): + display.set('status', + f"Uploading handshake to onlinehashcrack.com ({idx + 1}/{len(handshake_new)})") + display.update(force=True) + try: + self._upload_to_ohc(handshake) + reported.append(handshake) + self.report.update(data={'reported': reported}) + logging.info(f"OHC: Successfully uploaded {handshake}") + except requests.exceptions.RequestException as req_e: + self.skip.append(handshake) + logging.error("OHC: %s", req_e) + continue + except OSError as os_e: + self.skip.append(handshake) + logging.error("OHC: %s", os_e) + continue diff --git a/pwnagotchi/plugins/default/paw-gps.py b/pwnagotchi/plugins/default/paw-gps.py index a5268c42..a6810334 100644 --- a/pwnagotchi/plugins/default/paw-gps.py +++ b/pwnagotchi/plugins/default/paw-gps.py @@ -1,27 +1,27 @@ -__author__ = 'leont' -__version__ = '1.0.0' -__name__ = 'pawgps' -__license__ = 'GPL3' -__description__ = 'Saves GPS coordinates whenever an handshake is captured. The GPS data is get from PAW on android ' +import logging +import requests +import pwnagotchi.plugins as plugins ''' You need an bluetooth connection to your android phone which is running PAW server with the GPS "hack" from Systemic: https://raw.githubusercontent.com/systemik/pwnagotchi-bt-tether/master/GPS-via-PAW ''' -import logging -import requests -OPTIONS = dict() +class PawGPS(plugins.Plugin): + __author__ = 'leont' + __version__ = '1.0.0' + __name__ = 'pawgps' + __license__ = 'GPL3' + __description__ = 'Saves GPS coordinates whenever an handshake is captured. The GPS data is get from PAW on android ' + def on_loaded(self): + logging.info("PAW-GPS loaded") + if 'ip' not in self.options or ('ip' in self.options and self.options['ip'] is None): + logging.info("PAW-GPS: No IP Address in the config file is defined, it uses the default (192.168.44.1)") -def on_loaded(): - logging.info("PAW-GPS loaded") - if 'ip' not in OPTIONS or ('ip' in OPTIONS and OPTIONS['ip'] is None): - logging.info("PAW-GPS: No IP Address in the config file is defined, it uses the default (192.168.44.1)") - -def on_handshake(agent, filename, access_point, client_station): - if 'ip' not in OPTIONS or ('ip' in OPTIONS and OPTIONS['ip'] is None): + def on_handshake(self, agent, filename, access_point, client_station): + if 'ip' not in self.options or ('ip' in self.options and self.options['ip'] is None): ip = "192.168.44.1" gps = requests.get('http://' + ip + '/gps.xhtml') diff --git a/pwnagotchi/plugins/default/quickdic.py b/pwnagotchi/plugins/default/quickdic.py index b898b21a..7bdd6470 100644 --- a/pwnagotchi/plugins/default/quickdic.py +++ b/pwnagotchi/plugins/default/quickdic.py @@ -1,8 +1,8 @@ -__author__ = 'pwnagotchi [at] rossmarks [dot] uk' -__version__ = '1.0.0' -__name__ = 'quickdic' -__license__ = 'GPL3' -__description__ = 'Run a quick dictionary scan against captured handshakes' +import logging +import subprocess +import string +import re +import pwnagotchi.plugins as plugins ''' Aircrack-ng needed, to install: @@ -11,42 +11,41 @@ Upload wordlist files in .txt format to folder in config file (Default: /opt/wor Cracked handshakes stored in handshake folder as [essid].pcap.cracked ''' -import logging -import subprocess -import string -import re -OPTIONS = dict() +class QuickDic(plugins.Plugin): + __author__ = 'pwnagotchi [at] rossmarks [dot] uk' + __version__ = '1.0.0' + __license__ = 'GPL3' + __description__ = 'Run a quick dictionary scan against captured handshakes' -def on_loaded(): - logging.info("Quick dictionary check plugin loaded") + def __init__(self): + self.text_to_set = "" -def on_handshake(agent, filename, access_point, client_station): - display = agent._view + def on_loaded(self): + logging.info("Quick dictionary check plugin loaded") - result = subprocess.run(('/usr/bin/aircrack-ng '+ filename +' | grep "1 handshake" | awk \'{print $2}\''),shell=True, stdout=subprocess.PIPE) - result = result.stdout.decode('utf-8').translate({ord(c) :None for c in string.whitespace}) - if not result: - logging.info("[quickdic] No handshake") - else: - logging.info("[quickdic] Handshake confirmed") - result2 = subprocess.run(('aircrack-ng -w `echo '+OPTIONS['wordlist_folder']+'*.txt | sed \'s/\ /,/g\'` -l '+filename+'.cracked -q -b '+result+' '+filename+' | grep KEY'),shell=True,stdout=subprocess.PIPE) - result2 = result2.stdout.decode('utf-8').strip() - logging.info("[quickdic] "+result2) - if result2 != "KEY NOT FOUND": - key = re.search('\[(.*)\]', result2) - pwd = str(key.group(1)) - set_text("Cracked password: "+pwd) - display.update(force=True) + def on_handshake(self, agent, filename, access_point, client_station): + display = agent.view() + result = subprocess.run(('/usr/bin/aircrack-ng ' + filename + ' | grep "1 handshake" | awk \'{print $2}\''), + shell=True, stdout=subprocess.PIPE) + result = result.stdout.decode('utf-8').translate({ord(c): None for c in string.whitespace}) + if not result: + logging.info("[quickdic] No handshake") + else: + logging.info("[quickdic] Handshake confirmed") + result2 = subprocess.run(('aircrack-ng -w `echo ' + self.options[ + 'wordlist_folder'] + '*.txt | sed \'s/\ /,/g\'` -l ' + filename + '.cracked -q -b ' + result + ' ' + filename + ' | grep KEY'), + shell=True, stdout=subprocess.PIPE) + result2 = result2.stdout.decode('utf-8').strip() + logging.info("[quickdic] " + result2) + if result2 != "KEY NOT FOUND": + key = re.search('\[(.*)\]', result2) + pwd = str(key.group(1)) + self.text_to_set = "Cracked password: " + pwd + display.update(force=True) -text_to_set = ""; -def set_text(text): - global text_to_set - text_to_set = text - -def on_ui_update(ui): - global text_to_set - if text_to_set: - ui.set('face', "(·ω·)") - ui.set('status', text_to_set) - text_to_set = "" + def on_ui_update(self, ui): + if self.text_to_set: + ui.set('face', "(·ω·)") + ui.set('status', self.text_to_set) + self.text_to_set = "" diff --git a/pwnagotchi/plugins/default/screen_refresh.py b/pwnagotchi/plugins/default/screen_refresh.py index 3c7047fa..25ea3252 100644 --- a/pwnagotchi/plugins/default/screen_refresh.py +++ b/pwnagotchi/plugins/default/screen_refresh.py @@ -1,24 +1,23 @@ -__author__ = 'pwnagotchi [at] rossmarks [dot] uk' -__version__ = '1.0.0' -__name__ = 'screen_refresh' -__license__ = 'GPL3' -__description__ = 'Refresh he e-ink display after X amount of updates' - import logging - -OPTIONS = dict() -update_count = 0; +import pwnagotchi.plugins as plugins -def on_loaded(): - logging.info("Screen refresh plugin loaded") +class ScreenRefresh(plugins.Plugin): + __author__ = 'pwnagotchi [at] rossmarks [dot] uk' + __version__ = '1.0.0' + __license__ = 'GPL3' + __description__ = 'Refresh he e-ink display after X amount of updates' + def __init__(self): + self.update_count = 0; -def on_ui_update(ui): - global update_count - update_count += 1 - if update_count == OPTIONS['refresh_interval']: - ui.init_display() - ui.set('status', "Screen cleaned") - logging.info("Screen refreshing") - update_count = 0 + def on_loaded(self): + logging.info("Screen refresh plugin loaded") + + def on_ui_update(self, ui): + self.update_count += 1 + if self.update_count == self.options['refresh_interval']: + ui.init_display() + ui.set('status', "Screen cleaned") + logging.info("Screen refreshing") + self.update_count = 0 diff --git a/pwnagotchi/plugins/default/twitter.py b/pwnagotchi/plugins/default/twitter.py index fd247ca4..80bb2bac 100644 --- a/pwnagotchi/plugins/default/twitter.py +++ b/pwnagotchi/plugins/default/twitter.py @@ -1,50 +1,49 @@ -__author__ = '33197631+dadav@users.noreply.github.com' -__version__ = '1.0.0' -__name__ = 'twitter' -__license__ = 'GPL3' -__description__ = 'This plugin creates tweets about the recent activity of pwnagotchi' - import logging from pwnagotchi.voice import Voice - -OPTIONS = dict() - -def on_loaded(): - logging.info("twitter plugin loaded.") +import pwnagotchi.plugins as plugins -# called in manual mode when there's internet connectivity -def on_internet_available(agent): - config = agent.config() - display = agent.view() - last_session = agent.last_session +class Twitter(plugins.Plugin): + __author__ = 'evilsocket@gmail.com' + __version__ = '1.0.0' + __license__ = 'GPL3' + __description__ = 'This plugin creates tweets about the recent activity of pwnagotchi' - if last_session.is_new() and last_session.handshakes > 0: - try: - import tweepy - except ImportError: - logging.error("Couldn't import tweepy") - return + def on_loaded(self): + logging.info("twitter plugin loaded.") - logging.info("detected a new session and internet connectivity!") + # called in manual mode when there's internet connectivity + def on_internet_available(self, agent): + config = agent.config() + display = agent.view() + last_session = agent.last_session - picture = '/dev/shm/pwnagotchi.png' + if last_session.is_new() and last_session.handshakes > 0: + try: + import tweepy + except ImportError: + logging.error("Couldn't import tweepy") + return - display.on_manual_mode(last_session) - display.update(force=True) - display.image().save(picture, 'png') - display.set('status', 'Tweeting...') - display.update(force=True) + logging.info("detected a new session and internet connectivity!") - try: - auth = tweepy.OAuthHandler(OPTIONS['consumer_key'], OPTIONS['consumer_secret']) - auth.set_access_token(OPTIONS['access_token_key'], OPTIONS['access_token_secret']) - api = tweepy.API(auth) + picture = '/root/pwnagotchi.png' - tweet = Voice(lang=config['main']['lang']).on_last_session_tweet(last_session) - api.update_with_media(filename=picture, status=tweet) - last_session.save_session_id() + display.on_manual_mode(last_session) + display.update(force=True) + display.image().save(picture, 'png') + display.set('status', 'Tweeting...') + display.update(force=True) - logging.info("tweeted: %s" % tweet) - except Exception as e: - logging.exception("error while tweeting") + try: + auth = tweepy.OAuthHandler(self.options['consumer_key'], self.options['consumer_secret']) + auth.set_access_token(self.options['access_token_key'], self.options['access_token_secret']) + api = tweepy.API(auth) + + tweet = Voice(lang=config['main']['lang']).on_last_session_tweet(last_session) + api.update_with_media(filename=picture, status=tweet) + last_session.save_session_id() + + logging.info("tweeted: %s" % tweet) + except Exception as e: + logging.exception("error while tweeting") diff --git a/pwnagotchi/plugins/default/unfiltered_example.py b/pwnagotchi/plugins/default/unfiltered_example.py deleted file mode 100644 index 9fe1e496..00000000 --- a/pwnagotchi/plugins/default/unfiltered_example.py +++ /dev/null @@ -1,22 +0,0 @@ -__author__ = 'diemelcw@gmail.com' -__version__ = '1.0.0' -__name__ = 'unfiltered_example' -__license__ = 'GPL3' -__description__ = 'An example plugin for pwnagotchi that implements on_unfiltered_ap_list(agent,aps)' - -import logging - -# Will be set with the options in config.yml config['main']['plugins'][__name__] -OPTIONS = dict() - -# called when the plugin is loaded -def on_loaded(): - logging.warning("%s plugin loaded" % __name__) - -# called when AP list is ready, before whitelist filtering has occurred -def on_unfiltered_ap_list(agent,aps): - logging.info("Unfiltered AP list to follow") - for ap in aps: - logging.info(ap['hostname']) - - ## Additional logic here ## diff --git a/pwnagotchi/plugins/default/ups_lite.py b/pwnagotchi/plugins/default/ups_lite.py index 5c0d9d7b..74e0b627 100644 --- a/pwnagotchi/plugins/default/ups_lite.py +++ b/pwnagotchi/plugins/default/ups_lite.py @@ -7,17 +7,12 @@ # For Raspberry Pi Zero Ups Power Expansion Board with Integrated Serial Port S3U4 # https://www.ebay.de/itm/For-Raspberry-Pi-Zero-Ups-Power-Expansion-Board-with-Integrated-Serial-Port-S3U4/323873804310 # https://www.aliexpress.com/item/32888533624.html -__author__ = 'evilsocket@gmail.com' -__version__ = '1.0.0' -__name__ = 'ups_lite' -__license__ = 'GPL3' -__description__ = 'A plugin that will add a voltage indicator for the UPS Lite v1.1' - import struct from pwnagotchi.ui.components import LabeledValue from pwnagotchi.ui.view import BLACK import pwnagotchi.ui.fonts as fonts +import pwnagotchi.plugins as plugins # TODO: add enable switch in config.yml an cleanup all to the best place @@ -47,18 +42,21 @@ class UPS: return 0.0 -ups = None +class UPSLite(plugins.Plugin): + __author__ = 'evilsocket@gmail.com' + __version__ = '1.0.0' + __license__ = 'GPL3' + __description__ = 'A plugin that will add a voltage indicator for the UPS Lite v1.1' + def __init__(self): + self.ups = None -def on_loaded(): - global ups - ups = UPS() + def on_loaded(self): + self.ups = UPS() + def on_ui_setup(self, ui): + ui.add_element('ups', LabeledValue(color=BLACK, label='UPS', value='0%/0V', position=(ui.width() / 2 - 25, 0), + label_font=fonts.Bold, text_font=fonts.Medium)) -def on_ui_setup(ui): - ui.add_element('ups', LabeledValue(color=BLACK, label='UPS', value='0%/0V', position=(ui.width() / 2 - 25, 0), - label_font=fonts.Bold, text_font=fonts.Medium)) - - -def on_ui_update(ui): - ui.set('ups', "%4.2fV/%2i%%" % (ups.voltage(), ups.capacity())) + def on_ui_update(self, ui): + ui.set('ups', "%4.2fV/%2i%%" % (self.ups.voltage(), self.ups.capacity())) diff --git a/pwnagotchi/plugins/default/wigle.py b/pwnagotchi/plugins/default/wigle.py index 5437132b..d0d748f5 100644 --- a/pwnagotchi/plugins/default/wigle.py +++ b/pwnagotchi/plugins/default/wigle.py @@ -1,9 +1,3 @@ -__author__ = '33197631+dadav@users.noreply.github.com' -__version__ = '2.0.0' -__name__ = 'wigle' -__license__ = 'GPL3' -__description__ = 'This plugin automatically uploads collected wifis to wigle.net' - import os import logging import json @@ -12,24 +6,7 @@ import csv from datetime import datetime import requests from pwnagotchi.utils import WifiInfo, FieldNotFoundError, extract_from_pcap, StatusFile - -READY = False -REPORT = StatusFile('/root/.wigle_uploads', data_format='json') -SKIP = list() -OPTIONS = dict() - - -def on_loaded(): - """ - Gets called when the plugin gets loaded - """ - global READY - - if 'api_key' not in OPTIONS or ('api_key' in OPTIONS and OPTIONS['api_key'] is None): - logging.error("WIGLE: api_key isn't set. Can't upload to wigle.net") - return - - READY = True +import pwnagotchi.plugins as plugins def _extract_gps_data(path): @@ -54,14 +31,17 @@ def _format_auth(data): out = f"{out}[{auth}]" return out + def _transform_wigle_entry(gps_data, pcap_data): """ Transform to wigle entry in file """ dummy = StringIO() # write kismet header - dummy.write("WigleWifi-1.4,appRelease=20190201,model=Kismet,release=2019.02.01.{},device=kismet,display=kismet,board=kismet,brand=kismet\n") - dummy.write("MAC,SSID,AuthMode,FirstSeen,Channel,RSSI,CurrentLatitude,CurrentLongitude,AltitudeMeters,AccuracyMeters,Type") + dummy.write( + "WigleWifi-1.4,appRelease=20190201,model=Kismet,release=2019.02.01.{},device=kismet,display=kismet,board=kismet,brand=kismet\n") + dummy.write( + "MAC,SSID,AuthMode,FirstSeen,Channel,RSSI,CurrentLatitude,CurrentLongitude,AltitudeMeters,AccuracyMeters,Type") writer = csv.writer(dummy, delimiter=",", quoting=csv.QUOTE_NONE, escapechar="\\") writer.writerow([ @@ -75,10 +55,11 @@ def _transform_wigle_entry(gps_data, pcap_data): gps_data['Latitude'], gps_data['Longitude'], gps_data['Altitude'], - 0, # accuracy? + 0, # accuracy? 'WIFI']) return dummy.getvalue() + def _send_to_wigle(lines, api_key, timeout=30): """ Uploads the file to wigle-net @@ -109,87 +90,100 @@ def _send_to_wigle(lines, api_key, timeout=30): raise re_e -def on_internet_available(agent): - from scapy.all import Scapy_Exception - """ - Called in manual mode when there's internet connectivity - """ - global REPORT - global SKIP +class Wigle(plugins.Plugin): + __author__ = '33197631+dadav@users.noreply.github.com' + __version__ = '2.0.0' + __license__ = 'GPL3' + __description__ = 'This plugin automatically uploads collected wifis to wigle.net' - if READY: - config = agent.config() - display = agent.view() - reported = REPORT.data_field_or('reported', default=list()) + def __init__(self): + self.ready = False + self.report = StatusFile('/root/.wigle_uploads', data_format='json') + self.skip = list() - handshake_dir = config['bettercap']['handshakes'] - all_files = os.listdir(handshake_dir) - all_gps_files = [os.path.join(handshake_dir, filename) - for filename in all_files - if filename.endswith('.gps.json')] - new_gps_files = set(all_gps_files) - set(reported) - set(SKIP) + def on_loaded(self): + if 'api_key' not in self.options or ('api_key' in self.options and self.options['api_key'] is None): + logging.error("WIGLE: api_key isn't set. Can't upload to wigle.net") + return + self.ready = True - if new_gps_files: - logging.info("WIGLE: Internet connectivity detected. Uploading new handshakes to wigle.net") + def on_internet_available(self, agent): + from scapy.all import Scapy_Exception + """ + Called in manual mode when there's internet connectivity + """ + if self.ready: + config = agent.config() + display = agent.view() + reported = self.report.data_field_or('reported', default=list()) - csv_entries = list() - no_err_entries = list() + handshake_dir = config['bettercap']['handshakes'] + all_files = os.listdir(handshake_dir) + all_gps_files = [os.path.join(handshake_dir, filename) + for filename in all_files + if filename.endswith('.gps.json')] + new_gps_files = set(all_gps_files) - set(reported) - set(self.skip) - for gps_file in new_gps_files: - pcap_filename = gps_file.replace('.gps.json', '.pcap') + if new_gps_files: + logging.info("WIGLE: Internet connectivity detected. Uploading new handshakes to wigle.net") - if not os.path.exists(pcap_filename): - logging.error("WIGLE: Can't find pcap for %s", gps_file) - SKIP.append(gps_file) - continue + csv_entries = list() + no_err_entries = list() - try: - gps_data = _extract_gps_data(gps_file) - except OSError as os_err: - logging.error("WIGLE: %s", os_err) - SKIP.append(gps_file) - continue - except json.JSONDecodeError as json_err: - logging.error("WIGLE: %s", json_err) - SKIP.append(gps_file) - continue + for gps_file in new_gps_files: + pcap_filename = gps_file.replace('.gps.json', '.pcap') - if gps_data['Latitude'] == 0 and gps_data['Longitude'] == 0: - logging.warning("WIGLE: Not enough gps-information for %s. Trying again next time.", gps_file) - SKIP.append(gps_file) - continue + if not os.path.exists(pcap_filename): + logging.error("WIGLE: Can't find pcap for %s", gps_file) + self.skip.append(gps_file) + continue + try: + gps_data = _extract_gps_data(gps_file) + except OSError as os_err: + logging.error("WIGLE: %s", os_err) + self.skip.append(gps_file) + continue + except json.JSONDecodeError as json_err: + logging.error("WIGLE: %s", json_err) + self.skip.append(gps_file) + continue - try: - pcap_data = extract_from_pcap(pcap_filename, [WifiInfo.BSSID, - WifiInfo.ESSID, - WifiInfo.ENCRYPTION, - WifiInfo.CHANNEL, - WifiInfo.RSSI]) - except FieldNotFoundError: - logging.error("WIGLE: Could not extract all information. Skip %s", gps_file) - SKIP.append(gps_file) - continue - except Scapy_Exception as sc_e: - logging.error("WIGLE: %s", sc_e) - SKIP.append(gps_file) - continue + if gps_data['Latitude'] == 0 and gps_data['Longitude'] == 0: + logging.warning("WIGLE: Not enough gps-information for %s. Trying again next time.", gps_file) + self.skip.append(gps_file) + continue - new_entry = _transform_wigle_entry(gps_data, pcap_data) - csv_entries.append(new_entry) - no_err_entries.append(gps_file) + try: + pcap_data = extract_from_pcap(pcap_filename, [WifiInfo.BSSID, + WifiInfo.ESSID, + WifiInfo.ENCRYPTION, + WifiInfo.CHANNEL, + WifiInfo.RSSI]) + except FieldNotFoundError: + logging.error("WIGLE: Could not extract all information. Skip %s", gps_file) + self.skip.append(gps_file) + continue + except Scapy_Exception as sc_e: + logging.error("WIGLE: %s", sc_e) + self.skip.append(gps_file) + continue - if csv_entries: - display.set('status', "Uploading gps-data to wigle.net ...") - display.update(force=True) - try: - _send_to_wigle(csv_entries, OPTIONS['api_key']) - reported += no_err_entries - REPORT.update(data={'reported': reported}) - logging.info("WIGLE: Successfully uploaded %d files", len(no_err_entries)) - except requests.exceptions.RequestException as re_e: - SKIP += no_err_entries - logging.error("WIGLE: Got an exception while uploading %s", re_e) - except OSError as os_e: - SKIP += no_err_entries - logging.error("WIGLE: Got the following error: %s", os_e) + new_entry = _transform_wigle_entry(gps_data, pcap_data) + csv_entries.append(new_entry) + no_err_entries.append(gps_file) + + if csv_entries: + display.set('status', "Uploading gps-data to wigle.net ...") + display.update(force=True) + try: + _send_to_wigle(csv_entries, self.options['api_key']) + reported += no_err_entries + self.report.update(data={'reported': reported}) + logging.info("WIGLE: Successfully uploaded %d files", len(no_err_entries)) + except requests.exceptions.RequestException as re_e: + self.skip += no_err_entries + logging.error("WIGLE: Got an exception while uploading %s", re_e) + except OSError as os_e: + self.skip += no_err_entries + logging.error("WIGLE: Got the following error: %s", os_e) diff --git a/pwnagotchi/plugins/default/wpa-sec.py b/pwnagotchi/plugins/default/wpa-sec.py index 5b13f315..ee99f105 100644 --- a/pwnagotchi/plugins/default/wpa-sec.py +++ b/pwnagotchi/plugins/default/wpa-sec.py @@ -1,87 +1,84 @@ -__author__ = '33197631+dadav@users.noreply.github.com' -__version__ = '2.0.1' -__name__ = 'wpa-sec' -__license__ = 'GPL3' -__description__ = 'This plugin automatically uploads handshakes to https://wpa-sec.stanev.org' - import os import logging import requests from pwnagotchi.utils import StatusFile - -READY = False -REPORT = StatusFile('/root/.wpa_sec_uploads', data_format='json') -OPTIONS = dict() -SKIP = list() +import pwnagotchi.plugins as plugins -def on_loaded(): - """ - Gets called when the plugin gets loaded - """ - global READY +class WpaSec(plugins.Plugin): + __author__ = '33197631+dadav@users.noreply.github.com' + __version__ = '2.0.1' + __license__ = 'GPL3' + __description__ = 'This plugin automatically uploads handshakes to https://wpa-sec.stanev.org' - if 'api_key' not in OPTIONS or ('api_key' in OPTIONS and OPTIONS['api_key'] is None): - logging.error("WPA_SEC: API-KEY isn't set. Can't upload to wpa-sec.stanev.org") - return + def __init__(self): + self.ready = False + self.report = StatusFile('/root/.wpa_sec_uploads', data_format='json') + self.options = dict() + self.skip = list() - if 'api_url' not in OPTIONS or ('api_url' in OPTIONS and OPTIONS['api_url'] is None): - logging.error("WPA_SEC: API-URL isn't set. Can't upload, no endpoint configured.") - return - - READY = True + def _upload_to_wpasec(self, path, timeout=30): + """ + Uploads the file to https://wpa-sec.stanev.org, or another endpoint. + """ + with open(path, 'rb') as file_to_upload: + cookie = {'key': self.options['api_key']} + payload = {'file': file_to_upload} + try: + result = requests.post(self.options['api_url'], + cookies=cookie, + files=payload, + timeout=timeout) + if ' already submitted' in result.text: + logging.warning("%s was already submitted.", path) + except requests.exceptions.RequestException as req_e: + raise req_e -def _upload_to_wpasec(path, timeout=30): - """ - Uploads the file to https://wpa-sec.stanev.org, or another endpoint. - """ - with open(path, 'rb') as file_to_upload: - cookie = {'key': OPTIONS['api_key']} - payload = {'file': file_to_upload} + def on_loaded(self): + """ + Gets called when the plugin gets loaded + """ + if 'api_key' not in self.options or ('api_key' in self.options and self.options['api_key'] is None): + logging.error("WPA_SEC: API-KEY isn't set. Can't upload to wpa-sec.stanev.org") + return - try: - result = requests.post(OPTIONS['api_url'], - cookies=cookie, - files=payload, - timeout=timeout) - if ' already submitted' in result.text: - logging.warning("%s was already submitted.", path) - except requests.exceptions.RequestException as req_e: - raise req_e + if 'api_url' not in self.options or ('api_url' in self.options and self.options['api_url'] is None): + logging.error("WPA_SEC: API-URL isn't set. Can't upload, no endpoint configured.") + return + self.ready = True -def on_internet_available(agent): - """ - Called in manual mode when there's internet connectivity - """ - global REPORT - global SKIP - if READY: - config = agent.config() - display = agent.view() - reported = REPORT.data_field_or('reported', default=list()) + def on_internet_available(self, agent): + """ + Called in manual mode when there's internet connectivity + """ + if self.ready: + config = agent.config() + display = agent.view() + reported = self.report.data_field_or('reported', default=list()) - handshake_dir = config['bettercap']['handshakes'] - handshake_filenames = os.listdir(handshake_dir) - handshake_paths = [os.path.join(handshake_dir, filename) for filename in handshake_filenames if filename.endswith('.pcap')] - handshake_new = set(handshake_paths) - set(reported) - set(SKIP) + handshake_dir = config['bettercap']['handshakes'] + handshake_filenames = os.listdir(handshake_dir) + handshake_paths = [os.path.join(handshake_dir, filename) for filename in handshake_filenames if + filename.endswith('.pcap')] + handshake_new = set(handshake_paths) - set(reported) - set(self.skip) - if handshake_new: - logging.info("WPA_SEC: Internet connectivity detected. Uploading new handshakes to wpa-sec.stanev.org") + if handshake_new: + logging.info("WPA_SEC: Internet connectivity detected. Uploading new handshakes to wpa-sec.stanev.org") - for idx, handshake in enumerate(handshake_new): - display.set('status', f"Uploading handshake to wpa-sec.stanev.org ({idx + 1}/{len(handshake_new)})") - display.update(force=True) - try: - _upload_to_wpasec(handshake) - reported.append(handshake) - REPORT.update(data={'reported': reported}) - logging.info("WPA_SEC: Successfully uploaded %s", handshake) - except requests.exceptions.RequestException as req_e: - SKIP.append(handshake) - logging.error("WPA_SEC: %s", req_e) - continue - except OSError as os_e: - logging.error("WPA_SEC: %s", os_e) - continue + for idx, handshake in enumerate(handshake_new): + display.set('status', f"Uploading handshake to wpa-sec.stanev.org ({idx + 1}/{len(handshake_new)})") + display.update(force=True) + try: + self._upload_to_wpasec(handshake) + reported.append(handshake) + self.report.update(data={'reported': reported}) + logging.info("WPA_SEC: Successfully uploaded %s", handshake) + except requests.exceptions.RequestException as req_e: + self.skip.append(handshake) + logging.error("WPA_SEC: %s", req_e) + continue + except OSError as os_e: + logging.error("WPA_SEC: %s", os_e) + continue From 66dc03ec05c6adfcaa980e5edd2343e6967cf5b0 Mon Sep 17 00:00:00 2001 From: dadav <33197631+dadav@users.noreply.github.com> Date: Fri, 1 Nov 2019 14:25:20 +0100 Subject: [PATCH 159/168] Fix debug msg to fit new plugin class --- bin/pwnagotchi | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bin/pwnagotchi b/bin/pwnagotchi index e1409245..73f5f996 100755 --- a/bin/pwnagotchi +++ b/bin/pwnagotchi @@ -56,7 +56,7 @@ if __name__ == '__main__': logging.debug("effective configuration:\n\n%s\n\n" % yaml.dump(config, default_flow_style=False)) for _, plugin in plugins.loaded.items(): - logging.debug("plugin '%s' v%s loaded from %s" % (plugin.__name__, plugin.__version__, plugin.__file__)) + logging.debug("plugin '%s' v%s" % (plugin.__class__.__name__, plugin.__version__)) if args.do_clear: logging.info("clearing the display ...") From 31a89cbe4b91459fc17ab7750c54d629d2cfd598 Mon Sep 17 00:00:00 2001 From: Simone Margaritelli Date: Fri, 1 Nov 2019 15:53:31 +0100 Subject: [PATCH 160/168] new: added new angry state (closes #486) --- pwnagotchi/automata.py | 27 ++++++++++++++++++++++----- pwnagotchi/defaults.yml | 1 + pwnagotchi/ui/faces.py | 1 + pwnagotchi/ui/view.py | 5 +++++ pwnagotchi/voice.py | 7 +++++++ 5 files changed, 36 insertions(+), 5 deletions(-) diff --git a/pwnagotchi/automata.py b/pwnagotchi/automata.py index ee703dc2..d4e008fe 100644 --- a/pwnagotchi/automata.py +++ b/pwnagotchi/automata.py @@ -75,6 +75,15 @@ class Automata(object): logging.info("unit is grateful instead of sad") self.set_grateful() + def set_angry(self, factor): + if not self._has_support_network_for(factor): + logging.warning("%d epochs with no activity -> angry" % self._epoch.inactive_for) + self._view.on_angry() + plugins.on('angry', self) + else: + logging.info("unit is grateful instead of angry") + self.set_grateful() + def set_excited(self): logging.warning("%d epochs with activity -> excited" % self._epoch.active_for) self._view.on_excited() @@ -103,13 +112,21 @@ class Automata(object): self._epoch.next() - # after X misses during an epoch, set the status to lonely + # after X misses during an epoch, set the status to lonely or angry if was_stale: - logging.warning("agent missed %d interactions -> lonely" % did_miss) - self.set_lonely() - # after X times being bored, the status is set to sad + factor = did_miss / self._config['personality']['max_misses_for_recon'] + if factor >= 2.0: + self.set_angry(factor) + else: + logging.warning("agent missed %d interactions -> lonely" % did_miss) + self.set_lonely() + # after X times being bored, the status is set to sad or angry elif self._epoch.inactive_for >= self._config['personality']['sad_num_epochs']: - self.set_sad() + factor = self._epoch.inactive_for / self._config['personality']['sad_num_epochs'] + if factor >= 2.0: + self.set_angry(factor) + else: + self.set_sad() # after X times being inactive, the status is set to bored elif self._epoch.inactive_for >= self._config['personality']['bored_num_epochs']: self.set_bored() diff --git a/pwnagotchi/defaults.yml b/pwnagotchi/defaults.yml index bb78d8ad..4624e682 100644 --- a/pwnagotchi/defaults.yml +++ b/pwnagotchi/defaults.yml @@ -199,6 +199,7 @@ ui: smart: '(✜‿‿✜)' lonely: '(ب__ب)' sad: '(╥☁╥ )' + angry: "(-_-')" friend: '(♥‿‿♥)' broken: '(☓‿‿☓)' debug: '(#__#)' diff --git a/pwnagotchi/ui/faces.py b/pwnagotchi/ui/faces.py index f94eb0b7..c47a5e9a 100644 --- a/pwnagotchi/ui/faces.py +++ b/pwnagotchi/ui/faces.py @@ -16,6 +16,7 @@ DEMOTIVATED = '(≖__≖)' SMART = '(✜‿‿✜)' LONELY = '(ب__ب)' SAD = '(╥☁╥ )' +ANGRY = "(-_-')" FRIEND = '(♥‿‿♥)' BROKEN = '(☓‿‿☓)' DEBUG = '(#__#)' diff --git a/pwnagotchi/ui/view.py b/pwnagotchi/ui/view.py index 3a4358e1..71cd46b1 100644 --- a/pwnagotchi/ui/view.py +++ b/pwnagotchi/ui/view.py @@ -284,6 +284,11 @@ class View(object): self.set('status', self._voice.on_sad()) self.update() + def on_angry(self): + self.set('face', faces.ANGRY) + self.set('status', self._voice.on_angry()) + self.update() + def on_motivated(self, reward): self.set('face', faces.MOTIVATED) self.set('status', self._voice.on_motivated(reward)) diff --git a/pwnagotchi/voice.py b/pwnagotchi/voice.py index 0d06b6c3..b1abd8a5 100644 --- a/pwnagotchi/voice.py +++ b/pwnagotchi/voice.py @@ -67,6 +67,13 @@ class Voice: self._('I\'m sad'), '...']) + def on_angry(self): + # passive aggressive or not? :D + return random.choice([ + '...', + self._('Leave me alone ...'), + self._('I\'m mad at you!')]) + def on_excited(self): return random.choice([ self._('I\'m living the life!'), From 22e76f956cbe5e532d00c78072fd46e5198ccae6 Mon Sep 17 00:00:00 2001 From: Simone Margaritelli Date: Fri, 1 Nov 2019 17:45:53 +0100 Subject: [PATCH 161/168] fix: fixed memtemp for waveshare v2 --- pwnagotchi/plugins/default/memtemp.py | 14 ++++++++++++-- 1 file changed, 12 insertions(+), 2 deletions(-) diff --git a/pwnagotchi/plugins/default/memtemp.py b/pwnagotchi/plugins/default/memtemp.py index 521aa655..289fbabb 100644 --- a/pwnagotchi/plugins/default/memtemp.py +++ b/pwnagotchi/plugins/default/memtemp.py @@ -41,13 +41,23 @@ class MemTemp(plugins.Plugin): return int(pwnagotchi.cpu_load() * 100) def on_ui_setup(self, ui): + # self._layout['width'] = 250 + # self._layout['height'] = 122 + + if ui.is_waveshare_v2(): + h_pos = (180, 76) + v_pos = (180, 61) + else: + h_pos = (155, 76) + v_pos = (180, 61) + if self.options['orientation'] == "horizontal": ui.add_element('memtemp', LabeledValue(color=BLACK, label='', value='mem cpu temp\n - - -', - position=(ui.width() / 2 + 30, ui.height() / 2 + 15), + position=h_pos, label_font=fonts.Small, text_font=fonts.Small)) elif self.options['orientation'] == "vertical": ui.add_element('memtemp', LabeledValue(color=BLACK, label='', value=' mem:-\n cpu:-\ntemp:-', - position=(ui.width() / 2 + 55, ui.height() / 2), + position=v_pos, label_font=fonts.Small, text_font=fonts.Small)) def on_ui_update(self, ui): From 4b74de48bf7c40199fb8c282d2cf99fe68ac932b Mon Sep 17 00:00:00 2001 From: Simone Margaritelli Date: Fri, 1 Nov 2019 17:49:50 +0100 Subject: [PATCH 162/168] misc: small fix or general refactoring i did not bother commenting --- pwnagotchi/plugins/default/memtemp.py | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/pwnagotchi/plugins/default/memtemp.py b/pwnagotchi/plugins/default/memtemp.py index 289fbabb..9ac2571e 100644 --- a/pwnagotchi/plugins/default/memtemp.py +++ b/pwnagotchi/plugins/default/memtemp.py @@ -41,11 +41,8 @@ class MemTemp(plugins.Plugin): return int(pwnagotchi.cpu_load() * 100) def on_ui_setup(self, ui): - # self._layout['width'] = 250 - # self._layout['height'] = 122 - if ui.is_waveshare_v2(): - h_pos = (180, 76) + h_pos = (180, 80) v_pos = (180, 61) else: h_pos = (155, 76) From 5b66d687c44dfc209b103702f0cf977c0e6f4679 Mon Sep 17 00:00:00 2001 From: dadav <33197631+dadav@users.noreply.github.com> Date: Fri, 1 Nov 2019 18:00:07 +0100 Subject: [PATCH 163/168] Add multi bt-tether support --- pwnagotchi/defaults.yml | 26 ++- pwnagotchi/plugins/default/bt-tether.py | 218 ++++++++++++++---------- 2 files changed, 146 insertions(+), 98 deletions(-) diff --git a/pwnagotchi/defaults.yml b/pwnagotchi/defaults.yml index bb78d8ad..5de1137b 100644 --- a/pwnagotchi/defaults.yml +++ b/pwnagotchi/defaults.yml @@ -71,11 +71,27 @@ main: enabled: false bt-tether: enabled: false # if you want to use this, set ui.display.video.address to 0.0.0.0 - mac: ~ # mac of your bluetooth device - ip: '192.168.44.44' # ip from which your pwnagotchi should be reachable - netmask: 24 - interval: 1 # check every x minutes for device - share_internet: false + devices: + android-phone: + search_order: 1 # search for this first + mac: ~ # mac of your bluetooth device + ip: '192.168.44.44' # ip from which your pwnagotchi should be reachable + netmask: 24 + interval: 1 # check every minute for device + scantime: 10 # search for 10 seconds + max_tries: 10 # after 10 tries of "not found"; don't try anymore + share_internet: false + priority: 1 # low routing priority; ios (prio: 999) would win here + ios-phone: + search_order: 2 # search for this second + mac: ~ # mac of your bluetooth device + ip: '172.20.10.2' # ip from which your pwnagotchi should be reachable + netmask: 24 + interval: 5 # check every 5 minutes for device + scantime: 20 + max_tries: 0 # infinity + share_internet: false + priority: 999 # routing priority memtemp: # Display memory usage, cpu load and cpu temperature on screen enabled: false orientation: horizontal # horizontal/vertical diff --git a/pwnagotchi/plugins/default/bt-tether.py b/pwnagotchi/plugins/default/bt-tether.py index 7c89387f..2115c72e 100644 --- a/pwnagotchi/plugins/default/bt-tether.py +++ b/pwnagotchi/plugins/default/bt-tether.py @@ -149,24 +149,6 @@ class BTNap: return None - def is_connected(self): - """ - Check if already connected - """ - logging.debug("BT-TETHER: Checking if device is connected.") - - bt_dev = self.power(True) - - if not bt_dev: - logging.debug("BT-TETHER: No bluetooth device found.") - return None, False - - try: - dev_remote = BTNap.find_device(self._mac, bt_dev) - return dev_remote, bool(BTNap.prop_get(dev_remote, 'Connected')) - except BTError: - logging.debug("BT-TETHER: Device is not connected.") - return None, False def is_paired(self): """ @@ -388,8 +370,8 @@ class IfaceWrapper: return False @staticmethod - def set_route(addr): - process = subprocess.Popen(f"ip route replace default via {addr}", shell=True, stdin=None, + def set_route(gateway, device): + process = subprocess.Popen(f"ip route replace default via {gateway} dev {device}", shell=True, stdin=None, stdout=open("/dev/null", "w"), stderr=None, executable="/bin/bash") process.wait() @@ -399,6 +381,39 @@ class IfaceWrapper: return True +class Device: + def __init__(self, name, share_internet, mac, ip, netmask, interval, priority=10, scantime=15, search_order=0, max_tries=0, **kwargs): + self.name = name + self.status = StatusFile(f'/root/.bt-tether-{name}') + self.status.update() + self.tries = 0 + self.network = None + + self.max_tries = max_tries + self.search_order = search_order + self.share_internet = share_internet + self.ip = ip + self.netmask = netmask + self.interval = interval + self.mac = mac + self.scantime = scantime + self.priority = priority + + def connected(self): + """ + Checks if device is connected + """ + return self.network and BTNap.prop_get(self.network, 'Connected') + + def interface(self): + """ + Returns the interface name or None + """ + if not self.connected(): + return None + return BTNap.prop_get(self.network, 'Interface') + + class BTTether(plugins.Plugin): __author__ = '33197631+dadav@users.noreply.github.com' __version__ = '1.0.0' @@ -407,15 +422,33 @@ class BTTether(plugins.Plugin): def __init__(self): self.ready = False - self.interval = StatusFile('/root/.bt-tether') - self.network = None + self.options = dict() + self.devices = dict() def on_loaded(self): - for opt in ['share_internet', 'mac', 'ip', 'netmask', 'interval']: - if opt not in self.options or (opt in self.options and self.options[opt] is None): - logging.error("BT-TET: Please specify the %s in your config.yml.", opt) - return + if 'devices' in self.options: + for device, options in self.options['devices'].items(): + for device_opt in ['priority', 'scantime', 'search_order', 'max_tries', 'share_internet', 'mac', 'ip', 'netmask', 'interval']: + if device_opt not in options or (device_opt in options and options[device_opt] is None): + logging.error("BT-TET: Pleace specify the %s for device %s.", device_opt, device) + break + else: + self.devices[device] = Device(name=device, **options) + elif 'mac' in self.options: + # legacy + for opt in ['share_internet', 'mac', 'ip', 'netmask', 'interval']: + if opt not in self.options or (opt in self.options and self.options[opt] is None): + logging.error("BT-TET: Please specify the %s in your config.yml.", opt) + return + self.devices['default'] = Device(name='default', **self.options) + else: + logging.error("BT-TET: No configuration found") + return + + if not self.devices: + logging.error("BT-TET: No valid devices found") + return # ensure bluetooth is running bt_unit = SystemdUnitWrapper('bluetooth.service') if not bt_unit.is_active(): @@ -423,7 +456,6 @@ class BTTether(plugins.Plugin): logging.error("BT-TET: Can't start bluetooth.service") return - self.interval.update() self.ready = True def on_ui_setup(self, ui): @@ -434,88 +466,88 @@ class BTTether(plugins.Plugin): if not self.ready: return - if self.interval.newer_then_minutes(self.options['interval']): - return + devices_to_try = list() + connected_priorities = list() + any_device_connected = False # if this is true, last status on screen should be C - self.interval.update() + for _, device in self.devices.items(): + if device.connected(): + connected_priorities.append(device.priority) + any_device_connected = True + continue - bt = BTNap(self.options['mac']) + if not device.max_tries or (device.max_tries > device.tries): + if device.status.newer_then_minutes(device.interval): + devices_to_try.append(device) + device.status.update() + device.tries += 1 - logging.debug('BT-TETHER: Check if already connected and paired') - dev_remote, connected = bt.is_connected() + sorted_devices = sorted(devices_to_try, key=lambda x: x.search_order) - if connected: - logging.debug('BT-TETHER: Already connected.') - ui.set('bluetooth', 'C') - return + for device in sorted_devices: + bt = BTNap(device.mac) - try: - logging.info('BT-TETHER: Search device ...') - dev_remote = bt.wait_for_device() - if dev_remote is None: - logging.info('BT-TETHER: Could not find device.') + try: + logging.info('BT-TETHER: Search %d secs for %s ...', device.scantime, device.name) + dev_remote = bt.wait_for_device(timeout=device.scantime) + if dev_remote is None: + logging.info('BT-TETHER: Could not find %s, try again in %d minutes.', device.name, device.interval) + ui.set('bluetooth', 'NF') + continue + except Exception as bt_ex: + logging.error(bt_ex) ui.set('bluetooth', 'NF') - return - except Exception as bt_ex: - logging.error(bt_ex) - ui.set('bluetooth', 'NF') - return + continue - paired = bt.is_paired() - if not paired: - if BTNap.pair(dev_remote): - logging.info('BT-TETHER: Paired with device.') + paired = bt.is_paired() + if not paired: + if BTNap.pair(dev_remote): + logging.info('BT-TETHER: Paired with %s.', device.name) + else: + logging.info('BT-TETHER: Pairing with %s failed ...', device.name) + ui.set('bluetooth', 'PE') + continue else: - logging.info('BT-TETHER: Pairing failed ...') - ui.set('bluetooth', 'PE') - return - else: - logging.debug('BT-TETHER: Already paired.') + logging.debug('BT-TETHER: Already paired.') - btnap_iface = IfaceWrapper('bnep0') - logging.debug('BT-TETHER: Check interface') - if not btnap_iface.exists(): - # connected and paired but not napping - logging.debug('BT-TETHER: Try to connect to nap ...') - network, status = BTNap.nap(dev_remote) - self.network = network - if status: - logging.info('BT-TETHER: Napping!') + + logging.debug('BT-TETHER: Try to create nap connection with %s ...', device.name) + device.network, success = BTNap.nap(dev_remote) + + if success: + logging.info('BT-TETHER: Created interface (%s)', device.interface()) ui.set('bluetooth', 'C') - time.sleep(5) + any_device_connected = True + device.tries = 0 # reset tries else: - logging.info('BT-TETHER: Napping failed ...') + logging.info('BT-TETHER: Could not establish nap connection with %s', device.name) ui.set('bluetooth', 'NF') - return + continue - if btnap_iface.exists(): - logging.debug('BT-TETHER: Interface found') + interface = device.interface() + addr = f"{device.ip}/{device.netmask}" + gateway = ".".join(device.ip.split('.')[:-1] + ['1']) - # check ip - addr = f"{self.options['ip']}/{self.options['netmask']}" - - logging.debug('BT-TETHER: Try to set ADDR to interface') - if not btnap_iface.set_addr(addr): + wrapped_interface = IfaceWrapper(interface) + logging.debug('BT-TETHER: Add ip to %s', interface) + if not wrapped_interface.set_addr(addr): ui.set('bluetooth', 'AE') - logging.error("BT-TETHER: Could not set ip of bnep0 to %s", addr) - return + logging.error("BT-TETHER: Could not add ip to %s", interface) + continue - logging.debug('BT-TETHER: Set ADDR to interface') + if device.share_internet: + if not connected_priorities or device.priority > max(connected_priorities): + logging.debug('BT-TETHER: Set default route to %s via %s', gateway, interface) + IfaceWrapper.set_route(gateway, interface) + connected_priorities.append(device.priority) - # change route if sharking - if self.options['share_internet']: - logging.debug('BT-TETHER: Set routing and change resolv.conf') - IfaceWrapper.set_route( - ".".join(self.options['ip'].split('.')[:-1] + ['1'])) # im not proud about that - # fix resolv.conf; dns over https ftw! - with open('/etc/resolv.conf', 'r+') as resolv: - nameserver = resolv.read() - if 'nameserver 9.9.9.9' not in nameserver: - logging.info('BT-TETHER: Added nameserver') - resolv.seek(0) - resolv.write(nameserver + 'nameserver 9.9.9.9\n') + logging.debug('BT-TETHER: Change resolv.conf if necessary ...') + with open('/etc/resolv.conf', 'r+') as resolv: + nameserver = resolv.read() + if 'nameserver 9.9.9.9' not in nameserver: + logging.info('BT-TETHER: Added nameserver') + resolv.seek(0) + resolv.write(nameserver + 'nameserver 9.9.9.9\n') + if any_device_connected: ui.set('bluetooth', 'C') - else: - logging.error('BT-TETHER: bnep0 not found') - ui.set('bluetooth', 'BE') From 53ae8ea1cf38c908e6d62c73e6c09d81ffaa2e33 Mon Sep 17 00:00:00 2001 From: dadav <33197631+dadav@users.noreply.github.com> Date: Fri, 1 Nov 2019 18:13:38 +0100 Subject: [PATCH 164/168] Add check if connected but no interface created --- pwnagotchi/plugins/default/bt-tether.py | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/pwnagotchi/plugins/default/bt-tether.py b/pwnagotchi/plugins/default/bt-tether.py index 2115c72e..52306bd3 100644 --- a/pwnagotchi/plugins/default/bt-tether.py +++ b/pwnagotchi/plugins/default/bt-tether.py @@ -515,6 +515,11 @@ class BTTether(plugins.Plugin): device.network, success = BTNap.nap(dev_remote) if success: + if device.interface() is None: + ui.set('bluetooth', 'BE') + logging.info('BT-TETHER: Could not establish nap connection with %s', device.name) + continue + logging.info('BT-TETHER: Created interface (%s)', device.interface()) ui.set('bluetooth', 'C') any_device_connected = True From 9b58fed8621e48c8362cba68095c93bc10f70912 Mon Sep 17 00:00:00 2001 From: dadav <33197631+dadav@users.noreply.github.com> Date: Fri, 1 Nov 2019 18:23:55 +0100 Subject: [PATCH 165/168] Typo --- pwnagotchi/plugins/default/bt-tether.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pwnagotchi/plugins/default/bt-tether.py b/pwnagotchi/plugins/default/bt-tether.py index 52306bd3..5ce0d0db 100644 --- a/pwnagotchi/plugins/default/bt-tether.py +++ b/pwnagotchi/plugins/default/bt-tether.py @@ -477,7 +477,7 @@ class BTTether(plugins.Plugin): continue if not device.max_tries or (device.max_tries > device.tries): - if device.status.newer_then_minutes(device.interval): + if not device.status.newer_then_minutes(device.interval): devices_to_try.append(device) device.status.update() device.tries += 1 From a2bb66ad57d54bc7380e9edd90d3df1abaffbc42 Mon Sep 17 00:00:00 2001 From: dadav <33197631+dadav@users.noreply.github.com> Date: Fri, 1 Nov 2019 20:12:04 +0100 Subject: [PATCH 166/168] Fix case --- pwnagotchi/plugins/default/bt-tether.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pwnagotchi/plugins/default/bt-tether.py b/pwnagotchi/plugins/default/bt-tether.py index 5ce0d0db..4665fb80 100644 --- a/pwnagotchi/plugins/default/bt-tether.py +++ b/pwnagotchi/plugins/default/bt-tether.py @@ -122,7 +122,7 @@ class BTNap: device = ifaces.get(BTNap.IFACE_DEV) if device is None: continue - if str(device['Address']) == device_address and path.startswith(path_prefix): + if str(device['Address']).lower() == device_address.lower() and path.startswith(path_prefix): obj = bus.get_object(BTNap.IFACE_BASE, path) return dbus.Interface(obj, BTNap.IFACE_DEV) raise BTError('Bluetooth device not found') From fd506b15335a622dbaaa18e7adbea4308f578c1c Mon Sep 17 00:00:00 2001 From: dadav <33197631+dadav@users.noreply.github.com> Date: Fri, 1 Nov 2019 21:42:02 +0100 Subject: [PATCH 167/168] Fix bt-tether config --- pwnagotchi/defaults.yml | 4 +++- pwnagotchi/plugins/default/bt-tether.py | 17 +++++++++-------- 2 files changed, 12 insertions(+), 9 deletions(-) diff --git a/pwnagotchi/defaults.yml b/pwnagotchi/defaults.yml index 501211d3..0d7f9f7f 100644 --- a/pwnagotchi/defaults.yml +++ b/pwnagotchi/defaults.yml @@ -73,6 +73,7 @@ main: enabled: false # if you want to use this, set ui.display.video.address to 0.0.0.0 devices: android-phone: + enabled: false search_order: 1 # search for this first mac: ~ # mac of your bluetooth device ip: '192.168.44.44' # ip from which your pwnagotchi should be reachable @@ -83,9 +84,10 @@ main: share_internet: false priority: 1 # low routing priority; ios (prio: 999) would win here ios-phone: + enabled: false search_order: 2 # search for this second mac: ~ # mac of your bluetooth device - ip: '172.20.10.2' # ip from which your pwnagotchi should be reachable + ip: '172.20.10.6' # ip from which your pwnagotchi should be reachable netmask: 24 interval: 5 # check every 5 minutes for device scantime: 20 diff --git a/pwnagotchi/plugins/default/bt-tether.py b/pwnagotchi/plugins/default/bt-tether.py index 4665fb80..36e98166 100644 --- a/pwnagotchi/plugins/default/bt-tether.py +++ b/pwnagotchi/plugins/default/bt-tether.py @@ -426,29 +426,30 @@ class BTTether(plugins.Plugin): self.devices = dict() def on_loaded(self): + # new config if 'devices' in self.options: for device, options in self.options['devices'].items(): - for device_opt in ['priority', 'scantime', 'search_order', 'max_tries', 'share_internet', 'mac', 'ip', 'netmask', 'interval']: + for device_opt in ['enabled', 'priority', 'scantime', 'search_order', 'max_tries', 'share_internet', 'mac', 'ip', 'netmask', 'interval']: if device_opt not in options or (device_opt in options and options[device_opt] is None): logging.error("BT-TET: Pleace specify the %s for device %s.", device_opt, device) break else: - self.devices[device] = Device(name=device, **options) - elif 'mac' in self.options: - # legacy + if options['enabled']: + self.devices[device] = Device(name=device, **options) + + # legacy + if 'mac' in self.options: for opt in ['share_internet', 'mac', 'ip', 'netmask', 'interval']: if opt not in self.options or (opt in self.options and self.options[opt] is None): logging.error("BT-TET: Please specify the %s in your config.yml.", opt) return - self.devices['default'] = Device(name='default', **self.options) - else: - logging.error("BT-TET: No configuration found") - return + self.devices['legacy'] = Device(name='legacy', **self.options) if not self.devices: logging.error("BT-TET: No valid devices found") return + # ensure bluetooth is running bt_unit = SystemdUnitWrapper('bluetooth.service') if not bt_unit.is_active(): From bc1db7ceea0082a9d7f195ad70d2a7ad146cd61f Mon Sep 17 00:00:00 2001 From: LuckyFish <511225068@qq.com> Date: Sat, 2 Nov 2019 09:42:54 +0800 Subject: [PATCH 168/168] Add files via upload --- pwnagotchi/locale/ch/LC_MESSAGES/voice.mo | Bin 0 -> 3641 bytes pwnagotchi/locale/ch/LC_MESSAGES/voice.po | 225 ++++++++++++++++++++++ 2 files changed, 225 insertions(+) create mode 100644 pwnagotchi/locale/ch/LC_MESSAGES/voice.mo create mode 100644 pwnagotchi/locale/ch/LC_MESSAGES/voice.po diff --git a/pwnagotchi/locale/ch/LC_MESSAGES/voice.mo b/pwnagotchi/locale/ch/LC_MESSAGES/voice.mo new file mode 100644 index 0000000000000000000000000000000000000000..1ba067bcdb1f9aaa6e23f9c1be1f6ceeae6b4117 GIT binary patch literal 3641 zcmZ9OZE#dq8OIMwtzB(ZTD8`fo{O~<$u65rQH)qhDMH$WBBW(>oY9-zlih1}?@8~y zOIS0RNrWUD5)uNTkPs+fLI5EN$V=Ewh@W)E8K-{mvyRT5ySt7vPCq#6h%^45dv8eO zPR{=BJulCBp8xZlGx_71)h8LA8niE=U0cQ2c5ur_@q=gAYQ~-b>%h-~`@r>}0e%iV z0lolU1!?^U;3vSpfi(YL;HSZ-9%Jlz@OhBtZ3Q{_L-1*^16%_RgHM7J;OD`c0e=fV zj_<#KWbd!wi{O7iTDSINzCWA5XYgGQJ`3s~+4(6*>yLvPI2QEJgXGU2Kn{KgZUEO{ zGWqceNdDD>H1EeC2RlHD{~Sp6Z-X@Nci?m2pTQ9L0Q?&G_{SN86f6wFWmW@XE4CTL z6t*?^-Wh!F1}Uz0z^6b1)K)Qe5Tt#_kQCDQ2KX}g8<6~X0CMmdZ2m=X6G(A<4y8Kk`b z0QBsDl!q>m_8kXFw>gmJe*jX6|0CeP1O7MQDu_PW-WM?gibUb{%jJFMF z>(O2adSS|oh2~QX8TEly)N{OlN6?7#C#*|X+74#b_!Eien*SQ+EqL*xd~fN#xl8VZsw6l zgzc0v&YLYmz$(`*!FAr061ELrG=I=E5=n<9E_w0L13Sca>9H1$z4@M$ZVE?ZyF@C5 z5KKKSIyAo9hzSZQEri3fG84ggHVBZXgtN}(O=4+#EHE zhP?@X7uNV$v>hZxnozIaQy?LEv$P<5o1SXXSiO`jv{bib#*Fiq+HDcDJM8tWL63_S zg%Bhy#2O6S7IBR=WO>Xo(t;bd@11Psv5aL%+iDIhq3`f z2ouRq8O=gtZzt2T%NIC^5m$xOg_;HDyGkl~|rr%W;-XVT({hUBiy$0IDO=&~|A$D4B3$wiP})IH?;^Vutx`tkEr7 zIN!-Q&Ed_<^T^d^VTE^?F&Rh2)$+|vh7)4Nd|ut1bVu{$LksHeO?7NW9l0D~?)9Im z@yq4KbMD}z%FPD1tD3l4>AqXc_f&B=H(V@SbB~b^Hy8arGX7%cT&S-xo1 ze5ab~cSj1Uw-AhZH?7K@QH8vk>8Tu_EY4l8;^mpUs(;aa?|NzEHr-q9cwceus+wGI z$NSY#znZ<_o}MXP8ggexm^(VAru)?N+48Nkn0fSMd64|_-59#y_VufQ?hu2QHt92FhYkOa6=uN@=BeS4qm}(#6cP_A}aJ1=O^$A z1N4Ew{BN?hLj2;~(aLxyq$zieloo~&2(2t!D-}l7^dY1G1~rCy@RbcN)Z8W7vNvBjb-8?d7JqP6W97w#GIT$4Mh#!~7cAe-#r(vQITVjCLx`!| z82&0Fqvge33>W7*DRDFRDAwsio=W4LYGR6tZ9WfS{L&yDCVMGTH9O??Tv>Tpm+lW@ z7z+Wxp*2=c%}C!4iDYsw4hixf^8tPpBcKh)rnqgOHNLV`8v?eU79>sDh#RaLr6oZ zFuvjlmKR4+Hsr?0`)YUrN5mcPd?XHy!6on3RXs<`Q(b;NGkB=(cdv}!mlheW;Fu2< z)QPK=-T`Qft6QBIRKuMVLU4JOE*0Q41`)TPI~?(a^OPv>pR5(-4;_N5oYHqE=XT93 jAAI-J9Y#&%dc0PeI?kvC+aQ%F5m@yEdnD(, 2019. +# 还有很多未翻译和翻译不准确,后期希望大家加入进来一起翻译! +# 翻译可以联系QQ群:959559103 找 名字叫 初九 的 管理员 +msgid "" +msgstr "" +"Project-Id-Version: 0.0.1\n" +"Report-Msgid-Bugs-To: \n" +"POT-Creation-Date: 2019-10-23 20:56+0200\n" +"PO-Revision-Date: 2019-11-02 10:00+0008\n" +"Last-Translator: 极客之眼-初九 <511225068@qq.com>\n" +"Language-Team: LANGUAGE \n" +"Language: chinese\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=utf-8\n" +"Content-Transfer-Encoding: 8bit\n" + +msgid "ZzzzZZzzzzZzzz" +msgstr "" + +msgid "Hi, I'm Pwnagotchi! Starting ..." +msgstr "主人,你好.我是WiFi狩猎兽..." + +msgid "New day, new hunt, new pwns!" +msgstr "美好的一天,狩猎开始!" + +msgid "Hack the Planet!" +msgstr "我要入侵整个地球!" + +msgid "AI ready." +msgstr "人工智能已启动." + +msgid "The neural network is ready." +msgstr "神经元网络已启动." + +msgid "Generating keys, do not turn off ..." +msgstr "创建密钥中, 请勿断电..." + +#, python-brace-format +msgid "Hey, channel {channel} is free! Your AP will say thanks." +msgstr "" + +msgid "I'm bored ..." +msgstr "我无聊了..." + +msgid "Let's go for a walk!" +msgstr "主人带我出门走走吧!" + +msgid "This is the best day of my life!" +msgstr "这是我生命中最美好的一天!" + +msgid "Shitty day :/" +msgstr "今天不开心 :/" + +msgid "I'm extremely bored ..." +msgstr "主人,找点事做吧 ..." + +msgid "I'm very sad ..." +msgstr "我很伤心..." + +msgid "I'm sad" +msgstr "我伤心了" + +msgid "I'm living the life!" +msgstr "" + +msgid "I pwn therefore I am." +msgstr "" + +msgid "So many networks!!!" +msgstr "哇,好多猎物!!!" + +msgid "I'm having so much fun!" +msgstr "我玩的好开心!" + +msgid "My crime is that of curiosity ..." +msgstr "我最大的缺点就是好奇..." + +#, python-brace-format +msgid "Hello {name}! Nice to meet you." +msgstr "你好{name}!很高兴认识你." + +#, python-brace-format +msgid "Unit {name} is nearby!" +msgstr "" + +#, python-brace-format +msgid "Uhm ... goodbye {name}" +msgstr "额 ... 再见{name}" + +#, python-brace-format +msgid "{name} is gone ..." +msgstr "{name} 它走了 ..." + +#, python-brace-format +msgid "Whoops ... {name} is gone." +msgstr "哎呀... {name} 离开了." + +#, python-brace-format +msgid "{name} missed!" +msgstr "刚刚错过了{name}!" + +msgid "Missed!" +msgstr "刚刚错过了一个对的它" + +msgid "Good friends are a blessing!" +msgstr "有个好朋友就是福气" + +msgid "I love my friends!" +msgstr "我爱我的朋友!" + +msgid "Nobody wants to play with me ..." +msgstr "没有人愿意和我玩耍..." + +msgid "I feel so alone ..." +msgstr "我可能是天煞孤星..." + +msgid "Where's everybody?!" +msgstr "朋友们都去哪里了?!" + +#, python-brace-format +msgid "Napping for {secs}s ..." +msgstr "小憩{secs}s ..." + +msgid "Zzzzz" +msgstr "" + +#, python-brace-format +msgid "ZzzZzzz ({secs}s)" +msgstr "" + +msgid "Good night." +msgstr "晚安宝贝." + +msgid "Zzz" +msgstr "" + +#, python-brace-format +msgid "Waiting for {secs}s ..." +msgstr "等待{secs}s ..." + +#, python-brace-format +msgid "Looking around ({secs}s)" +msgstr "追踪四周猎物({secs}s)" + +#, python-brace-format +msgid "Hey {what} let's be friends!" +msgstr "嗨{what}我们做朋友吧!" + +#, python-brace-format +msgid "Associating to {what}" +msgstr "正在连接到{what}" + +#, python-brace-format +msgid "Yo {what}!" +msgstr "追踪到你了{what}!" + +#, python-brace-format +msgid "Just decided that {mac} needs no WiFi!" +msgstr "猎物{mac}不需要联网,我们给它断开!" + +#, python-brace-format +msgid "Deauthenticating {mac}" +msgstr "开始攻击猎物{mac}" + +#, python-brace-format +msgid "Kickbanning {mac}!" +msgstr "已捕获{mac}!" + +#, python-brace-format +msgid "Cool, we got {num} new handshake{plural}!" +msgstr "太酷了, 我们抓到了{num}新的猎物{plural}!" + +#, python-brace-format +msgid "You have {count} new message{plural}!" +msgstr "主人,有{count}新消息{plural}!" + +msgid "Ops, something went wrong ... Rebooting ..." +msgstr "行动,额等等有点小问题... 重启ing ..." + +#, python-brace-format +msgid "Kicked {num} stations\n" +msgstr "限制了{num}个猎物\n" + +#, python-brace-format +msgid "Made {num} new friends\n" +msgstr "交了{num}新朋友\n" + +#, python-brace-format +msgid "Got {num} handshakes\n" +msgstr "捕获了{num}握手包\n" + +msgid "Met 1 peer" +msgstr "" + +#, python-brace-format +msgid "Met {num} peers" +msgstr "" + +#, 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 "" + +msgid "hours" +msgstr "时" + +msgid "minutes" +msgstr "分" + +msgid "seconds" +msgstr "秒" + +msgid "hour" +msgstr "时" + +msgid "minute" +msgstr "分" + +msgid "second" +msgstr "秒"