From 6117235c52e37cc6517cfd3ab27beaf421771dba Mon Sep 17 00:00:00 2001
From: "mohesh.mohan" <moheshmohan@gmail.com>
Date: Wed, 11 Dec 2019 23:08:29 +0400
Subject: [PATCH 01/22] added 213bc support

---
 pwnagotchi/ui/display.py                      |   3 +
 .../ui/hw/libs/waveshare/v213bc/epd2in13bc.py | 159 ++++++++++++++++++
 .../ui/hw/libs/waveshare/v213bc/epdconfig.py  | 154 +++++++++++++++++
 pwnagotchi/ui/hw/waveshare213bc.py            |  49 ++++++
 4 files changed, 365 insertions(+)
 create mode 100644 pwnagotchi/ui/hw/libs/waveshare/v213bc/epd2in13bc.py
 create mode 100644 pwnagotchi/ui/hw/libs/waveshare/v213bc/epdconfig.py
 create mode 100644 pwnagotchi/ui/hw/waveshare213bc.py

diff --git a/pwnagotchi/ui/display.py b/pwnagotchi/ui/display.py
index 9ef8774..181ed76 100644
--- a/pwnagotchi/ui/display.py
+++ b/pwnagotchi/ui/display.py
@@ -61,6 +61,9 @@ class Display(View):
     def is_waveshare213d(self):
         return self._implementation.name == 'waveshare213d'
 
+    def is_waveshare213bc(self):
+        return self._implementation.name == 'waveshare213bc'
+
     def is_spotpear24inch(self):
         return self._implementation.name == 'spotpear24inch'
 
diff --git a/pwnagotchi/ui/hw/libs/waveshare/v213bc/epd2in13bc.py b/pwnagotchi/ui/hw/libs/waveshare/v213bc/epd2in13bc.py
new file mode 100644
index 0000000..864d8c5
--- /dev/null
+++ b/pwnagotchi/ui/hw/libs/waveshare/v213bc/epd2in13bc.py
@@ -0,0 +1,159 @@
+# *****************************************************************************
+# * | File        :	  epd2in13bc.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       = 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
+
+    # 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
+            epdconfig.delay_ms(100)
+        logging.debug("e-Paper busy release")
+
+    def init(self):
+        if (epdconfig.module_init() != 0):
+            return -1
+            
+        self.reset()
+
+        self.send_command(0x06) # BOOSTER_SOFT_START
+        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)
+        self.send_data(self.height & 0xff)
+        return 0
+
+    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, imageblack, imagered):
+        self.send_command(0x10)
+        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(imagered[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(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()
+
+    def sleep(self):
+        self.send_command(0x02) # POWER_OFF
+        self.ReadBusy()
+        self.send_command(0x07) # DEEP_SLEEP
+        self.send_data(0xA5) # check code
+        
+        epdconfig.module_exit()
+### END OF FILE ###
+
diff --git a/pwnagotchi/ui/hw/libs/waveshare/v213bc/epdconfig.py b/pwnagotchi/ui/hw/libs/waveshare/v213bc/epdconfig.py
new file mode 100644
index 0000000..861f43d
--- /dev/null
+++ b/pwnagotchi/ui/hw/libs/waveshare/v213bc/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/waveshare213bc.py b/pwnagotchi/ui/hw/waveshare213bc.py
new file mode 100644
index 0000000..6ceebb7
--- /dev/null
+++ b/pwnagotchi/ui/hw/waveshare213bc.py
@@ -0,0 +1,49 @@
+import logging
+import time
+
+import pwnagotchi.ui.fonts as fonts
+from pwnagotchi.ui.hw.base import DisplayImpl
+
+
+class Waveshare213bc(DisplayImpl):
+    def __init__(self, config):
+        super(Waveshare213bc, self).__init__(config, 'waveshare213bc')
+        self._display = None
+
+    def layout(self):
+        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):
+        logging.info("initializing waveshare 213bc display")
+        from pwnagotchi.ui.hw.libs.waveshare.v213bc.epd2in13bc import EPD
+        self._display = EPD()
+        self._display.init()
+        self._display.Clear()
+        time.sleep(1)
+
+    def render(self, canvas):
+        buf = self._display.getbuffer(canvas)
+        self._display.display(buf)
+
+    def clear(self):
+        #pass
+        self._display.Clear()

From eddcf32b6277699ddfdef503c17f23d862a31f0d Mon Sep 17 00:00:00 2001
From: "mohesh.mohan" <moheshmohan@gmail.com>
Date: Wed, 11 Dec 2019 23:47:14 +0400
Subject: [PATCH 02/22] 213bc support additions

---
 pwnagotchi/ui/hw/__init__.py | 10 +++++++---
 pwnagotchi/utils.py          |  3 +++
 2 files changed, 10 insertions(+), 3 deletions(-)

diff --git a/pwnagotchi/ui/hw/__init__.py b/pwnagotchi/ui/hw/__init__.py
index 696a41f..432cb54 100644
--- a/pwnagotchi/ui/hw/__init__.py
+++ b/pwnagotchi/ui/hw/__init__.py
@@ -10,6 +10,7 @@ from pwnagotchi.ui.hw.waveshare29inch import Waveshare29inch
 from pwnagotchi.ui.hw.waveshare144lcd import Waveshare144lcd
 from pwnagotchi.ui.hw.waveshare154inch import Waveshare154inch
 from pwnagotchi.ui.hw.waveshare213d import Waveshare213d
+from pwnagotchi.ui.hw.waveshare213bc import Waveshare213bc
 from pwnagotchi.ui.hw.spotpear24inch import Spotpear24inch
 
 def display_for(config):
@@ -37,10 +38,10 @@ def display_for(config):
 
     elif config['ui']['display']['type'] == 'waveshare27inch':
         return Waveshare27inch(config)
-    
+
     elif config['ui']['display']['type'] == 'waveshare29inch':
         return Waveshare29inch(config)
-    
+
     elif config['ui']['display']['type'] == 'waveshare144lcd':
         return Waveshare144lcd(config)
 
@@ -50,5 +51,8 @@ def display_for(config):
     elif config['ui']['display']['type'] == 'waveshare213d':
         return Waveshare213d(config)
 
+    elif config['ui']['display']['type'] == 'waveshare213bc':
+        return Waveshare213bc(config)
+
     elif config['ui']['display']['type'] == 'spotpear24inch':
-        return Spotpear24inch(config)
\ No newline at end of file
+        return Spotpear24inch(config)
diff --git a/pwnagotchi/utils.py b/pwnagotchi/utils.py
index 4d0ba90..d645155 100644
--- a/pwnagotchi/utils.py
+++ b/pwnagotchi/utils.py
@@ -115,6 +115,9 @@ def load_config(args):
     elif config['ui']['display']['type'] in ('ws_213d', 'ws213d', 'waveshare_213d', 'waveshare213d'):
         config['ui']['display']['type'] = 'waveshare213d'
 
+    elif config['ui']['display']['type'] in ('ws_213bc', 'ws213bc', 'waveshare_213bc', 'waveshare213bc'):
+        config['ui']['display']['type'] = 'waveshare213bc'
+
     elif config['ui']['display']['type'] in ('spotpear24inch'):
         config['ui']['display']['type'] = 'spotpear24inch'
 

From a4daf4af61d27643f8f8fb94dc3baea75ee6522d Mon Sep 17 00:00:00 2001
From: "mohesh.mohan" <moheshmohan@gmail.com>
Date: Thu, 12 Dec 2019 00:37:24 +0400
Subject: [PATCH 03/22] waveshare213b and waveshare213c support bug fixes

---
 .../ui/hw/libs/waveshare/v213bc/epd2in13bc.py | 41 +++++++++++--------
 pwnagotchi/ui/hw/waveshare213bc.py            |  2 +-
 2 files changed, 26 insertions(+), 17 deletions(-)

diff --git a/pwnagotchi/ui/hw/libs/waveshare/v213bc/epd2in13bc.py b/pwnagotchi/ui/hw/libs/waveshare/v213bc/epd2in13bc.py
index 864d8c5..1d321eb 100644
--- a/pwnagotchi/ui/hw/libs/waveshare/v213bc/epd2in13bc.py
+++ b/pwnagotchi/ui/hw/libs/waveshare/v213bc/epd2in13bc.py
@@ -46,11 +46,11 @@ class EPD:
     # Hardware reset
     def reset(self):
         epdconfig.digital_write(self.reset_pin, 1)
-        epdconfig.delay_ms(200) 
+        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)   
+        epdconfig.delay_ms(200)
 
     def send_command(self, command):
         epdconfig.digital_write(self.dc_pin, 0)
@@ -63,7 +63,7 @@ class EPD:
         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
@@ -73,23 +73,23 @@ class EPD:
     def init(self):
         if (epdconfig.module_init() != 0):
             return -1
-            
+
         self.reset()
 
         self.send_command(0x06) # BOOSTER_SOFT_START
         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)
@@ -125,26 +125,36 @@ 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(imagered[i])
         self.send_command(0x92)
-        
+
         self.send_command(0x12) # REFRESH
         self.ReadBusy()
-        
+
+    def pwndisplay(self, imageblack):
+        self.send_command(0x10)
+        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()
+
+
     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()
 
@@ -153,7 +163,6 @@ class EPD:
         self.ReadBusy()
         self.send_command(0x07) # DEEP_SLEEP
         self.send_data(0xA5) # check code
-        
+
         epdconfig.module_exit()
 ### END OF FILE ###
-
diff --git a/pwnagotchi/ui/hw/waveshare213bc.py b/pwnagotchi/ui/hw/waveshare213bc.py
index 6ceebb7..81b5770 100644
--- a/pwnagotchi/ui/hw/waveshare213bc.py
+++ b/pwnagotchi/ui/hw/waveshare213bc.py
@@ -42,7 +42,7 @@ class Waveshare213bc(DisplayImpl):
 
     def render(self, canvas):
         buf = self._display.getbuffer(canvas)
-        self._display.display(buf)
+        self._display.pwndisplay(buf)
 
     def clear(self):
         #pass

From 704d7ceaa1e78839de317708570689792025f277 Mon Sep 17 00:00:00 2001
From: "mohesh.mohan" <moheshmohan@gmail.com>
Date: Thu, 12 Dec 2019 00:46:14 +0400
Subject: [PATCH 04/22] waveshare213b and waveshare213c support bug fixes

---
 pwnagotchi/ui/hw/libs/waveshare/v213bc/epd2in13bc.py | 9 +++++++++
 pwnagotchi/ui/hw/waveshare213bc.py                   | 4 +---
 2 files changed, 10 insertions(+), 3 deletions(-)

diff --git a/pwnagotchi/ui/hw/libs/waveshare/v213bc/epd2in13bc.py b/pwnagotchi/ui/hw/libs/waveshare/v213bc/epd2in13bc.py
index 1d321eb..d542dc9 100644
--- a/pwnagotchi/ui/hw/libs/waveshare/v213bc/epd2in13bc.py
+++ b/pwnagotchi/ui/hw/libs/waveshare/v213bc/epd2in13bc.py
@@ -158,6 +158,15 @@ class EPD:
         self.send_command(0x12) # REFRESH
         self.ReadBusy()
 
