diff --git a/preload/apps/ptt/__init__.py b/preload/apps/ptt/__init__.py
new file mode 100644
index 0000000000000000000000000000000000000000..f8f305ec385d0d53413be74f87ba28d2807e06f3
--- /dev/null
+++ b/preload/apps/ptt/__init__.py
@@ -0,0 +1,398 @@
+import ble_hid
+import buttons
+import max86150
+import leds
+import vibra
+import display
+import simple_menu
+import color
+import config
+
+from adafruit_hid.keyboard import Keyboard
+from adafruit_hid.keycode import Keycode
+
+from adafruit_hid.consumer_control_code import ConsumerControlCode
+from adafruit_hid.consumer_control import ConsumerControl
+
+import json
+import os
+import gc
+import time
+
+
+keys = [
+    "ESCAPE",
+    "SPACEBAR",
+    "CAPS_LOCK",
+    "F1",
+    "F2",
+    "F3",
+    "F4",
+    "F5",
+    "F6",
+    "F7",
+    "F8",
+    "F9",
+    "F10",
+    "F11",
+    "F12",
+    "PRINT_SCREEN",
+    "SCROLL_LOCK",
+    "PAUSE",
+    "APPLICATION",
+    "POWER",
+    "F13",
+    "F14",
+    "F15",
+    "F16",
+    "F17",
+    "F18",
+    "F19",
+    "LEFT_CONTROL",
+    "LEFT_SHIFT",
+    "LEFT_ALT",
+    "LEFT_GUI",
+    "RIGHT_CONTROL",
+    "RIGHT_SHIFT",
+    "RIGHT_ALT",
+    "RIGHT_GUI",
+]
+
+codes = [
+    "RECORD",
+    "FAST_FORWARD",
+    "REWIND",
+    "SCAN_NEXT_TRACK",
+    "SCAN_PREVIOUS_TRACK",
+    "STOP",
+    "EJECT",
+    "PLAY_PAUSE",
+    "MUTE",
+    "VOLUME_DECREMENT",
+    "VOLUME_INCREMENT",
+]
+
+KEYBOARD = "Keyboard"
+CONSUMER = "Consumer Control"
+classes = [KEYBOARD, CONSUMER]
+
+CONFIG_NAME = "ptt.json"
+
+
+def ble_check():
+    ble_enabled = False
+    try:
+        active = config.get_string("ble_enable")
+        if active.lower() == "true" or active == "1":
+            ble_enabled = True
+    except OSError:
+        pass
+
+    if not ble_enabled:
+        disp.clear()
+        disp.print("BLE", posy=0, fg=color.BLUE)
+        disp.print("disabled", posy=0, posx=45, fg=color.RED)
+
+        disp.print("Enable BLE", posy=40)
+        disp.print("in BLE Aapp", posy=60)
+        disp.update()
+        while buttons.read() == 0:
+            time.sleep(0.1)
+        os.exit()
+
+
+def ble_hid_check():
+    ble_hid_enabled = False
+    try:
+        active = config.get_string("ble_hid_enable")
+        if active.lower() == "true" or active == "1":
+            ble_hid_enabled = True
+    except OSError:
+        pass
+
+    if not ble_hid_enabled:
+        disp.clear()
+        disp.print("HID", posy=0, fg=color.BLUE)
+        disp.print("disabled", posy=0, posx=45, fg=color.RED)
+
+        disp.print("Enable HID", posy=40)
+        disp.print("in HID App", posy=60)
+        disp.update()
+        while buttons.read() == 0:
+            time.sleep(0.1)
+        os.exit()
+
+
+disp = display.open()
+ble_check()
+ble_hid_check()
+connected = False
+blocked = False
+
+
+def save_config():
+    open(CONFIG_NAME, "w").write(json.dumps(config))
+
+
+config = {
+    "key": "LEFT_CONTROL",
+    "code": "PLAY_PAUSE",
+    "class": KEYBOARD,
+    "optical_sensor": True,
+    "toggle": False,
+}
+
+
+try:
+    config.update(json.loads(open(CONFIG_NAME, "r").read()))
+except Exception as e:
+    print(e)
+    print("Writing default config")
+    save_config()
+
+
+print(config)
+
+
+class ProximitySensor:
+    def __init__(self, callback):
+        self.callback = callback
+        self.pressed = False
+
+    def open(self):
+        def callback(datasets):
+            if len(datasets) > 0 and self.pressed:
+                pressed = True
+                for val in datasets:
+                    # Threshold of proximity interrupt: 32 * (2 ** 10)
+                    if val.infrared < 32 * (2 ** 10) / 2:
+                        pressed = False
+                if not pressed:
+                    self.pressed = pressed
+                    self.callback(self.pressed)
+
+        def prox():
+            self.pressed = True
+            self.callback(self.pressed)
+
+        self.sensor = max86150.MAX86150(
+            callback=callback, prox_callback=prox, sample_rate=400
+        )
+
+    def close(self):
+        if self.self is not None:
+            self.sensor.close()
+            self.sensor = None
+
+
+class PTTMenu(simple_menu.Menu):
+    def on_select(self, name, index):
+        if config["class"] == KEYBOARD:
+            config["key"] = name
+            print(gc.mem_free())
+        elif config["class"] == CONSUMER:
+            config["code"] = name
+        save_config()
+        self.exit()
+
+
+class HIDClassMenu(simple_menu.Menu):
+    def on_select(self, name, index):
+        config["class"] = name
+        save_config()
+        self.exit()
+
+
+class OpticalSensorMenu(simple_menu.Menu):
+    def on_select(self, name, index):
+        if name == "Enable":
+            config["optical_sensor"] = True
+        elif name == "Disable":
+            config["optical_sensor"] = False
+        save_config()
+        self.exit()
+
+
+class ToggleMenu(simple_menu.Menu):
+    def on_select(self, name, index):
+        if name == "Enable":
+            config["toggle"] = True
+        elif name == "Disable":
+            config["toggle"] = False
+        save_config()
+        self.exit()
+
+
+class MainMenu(simple_menu.Menu):
+    def on_select(self, name, index):
+        print(name)
+        if name == "PTT Key":
+            if config["class"] == KEYBOARD:
+                m = PTTMenu(keys)
+                try:
+                    m.idx = keys.index(config["key"])
+                except Exception as e:
+                    print(e)
+            elif config["class"] == CONSUMER:
+                m = PTTMenu(codes)
+                try:
+                    m.idx = codes.index(config["code"])
+                except Exception as e:
+                    print(e)
+        elif name == "HID Class":
+            m = HIDClassMenu(classes)
+            try:
+                m.idx = classes.index(config["class"])
+            except Exception as e:
+                print(e)
+        elif name == "Optical Sensor":
+            if config["optical_sensor"]:
+                m = OpticalSensorMenu(("Enabled", "Disable", "", ""))
+            else:
+                m = OpticalSensorMenu(("Enable", "Disabled", "", ""))
+                m.idx = 1
+        elif name == "Key Toggle":
+            if config["toggle"]:
+                m = ToggleMenu(("Enabled", "Disable", "", ""))
+            else:
+                m = ToggleMenu(("Enable", "Disabled", "", ""))
+                m.idx = 1
+
+        m.run()
+        self.exit()
+
+
+def push():
+    global connected
+    if config["class"] == KEYBOARD:
+        key = config["key"]
+        if hasattr(Keycode, key):
+            # k.press(192)
+            try:
+                if config["toggle"]:
+                    k.send(getattr(Keycode, key))
+                else:
+                    k.press(getattr(Keycode, key))
+            except OSError as e:
+                if e.args[0] == 5:
+                    connected = False
+                    return
+                else:
+                    raise e
+    elif config["class"] == CONSUMER:
+        code = config["code"]
+        if hasattr(ConsumerControlCode, code):
+            try:
+                if config["toggle"]:
+                    c.send(getattr(ConsumerControlCode, code))
+                    c.send(0)
+                else:
+                    c.send(getattr(ConsumerControlCode, code))
+            except OSError as e:
+                if e.args[0] == 5:
+                    connected = False
+                    return
+                else:
+                    raise e
+
+    leds.set_rocket(1, 31)
+    vibra.vibrate(20)
+    disp.print("Talk now", posy=30, font=4, fg=color.RED)
+    disp.update()
+    disp.backlight(20)
+
+
+def release():
+    global connected
+    key = config["key"]
+    if config["class"] == KEYBOARD:
+        if hasattr(Keycode, key):
+            # k.release(192)
+            try:
+                if config["toggle"]:
+                    k.send(getattr(Keycode, key))
+                else:
+                    k.release(getattr(Keycode, key))
+            except OSError as e:
+                if e.args[0] == 5:
+                    connected = False
+                else:
+                    raise e
+
+    elif config["class"] == CONSUMER:
+        try:
+            if config["toggle"]:
+                code = config["code"]
+                c.send(getattr(ConsumerControlCode, code))
+                c.send(0)
+            else:
+                c.send(0)
+        except OSError as e:
+            if e.args[0] == 5:
+                connected = False
+            else:
+                raise e
+
+    leds.set_rocket(1, 0)
+    disp.backlight(0)
+
+
+def prox_callback(state):
+    if blocked:
+        return
+    if not connected:
+        return
+
+    if "optical_sensor" in config and config["optical_sensor"]:
+        if state:
+            push()
+        else:
+            release()
+
+
+sensor = ProximitySensor(prox_callback).open()
+
+disp.clear()
+disp.print("PushToTalk", posy=0)
+disp.print("via BLE", posy=20, fg=color.BLUE)
+disp.print("    Menu ->", posy=40)
+disp.print("<- PTT", posy=60, fg=color.RED)
+disp.update()
+
+time.sleep(3)
+
+b_old = buttons.read()
+while True:
+    if not connected:
+        try:
+            k = Keyboard(ble_hid.devices)
+            c = ConsumerControl(ble_hid.devices)
+            connected = True
+            disp.clear().update()
+            disp.backlight(0)
+        except OSError as e:
+            if e.args[0] == 5:
+                disp.backlight(20)
+                disp.clear()
+                disp.print("Waiting for")
+                disp.print("BLE Connection", posy=20, fg=color.BLUE)
+                disp.update()
+            else:
+                raise e
+    else:
+        b_new = buttons.read()
+        if not b_old == b_new:
+            b_old = b_new
+            if b_new == buttons.BOTTOM_LEFT:
+                push()
+            elif b_new == buttons.TOP_RIGHT:
+                disp.backlight(20)
+                blocked = True
+                MainMenu(("PTT Key", "HID Class", "Optical Sensor", "Key Toggle")).run()
+                blocked = False
+                disp.clear().update()
+                disp.backlight(0)
+            else:
+                release()
+
+    time.sleep(0.05)
diff --git a/preload/apps/ptt/metadata.json b/preload/apps/ptt/metadata.json
new file mode 100644
index 0000000000000000000000000000000000000000..1eca3130701b85f0f345fdcc5f167dcf1fb11b67
--- /dev/null
+++ b/preload/apps/ptt/metadata.json
@@ -0,0 +1 @@
+{"author": "card10 contributors", "name": "PTT", "description": "Push-To-Talk via BLE", "category": "Hardware", "revision": -1, "source":"preload"}