diff --git a/python_payload/st3m/run.py b/python_payload/st3m/run.py
index 776adff98a66f9336bc5d0efdacd169f318d58cd..68a3c4613bd32958648cde586064efe33f402718 100644
--- a/python_payload/st3m/run.py
+++ b/python_payload/st3m/run.py
@@ -19,6 +19,7 @@ import captouch, audio, leds, gc
 import os
 
 import machine
+import network
 
 
 log = logging.Log(__name__, level=logging.INFO)
@@ -36,8 +37,8 @@ def _make_reactor() -> Reactor:
     settings.onoff_button_swap.subscribe(_onoff_button_swap_update)
     _onoff_button_swap_update()
 
-    settings.onoff_camp_wifi.subscribe(wifi._onoff_camp_wifi_update)
-    wifi._onoff_camp_wifi_update()
+    settings.onoff_wifi.subscribe(wifi._onoff_wifi_update)
+    wifi._onoff_wifi_update()
     return reactor
 
 
@@ -142,6 +143,14 @@ def run_main() -> None:
     bundles.update()
 
     settings.load_all()
+
+    try:
+        network.hostname(
+            settings.str_hostname.value if settings.str_hostname.value else "flow3r"
+        )
+    except Exception as e:
+        log.error(f"Failed to set hostname {e}")
+
     menu_settings = settings.build_menu()
     menu_system = SimpleMenu(
         [
diff --git a/python_payload/st3m/settings.py b/python_payload/st3m/settings.py
index b4ee85a9b6416ed3ebaf77615dbbed79f79ac4b3..69d2fbbae9b288165902d03aa5595ec971a01503 100644
--- a/python_payload/st3m/settings.py
+++ b/python_payload/st3m/settings.py
@@ -242,6 +242,42 @@ class OnOffWidget(TunableWidget):
         self._tunable.set_value(not self._state)
 
 
+class StringTunable(UnaryTunable):
+    """
+    StringTunable is a UnaryTunable that has a string value
+    """
+
+    def __init__(self, name: str, key: str, default: Optional[str]) -> None:
+        super().__init__(name, key, default)
+
+    def get_widget(self) -> TunableWidget:
+        return StringWidget(self)
+
+    def press(self, vm: Optional[ViewManager]) -> None:
+        # Text input not supported at the moment
+        pass
+
+
+class StringWidget(TunableWidget):
+    """
+    StringWidget is a TunableWidget for StringTunables. It renders a string.
+    """
+
+    def __init__(self, tunable: StringTunable) -> None:
+        self._tunable = tunable
+
+    def think(self, ins: InputState, delta_ms: int) -> None:
+        pass
+
+    def draw(self, ctx: Context) -> None:
+        ctx.text_align = ctx.LEFT
+        ctx.text(self._tunable.value if self._tunable.value else "")
+
+    def press(self, vm: Optional[ViewManager]) -> None:
+        # Text input not supported at the moment
+        pass
+
+
 class SettingsMenuItem(MenuItem):
     """
     A MenuItem which draws its label offset to the left, and a Tunable's widget
@@ -293,17 +329,23 @@ class SettingsMenu(SimpleMenu):
 
 
 # Actual tunables / settings.
-onoff_camp_wifi = OnOffTunable("Connect Camp WiFi", "system.camp_wifi_enabled", False)
 onoff_button_swap = OnOffTunable("Swap Buttons", "system.swap_buttons", False)
 onoff_debug = OnOffTunable("Debug Overlay", "system.debug", False)
 onoff_debug_touch = OnOffTunable("Touch Overlay", "system.debug_touch", False)
 onoff_show_tray = OnOffTunable("Show Icons", "system.show_icons", True)
+onoff_wifi = OnOffTunable("Enable WiFi", "system.wifi.enabled", False)
+str_wifi_ssid = StringTunable("WiFi SSID", "system.wifi.ssid", "Camp2023-open")
+str_wifi_psk = StringTunable("WiFi Password", "system.wifi.psk", None)
+str_hostname = StringTunable("Hostname", "system.hostname", "flow3r")
 all_settings: List[UnaryTunable] = [
-    onoff_camp_wifi,
     onoff_show_tray,
     onoff_button_swap,
     onoff_debug,
     onoff_debug_touch,
+    onoff_wifi,
+    str_wifi_ssid,
+    str_wifi_psk,
+    str_hostname,
 ]
 
 
diff --git a/python_payload/st3m/wifi.py b/python_payload/st3m/wifi.py
index d9daa67d779dcb9ea38c168f76e8c386d3bdc7c2..abc31a662857f6d5d8b05d704b32a23d18e7d461 100644
--- a/python_payload/st3m/wifi.py
+++ b/python_payload/st3m/wifi.py
@@ -1,20 +1,29 @@
 import network
+from network import WLAN
 from st3m import settings
+from st3m.goose import Optional
 from st3m.logging import Log
 
 log = Log(__name__)
 
-iface = None
+iface: Optional[WLAN] = None
 
 
-def setup_camp_wifi() -> None:
+def setup_wifi() -> None:
     global iface
-    iface = network.WLAN(network.STA_IF)
-    iface.active(True)
+    enable()
+    assert iface
     try:
-        iface.connect(b"Camp2023-open")
+        if settings.str_wifi_ssid.value:
+            iface.connect(settings.str_wifi_ssid.value, settings.str_wifi_psk.value)
     except OSError as e:
-        log.error(f"Could not connect to camp wifi: {e}")
+        log.error(f"Could not connect to wifi: {e}")
+
+
+def enable() -> None:
+    global iface
+    iface = network.WLAN(network.STA_IF)
+    iface.active(True)
 
 
 def disable() -> None:
@@ -35,9 +44,9 @@ def is_connected() -> bool:
         return False
 
 
-def _onoff_camp_wifi_update() -> None:
-    if settings.onoff_camp_wifi.value:
-        setup_camp_wifi()
+def _onoff_wifi_update() -> None:
+    if settings.onoff_wifi.value:
+        setup_wifi()
     else:
         disable()