+    def pwnclear(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(0x12) # REFRESH
+        self.ReadBusy()
+
     def sleep(self):
         self.send_command(0x02) # POWER_OFF
         self.ReadBusy()
diff --git a/pwnagotchi/ui/hw/waveshare213bc.py b/pwnagotchi/ui/hw/waveshare213bc.py
index 81b5770..70309a8 100644
--- a/pwnagotchi/ui/hw/waveshare213bc.py
+++ b/pwnagotchi/ui/hw/waveshare213bc.py
@@ -1,5 +1,4 @@
 import logging
-import time
 
 import pwnagotchi.ui.fonts as fonts
 from pwnagotchi.ui.hw.base import DisplayImpl
@@ -38,7 +37,6 @@ class Waveshare213bc(DisplayImpl):
         self._display = EPD()
         self._display.init()
         self._display.Clear()
-        time.sleep(1)
 
     def render(self, canvas):
         buf = self._display.getbuffer(canvas)
@@ -46,4 +44,4 @@ class Waveshare213bc(DisplayImpl):
 
     def clear(self):
         #pass
-        self._display.Clear()
+        self._display.pwnclear()

From cdd4c133366aac2749467bf4061d4460a849ba8e Mon Sep 17 00:00:00 2001
From: "mohesh.mohan" <moheshmohan@gmail.com>
Date: Thu, 12 Dec 2019 01:09:03 +0400
Subject: [PATCH 05/22] waveshare213b and waveshare213c support bug fixes

---
 .../ui/hw/libs/waveshare/v213bc/epd2in13bc.py | 143 +++++++++++++++++-
 1 file changed, 137 insertions(+), 6 deletions(-)

diff --git a/pwnagotchi/ui/hw/libs/waveshare/v213bc/epd2in13bc.py b/pwnagotchi/ui/hw/libs/waveshare/v213bc/epd2in13bc.py
index d542dc9..6c7467b 100644
--- a/pwnagotchi/ui/hw/libs/waveshare/v213bc/epd2in13bc.py
+++ b/pwnagotchi/ui/hw/libs/waveshare/v213bc/epd2in13bc.py
@@ -43,6 +43,109 @@ class EPD:
         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)
@@ -70,6 +173,11 @@ class EPD:
             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
@@ -96,6 +204,29 @@ class EPD:
         self.send_data(self.height & 0xff)
         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 getbuffer(self, image):
         # logging.debug("bufsiz = ",int(self.width/8) * self.height)
         buf = [0xFF] * (int(self.width/8) * self.height)
@@ -138,10 +269,10 @@ class EPD:
         self.send_command(0x10)
         for i in range(0, int(self.width * self.height / 8)):
             self.send_data(imageblack[i])
-        self.send_command(0x92)
+        epdconfig.delay_ms(10)
 
-        self.send_command(0x12) # REFRESH
-        self.ReadBusy()
+        self.SetFullReg()
+        self.TurnOnDisplay()
 
 
     def Clear(self):
@@ -162,10 +293,10 @@ class EPD:
         self.send_command(0x10)
         for i in range(0, int(self.width * self.height / 8)):
             self.send_data(0xFF)
-        self.send_command(0x92)
+        epdconfig.delay_ms(10)
 
-        self.send_command(0x12) # REFRESH
-        self.ReadBusy()
+        self.SetFullReg()
+        self.TurnOnDisplay()
 
     def sleep(self):
         self.send_command(0x02) # POWER_OFF

From 819146f83ac2924ee1096148e60b8093201afe8a Mon Sep 17 00:00:00 2001
From: "mohesh.mohan" <moheshmohan@gmail.com>
Date: Thu, 12 Dec 2019 01:56:52 +0400
Subject: [PATCH 06/22] waveshare213b and waveshare213c support bug fixes

---
 .../ui/hw/libs/waveshare/v213bc/epd2in13bc.py | 43 +++++++++++++++----
 1 file changed, 35 insertions(+), 8 deletions(-)

diff --git a/pwnagotchi/ui/hw/libs/waveshare/v213bc/epd2in13bc.py b/pwnagotchi/ui/hw/libs/waveshare/v213bc/epd2in13bc.py
index 6c7467b..6f3e685 100644
--- a/pwnagotchi/ui/hw/libs/waveshare/v213bc/epd2in13bc.py
+++ b/pwnagotchi/ui/hw/libs/waveshare/v213bc/epd2in13bc.py
@@ -29,6 +29,7 @@
 
 import logging
 from . import epdconfig
+from PIL import Image
 
 # Display resolution
 EPD_WIDTH       = 104
@@ -192,16 +193,29 @@ class EPD:
         self.send_command(0x04) # POWER_ON
         self.ReadBusy()
 
-        self.send_command(0x00) # PANEL_SETTING
-        self.send_data(0x8F)
+#        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)
+#        self.send_data(self.height & 0xff)
 
-        self.send_command(0x50) # VCOM_AND_DATA_INTERVAL_SETTING
-        self.send_data(0xF0)
+        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(0x61) # RESOLUTION_SETTING
-        self.send_data(self.width & 0xff)
-        self.send_data(self.height >> 8)
-        self.send_data(self.height & 0xff)
+        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):
@@ -266,7 +280,15 @@ class EPD:
         self.ReadBusy()
 
     def pwndisplay(self, imageblack):
+        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(imageblack[i])
         epdconfig.delay_ms(10)
@@ -291,6 +313,11 @@ class EPD:
 
     def pwnclear(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(0xFF)
         epdconfig.delay_ms(10)

From e06480e474c5e1704ec56911fbe02996396dd0e0 Mon Sep 17 00:00:00 2001
From: "mohesh.mohan" <moheshmohan@gmail.com>
Date: Fri, 13 Dec 2019 10:33:41 +0400
Subject: [PATCH 07/22] Waveshare213bc hung issues workaround

---
 .../ui/hw/libs/waveshare/v213bc/epd2in13bc.py | 32 +++++++++++++++++--
 1 file changed, 30 insertions(+), 2 deletions(-)

diff --git a/pwnagotchi/ui/hw/libs/waveshare/v213bc/epd2in13bc.py b/pwnagotchi/ui/hw/libs/waveshare/v213bc/epd2in13bc.py
index 6f3e685..39e986a 100644
--- a/pwnagotchi/ui/hw/libs/waveshare/v213bc/epd2in13bc.py
+++ b/pwnagotchi/ui/hw/libs/waveshare/v213bc/epd2in13bc.py
@@ -44,6 +44,7 @@ class EPD:
         self.width = EPD_WIDTH
         self.height = EPD_HEIGHT
 
+
     lut_vcomDC = [
         0x00, 0x08, 0x00, 0x00, 0x00, 0x02,
         0x60, 0x28, 0x28, 0x00, 0x00, 0x01,
@@ -183,15 +184,42 @@ class EPD:
         if (epdconfig.module_init() != 0):
             return -1
 
+        logging.debug("e-Paper 2.13bc preboot hang check")
+        while(epdconfig.digital_read(self.busy_pin) == 0):      # 0: idle, 1: busy
+            epdconfig.delay_ms(100)
+            self.reset()
+            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.GPIO.output(epdconfig.RST_PIN, 0)
+            epdconfig.GPIO.output(epdconfig.DC_PIN, 0)
+            epdconfig.GPIO.output(epdconfig.CS_PIN, 0)
+            logging.debug("Reset, powerdown, voltage off done")
+        logging.debug("e-Paper did not hungup")
+
+
         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) # BOOSTER_SOFT_START
         self.send_data(0x17)
         self.send_data(0x17)
         self.send_data(0x17)
 
         self.send_command(0x04) # POWER_ON
-        self.ReadBusy()
+        logging.debug("e-Paper 2.13bc bootup busy")
+        while(epdconfig.digital_read(self.busy_pin) == 0):      # 0: idle, 1: busy
+            epdconfig.delay_ms(100)
+        logging.debug("e-Paper booted")
+
 
 #        self.send_command(0x00) # PANEL_SETTING
 #        self.send_data(0x8F)
@@ -210,7 +238,7 @@ class EPD:
         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.width & 0xff)
         self.send_data((self.height >> 8) & 0xff)
         self.send_data(self.height& 0xff)
 

From 91447a2a3105ed26b0050d6d1208a9957c356448 Mon Sep 17 00:00:00 2001
From: "mohesh.mohan" <moheshmohan@gmail.com>
Date: Fri, 13 Dec 2019 11:01:40 +0400
Subject: [PATCH 08/22] Waveshare213bc hung issues workaround - optimizations

---
 .../ui/hw/libs/waveshare/v213bc/epd2in13bc.py       | 13 +++++++------
 1 file changed, 7 insertions(+), 6 deletions(-)

diff --git a/pwnagotchi/ui/hw/libs/waveshare/v213bc/epd2in13bc.py b/pwnagotchi/ui/hw/libs/waveshare/v213bc/epd2in13bc.py
index 39e986a..11451d3 100644
--- a/pwnagotchi/ui/hw/libs/waveshare/v213bc/epd2in13bc.py
+++ b/pwnagotchi/ui/hw/libs/waveshare/v213bc/epd2in13bc.py
@@ -184,7 +184,7 @@ class EPD:
         if (epdconfig.module_init() != 0):
             return -1
 
-        logging.debug("e-Paper 2.13bc preboot hang check")
+        logging.debug("e-Paper 2.13bc preboot Freeze recovery")
         while(epdconfig.digital_read(self.busy_pin) == 0):      # 0: idle, 1: busy
             epdconfig.delay_ms(100)
             self.reset()
@@ -196,8 +196,8 @@ class EPD:
             epdconfig.GPIO.output(epdconfig.RST_PIN, 0)
             epdconfig.GPIO.output(epdconfig.DC_PIN, 0)
             epdconfig.GPIO.output(epdconfig.CS_PIN, 0)
-            logging.debug("Reset, powerdown, voltage off done")
-        logging.debug("e-Paper did not hungup")
+            #logging.debug("Reset, powerdown, voltage off done")
+        logging.debug("e-Paper is not frozen now :)")
 
 
         self.reset()
@@ -218,8 +218,6 @@ class EPD:
         logging.debug("e-Paper 2.13bc bootup busy")
         while(epdconfig.digital_read(self.busy_pin) == 0):      # 0: idle, 1: busy
             epdconfig.delay_ms(100)
-        logging.debug("e-Paper booted")
-
 
 #        self.send_command(0x00) # PANEL_SETTING
 #        self.send_data(0x8F)
@@ -244,6 +242,9 @@ class EPD:
 
         self.send_command(0x82)	# vcom_DC setting
         self.send_data(0x28)
+
+        #self.Clear()
+        logging.debug("e-Paper booted")
         return 0
 
     def SetFullReg(self):
@@ -342,7 +343,7 @@ class EPD:
     def pwnclear(self):
         self.send_command(0x10)
         for i in range(0, int(self.width * self.height / 8)):
-            self.send_data(0x00)
+            self.send_data(0xFF)
         epdconfig.delay_ms(10)
 
         self.send_command(0x13)

From 6d71bcd9650858dae95593e18e94677394d22bab Mon Sep 17 00:00:00 2001
From: "mohesh.mohan" <moheshmohan@gmail.com>
Date: Fri, 13 Dec 2019 21:08:56 +0400
Subject: [PATCH 09/22] Display freeze recover enhancements

---
 pwnagotchi/ui/hw/libs/waveshare/v213bc/epd2in13bc.py | 5 +++++
 1 file changed, 5 insertions(+)

diff --git a/pwnagotchi/ui/hw/libs/waveshare/v213bc/epd2in13bc.py b/pwnagotchi/ui/hw/libs/waveshare/v213bc/epd2in13bc.py
index 11451d3..350db9d 100644
--- a/pwnagotchi/ui/hw/libs/waveshare/v213bc/epd2in13bc.py
+++ b/pwnagotchi/ui/hw/libs/waveshare/v213bc/epd2in13bc.py
@@ -188,6 +188,11 @@ class EPD:
         while(epdconfig.digital_read(self.busy_pin) == 0):      # 0: idle, 1: busy
             epdconfig.delay_ms(100)
             self.reset()
+            self.send_command(0x06) # BOOSTER_SOFT_START
+            self.send_data(0x17)
+            self.send_data(0x17)
+            self.send_data(0x17)
+            self.send_command(0x04) # POWER_ON
             self.send_command(0X50)
             self.send_data(0xf7)
             self.send_command(0X02) # power off

From a0bc911c0e670a588fba21ae15699bd6bacc1416 Mon Sep 17 00:00:00 2001
From: "mohesh.mohan" <moheshmohan@gmail.com>
Date: Fri, 13 Dec 2019 21:32:52 +0400
Subject: [PATCH 10/22] Display freeze recover enhancements - delay between
 poweron and off

---
 pwnagotchi/ui/hw/libs/waveshare/v213bc/epd2in13bc.py | 9 +++++++++
 1 file changed, 9 insertions(+)

diff --git a/pwnagotchi/ui/hw/libs/waveshare/v213bc/epd2in13bc.py b/pwnagotchi/ui/hw/libs/waveshare/v213bc/epd2in13bc.py
index 350db9d..71386fc 100644
--- a/pwnagotchi/ui/hw/libs/waveshare/v213bc/epd2in13bc.py
+++ b/pwnagotchi/ui/hw/libs/waveshare/v213bc/epd2in13bc.py
@@ -188,11 +188,20 @@ class EPD:
         while(epdconfig.digital_read(self.busy_pin) == 0):      # 0: idle, 1: busy
             epdconfig.delay_ms(100)
             self.reset()
+            epdconfig.delay_ms(200)
+            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)
+            epdconfig.delay_ms(200)
             self.send_command(0x06) # BOOSTER_SOFT_START
             self.send_data(0x17)
             self.send_data(0x17)
             self.send_data(0x17)
             self.send_command(0x04) # POWER_ON
+            epdconfig.delay_ms(200)
             self.send_command(0X50)
             self.send_data(0xf7)
             self.send_command(0X02) # power off

From d981b26842ff772215514abc0e01f534b171b557 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Beno=C3=AEt=20Allard?= <benoit.allard@gmx.de>
Date: Thu, 19 Dec 2019 19:37:16 +0100
Subject: [PATCH 11/22] ups_lite: Add auto-shutdown
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

Signed-off-by: Benoît Allard <benoit.allard@gmx.de>
---
 pwnagotchi/defaults.yml                | 3 +++
 pwnagotchi/plugins/default/ups_lite.py | 9 ++++++++-
 2 files changed, 11 insertions(+), 1 deletion(-)

diff --git a/pwnagotchi/defaults.yml b/pwnagotchi/defaults.yml
index bb0672d..029b449 100644
--- a/pwnagotchi/defaults.yml
+++ b/pwnagotchi/defaults.yml
@@ -116,6 +116,9 @@ main:
           enabled: false
         session-stats:
           enabled: true
+        ups_lite:
+          enabled: false
+          shutdown: 2  # Auto-shutdown when <= 2%
     # 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/ups_lite.py b/pwnagotchi/plugins/default/ups_lite.py
index ba86412..482472f 100644
--- a/pwnagotchi/plugins/default/ups_lite.py
+++ b/pwnagotchi/plugins/default/ups_lite.py
@@ -7,12 +7,14 @@
 # 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
+import logging
 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
+import pwnagotchi
 
 
 # TODO: add enable switch in config.yml an cleanup all to the best place
@@ -63,4 +65,9 @@ class UPSLite(plugins.Plugin):
             ui.remove_element('ups')
 
     def on_ui_update(self, ui):
-        ui.set('ups', "%2i%%" % self.ups.capacity())
+        capacity = self.ups.capacity()
+        ui.set('ups', "%2i%%" % capacity)
+        if capacity <= self.options['shutdown']:
+            logging.info('[ups_lite] Empty battery (<= %s%%): shuting down' % self.options['shutdown'])
+            ui.update(force=True, new_data={'status': 'Battery exhausted, bye ...'})
+            pwnagotchi.shutdown()

From 8d17cf0bd2153ae15d887b939a995e7e86229949 Mon Sep 17 00:00:00 2001
From: "T.A.C.T.I.C.A.L" <louis.dalibard@gmail.com>
Date: Fri, 20 Dec 2019 17:44:40 +0100
Subject: [PATCH 12/22] Updated French Translations

Updated French translations to fix typos and translation issues...
---
 pwnagotchi/locale/fr/LC_MESSAGES/voice.po | 22 +++++++++++-----------
 1 file changed, 11 insertions(+), 11 deletions(-)

diff --git a/pwnagotchi/locale/fr/LC_MESSAGES/voice.po b/pwnagotchi/locale/fr/LC_MESSAGES/voice.po
index 1ad744b..ea87698 100644
--- a/pwnagotchi/locale/fr/LC_MESSAGES/voice.po
+++ b/pwnagotchi/locale/fr/LC_MESSAGES/voice.po
@@ -72,13 +72,13 @@ msgstr "Je suis triste"
 
 #, fuzzy
 msgid "Leave me alone ..."
-msgstr "Je me sens si seul..."
+msgstr "Lache moi..."
 
 msgid "I'm mad at you!"
 msgstr "Je t'en veux !"
 
 msgid "I'm living the life!"
-msgstr "Je vis la vie !"
+msgstr "Je vis la belle vie !"
 
 msgid "I pwn therefore I am."
 msgstr "Je pwn donc je suis."
@@ -114,7 +114,7 @@ msgstr "Hum... au revoir {name}"
 
 #, python-brace-format
 msgid "{name} is gone ..."
-msgstr "{name} est part ..."
+msgstr "{name} est parti ..."
 
 #, python-brace-format
 msgid "Whoops ... {name} is gone."
@@ -144,7 +144,7 @@ msgstr "Où est tout le monde ?!"
 
 #, python-brace-format
 msgid "Napping for {secs}s ..."
-msgstr "Fais la sieste pendant {secs}s..."
+msgstr "Je fais la sieste pendant {secs}s..."
 
 msgid "Zzzzz"
 msgstr "Zzzzz"
@@ -161,11 +161,11 @@ msgstr "Zzz"
 
 #, python-brace-format
 msgid "Waiting for {secs}s ..."
-msgstr "Attends pendant {secs}s..."
+msgstr "J'attends pendant {secs}s..."
 
 #, python-brace-format
 msgid "Looking around ({secs}s)"
-msgstr "Regarde autour ({secs}s)"
+msgstr "J'observe ({secs}s)"
 
 #, python-brace-format
 msgid "Hey {what} let's be friends!"
@@ -193,11 +193,11 @@ msgstr "Je kick et je bannis {mac} !"
 
 #, python-brace-format
 msgid "Cool, we got {num} new handshake{plural}!"
-msgstr "Cool, on a {num} nouveaux handshake{plural} !"
+msgstr "Cool, on a {num} nouve(l/aux) handshake{plural} !"
 
 #, python-brace-format
 msgid "You have {count} new message{plural}!"
-msgstr "Tu as {num} nouveaux message{plural} !"
+msgstr "Tu as {num} nouveau(x) message{plural} !"
 
 msgid "Ops, something went wrong ... Rebooting ..."
 msgstr "Oups, quelque chose s'est mal passé... Redémarrage..."
@@ -208,18 +208,18 @@ msgstr "{num} stations kick\n"
 
 #, python-brace-format
 msgid "Made {num} new friends\n"
-msgstr "A {num} nouveaux amis\n"
+msgstr "A fait {num} nouve(l/aux) ami(s)\n"
 
 #, python-brace-format
 msgid "Got {num} handshakes\n"
 msgstr "A {num} handshakes\n"
 
 msgid "Met 1 peer"
-msgstr "1 pair rencontré"
+msgstr "1 camarade rencontré"
 
 #, python-brace-format
 msgid "Met {num} peers"
-msgstr "{num} pairs recontrés"
+msgstr "{num} camarades recontrés"
 
 #, python-brace-format
 msgid ""

From f375e4905f31a48f496aeac5f88f6e2b35f757ae Mon Sep 17 00:00:00 2001
From: "T.A.C.T.I.C.A.L" <louis.dalibard@gmail.com>
Date: Fri, 20 Dec 2019 17:46:42 +0100
Subject: [PATCH 13/22] Recompiled voice.mo

Recompiled voice.mo
---
 pwnagotchi/locale/fr/LC_MESSAGES/voice.mo | Bin 5194 -> 5267 bytes
 1 file changed, 0 insertions(+), 0 deletions(-)

diff --git a/pwnagotchi/locale/fr/LC_MESSAGES/voice.mo b/pwnagotchi/locale/fr/LC_MESSAGES/voice.mo
index 84418d032c45039e2ab34ddfef8f795374a3d4e8..ee4a5902214f3a2651a008a78a3af769bc2c389b 100644
GIT binary patch
delta 753
zcmXxgO=}ZD7{KvK(i%}~jIl<I+Nnshp-s#NV@dU56e~GY1o2Qrm?RSnCL7qi7*fI_
z2)!FU>d8yRf{-2s!5kDl)sG<R!Gjn50RD%ru<UQ<nSEYnKU<^J+ohoA1%%i=D8vyV
z#O)a&X0ePm_VEn9#zp*u3-}jF5sAY>#4w3R@iHcH4HMYIllT;G;2Sh`{-EhI7ZIL_
zvdBk-IEJgpnYf3hz(YKan`jbV`sX{o6O0r8iXZS7F5@=0IgLNi)D6<a)QO`RID_+8
zjCy;8s{~FF7+@Hm`yaf(^X%Va2EXGujB#Dl=o049!6kf!rr|D{oBzbKXu6ubIWz+o
z(G0lm`HM$*g1|F0iQ8xf`iSONe?tp@V+iAPVHS}<^T*OSjaPh&xR3o!-&<%dcGvel
z?q?sZvEUIcUZ&8KabfC9c;Qa^nyXc(-g45{RY$MLT-Gk6Z9APS$eg{B&n{-|Y<3(t
zR1Ji!#s$^wr^XLvV!`RMZZ!0G5P2L}FC|;$wsw13S{rR$X%E{eaaGn-y(2fAZgVJ|
zR=219Y^j=ctDQO^Y*|rF<*KTdu68Odr{hkx#9qSxlhJB)d8(vkZR|yN0-;h;jV{JE
cLsmaEdKVka)@3G>Aw@OyP`au!*^=|(A7&AA)&Kwi

delta 682
zcmXZYK}Zx)7{KvwbaN$hZQ5;3D|zdoT~k*iLL?Nt2tgD`bchZPI>;V&S7&!bR@QM(
zdJ%%9Q-RVcFA`1ArBe$9by%XK*SdC)P6pBc(}7`r?|bii-}~Ncj(p9%9qS05SR&th
zL@tX+u2<wLPUAQ}!kbvfQGAKF@C&j<eq$0(@d_pqB3aDfb-a%Q=;AyEsC7Q0o^z6r
zKx_uxeIh9wMAl>iwZJT<@hS4j8b4icL_WuU;(h#tA8-n5+~x+pL#^9Jt@9JF;om4u
z*@2ujFiIDiFoSV?j2V0uxrKS+102RS>Om)X7jJX@F<eAFa1Zt7uW<-JqV79JZTKH*
zgXw_x8)R??wX=t)8&{(F2I^D4LVfx-*okfAZ25uuBERqg{)s%rE@JIQjiX*H5t+tr
zVta@IxspXKbT6EaeT#?VmmI6p@i)R3y~&R7sBhbPu;8vZ-m+^PzgqS^liRGgOO<+M
zMCQz9$uHJTsqC-2>@iNUP`N0RX4NTp#&b(c<x<sa9m;u{&Ng@LDQiA#+3ziDu31a&
aSR2h)s+KbOe4aF?=+=$rR9lB8BmV*Z;9@rb


From 0587c4b09a3f605a838f70bf3129909237b354f9 Mon Sep 17 00:00:00 2001
From: dadav <33197631+dadav@users.noreply.github.com>
Date: Sun, 22 Dec 2019 13:17:07 +0100
Subject: [PATCH 14/22] Add switcher plugin

---
 pwnagotchi/defaults.yml                |   9 ++
 pwnagotchi/plugins/__init__.py         |   4 +
 pwnagotchi/plugins/default/switcher.py | 147 +++++++++++++++++++++++++
 3 files changed, 160 insertions(+)
 create mode 100644 pwnagotchi/plugins/default/switcher.py

diff --git a/pwnagotchi/defaults.yml b/pwnagotchi/defaults.yml
index bb0672d..8fe9bdd 100644
--- a/pwnagotchi/defaults.yml
+++ b/pwnagotchi/defaults.yml
@@ -13,6 +13,15 @@ main:
     custom_plugins:
     # which plugins to load and enable
     plugins:
+        switcher:
+            enabled: false
+            tasks:
+                bored:
+                    enabled: false
+                    reboot: true
+                    commands:
+                      - systemctl start fisch # see https://github.com/dadav/fisch
+                    stopwatch: 15 # timeout of the task (in minutes)
         grid:
             enabled: true
             report: false # don't report pwned networks by default!
diff --git a/pwnagotchi/plugins/__init__.py b/pwnagotchi/plugins/__init__.py
index 72a3909..c8a70c6 100644
--- a/pwnagotchi/plugins/__init__.py
+++ b/pwnagotchi/plugins/__init__.py
@@ -60,6 +60,10 @@ def on(event_name, *args, **kwargs):
 
 def locked_cb(lock_name, cb, *args, **kwargs):
     global locks
+
+    if lock_name not in locks:
+        locks[lock_name] = threading.Lock()
+
     with locks[lock_name]:
         cb(*args, *kwargs)
 
diff --git a/pwnagotchi/plugins/default/switcher.py b/pwnagotchi/plugins/default/switcher.py
new file mode 100644
index 0000000..2faceed
--- /dev/null
+++ b/pwnagotchi/plugins/default/switcher.py
@@ -0,0 +1,147 @@
+import os
+import logging
+from threading import Lock
+from functools import partial
+from pwnagotchi import plugins
+from pwnagotchi import reboot
+
+
+def systemd_dropin(name, content):
+    if not name.endswith('.service'):
+        name = '%s.service' % name
+
+    dropin_dir = "/etc/systemd/system/%s.d/" % name
+    os.makedirs(dropin_dir, exist_ok=True)
+
+    with open(os.path.join(dropin_dir, "switcher.conf"), "wt") as dropin:
+        dropin.write(content)
+
+    systemctl("daemon-reload")
+
+def systemctl(command, unit=None):
+    if unit:
+        os.system("/bin/systemctl %s %s" % (command, unit))
+    else:
+        os.system("/bin/systemctl %s" % command)
+
+def run_task(name, options):
+    task_service_name = "switcher-%s-task.service" % name
+    # save all the commands to a shell script
+    script_dir = '/usr/local/bin/'
+    script_path = os.path.join(script_dir, 'switcher-%s.sh' % name)
+    os.makedirs(script_dir, exist_ok=True)
+
+    with open(script_path, 'wt') as script_file:
+        script_file.write('#!/bin/bash\n')
+        for cmd in options['commands']:
+            script_file.write('%s\n' % cmd)
+
+    os.system("chmod a+x %s" % script_path)
+
+    # here we create the service which runs the tasks
+    with open('/etc/systemd/system/%s' % task_service_name, 'wt') as task_service:
+        task_service.write("""
+        [Unit]
+        Description=Executes the tasks of the pwnagotchi switcher plugin
+        After=pwnagotchi.service bettercap.service
+
+        [Service]
+        Type=oneshot
+        RemainAfterExit=yes
+        ExecStart=-/usr/local/bin/switcher-%s.sh
+        ExecStart=-/bin/rm /etc/systemd/system/%s
+        ExecStart=-/bin/rm /usr/local/bin/switcher-%s.sh
+
+        [Install]
+        WantedBy=multi-user.target
+        """ % (name, task_service_name, name))
+
+    if 'reboot' in options and options['reboot']:
+        # create a indication file!
+        # if this file is set, we want the switcher-tasks to run
+        open('/root/.switcher', 'a').close()
+
+        # add condition
+        systemd_dropin("pwnagotchi.service", """
+        [Unit]
+        ConditionPathExists=!/root/.switcher""")
+
+        systemd_dropin("bettercap.service", """
+        [Unit]
+        ConditionPathExists=!/root/.switcher""")
+
+        systemd_dropin(task_service_name, """
+        [Service]
+        ExecStart=-/bin/rm /root/.switcher
+        ExecStart=-/bin/rm /etc/systemd/system/switcher-reboot.timer""")
+
+        with open('/etc/systemd/system/switcher-reboot.timer', 'wt') as reboot_timer:
+            reboot_timer.write("""
+            [Unit]
+            Description=Reboot when time is up
+            ConditionPathExists=/root/.switcher
+
+            [Timer]
+            OnBootSec=%sm
+            Unit=reboot.target
+
+            [Install]
+            WantedBy=timers.target
+            """ % options['stopwatch'])
+
+        systemctl("daemon-reload")
+        systemctl("enable", "switcher-reboot.timer")
+        systemctl("enable", task_service_name)
+        reboot()
+        return
+
+    systemctl("daemon-reload")
+    systemctl("start", task_service_name)
+
+class Switcher(plugins.Plugin):
+    __author__ = '33197631+dadav@users.noreply.github.com'
+    __version__ = '0.0.1'
+    __name__ = 'switcher'
+    __license__ = 'GPL3'
+    __description__ = 'This plugin is a generic task scheduler.'
+
+    def __init__(self):
+        self.ready = False
+        self.lock = Lock()
+
+    def trigger(self, name, *args, **kwargs):
+        with self.lock:
+            function_name = name.lstrip('on_')
+            if function_name in self.tasks:
+                task = self.tasks[function_name]
+
+                # is this task enabled?
+                if 'enabled' not in task or ('enabled' in task and not task['enabled']):
+                    return
+
+                run_task(function_name, task)
+
+    def on_loaded(self):
+        if 'tasks' in self.options and self.options['tasks']:
+            self.tasks = self.options['tasks']
+        else:
+            logging.debug('[switcher] No tasks found...')
+            return
+
+        logging.info("[switcher] is loaded.")
+
+        # create hooks
+        logging.debug("[switcher] creating hooks...")
+        methods = ['webhook', 'internet_available', 'ui_setup', 'ui_update',
+                   'unload', 'display_setup', 'ready', 'ai_ready', 'ai_policy',
+                   'ai_training_start', 'ai_training_step', 'ai_training_end',
+                   'ai_best_reward', 'ai_worst_reward', 'free_channel',
+                   'bored', 'sad', 'excited', 'lonely', 'rebooting', 'wait',
+                   'sleep', 'wifi_update', 'unfiltered_ap_list', 'association',
+                   'deauthentication', 'channel_hop', 'handshake', 'epoch',
+                   'peer_detected', 'peer_lost']
+
+        for m in methods:
+            setattr(Switcher, 'on_%s' % m, partial(self.trigger, m))
+
+        logging.debug("[switcher] triggers are ready to fire...")

From c300e737266dc97a9affc7e6ab4823526c77250c Mon Sep 17 00:00:00 2001
From: xenDE <daniel@mameso.com>
Date: Thu, 26 Dec 2019 18:51:00 +0100
Subject: [PATCH 15/22] webgpsmap: add function for download the map as one
 html file with json-positions inside

now it's possible to download the map as one Html file for local use on your pc or host somewhere for mobile use without your pownagotchi. uri: /plugins/webgpsmap/offlinemap
---
 pwnagotchi/plugins/default/webgpsmap.py | 20 +++++++++++++++++++-
 1 file changed, 19 insertions(+), 1 deletion(-)

diff --git a/pwnagotchi/plugins/default/webgpsmap.py b/pwnagotchi/plugins/default/webgpsmap.py
index 3d7ae4d..f033da4 100644
--- a/pwnagotchi/plugins/default/webgpsmap.py
+++ b/pwnagotchi/plugins/default/webgpsmap.py
@@ -22,7 +22,7 @@ from functools import lru_cache
 
 class Webgpsmap(plugins.Plugin):
     __author__ = 'https://github.com/xenDE and https://github.com/dadav'
-    __version__ = '1.3.0'
+    __version__ = '1.3.1'
     __name__ = 'webgpsmap'
     __license__ = 'GPL3'
     __description__ = 'a plugin for pwnagotchi that shows a openstreetmap with positions of ap-handshakes in your webbrowser'
@@ -49,6 +49,7 @@ class Webgpsmap(plugins.Plugin):
         """
         # defaults:
         response_header_contenttype = None
+        response_header_contentdisposition = None
         response_mimetype = "application/xhtml+xml"
         if not self.ready:
             try:
@@ -89,6 +90,21 @@ class Webgpsmap(plugins.Plugin):
                     except Exception as error:
                         logging.error(f"[webgpsmap] error: {error}")
                         return
+                elif path.startswith('offlinemap'):
+                    # for download an all-in-one html file with positions.json inside
+                    try:
+                        self.ALREADY_SENT = list()
+                        json_data = json.dumps(self.load_gps_from_dir(self.config['bettercap']['handshakes']))
+                        html_data = self.get_html()
+                        html_data = html_data.replace('var positions = [];', 'var positions = ' + json_data + ';positionsLoaded=true;drawPositions();')
+                        response_data = bytes(html_data, "utf-8")
+                        response_status = 200
+                        response_mimetype = "application/xhtml+xml"
+                        response_header_contenttype = 'text/html'
+                        response_header_contentdisposition = 'attachment; filename=webgpsmap.html';
+                    except Exception as error:
+                        logging.error(f"[webgpsmap] offlinemap: error: {error}")
+                        return
                 # elif path.startswith('/newest'):
                 #     # returns all positions newer then timestamp
                 #     response_data = bytes(json.dumps(self.load_gps_from_dir(self.config['bettercap']['handshakes']), newest_only=True), "utf-8")
@@ -119,6 +135,8 @@ class Webgpsmap(plugins.Plugin):
             r = Response(response=response_data, status=response_status, mimetype=response_mimetype)
             if response_header_contenttype is not None:
                 r.headers["Content-Type"] = response_header_contenttype
+            if response_header_contentdisposition is not None:
+                r.headers["Content-Disposition"] = response_header_contentdisposition
             return r
         except Exception as error:
             logging.error(f"[webgpsmap] error: {error}")

From bb7737762cca7c35c2d71f44aa2d579229845d2a Mon Sep 17 00:00:00 2001
From: WheresAlice <WheresAlice@users.noreply.github.com>
Date: Fri, 27 Dec 2019 17:57:03 +0000
Subject: [PATCH 16/22] fix incorrect dependency for Crypto

Signed-off-by: WheresAlice <WheresAlice@users.noreply.github.com>
---
 requirements.txt | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/requirements.txt b/requirements.txt
index 2b8eecf..1cb7d5e 100644
--- a/requirements.txt
+++ b/requirements.txt
@@ -1,4 +1,4 @@
-crypto==1.4.1
+pycryptodome==3.9.4
 requests==2.21.0
 PyYAML==5.1
 scapy==2.4.3

From 4164e7c0673e8695e3adc2802d23454465ebc0c7 Mon Sep 17 00:00:00 2001
From: xenDE <daniel@mameso.com>
Date: Sun, 29 Dec 2019 14:57:17 +0100
Subject: [PATCH 17/22] webgpsmap: get current position and set marker on map
 in interval (30s)

used the browsers geolocation function to get the current location, show a marker and center the map there.
on success (user allows usage of get-current-position) it gets position every 30s and reposition the marker without center the map to the marker.
---
 pwnagotchi/plugins/default/webgpsmap.html | 11 +++++++++++
 1 file changed, 11 insertions(+)

diff --git a/pwnagotchi/plugins/default/webgpsmap.html b/pwnagotchi/plugins/default/webgpsmap.html
index 6c401ec..905a13c 100644
--- a/pwnagotchi/plugins/default/webgpsmap.html
+++ b/pwnagotchi/plugins/default/webgpsmap.html
@@ -270,5 +270,16 @@
       positionsLoaded = true;
       drawPositions();
     });
+    // get current position and set marker in interval
+    var myLocationMarker = {};
+    function onLocationFound(e) {
+      if (myLocationMarker != undefined) {
+        mymap.removeLayer(myLocationMarker);
+      };
+      myLocationMarker = L.marker(e.latlng).addTo(mymap);
+      setTimeout(function(){ mymap.locate(); }, 30000);
+    }
+    mymap.on('locationfound', onLocationFound);
+    mymap.locate({setView: true});
   </script>
 </body></html>

From d435ef2ba9448b29d84c64f05e283a96d81ddeb0 Mon Sep 17 00:00:00 2001
From: foreign-sub <51928805+foreign-sub@users.noreply.github.com>
Date: Sun, 29 Dec 2019 22:10:22 +0100
Subject: [PATCH 18/22] Add PACKER_VERSION to Makefile, bump packer to 1.4.5

Signed-off-by: foreign-sub <51928805+foreign-sub@users.noreply.github.com>
---
 Makefile | 3 ++-
 1 file changed, 2 insertions(+), 1 deletion(-)

diff --git a/Makefile b/Makefile
index 117e222..cbf4f6f 100644
--- a/Makefile
+++ b/Makefile
@@ -1,10 +1,11 @@
+PACKER_VERSION=1.4.5
 PWN_HOSTNAME=pwnagotchi
 PWN_VERSION=master
 
 all: clean install image
 
 install:
-	curl https://releases.hashicorp.com/packer/1.3.5/packer_1.3.5_linux_amd64.zip -o /tmp/packer.zip
+	curl https://releases.hashicorp.com/packer/$(PACKER_VERSION)/packer_$(PACKER_VERSION)_linux_amd64.zip -o /tmp/packer.zip
 	unzip /tmp/packer.zip -d /tmp
 	sudo mv /tmp/packer /usr/bin/packer
 	git clone https://github.com/solo-io/packer-builder-arm-image /tmp/packer-builder-arm-image

From c09b72ff7eaebcf328f551ee15337b1054e5e8f9 Mon Sep 17 00:00:00 2001
From: Enrico Spinielli <enrico.spinielli@gmail.com>
Date: Tue, 31 Dec 2019 14:54:38 +0100
Subject: [PATCH 19/22] typo (maybe)

---
 builder/pwnagotchi.yml | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/builder/pwnagotchi.yml b/builder/pwnagotchi.yml
index 8e68b39..be48177 100644
--- a/builder/pwnagotchi.yml
+++ b/builder/pwnagotchi.yml
@@ -47,7 +47,7 @@
           - firmware-misc-nonfree
           - firmware-realtek
         remove:
-          - rasberrypi-net-mods
+          - raspberrypi-net-mods
           - dhcpcd5
           - triggerhappy
           - wpa_supplicant

From 215af0fc88971c01ef7d45cba126579073a745c1 Mon Sep 17 00:00:00 2001
From: dadav <33197631+dadav@users.noreply.github.com>
Date: Thu, 2 Jan 2020 14:07:27 +0100
Subject: [PATCH 20/22] Prevents permanent tfevent files

---
 builder/data/etc/systemd/system/pwnagotchi.service | 1 +
 1 file changed, 1 insertion(+)

diff --git a/builder/data/etc/systemd/system/pwnagotchi.service b/builder/data/etc/systemd/system/pwnagotchi.service
index 84287c5..1c75960 100644
--- a/builder/data/etc/systemd/system/pwnagotchi.service
+++ b/builder/data/etc/systemd/system/pwnagotchi.service
@@ -6,6 +6,7 @@ After=pwngrid-peer.service
 
 [Service]
 Type=simple
+WorkingDirectory=/tmp
 PermissionsStartOnly=true
 ExecStart=/usr/bin/pwnagotchi-launcher
 Restart=always

From b1d61d95e60b76df651e7ab23e540a32b72e84bf Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Hendrik=20S=C3=B6bbing?= <h.soebbing@shopware.com>
Date: Fri, 3 Jan 2020 09:12:26 +0100
Subject: [PATCH 21/22] Small german language fixes
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

Signed-off-by: Hendrik Söbbing <h.soebbing@shopware.com>
---
 pwnagotchi/locale/de/LC_MESSAGES/voice.mo | Bin 4928 -> 4943 bytes
 pwnagotchi/locale/de/LC_MESSAGES/voice.po |  13 +++++++------
 2 files changed, 7 insertions(+), 6 deletions(-)

diff --git a/pwnagotchi/locale/de/LC_MESSAGES/voice.mo b/pwnagotchi/locale/de/LC_MESSAGES/voice.mo
index f0885f3437aaaf463bb7426625d40f732547c565..06ce4f7b4fdb3da745a793b664cef142559e12af 100644
GIT binary patch
delta 536
zcmXZYze@sP9LMqJOfv1XQ>p9+WMyKZ5fl^{EkQ_YGzN(sSzK3O4jN%o|A5gDZ7og_
z2~JUg4J}~=4bj>zO%WvYe(;%I&*$!Y-|zFi$7myZGv_Ex2_c%g5W_g=6`~vGaR8Sw
zfEzf82iT7nIEJ^li0?Ro0bje%0*1)zDC=$P#Z#1hFO@Z)D2X5sURB{2_K^Qn)(LOq
z2*&USxvK~p?Ewjt4Odj&M7c--{dkUYa0T6Xk5PO^d9kKZq8SGl)gu_g8BF0c7BGSp
zWJ&RYvf(GnLEq}VkKfcso<-Sb6=mHn$^}nQUi3^ozeW%FDa9edp@E&)LKiyuN@g8p
z4l0MRi#(3Z7I9=5v2^v@;r6U$vR3^rmou$$OWz5Z_NYjuEo(n~WT*Gc<9Ili$<}Lj
cwdj3tXouybk#yQ--mK=0Eln#{Km6O;KNi?StN;K2

delta 520
zcmXZYu`feW6vy$?*M^q1UU`a!DC#j#Bqr6((4>Dr2UDXjh_;9>f^@0GD7tkqSPX_H
zjm6SV77>Zj#$-VvF?0~VH@(y6{?5DaoO|z+-LmV;MmZ)TUw)BMv;rc%xP&8^L=!VO
zgIhR+=QxSixQY)rjox6VPaKEIX;k}jIEcrn`d(;nf>M?N3io>B3-*z}bv6jk<Ot5<
z2Xa;kb1KznN}EJAFoSB~9)|D?)!-WXu#Ps~q1yLqmPMj0+7!ky!e5KHf^pnI^+Gk|
zlF~#AA5p#7oBsZXVREd$(<gyy@H(ms?xQ;JsjgpQ5BWC5VxC0{yYLgc@ki%hojr6>
zbw7I1LT1Vsa&}p`YP)@&v~ze+bqBVs2}x!1`Q7MIF<Wp>=AuQXRBBWLO~Y_<)ipEG
NsF)kB#(QYf^$%vLJn8@d

diff --git a/pwnagotchi/locale/de/LC_MESSAGES/voice.po b/pwnagotchi/locale/de/LC_MESSAGES/voice.po
index a7ae871..954fce1 100644
--- a/pwnagotchi/locale/de/LC_MESSAGES/voice.po
+++ b/pwnagotchi/locale/de/LC_MESSAGES/voice.po
@@ -26,7 +26,7 @@ msgid "New day, new hunt, new pwns!"
 msgstr "Neuer Tag, neue Jagd, neue Pwns!"
 
 msgid "Hack the Planet!"
-msgstr "Hack den Planet!"
+msgstr "Hack den Planeten!"
 
 msgid "AI ready."
 msgstr "KI bereit."
@@ -35,7 +35,7 @@ msgid "The neural network is ready."
 msgstr "Das neurale Netz ist bereit."
 
 msgid "Generating keys, do not turn off ..."
-msgstr "Generiere Keys, nicht ausschalten..."
+msgstr "Generiere Schlüssel, nicht ausschalten..."
 
 #, python-brace-format
 msgid "Hey, channel {channel} is free! Your AP will say thanks."
@@ -83,7 +83,7 @@ msgid "I pwn therefore I am."
 msgstr "Ich pwne, also bin ich."
 
 msgid "So many networks!!!"
-msgstr "So viele Netwerke!!!"
+msgstr "So viele Netzwerke!!!"
 
 msgid "I'm having so much fun!"
 msgstr "Ich habe sooo viel Spaß!"
@@ -93,7 +93,7 @@ msgstr "Mein Verbrechen ist das der Neugier..."
 
 #, python-brace-format
 msgid "Hello {name}! Nice to meet you."
-msgstr "Hallo {name}, nett Dich kennenzulernen."
+msgstr "Hallo {name}, schön Dich kennenzulernen."
 
 #, python-brace-format
 msgid "Yo {name}! Sup?"
@@ -203,11 +203,11 @@ msgstr "Ops, da ist was schief gelaufen... Starte neu..."
 
 #, python-brace-format
 msgid "Kicked {num} stations\n"
-msgstr "{num} Stationen gekicked\n"
+msgstr "{num} Stationen gekickt\n"
 
 #, python-brace-format
 msgid "Made {num} new friends\n"
-msgstr "{num} Freunde gefunden\n"
+msgstr "{num} neue Freunde gefunden\n"
 
 #, python-brace-format
 msgid "Got {num} handshakes\n"
@@ -247,3 +247,4 @@ msgstr "Minute"
 
 msgid "second"
 msgstr "Sekunde"
+

From 6075296884e7e7b47df46a5cce433d2d0b508679 Mon Sep 17 00:00:00 2001
From: dadav <33197631+dadav@users.noreply.github.com>
Date: Wed, 18 Dec 2019 18:58:28 +0100
Subject: [PATCH 22/22] Switch to toml

---
 bin/pwnagotchi                                |   6 +-
 pwnagotchi/agent.py                           |   2 +-
 pwnagotchi/defaults.toml                      | 205 ++++++++++++
 pwnagotchi/defaults.yml                       | 307 ------------------
 pwnagotchi/plugins/default/auto-update.py     |   2 +-
 pwnagotchi/plugins/default/bt-tether.py       |   4 +-
 pwnagotchi/plugins/default/net-pos.py         |   2 +-
 pwnagotchi/plugins/default/onlinehashcrack.py |   2 +-
 pwnagotchi/plugins/default/webcfg.py          |  13 +-
 pwnagotchi/plugins/default/wpa-sec.py         |   4 +-
 pwnagotchi/utils.py                           |  44 ++-
 requirements.txt                              |   1 +
 12 files changed, 252 insertions(+), 340 deletions(-)
 create mode 100644 pwnagotchi/defaults.toml
 delete mode 100644 pwnagotchi/defaults.yml

diff --git a/bin/pwnagotchi b/bin/pwnagotchi
index e990793..ac488b6 100755
--- a/bin/pwnagotchi
+++ b/bin/pwnagotchi
@@ -90,9 +90,9 @@ def do_auto_mode(agent):
 if __name__ == '__main__':
     parser = argparse.ArgumentParser()
 
-    parser.add_argument('-C', '--config', action='store', dest='config', default='/etc/pwnagotchi/default.yml',
+    parser.add_argument('-C', '--config', action='store', dest='config', default='/etc/pwnagotchi/default.toml',
                         help='Main configuration file.')
-    parser.add_argument('-U', '--user-config', action='store', dest='user_config', default='/etc/pwnagotchi/config.yml',
+    parser.add_argument('-U', '--user-config', action='store', dest='user_config', default='/etc/pwnagotchi/config.toml',
                         help='If this file exists, configuration will be merged and this will override default values.')
 
     parser.add_argument('--manual', dest="do_manual", action="store_true", default=False, help="Manual mode.")
@@ -119,7 +119,7 @@ if __name__ == '__main__':
 
     config = utils.load_config(args)
     if args.print_config:
-        print(yaml.dump(config, default_flow_style=False))
+        print(toml.dumps(config))
         exit(0)
 
     utils.setup_logging(args, config)
diff --git a/pwnagotchi/agent.py b/pwnagotchi/agent.py
index b5e2d2f..bfaf1db 100644
--- a/pwnagotchi/agent.py
+++ b/pwnagotchi/agent.py
@@ -30,7 +30,7 @@ class Agent(Client, Automata, AsyncAdvertiser, AsyncTrainer):
         AsyncTrainer.__init__(self, config)
 
         self._started_at = time.time()
-        self._filter = None if config['main']['filter'] is None else re.compile(config['main']['filter'])
+        self._filter = None if not config['main']['filter'] else re.compile(config['main']['filter'])
         self._current_channel = 0
         self._tot_aps = 0
         self._aps_on_channel = 0
diff --git a/pwnagotchi/defaults.toml b/pwnagotchi/defaults.toml
new file mode 100644
index 0000000..5f1b95e
--- /dev/null
+++ b/pwnagotchi/defaults.toml
@@ -0,0 +1,205 @@
+main.name = ""
+main.lang = "en"
+main.custom_plugins = ""
+main.iface = "mon0"
+main.mon_start_cmd = "/usr/bin/monstart"
+main.mon_stop_cmd = "/usr/bin/monstop"
+main.mon_max_blind_epochs = 50.0
+main.no_restart = false
+main.whitelist = [
+  "EXAMPLE_NETWORK",
+  "ANOTHER_EXAMPLE_NETWORK",
+  "fo:od:ba:be:fo:od",
+  "fo:od:ba"
+]
+main.filter = ""
+
+
+main.plugins.grid.enabled = true
+main.plugins.grid.report = false
+main.plugins.grid.exclude = [
+  "YourHomeNetworkHere"
+]
+
+main.plugins.auto-update.enabled = true
+main.plugins.auto-update.install = true
+main.plugins.auto-update.interval = 1.0
+
+main.plugins.net-pos.enabled = false
+main.plugins.net-pos.api_key = "test"
+
+main.plugins.gps.enabled = false
+main.plugins.gps.speed = 19200.0
+main.plugins.gps.device = "/dev/ttyUSB0"
+
+main.plugins.webgpsmap.enabled = false
+
+main.plugins.onlinehashcrack.enabled = false
+main.plugins.onlinehashcrack.email = ""
+
+main.plugins.wpa-sec.enabled = false
+main.plugins.wpa-sec.api_key = ""
+main.plugins.wpa-sec.api_url = "https://wpa-sec.stanev.org"
+main.plugins.wpa-sec.download_results = false
+
+main.plugins.wigle.enabled = false
+main.plugins.wigle.api_key = ""
+
+main.plugins.bt-tether.enabled = false
+
+main.plugins.bt-tether.devices.android-phone.enabled = false
+main.plugins.bt-tether.devices.android-phone.search_order = 1.0
+main.plugins.bt-tether.devices.android-phone.mac = ""
+main.plugins.bt-tether.devices.android-phone.ip = "192.168.44.44"
+main.plugins.bt-tether.devices.android-phone.netmask = 24.0
+main.plugins.bt-tether.devices.android-phone.interval = 1.0
+main.plugins.bt-tether.devices.android-phone.scantime = 10.0
+main.plugins.bt-tether.devices.android-phone.max_tries = 10.0
+main.plugins.bt-tether.devices.android-phone.share_internet = false
+main.plugins.bt-tether.devices.android-phone.priority = 1.0
+
+main.plugins.bt-tether.devices.ios-phone.enabled = false
+main.plugins.bt-tether.devices.ios-phone.search_order = 2.0
+main.plugins.bt-tether.devices.ios-phone.mac = ""
+main.plugins.bt-tether.devices.ios-phone.ip = "172.20.10.6"
+main.plugins.bt-tether.devices.ios-phone.netmask = 24.0
+main.plugins.bt-tether.devices.ios-phone.interval = 5.0
+main.plugins.bt-tether.devices.ios-phone.scantime = 20.0
+main.plugins.bt-tether.devices.ios-phone.max_tries = 0.0
+main.plugins.bt-tether.devices.ios-phone.share_internet = false
+main.plugins.bt-tether.devices.ios-phone.priority = 999.0
+
+main.plugins.memtemp.enabled = false
+main.plugins.memtemp.scale = "celsius"
+main.plugins.memtemp.orientation = "horizontal"
+
+main.plugins.paw-gps.enabled = false
+main.plugins.paw-gps.ip = ""
+
+main.plugins.gpio_buttons.enabled = false
+
+main.plugins.led.enabled = true
+main.plugins.led.led = 0.0
+main.plugins.led.delay = 200.0
+main.plugins.led.patterns.loaded = "oo  oo  oo oo  oo  oo  oo"
+main.plugins.led.patterns.updating = "oo  oo  oo oo  oo  oo  oo"
+main.plugins.led.patterns.unread_inbox = "oo  oo  oo oo  oo  oo  oo"
+main.plugins.led.patterns.ready = "oo  oo  oo oo  oo  oo  oo"
+main.plugins.led.patterns.ai_ready = "oo  oo  oo oo  oo  oo  oo"
+main.plugins.led.patterns.ai_training_start = "oo  oo  oo oo  oo  oo  oo"
+main.plugins.led.patterns.ai_best_reward = "oo  oo  oo oo  oo  oo  oo"
+main.plugins.led.patterns.ai_worst_reward = "oo  oo  oo oo  oo  oo  oo"
+main.plugins.led.patterns.bored = "oo  oo  oo oo  oo  oo  oo"
+main.plugins.led.patterns.sad = "oo  oo  oo oo  oo  oo  oo"
+main.plugins.led.patterns.excited = "oo  oo  oo oo  oo  oo  oo"
+main.plugins.led.patterns.lonely = "oo  oo  oo oo  oo  oo  oo"
+main.plugins.led.patterns.rebooting = "oo  oo  oo oo  oo  oo  oo"
+main.plugins.led.patterns.wait = "oo  oo  oo oo  oo  oo  oo"
+main.plugins.led.patterns.sleep = "oo  oo  oo oo  oo  oo  oo"
+main.plugins.led.patterns.wifi_update = "oo  oo  oo oo  oo  oo  oo"
+main.plugins.led.patterns.association = "oo  oo  oo oo  oo  oo  oo"
+main.plugins.led.patterns.deauthentication = "oo  oo  oo oo  oo  oo  oo"
+main.plugins.led.patterns.handshake = "oo  oo  oo oo  oo  oo  oo"
+main.plugins.led.patterns.epoch = "oo  oo  oo oo  oo  oo  oo"
+main.plugins.led.patterns.peer_detected = "oo  oo  oo oo  oo  oo  oo"
+main.plugins.led.patterns.peer_lost = "oo  oo  oo oo  oo  oo  oo"
+
+main.plugins.session-stats.enabled = true
+
+main.log.path = "/var/log/pwnagotchi.log"
+main.log.rotation.enabled = true
+main.log.rotation.size = "10M"
+
+ai.enabled = true
+ai.path = "/root/brain.nn"
+ai.laziness = 0.1
+ai.epochs_per_episode = 50.0
+
+ai.params.gamma = 0.99
+ai.params.n_steps = 1.0
+ai.params.vf_coef = 0.25
+ai.params.ent_coef = 0.01
+ai.params.max_grad_norm = 0.5
+ai.params.learning_rate = 0.001
+ai.params.alpha = 0.99
+ai.params.epsilon = 0.00001
+ai.params.verbose = 1.0
+ai.params.lr_schedule = "constant"
+
+personality.advertise = true
+personality.deauth = true
+personality.associate = true
+personality.channels = []
+personality.min_rssi = -200.0
+personality.ap_ttl = 120.0
+personality.sta_ttl = 300.0
+personality.recon_time = 30.0
+personality.max_inactive_scale = 2.0
+personality.recon_inactive_multiplier = 2.0
+personality.hop_recon_time = 10.0
+personality.min_recon_time = 5.0
+personality.max_interactions = 3.0
+personality.max_misses_for_recon = 5.0
+personality.excited_num_epochs = 10.0
+personality.bored_num_epochs = 15.0
+personality.sad_num_epochs = 25.0
+personality.bond_encounters_factor = 20000.0
+
+ui.fps = 0.0
+
+ui.faces.look_r = "( ⚆_⚆)"
+ui.faces.look_l = "(☉_☉ )"
+ui.faces.look_r_happy = "( ◕‿◕)"
+ui.faces.look_l_happy = "(◕‿◕ )"
+ui.faces.sleep = "(⇀‿‿↼)"
+ui.faces.sleep2 = "(≖‿‿≖)"
+ui.faces.awake = "(◕‿‿◕)"
+ui.faces.bored = "(-__-)"
+ui.faces.intense = "(°▃▃°)"
+ui.faces.cool = "(⌐■_■)"
+ui.faces.happy = "(•‿‿•)"
+ui.faces.excited = "(ᵔ◡◡ᵔ)"
+ui.faces.grateful = "(^‿‿^)"
+ui.faces.motivated = "(☼‿‿☼)"
+ui.faces.demotivated = "(≖__≖)"
+ui.faces.smart = "(✜‿‿✜)"
+ui.faces.lonely = "(ب__ب)"
+ui.faces.sad = "(╥☁╥ )"
+ui.faces.angry = "(-_-')"
+ui.faces.friend = "(♥‿‿♥)"
+ui.faces.broken = "(☓‿‿☓)"
+ui.faces.debug = "(#__#)"
+
+ui.web.enabled = true
+ui.web.address = "0.0.0.0"
+ui.web.username = "changeme"
+ui.web.password = "changeme"
+ui.web.origin = ""
+ui.web.port = 8080.0
+ui.web.on_frame = ""
+
+ui.display.enabled = true
+ui.display.rotation = 180.0
+ui.display.type = "waveshare_2"
+ui.display.color = "black"
+
+bettercap.scheme = "http"
+bettercap.hostname = "localhost"
+bettercap.port = 8081.0
+bettercap.username = "pwnagotchi"
+bettercap.password = "pwnagotchi"
+bettercap.handshakes = "/root/handshakes"
+bettercap.silence = [
+  "ble.device.new",
+  "ble.device.lost",
+  "ble.device.disconnected",
+  "ble.device.connected",
+  "ble.device.service.discovered",
+  "ble.device.characteristic.discovered",
+  "wifi.client.new",
+  "wifi.client.lost",
+  "wifi.client.probe",
+  "wifi.ap.new",
+  "wifi.ap.lost",
+  "mod.started"
+]
diff --git a/pwnagotchi/defaults.yml b/pwnagotchi/defaults.yml
deleted file mode 100644
index 8dc8623..0000000
--- a/pwnagotchi/defaults.yml
+++ /dev/null
@@ -1,307 +0,0 @@
-#
-# This file is recreated with default settings on every pwnagotchi restart,
-# use /etc/pwnagotchi/config.yml to configure this unit.
-#
-#
-# main algorithm configuration
-main:
-    # 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
-    # custom plugins path, if null only default plugins with be loaded
-    custom_plugins:
-    # which plugins to load and enable
-    plugins:
-        switcher:
-            enabled: false
-            tasks:
-                bored:
-                    enabled: false
-                    reboot: true
-                    commands:
-                      - systemctl start fisch # see https://github.com/dadav/fisch
-                    stopwatch: 15 # timeout of the task (in minutes)
-        grid:
-            enabled: true
-            report: false # don't report pwned networks by default!
-            exclude: # do not report the following networks (accepts both ESSIDs and BSSIDs)
-                - YourHomeNetworkHere
-        auto-update:
-            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
-        net-pos:
-            enabled: false
-            api_key: 'test'
-        gps:
-            enabled: false
-            speed: 19200
-            device: /dev/ttyUSB0
-        webgpsmap:
-            enabled: false
-        onlinehashcrack:
-            enabled: false
-            email: ~
-        wpa-sec:
-            enabled: false
-            api_key: ~
-            api_url: "https://wpa-sec.stanev.org"
-            download_results: false
-        wigle:
-            enabled: false
-            api_key: ~
-        bt-tether:
-            enabled: false # if you want to use this, set ui.display.web.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
-                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:
-                enabled: false
-                search_order: 2 # search for this second
-                mac: ~ # mac of your bluetooth device
-                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
-                max_tries: 0 # infinity
-                share_internet: false
-                priority: 999 # routing priority
-        memtemp: # Display memory usage, cpu load and cpu temperature on screen
-            enabled: false
-            scale: celsius
-            orientation: horizontal # horizontal/vertical
-        paw-gps:
-            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: 'touch /root/.pwnagotchi-auto && systemctl restart pwnagotchi'
-              #21: 'shutdown -h now'
-        led:
-            enabled: true
-            # for /sys/class/leds/led0/brightness
-            led: 0
-            # time in milliseconds for each element of the patterns
-            delay: 200
-            # o=on space=off, comment the ones you don't want
-            patterns:
-                loaded: 'oo  oo  oo oo  oo  oo  oo'
-                updating: 'oo  oo  oo oo  oo  oo  oo'
-                # internet_available: 'oo  oo  oo oo  oo  oo  oo'
-                unread_inbox: 'oo  oo  oo oo  oo  oo  oo'
-                ready: 'oo  oo  oo oo  oo  oo  oo'
-                ai_ready: 'oo  oo  oo oo  oo  oo  oo'
-                ai_training_start: 'oo  oo  oo oo  oo  oo  oo'
-                ai_best_reward: 'oo  oo  oo oo  oo  oo  oo'
-                ai_worst_reward: 'oo  oo  oo oo  oo  oo  oo'
-                bored: 'oo  oo  oo oo  oo  oo  oo'
-                sad: 'oo  oo  oo oo  oo  oo  oo'
-                excited: 'oo  oo  oo oo  oo  oo  oo'
-                lonely: 'oo  oo  oo oo  oo  oo  oo'
-                rebooting: 'oo  oo  oo oo  oo  oo  oo'
-                wait: 'oo  oo  oo oo  oo  oo  oo'
-                sleep: 'oo  oo  oo oo  oo  oo  oo'
-                wifi_update: 'oo  oo  oo oo  oo  oo  oo'
-                association: 'oo  oo  oo oo  oo  oo  oo'
-                deauthentication: 'oo  oo  oo oo  oo  oo  oo'
-                handshake: 'oo  oo  oo oo  oo  oo  oo'
-                epoch: 'oo  oo  oo oo  oo  oo  oo'
-                peer_detected: 'oo  oo  oo oo  oo  oo  oo'
-                peer_lost: 'oo  oo  oo oo  oo  oo  oo'
-        webcfg:
-          enabled: false
-        session-stats:
-          enabled: true
-        ups_lite:
-          enabled: false
-          shutdown: 2  # Auto-shutdown when <= 2%
-    # monitor interface to use
-    iface: mon0
-    # command to run to bring the mon interface up in case it's not up already
-    mon_start_cmd: /usr/bin/monstart
-    mon_stop_cmd: /usr/bin/monstop
-    mon_max_blind_epochs: 50
-    # if true, will not restart the wifi module
-    no_restart: false
-    # access points to ignore. Could be the ssid, bssid or the vendor part of bssid.
-    whitelist:
-        - EXAMPLE_NETWORK
-        - ANOTHER_EXAMPLE_NETWORK
-        - fo:od:ba:be:fo:od   # BSSID
-        - fo:od:ba            # Vendor BSSID
-    # if not null, filter access points by this regular expression
-    filter: null
-    # logging
-    log:
-        # file to log to
-        path: /var/log/pwnagotchi.log
-        rotation:
-            enabled: true
-            # specify a maximum size to rotate ( format is 10/10B, 10K, 10M 10G )
-            size: '10M'
-
-ai:
-    # if false, only the default 'personality' will be used
-    enabled: true
-    path: /root/brain.nn
-    # 1.0 - laziness = probability of start training
-    laziness: 0.1
-    # how many epochs to train on
-    epochs_per_episode: 50
-    params:
-        # discount factor
-        gamma: 0.99
-        # the number of steps to run for each environment per update
-        n_steps: 1
-        # value function coefficient for the loss calculation
-        vf_coef: 0.25
-        # entropy coefficient for the loss calculation
-        ent_coef: 0.01
-        # maximum value for the gradient clipping
-        max_grad_norm: 0.5
-        # the learning rate
-        learning_rate: 0.0010
-        # rmsprop decay parameter
-        alpha: 0.99
-        # rmsprop epsilon
-        epsilon: 0.00001
-        # the verbosity level: 0 none, 1 training information, 2 tensorflow debug
-        verbose: 1
-        # type of scheduler for the learning rate update ('linear', 'constant', 'double_linear_con', 'middle_drop' or 'double_middle_drop')
-        lr_schedule: 'constant'
-        # the log location for tensorboard (if None, no logging)
-        tensorboard_log: null
-
-personality:
-    # advertise our presence
-    advertise: true
-    # perform a deauthentication attack to client stations in order to get full or half handshakes
-    deauth: true
-    # send association frames to APs in order to get the PMKID
-    associate: true
-    # list of channels to recon on, or empty for all channels
-    channels: []
-    # minimum WiFi signal strength in dBm
-    min_rssi: -200
-    # number of seconds for wifi.ap.ttl
-    ap_ttl: 120
-    # number of seconds for wifi.sta.ttl
-    sta_ttl: 300
-    # time in seconds to wait during channel recon
-    recon_time: 30
-    # number of inactive epochs after which recon_time gets multiplied by recon_inactive_multiplier
-    max_inactive_scale: 2
-    # if more than max_inactive_scale epochs are inactive, recon_time *= recon_inactive_multiplier
-    recon_inactive_multiplier: 2
-    # time in seconds to wait during channel hopping if activity has been performed
-    hop_recon_time: 10
-    # time in seconds to wait during channel hopping if no activity has been performed
-    min_recon_time: 5
-    # maximum amount of deauths/associations per BSSID per session
-    max_interactions: 3
-    # maximum amount of misses before considering the data stale and triggering a new recon
-    max_misses_for_recon: 5
-    # number of active epochs that triggers the excited state
-    excited_num_epochs: 10
-    # number of inactive epochs that triggers the bored state
-    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: 20000
-
-# ui configuration
-ui:
-    # here you can customize the faces
-    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: '(╥☁╥ )'
-        angry: "(-_-')"
-        friend: '(♥‿‿♥)'
-        broken: '(☓‿‿☓)'
-        debug: '(#__#)'
-    # ePaper display can update every 3 secs anyway, set to 0 to only refresh for major data changes
-    # IMPORTANT: The lifespan of an eINK display depends on the cumulative amount of refreshes. If you want to
-    # preserve your display over time, you should set this value to 0.0 so that the display will be refreshed only
-    # if any of the important data fields changed (the uptime and blinking cursor won't trigger a refresh).
-    fps: 0.0
-    # web ui
-    web:
-      enabled: true
-      address: '0.0.0.0'
-      username: changeme # !!! CHANGE THIS !!!
-      password: changeme # !!! CHANGE THIS !!!
-      origin: null
-      port: 8080
-      # command to be executed when a new png frame is available
-      # for instance, to use with framebuffer based displays:
-      # on_frame: 'fbi --noverbose -a -d /dev/fb1 -T 1 /root/pwnagotchi.png > /dev/null 2>&1'
-      on_frame: ''
-    # hardware display
-    display:
-        enabled: true
-        rotation: 180
-        # Possible options inkyphat/inky, papirus/papi, waveshare_1/ws_1 or waveshare_2/ws_2, oledhat, lcdhat, waveshare154inch, waveshare27inch, waveshare29inch, dfrobot/df, waveshare144lcd/ws144inch
-        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'
-
-# bettercap rest api configuration
-bettercap:
-    # api scheme://hostname:port username and password
-    scheme: http
-    hostname: localhost
-    port: 8081
-    username: pwnagotchi
-    password: pwnagotchi
-    # folder where bettercap stores the WPA handshakes, given that
-    # wifi.handshakes.aggregate will be set to false and individual
-    # pcap files will be created in order to minimize the chances
-    # of a single pcap file to get corrupted
-    handshakes: /root/handshakes
-    # events to mute in bettercap's events stream
-    silence:
-        - ble.device.new
-        - ble.device.lost
-        - ble.device.disconnected
-        - ble.device.connected
-        - ble.device.service.discovered
-        - ble.device.characteristic.discovered
-        - wifi.client.new
-        - wifi.client.lost
-        - wifi.client.probe
-        - wifi.ap.new
-        - wifi.ap.lost
-        - mod.started
diff --git a/pwnagotchi/plugins/default/auto-update.py b/pwnagotchi/plugins/default/auto-update.py
index cb8bc79..8a0db9e 100644
--- a/pwnagotchi/plugins/default/auto-update.py
+++ b/pwnagotchi/plugins/default/auto-update.py
@@ -154,7 +154,7 @@ class AutoUpdate(plugins.Plugin):
         self.lock = Lock()
 
     def on_loaded(self):
-        if 'interval' not in self.options or ('interval' in self.options and self.options['interval'] is None):
+        if 'interval' not in self.options or ('interval' in self.options and not self.options['interval']):
             logging.error("[update] main.plugins.auto-update.interval is not set")
             return
         self.ready = True
diff --git a/pwnagotchi/plugins/default/bt-tether.py b/pwnagotchi/plugins/default/bt-tether.py
index a0408ee..0054ba3 100644
--- a/pwnagotchi/plugins/default/bt-tether.py
+++ b/pwnagotchi/plugins/default/bt-tether.py
@@ -437,7 +437,7 @@ class BTTether(plugins.Plugin):
                     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):
+                        if device_opt not in options or (device_opt in options and not options[device_opt]):
                             logging.error("BT-TETHER: Please specify the %s for device %s.",
                                           device_opt, device)
                             break
@@ -448,7 +448,7 @@ class BTTether(plugins.Plugin):
         # 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):
+                if opt not in self.options or (opt in self.options and not self.options[opt]):
                     logging.error("BT-TETHER: Please specify the %s in your config.yml.", opt)
                     return
 
diff --git a/pwnagotchi/plugins/default/net-pos.py b/pwnagotchi/plugins/default/net-pos.py
index 8158437..12c3492 100644
--- a/pwnagotchi/plugins/default/net-pos.py
+++ b/pwnagotchi/plugins/default/net-pos.py
@@ -26,7 +26,7 @@ class NetPos(plugins.Plugin):
         self.lock = threading.Lock()
 
     def on_loaded(self):
-        if 'api_key' not in self.options or ('api_key' in self.options and self.options['api_key'] is None):
+        if 'api_key' not in self.options or ('api_key' in self.options and not self.options['api_key']):
             logging.error("NET-POS: api_key isn't set. Can't use mozilla's api.")
             return
 
diff --git a/pwnagotchi/plugins/default/onlinehashcrack.py b/pwnagotchi/plugins/default/onlinehashcrack.py
index 11b37f9..534ddc4 100644
--- a/pwnagotchi/plugins/default/onlinehashcrack.py
+++ b/pwnagotchi/plugins/default/onlinehashcrack.py
@@ -28,7 +28,7 @@ class OnlineHashCrack(plugins.Plugin):
         """
         Gets called when the plugin gets loaded
         """
-        if 'email' not in self.options or ('email' in self.options and self.options['email'] is None):
+        if 'email' not in self.options or ('email' in self.options and not self.options['email']):
             logging.error("OHC: Email isn't set. Can't upload to onlinehashcrack.com")
             return
 
diff --git a/pwnagotchi/plugins/default/webcfg.py b/pwnagotchi/plugins/default/webcfg.py
index 40f7477..d870953 100644
--- a/pwnagotchi/plugins/default/webcfg.py
+++ b/pwnagotchi/plugins/default/webcfg.py
@@ -1,13 +1,12 @@
 import logging
 import json
-import yaml
+import toml
 import _thread
 import pwnagotchi.plugins as plugins
 from pwnagotchi import restart
 from flask import abort
 from flask import render_template_string
 
-
 INDEX = """
 <html>
     <head>
@@ -500,13 +499,13 @@ class WebConfig(plugins.Plugin):
         elif request.method == "POST":
             if path == "save-config":
                 try:
-                    parsed_yaml = yaml.safe_load(str(request.get_json()))
-                    with open('/etc/pwnagotchi/config.yml', 'w') as config_file:
-                        yaml.safe_dump(parsed_yaml, config_file, encoding='utf-8',
-                                allow_unicode=True, default_flow_style=False)
+                    parsed_toml = toml.loads(request.get_json())
+                    with open('/etc/pwnagotchi/config.toml') as config_file:
+                        toml.dump(parsed_toml, config_file)
 
                     _thread.start_new_thread(restart, (self.mode,))
                     return "success"
-                except yaml.YAMLError as yaml_ex:
+                except Exception as ex:
+                    logging.error(ex)
                     return "config error"
         abort(404)
diff --git a/pwnagotchi/plugins/default/wpa-sec.py b/pwnagotchi/plugins/default/wpa-sec.py
index 4c4dac3..373789a 100644
--- a/pwnagotchi/plugins/default/wpa-sec.py
+++ b/pwnagotchi/plugins/default/wpa-sec.py
@@ -70,11 +70,11 @@ class WpaSec(plugins.Plugin):
         """
         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):
+        if 'api_key' not in self.options or ('api_key' in self.options and not self.options['api_key']):
             logging.error("WPA_SEC: API-KEY isn't set. Can't upload to wpa-sec.stanev.org")
             return
 
-        if 'api_url' not in self.options or ('api_url' in self.options and self.options['api_url'] is None):
+        if 'api_url' not in self.options or ('api_url' in self.options and not self.options['api_url']):
             logging.error("WPA_SEC: API-URL isn't set. Can't upload, no endpoint configured.")
             return
 
diff --git a/pwnagotchi/utils.py b/pwnagotchi/utils.py
index c7ac6d5..99dc936 100644
--- a/pwnagotchi/utils.py
+++ b/pwnagotchi/utils.py
@@ -12,6 +12,7 @@ import shutil
 import gzip
 import contextlib
 import tempfile
+import toml
 
 import pwnagotchi
 
@@ -32,15 +33,17 @@ def load_config(args):
     if not os.path.exists(default_config_path):
         os.makedirs(default_config_path)
 
-    ref_defaults_file = os.path.join(os.path.dirname(pwnagotchi.__file__), 'defaults.yml')
+    ref_defaults_file = os.path.join(os.path.dirname(pwnagotchi.__file__), 'defaults.toml')
     ref_defaults_data = None
 
     # check for a config.yml file on /boot/
-    if os.path.exists("/boot/config.yml"):
-        # 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)
+    for boot_conf in ['/boot/config.yml', '/boot/config.toml']:
+        if os.path.exists(boot_conf):
+            # logging not configured here yet
+            print("installing %s to %s ...", boot_conf, args.user_config)
+            # https://stackoverflow.com/questions/42392600/oserror-errno-18-invalid-cross-device-link
+            shutil.move(boot_conf, args.user_config)
+            break
 
     # check for an entire pwnagotchi folder on /boot/
     if os.path.isdir('/boot/pwnagotchi'):
@@ -54,6 +57,7 @@ def load_config(args):
         shutil.copy(ref_defaults_file, args.config)
     else:
         # check if the user messed with the defaults
+
         with open(ref_defaults_file) as fp:
             ref_defaults_data = fp.read()
 
@@ -66,18 +70,28 @@ def load_config(args):
 
     # load the defaults
     with open(args.config) as fp:
-        config = yaml.safe_load(fp)
+        config = toml.load(fp)
 
     # load the user 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)
+        user_config = None
+        # migrate
+        yaml_name = args.user_config.replace('.toml', '.yml')
+        if not os.path.exists(args.user_config) and os.path.exists(yaml_name):
+            # no toml found; convert yaml
+            logging.info('Old yaml-config found. Converting to toml...')
+            with open(args.user_config, 'w') as toml_file, open(yaml_name) as yaml_file:
+                user_config = yaml.safe_load(yaml_file)
+                # convert to toml but use loaded yaml
+                toml.dump(user_config, toml_file)
+        elif os.path.exists(args.user_config):
+            with open(args.user_config) as toml_file:
+                user_config = toml.load(toml_file)
+
+        if user_config:
+            config = merge_config(user_config, config)
+    except Exception as ex:
+        logging.error("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
diff --git a/requirements.txt b/requirements.txt
index 1cb7d5e..9a93bdc 100644
--- a/requirements.txt
+++ b/requirements.txt
@@ -18,3 +18,4 @@ gast==0.2.2
 flask==1.0.2
 flask-cors==3.0.7
 flask-wtf==0.14.2
+toml==0.10.0