diff --git a/.vscode/launch.json b/.vscode/launch.json
index fbd05eb..fa35cf8 100644
--- a/.vscode/launch.json
+++ b/.vscode/launch.json
@@ -14,7 +14,9 @@
                 "wlanpi_core.asgi:app",
                 "--reload",
                 "--host",
-                "0.0.0.0"
+                "0.0.0.0",
+                "--port",
+                "31415"
             ],
             "jinja": true,
             "sudo": true
diff --git a/debian/changelog b/debian/changelog
index eb066d2..56bdc2e 100644
--- a/debian/changelog
+++ b/debian/changelog
@@ -1,3 +1,9 @@
+wlanpi-core (1.0.1) UNRELEASED; urgency=medium
+
+  * Add Network APIs for managing wlan0 via DBUS
+
+ --  Ben Toner <ben@numerousnetworks.co.uk>  Wed, 11 Sep 2024 10:32:40 -0500
+
 wlanpi-core (1.0.0-7) unstable; urgency=medium
 
   * Add "wlanpi-grafana-wispy-*" and "wlanpi-grafana-wipry-*" to allowlist.
diff --git a/debian/control b/debian/control
index 97c7591..d2e750f 100644
--- a/debian/control
+++ b/debian/control
@@ -15,7 +15,10 @@ Build-Depends: debhelper (>= 11),
                dbus,
                libdbus-1-dev,
                libdbus-glib-1-dev,
-               libglib2.0-dev
+               libglib2.0-dev,
+               libcairo2-dev,
+               libgirepository1.0-dev,
+               libffi-dev
 Standards-Version: 4.6.0
 X-Python3-Version: >= 3.9
 Homepage: https://github.com/WLAN-Pi/wlanpi-core
diff --git a/debian/rules b/debian/rules
index b8ec9d2..50dfb43 100755
--- a/debian/rules
+++ b/debian/rules
@@ -21,6 +21,10 @@ SDIST_DIR=debian/$(PACKAGE)-$(VERSION)
 
 .PHONY: override_dh_virtualenv override_dh_installexamples 
 
+# ensure that the systemd services are handled by systemd.
+override_dh_installsystemd:
+	dh_installsystemd --name=wpa_supplicant@wlan0 wpa_supplicant@wlan0.service
+
 # we don't really want to strip the symbols from our object files.
 override_dh_strip:
 
diff --git a/debian/wlanpi-core.install b/debian/wlanpi-core.install
index da48490..751fea3 100644
--- a/debian/wlanpi-core.install
+++ b/debian/wlanpi-core.install
@@ -1,4 +1,5 @@
 /install/etc/wlanpi-core/nginx/nginx-sample.conf /etc/wlanpi-core/nginx
 /install/etc/wlanpi-core/nginx/wlanpi_core.conf /etc/wlanpi-core/nginx/sites-enabled
 /install/etc/wlanpi-core/nginx/link.sh /etc/wlanpi-core/scripts
-/install/etc/wlanpi-core/nginx/unlink.sh /etc/wlanpi-core/scripts
\ No newline at end of file
+/install/etc/wlanpi-core/nginx/unlink.sh /etc/wlanpi-core/scripts
+/install/etc/wpa_supplicant/wpa_supplicant-wlan0.conf /etc/wpa_supplicant
\ No newline at end of file
diff --git a/debian/wlanpi-core.substvars b/debian/wlanpi-core.substvars
index dd461a1..27d015a 100644
--- a/debian/wlanpi-core.substvars
+++ b/debian/wlanpi-core.substvars
@@ -1,3 +1,3 @@
-shlibs:Depends=libc6 (>= 2.17), libdbus-1-3 (>= 1.9.14), libgcc-s1 (>= 4.2), libglib2.0-0 (>= 2.16.0)
+shlibs:Depends=libc6 (>= 2.17), libcairo-gobject2 (>= 1.12.16), libcairo2 (>= 1.15.12), libdbus-1-3 (>= 1.9.14), libffi7 (>= 3.3~20180313), libgcc-s1 (>= 4.2), libgirepository-1.0-1 (>= 1.62.0-4~), libglib2.0-0 (>= 2.53.1)
 misc:Depends=
 misc:Pre-Depends=
diff --git a/debian/wpa_supplicant@wlan0.service b/debian/wpa_supplicant@wlan0.service
new file mode 100644
index 0000000..c456a38
--- /dev/null
+++ b/debian/wpa_supplicant@wlan0.service
@@ -0,0 +1,20 @@
+#DBUS Managed WPA_Supplicant Service on WLAN0
+
+[Unit]
+Description=WPA supplicant daemon (interface-specific version)
+Requires=sys-subsystem-net-devices-%i.device
+After=sys-subsystem-net-devices-%i.device
+Before=network.target
+Wants=network.target
+
+# NetworkManager users will probably want the dbus version instead.
+
+[Service]
+Type=simple
+#ExecStart=/sbin/wpa_supplicant -c/etc/wpa_supplicant/wpa_supplicant-%I.conf -i%I
+ExecStart=/sbin/wpa_supplicant -u -s -O /run/wpa_supplicant -c/etc/wpa_supplicant/wpa_supplicant-%I.conf -i%I
+ExecReload=/bin/kill -HUP \$MAINPID
+
+[Install]
+WantedBy=multi-user.target
+Alias=dbus-fi.w1.wpa_supplicant1.service
\ No newline at end of file
diff --git a/install/etc/wpa_supplicant/wpa_supplicant-wlan0.conf b/install/etc/wpa_supplicant/wpa_supplicant-wlan0.conf
new file mode 100644
index 0000000..f4a9471
--- /dev/null
+++ b/install/etc/wpa_supplicant/wpa_supplicant-wlan0.conf
@@ -0,0 +1,64 @@
+ctrl_interface=DIR=/run/wpa_supplicant
+ap_scan=1
+p2p_disabled=1
+
+#######################################################################################
+# NOTE: to use the templates below, remove the hash symbols at the start of each line
+#######################################################################################
+
+# WPA2 PSK Network sample (highest priority - joined first)
+#network={
+#  ssid="enter SSID Name"
+#  psk="enter key"
+#  priority=10
+#}
+
+# WPA2 PSK Network sample (next priority - joined if first priority not available) - don't unhash this line
+
+#network={
+#    ssid="enter SSID Name"
+#    psk="enter key"
+#    priority=3
+#}
+
+# WPA2 PEAP example (next priority - joined if second priority not available) - don't unhash this line
+
+#network={
+#  ssid="enter SSID Name"
+#  key_mgmt=WPA-EAP
+#  eap=PEAP
+#  anonymous_identity="anonymous"
+#  identity="enter your username"
+#  password="enter your password"
+#  phase2="autheap=MSCHAPV2"
+#  priority=2
+#}
+
+# Open network example (lowest priority, only joined other 3 networks not available) - don't unhash this line
+
+#network={
+#   ssid="enter SSID Name"
+#   key_mgmt=NONE
+#   priority=1
+#}
+
+# SAE mechanism for PWE derivation
+# 0 = hunting-and-pecking (HNP) loop only (default without password identifier)
+# 1 = hash-to-element (H2E) only (default with password identifier)
+# 2 = both hunting-and-pecking loop and hash-to-element enabled
+# Note: The default value is likely to change from 0 to 2 once the new
+# hash-to-element mechanism has received more interoperability testing.
+# When using SAE password identifier, the hash-to-element mechanism is used
+# regardless of the sae_pwe parameter value.
+#
+#sae_pwe=0 <--- default value, change to 1 or 2 if AP forces H2E.
+
+# WPA3 PSK network sample for 6 GHz (note SAE and PMF is required) - don't unhash this line
+
+#network={
+#  ssid="6 GHz SSID"
+#  psk="password"
+#  priority=10
+#  key_mgmt=SAE
+#  ieee80211w=2
+#}
diff --git a/requirements.in b/requirements.in
index 7e0257f..e146a71 100644
--- a/requirements.in
+++ b/requirements.in
@@ -11,4 +11,5 @@ requests
 
 # endpoint requires
 psutil
-dbus-python
\ No newline at end of file
+dbus-python
+PyGObject
diff --git a/requirements.txt b/requirements.txt
index 3d0f831..1094a16 100644
--- a/requirements.txt
+++ b/requirements.txt
@@ -52,6 +52,8 @@ packaging==23.1
     # via gunicorn
 psutil==5.9.5
     # via -r requirements.in
+pycairo==1.25.1
+    # via pygobject
 pydantic==2.3.0
     # via
     #   fastapi
@@ -60,6 +62,8 @@ pydantic-core==2.6.3
     # via pydantic
 pydantic-settings==2.0.3
     # via -r requirements.in
+pygobject==3.46.0
+    # via -r requirements.in
 python-dotenv==1.0.0
     # via
     #   -r requirements.in
diff --git a/wlanpi_core/__main__.py b/wlanpi_core/__main__.py
index 4a0f819..5dbc987 100644
--- a/wlanpi_core/__main__.py
+++ b/wlanpi_core/__main__.py
@@ -26,6 +26,23 @@
 from .__version__ import __version__
 
 
+def port(port) -> int:
+    """Check if the provided port is valid"""
+    try:
+        # make sure port is an int
+        port = int(port)
+    except ValueError:
+        raise ValueError("%s is not a number")
+
+    port_ranges = [(1024, 65353)]
+
+    for _range in port_ranges:
+        if _range[0] <= port <= _range[1]:
+            return port
+
+    raise ValueError("%s not a valid. Pick a port between %s.", port, port_ranges)
+
+
 def setup_parser() -> argparse.ArgumentParser:
     """Set default values and handle arg parser"""
     parser = argparse.ArgumentParser(
@@ -35,6 +52,8 @@ def setup_parser() -> argparse.ArgumentParser:
     parser.add_argument(
         "--reload", dest="livereload", action="store_true", default=False
     )
+    parser.add_argument("--port", "-p", dest="port", type=port, default=8000)
+
     parser.add_argument(
         "--version", "-V", "-v", action="version", version=f"{__version__}"
     )
@@ -68,7 +87,7 @@ def main() -> None:
     if lets_go:
         uvicorn.run(
             "wlanpi_core.asgi:app",
-            port=8000,
+            port=args.port,
             host="0.0.0.0",
             reload=args.livereload,
             log_level="debug",
diff --git a/wlanpi_core/__version__.py b/wlanpi_core/__version__.py
index 9520c5e..afb6c20 100644
--- a/wlanpi_core/__version__.py
+++ b/wlanpi_core/__version__.py
@@ -10,7 +10,7 @@
 __url__ = "https://github.com/wlan-pi/wlanpi-core"
 __author__ = "Josh Schmelzle"
 __author_email__ = "josh@joshschmelzle.com"
-__version__ = "1.0.0"
+__version__ = "1.0.1"
 __status__ = "alpha"
 __license__ = "BSD-3-Clause"
 __license_url__ = "https://opensource.org/licenses/BSD-3-Clause"
diff --git a/wlanpi_core/api/api_v1/endpoints/network_api.py b/wlanpi_core/api/api_v1/endpoints/network_api.py
index aa9377d..765ed2c 100644
--- a/wlanpi_core/api/api_v1/endpoints/network_api.py
+++ b/wlanpi_core/api/api_v1/endpoints/network_api.py
@@ -1,7 +1,92 @@
 import logging
 
-from fastapi import APIRouter
+from fastapi import APIRouter, Response
+
+from wlanpi_core.models.validation_error import ValidationError
+from wlanpi_core.schemas import network
+from wlanpi_core.services import network_service
 
 router = APIRouter()
 
+API_DEFAULT_TIMEOUT = 20
+
 log = logging.getLogger("uvicorn")
+
+
+@router.get("/wlan/getInterfaces", response_model=network.Interfaces)
+async def get_a_systemd_network_interfaces(timeout: int = API_DEFAULT_TIMEOUT):
+    """
+    Queries systemd via dbus to get the details of the currently connected network.
+    """
+
+    try:
+        return await network_service.get_systemd_network_interfaces(timeout)
+    except ValidationError as ve:
+        return Response(content=ve.error_msg, status_code=ve.status_code)
+    except Exception as ex:
+        log.error(ex)
+        return Response(content="Internal Server Error", status_code=500)
+
+
+@router.get(
+    "/wlan/scan", response_model=network.ScanResults, response_model_exclude_none=True
+)
+async def get_a_systemd_network_scan(
+    type: str, interface: str, timeout: int = API_DEFAULT_TIMEOUT
+):
+    """
+    Queries systemd via dbus to get a scan of the available networks.
+    """
+
+    try:
+        # return await network_service.get_systemd_network_scan(type)
+        return await network_service.get_async_systemd_network_scan(
+            type, interface, timeout
+        )
+    except ValidationError as ve:
+        return Response(content=ve.error_msg, status_code=ve.status_code)
+    except Exception as ex:
+        log.error(ex)
+        return Response(content="Internal Server Error", status_code=500)
+
+
+@router.post("/wlan/set", response_model=network.NetworkSetupStatus)
+async def set_a_systemd_network(
+    setup: network.WlanInterfaceSetup, timeout: int = API_DEFAULT_TIMEOUT
+):
+    """
+    Queries systemd via dbus to set a single network.
+    """
+
+    try:
+        return await network_service.set_systemd_network_addNetwork(
+            setup.interface, setup.netConfig, setup.removeAllFirst, timeout
+        )
+    except ValidationError as ve:
+        return Response(content=ve.error_msg, status_code=ve.status_code)
+    except Exception as ex:
+        log.error(ex)
+        return Response(content="Internal Server Error", status_code=500)
+
+
+@router.get(
+    "/wlan/getConnected",
+    response_model=network.ConnectedNetwork,
+    response_model_exclude_none=True,
+)
+async def get_a_systemd_currentNetwork_details(
+    interface: str, timeout: int = API_DEFAULT_TIMEOUT
+):
+    """
+    Queries systemd via dbus to get the details of the currently connected network.
+    """
+
+    try:
+        return await network_service.get_systemd_network_currentNetwork_details(
+            interface, timeout
+        )
+    except ValidationError as ve:
+        return Response(content=ve.error_msg, status_code=ve.status_code)
+    except Exception as ex:
+        log.error(ex)
+        return Response(content="Internal Server Error", status_code=500)
diff --git a/wlanpi_core/core/__init__.py b/wlanpi_core/core/__init__.py
index e69de29..f40cc53 100644
--- a/wlanpi_core/core/__init__.py
+++ b/wlanpi_core/core/__init__.py
@@ -0,0 +1,3 @@
+from dbus.mainloop.glib import DBusGMainLoop
+
+DBusGMainLoop(set_as_default=True)
diff --git a/wlanpi_core/schemas/network/__init__.py b/wlanpi_core/schemas/network/__init__.py
index 5f92637..ce9f921 100644
--- a/wlanpi_core/schemas/network/__init__.py
+++ b/wlanpi_core/schemas/network/__init__.py
@@ -1 +1,13 @@
-from .network import PublicIP
+from .network import (
+    APIConfig,
+    ConnectedNetwork,
+    Interface,
+    Interfaces,
+    NetworkEvent,
+    NetworkSetupLog,
+    NetworkSetupStatus,
+    PublicIP,
+    ScanResults,
+    WlanConfig,
+    WlanInterfaceSetup,
+)
diff --git a/wlanpi_core/schemas/network/network.py b/wlanpi_core/schemas/network/network.py
index 419524d..0184862 100644
--- a/wlanpi_core/schemas/network/network.py
+++ b/wlanpi_core/schemas/network/network.py
@@ -1,3 +1,5 @@
+from typing import List, Union
+
 from pydantic import BaseModel, Field
 
 
@@ -13,3 +15,64 @@ class PublicIP(BaseModel):
     asn: str = Field(example="AS12345")
     asn_org: str = Field(example="INTERNET")
     hostname: str = Field(example="d-192-168-1-50.paw.cpe.chicagoisp.net")
+
+
+class ScanItem(BaseModel):
+    ssid: str = Field(example="A Network")
+    bssid: str = Field(example="11:22:33:44:55")
+    key_mgmt: str = Field(example="wpa-psk")
+    signal: int = Field(example=-65)
+    freq: int = Field(example=5650)
+    minrate: int = Field(example=1000000)
+
+
+class ScanResults(BaseModel):
+    nets: List[ScanItem]
+
+
+class WlanConfig(BaseModel):
+    ssid: str = Field(example="SSID Name")
+    psk: Union[str, None] = None
+    proto: Union[str, None] = None
+    key_mgmt: str = Field(example="NONE, SAE")
+    ieee80211w: Union[int, None] = None
+
+
+class WlanInterfaceSetup(BaseModel):
+    interface: str = Field(example="wlan0")
+    netConfig: WlanConfig
+    removeAllFirst: bool
+
+
+class NetworkEvent(BaseModel):
+    event: str = Field(example="authenticated")
+    time: str = Field(example="2024-09-01 03:52:31.232828")
+
+
+class NetworkSetupLog(BaseModel):
+    selectErr: str = Field(example="fi.w1.wpa_supplicant1.NetworkUnknown")
+    eventLog: List[NetworkEvent]
+
+
+class NetworkSetupStatus(BaseModel):
+    status: str = Field(example="connected")
+    response: NetworkSetupLog
+    connectedNet: ScanItem
+    input: str
+
+
+class ConnectedNetwork(BaseModel):
+    connectedStatus: bool = Field(example=True)
+    connectedNet: Union[ScanItem, None]
+
+
+class Interface(BaseModel):
+    interface: str = Field(example="wlan0")
+
+
+class Interfaces(BaseModel):
+    interfaces: List[Interface]
+
+
+class APIConfig(BaseModel):
+    timeout: int = Field(example=20)
diff --git a/wlanpi_core/services/network_service.py b/wlanpi_core/services/network_service.py
index e69de29..5eb4bce 100644
--- a/wlanpi_core/services/network_service.py
+++ b/wlanpi_core/services/network_service.py
@@ -0,0 +1,695 @@
+import subprocess
+import time
+from datetime import datetime
+
+import dbus
+from dbus import Interface
+from dbus.exceptions import DBusException
+from gi.repository import GLib
+
+from wlanpi_core.models.validation_error import ValidationError
+from wlanpi_core.schemas import network
+
+# For running locally (not in API)
+# import asyncio
+
+WPAS_DBUS_SERVICE = "fi.w1.wpa_supplicant1"
+WPAS_DBUS_INTERFACE = "fi.w1.wpa_supplicant1"
+WPAS_DBUS_OPATH = "/fi/w1/wpa_supplicant1"
+WPAS_DBUS_INTERFACES_INTERFACE = "fi.w1.wpa_supplicant1.Interface"
+WPAS_DBUS_INTERFACES_OPATH = "/fi/w1/wpa_supplicant1/Interfaces"
+WPAS_DBUS_BSS_INTERFACE = "fi.w1.wpa_supplicant1.BSS"
+WPAS_DBUS_NETWORK_INTERFACE = "fi.w1.wpa_supplicant1.Network"
+
+API_TIMEOUT = 20
+
+# Define a global debug level variable
+DEBUG_LEVEL = 1
+# Debug Level 0: No messages are printed.
+# Debug Level 1: Only low-level messages (level 1) are printed.
+# Debug Level 2: Low-level and medium-level messages (levels 1 and 2) are printed.
+# Debug Level 3: All messages (levels 1, 2, and 3) are printed.
+
+
+def set_debug_level(level):
+    """
+    Sets the global debug level.
+
+    :param level: The desired debug level (0 for no debug, higher values for more verbosity).
+    """
+    global DEBUG_LEVEL
+    DEBUG_LEVEL = level
+
+
+def debug_print(message, level):
+    """
+    Prints a message to the console based on the global debug level.
+
+    :param message: The message to be printed.
+    :param level: The level of the message (e.g., 1 for low, 2 for medium, 3 for high).
+    """
+    if level <= DEBUG_LEVEL:
+        print(message)
+
+
+allowed_scan_types = [
+    "active",
+    "passive",
+]
+
+
+def is_allowed_scan_type(scan: str):
+    for allowed_scan_type in allowed_scan_types:
+        if scan == allowed_scan_type:
+            return True
+    return False
+
+
+def is_allowed_interface(interface: str, wpas_obj):
+    available_interfaces = fetch_interfaces(wpas_obj)
+    for allowed_interface in available_interfaces:
+        if interface == allowed_interface:
+            return True
+    return False
+
+
+def byte_array_to_string(s):
+    r = ""
+    for c in s:
+        if c >= 32 and c < 127:
+            r += "%c" % c
+        else:
+            r += " "
+            # r += urllib.quote(chr(c))
+    return r
+
+
+def renew_dhcp(interface):
+    """
+    Uses dhclient to release and request a new DHCP lease
+    """
+    try:
+        # Release the current DHCP lease
+        subprocess.run(["sudo", "dhclient", "-r", interface], check=True)
+        time.sleep(3)
+        # Obtain a new DHCP lease
+        subprocess.run(["sudo", "dhclient", interface], check=True)
+    except subprocess.CalledProcessError as spe:
+        debug_print(f"Failed to renew DHCP. Error {spe}", 1)
+
+
+def get_ip_address(interface):
+    """
+    Extract the IP Address from the linux ip add show <if> command
+    """
+    try:
+        # Run the command to get details for a specific interface
+        result = subprocess.run(
+            ["ip", "addr", "show", interface],
+            capture_output=True,
+            text=True,
+            check=True,
+        )
+
+        # Process the output to find the inet line which contains the IPv4 address
+        for line in result.stdout.split("\n"):
+            if "inet " in line:
+                # Extract the IP address from the line
+                ip_address = line.strip().split(" ")[1].split("/")[0]
+                return ip_address
+    except subprocess.CalledProcessError as spe:
+        debug_print("Failed to get IP address. Error {spe}", 1)
+        return None
+
+
+def getBss(bss):
+    """
+    Queries DBUS_BSS_INTERFACE through dbus for a BSS Path
+
+    Example path: /fi/w1/wpa_supplicant1/Interfaces/0/BSSs/567
+    """
+
+    try:
+        net_obj = bus.get_object(WPAS_DBUS_SERVICE, bss)
+        # dbus.Interface(net_obj, WPAS_DBUS_BSS_INTERFACE)
+        # Convert the byte-array to printable strings
+
+        # Get the BSSID from the byte array
+        val = net_obj.Get(
+            WPAS_DBUS_BSS_INTERFACE, "BSSID", dbus_interface=dbus.PROPERTIES_IFACE
+        )
+        bssid = ""
+        for item in val:
+            bssid = bssid + ":%02x" % item
+        bssid = bssid[1:]
+
+        # Get the SSID from the byte array
+        val = net_obj.Get(
+            WPAS_DBUS_BSS_INTERFACE, "SSID", dbus_interface=dbus.PROPERTIES_IFACE
+        )
+        ssid = byte_array_to_string(val)
+
+        # Get the WPA Type from the byte array
+        val = net_obj.Get(
+            WPAS_DBUS_BSS_INTERFACE, "WPA", dbus_interface=dbus.PROPERTIES_IFACE
+        )
+        if len(val["KeyMgmt"]) > 0:
+            pass
+
+        # Get the RSN Info from the byte array
+        val = net_obj.Get(
+            WPAS_DBUS_BSS_INTERFACE, "RSN", dbus_interface=dbus.PROPERTIES_IFACE
+        )
+        key_mgmt = "/".join([str(r) for r in val["KeyMgmt"]])
+
+        # Get the Frequency from the byte array
+        freq = net_obj.Get(
+            WPAS_DBUS_BSS_INTERFACE, "Frequency", dbus_interface=dbus.PROPERTIES_IFACE
+        )
+
+        # Get the RSSI from the byte array
+        signal = net_obj.Get(
+            WPAS_DBUS_BSS_INTERFACE, "Signal", dbus_interface=dbus.PROPERTIES_IFACE
+        )
+
+        # Get the Phy Rates from the byte array
+        rates = net_obj.Get(
+            WPAS_DBUS_BSS_INTERFACE, "Rates", dbus_interface=dbus.PROPERTIES_IFACE
+        )
+
+        minrate = 0
+        if len(rates) > 0:
+            minrate = rates[-1]
+
+        # Get the IEs from the byte array
+        IEs = net_obj.Get(
+            WPAS_DBUS_BSS_INTERFACE, "IEs", dbus_interface=dbus.PROPERTIES_IFACE
+        )
+        debug_print(f"IEs: {IEs}", 3)
+
+        return {
+            "ssid": ssid,
+            "bssid": bssid,
+            "key_mgmt": key_mgmt,
+            "signal": signal,
+            "freq": freq,
+            "minrate": minrate,
+        }
+
+    except DBusException:
+        return None
+    except ValueError as error:
+        raise ValidationError(f"{error}", status_code=400)
+
+
+def pretty_print_BSS(BSSPath):
+    BSSDetails = getBss(BSSPath)
+    if BSSDetails:
+        ssid = BSSDetails["ssid"] if BSSDetails["ssid"] else "<hidden>"
+        bssid = BSSDetails["bssid"]
+        freq = BSSDetails["freq"]
+        rssi = BSSDetails["signal"]
+        key_mgmt = BSSDetails["key_mgmt"]
+        minrate = BSSDetails["minrate"]
+
+        result = f"[{bssid}] {freq}, {rssi}dBm, {minrate} | {ssid} [{key_mgmt}] "
+        return result
+    else:
+        return f"BSS Path {BSSPath} could not be resolved"
+
+
+def fetch_interfaces(wpas_obj):
+    available_interfaces = []
+    ifaces = wpas_obj.Get(
+        WPAS_DBUS_INTERFACE, "Interfaces", dbus_interface=dbus.PROPERTIES_IFACE
+    )
+    debug_print("InterfacesRequested: %s" % (ifaces), 2)
+    # time.sleep(3)
+    for path in ifaces:
+        debug_print("Resolving Interface Path: %s" % (path), 2)
+        if_obj = bus.get_object(WPAS_DBUS_SERVICE, path)
+        ifname = if_obj.Get(
+            WPAS_DBUS_INTERFACES_INTERFACE,
+            "Ifname",
+            dbus_interface=dbus.PROPERTIES_IFACE,
+        )
+        available_interfaces.append({"interface": ifname})
+        debug_print(f"Found interface : {ifname}", 2)
+    return available_interfaces
+
+
+def fetch_currentBSS(interface):
+    # Refresh the path to the adapter and read back the current BSSID
+    bssid = ""
+
+    try:
+        path = wpas.GetInterface(interface)
+    except dbus.DBusException as exc:
+        if not str(exc).startswith("fi.w1.wpa_supplicant1.InterfaceUnknown:"):
+            raise ValidationError(f"Interface unknown : {exc}", status_code=400)
+        try:
+            path = wpas.CreateInterface({"Ifname": interface, "Driver": "test"})
+            time.sleep(1)
+        except dbus.DBusException as exc:
+            if not str(exc).startswith("fi.w1.wpa_supplicant1.InterfaceExists:"):
+                raise ValidationError(
+                    f"Interface cannot be created : {exc}", status_code=400
+                )
+
+    time.sleep(1)
+
+    if_obj = bus.get_object(WPAS_DBUS_SERVICE, path)
+    # time.sleep(2)
+    currentBssPath = if_obj.Get(
+        WPAS_DBUS_INTERFACES_INTERFACE,
+        "CurrentBSS",
+        dbus_interface=dbus.PROPERTIES_IFACE,
+    )
+    debug_print("Checking BSS", 2)
+    if currentBssPath != "/":
+        currentBssPath.split("/")[-1]
+        bssid = getBss(currentBssPath)
+    debug_print(currentBssPath, 2)
+    return bssid
+
+
+"""
+Call back functions from GLib
+"""
+
+
+def scanDone(success):
+    debug_print(f"Scan done: success={success}", 1)
+    global scan
+    local_scan = []
+    res = if_obj.Get(
+        WPAS_DBUS_INTERFACES_INTERFACE, "BSSs", dbus_interface=dbus.PROPERTIES_IFACE
+    )
+    debug_print("Scanned wireless networks:", 1)
+    for opath in res:
+        bss = getBss(opath)
+        if bss:
+            local_scan.append(bss)
+    scan = local_scan
+    scancount = len(scan)
+    debug_print(f"A Scan has completed with {scancount} results", 1)
+    debug_print(scan, 3)
+
+
+def networkSelected(network):
+    # returns the current selected network path
+    debug_print(f"Network Selected (Signal) : {network}", 1)
+    selectedNetworkSSID.append(network)
+
+
+def propertiesChanged(properties):
+    debug_print(f"PropertiesChanged: {properties}", 2)
+    if properties.get("State") is not None:
+        state = properties["State"]
+
+        if state == "completed":
+            time.sleep(2)
+            if currentInterface:
+                renew_dhcp(currentInterface)
+                ipaddr = get_ip_address(currentInterface)
+                connectionEvents.append(
+                    network.NetworkEvent(
+                        event=f"IP Address {ipaddr} on {currentInterface}",
+                        time=f"{datetime.now()}",
+                    )
+                )
+            supplicantState.append(state)
+            debug_print(f"Connection Completed: State: {state}", 1)
+            # elif state == "scanning":
+            debug_print("SCAN---", 3)
+        elif state == "associating":
+            debug_print(f"PropertiesChanged: {state}", 1)
+        elif state == "authenticating":
+            # scanning = properties["Scanning"]
+            debug_print(f"PropertiesChanged: {state}", 1)
+        elif state == "4way_handshake":
+            debug_print(f"PropertiesChanged: {state}", 1)
+            if properties.get("CurrentBSS"):
+                bssidpath = properties["CurrentBSS"]
+                debug_print(f"Handshake attempt to: {pretty_print_BSS(bssidpath)}", 1)
+        else:
+            debug_print(f"PropertiesChanged: State: {state}", 1)
+        connectionEvents.append(
+            network.NetworkEvent(event=f"{state}", time=f"{datetime.now()}")
+        )
+    elif properties.get("DisconnectReason") is not None:
+        disconnectReason = properties["DisconnectReason"]
+        debug_print(f"Disconnect Reason: {disconnectReason}", 1)
+        if disconnectReason != 0:
+            if disconnectReason == 3 or disconnectReason == -3:
+                connectionEvents.append(
+                    network.NetworkEvent(
+                        event="Station is Leaving", time=f"{datetime.now()}"
+                    )
+                )
+            elif disconnectReason == 15:
+                connectionEvents.append(
+                    network.NetworkEvent(
+                        event="4-Way Handshake Fail (check password)",
+                        time=f"{datetime.now()}",
+                    )
+                )
+                supplicantState.append("authentication error")
+            else:
+                connectionEvents.append(
+                    network.NetworkEvent(
+                        event=f"Error: Disconnected [{disconnectReason}]",
+                        time=f"{datetime.now()}",
+                    )
+                )
+                supplicantState.append("disconnected")
+
+    # For debugging purposes only
+    # if properties.get("BSSs") is not None:
+    #     print("Supplicant has found the following BSSs")
+    #     for BSS in properties["BSSs"]:
+    #         if len(BSS) > 0:
+    #             print(pretty_print_BSS(BSS))
+
+    if properties.get("CurrentAuthMode") is not None:
+        currentAuthMode = properties["CurrentAuthMode"]
+        debug_print(f"Current Auth Mode is {currentAuthMode}", 1)
+
+    if properties.get("AuthStatusCode") is not None:
+        authStatus = properties["AuthStatusCode"]
+        debug_print(f"Auth Status: {authStatus}", 1)
+        if authStatus == 0:
+            connectionEvents.append(
+                network.NetworkEvent(event="authenticated", time=f"{datetime.now()}")
+            )
+        else:
+            connectionEvents.append(
+                network.NetworkEvent(
+                    event=f"authentication failed with code {authStatus}",
+                    time=f"{datetime.now()}",
+                )
+            )
+            supplicantState.append(f"authentication fail {authStatus}")
+
+
+def setup_DBus_Supplicant_Access(interface):
+    global bus
+    global if_obj
+    global iface
+    global wpas
+    global currentInterface
+
+    bus = dbus.SystemBus()
+    proxy = bus.get_object(WPAS_DBUS_SERVICE, WPAS_DBUS_OPATH)
+    wpas = Interface(proxy, WPAS_DBUS_INTERFACE)
+
+    try:
+        path = wpas.GetInterface(interface)
+        currentInterface = interface
+    except dbus.DBusException as exc:
+        if not str(exc).startswith("fi.w1.wpa_supplicant1.InterfaceUnknown:"):
+            raise ValidationError(f"Interface unknown : {exc}", status_code=400)
+        try:
+            path = wpas.CreateInterface({"Ifname": interface, "Driver": "test"})
+            time.sleep(1)
+        except dbus.DBusException as exc:
+            if not str(exc).startswith("fi.w1.wpa_supplicant1.InterfaceExists:"):
+                raise ValidationError(
+                    f"Interface cannot be created : {exc}", status_code=400
+                )
+    time.sleep(1)
+    debug_print(path, 3)
+    if_obj = bus.get_object(WPAS_DBUS_SERVICE, path)
+    # time.sleep(1)
+    iface = dbus.Interface(if_obj, WPAS_DBUS_INTERFACES_INTERFACE)
+
+
+"""
+These are the functions used to deliver the API
+"""
+
+
+async def get_systemd_network_interfaces(timeout: network.APIConfig):
+    """
+    Queries systemd via dbus to get a list of the available interfaces.
+    """
+    global bus
+    bus = dbus.SystemBus()
+    wpas_obj = bus.get_object(WPAS_DBUS_SERVICE, WPAS_DBUS_OPATH)
+    debug_print("Checking available interfaces", 3)
+    available_interfaces = fetch_interfaces(wpas_obj)
+    debug_print(f"Available interfaces: {available_interfaces}", 3)
+    return {"interfaces": available_interfaces}
+
+
+async def get_async_systemd_network_scan(
+    type: str, interface: network.Interface, timeout: network.APIConfig
+):
+    """
+    Queries systemd via dbus to get a scan of the available networks.
+    """
+    API_TIMEOUT = timeout
+    dbus.mainloop.glib.DBusGMainLoop(set_as_default=True)
+
+    type = type.strip().lower()
+    if is_allowed_scan_type(type):
+
+        try:
+            setup_DBus_Supplicant_Access(interface)
+
+            global scan
+            scan = []
+            scanConfig = dbus.Dictionary({"Type": type}, signature="sv")
+
+            scan_handler = bus.add_signal_receiver(
+                scanDone,
+                dbus_interface=WPAS_DBUS_INTERFACES_INTERFACE,
+                signal_name="ScanDone",
+            )
+
+            iface.Scan(scanConfig)
+
+            main_context = GLib.MainContext.default()
+            timeout_check = 0
+            while scan == [] and timeout_check <= API_TIMEOUT:
+                time.sleep(1)
+                timeout_check += 1
+                debug_print(
+                    f"Scan request timeout state: {timeout_check} / {API_TIMEOUT}", 2
+                )
+                main_context.iteration(False)
+
+            scan_handler.remove()
+
+            # scan = [{"ssid": "A Network", "bssid": "11:22:33:44:55", "wpa": "no", "wpa2": "yes", "signal": -65, "freq": 5650}]
+            return {"nets": scan}
+        except DBusException as de:
+            debug_print(f"DBUS Error State: {de}", 0)
+        except ValueError as error:
+            raise ValidationError(f"{error}", status_code=400)
+    raise ValidationError(f"{type} is not a valid scan type", status_code=400)
+
+
+async def set_systemd_network_addNetwork(
+    interface: network.Interface,
+    netConfig: network.WlanConfig,
+    removeAllFirst: bool,
+    timeout: network.APIConfig,
+):
+    """
+    Uses wpa_supplicant to connect to a WLAN network.
+    """
+    global selectedNetworkSSID
+    selectedNetworkSSID = []
+    global supplicantState
+    supplicantState = []
+    global connectionEvents
+    connectionEvents = []
+
+    API_TIMEOUT = timeout
+
+    debug_print("Setting up supplicant access", 3)
+    dbus.mainloop.glib.DBusGMainLoop(set_as_default=True)
+    setup_DBus_Supplicant_Access(interface)
+
+    selectErr = None
+    status = "uninitialised"
+    bssid = {
+        "ssid": "",
+        "bssid": "",
+        "key_mgmt": "",
+        "signal": 0,
+        "freq": 0,
+        "minrate": 0,
+    }
+    response = network.NetworkSetupLog(selectErr="", eventLog=[])
+
+    try:
+        debug_print("Configuring DBUS", 3)
+        network_change_handler = bus.add_signal_receiver(
+            networkSelected,
+            dbus_interface=WPAS_DBUS_INTERFACES_INTERFACE,
+            signal_name="NetworkSelected",
+        )
+
+        properties_change_handler = bus.add_signal_receiver(
+            propertiesChanged,
+            dbus_interface=WPAS_DBUS_INTERFACES_INTERFACE,
+            signal_name="PropertiesChanged",
+        )
+
+        # Remove all configured networks and apply the new network
+        if removeAllFirst:
+            debug_print("Removing existing connections", 2)
+            netw = iface.RemoveAllNetworks()
+
+        netConfig_cleaned = {k: v for k, v in netConfig if v is not None}
+        netConfig_DBUS = dbus.Dictionary(netConfig_cleaned, signature="sv")
+        netw = iface.AddNetwork(netConfig_DBUS)
+
+        if netw != "/":
+            debug_print("Valid network entry received", 2)
+            # A valid network entry has been created - get the Index
+            netw.split("/")[-1]
+
+            # Select this network using its full path name
+            selectErr = iface.SelectNetwork(netw)
+            # time.sleep(10)
+            debug_print(f"Network selected with result: {selectErr}", 2)
+
+            if selectErr == None:
+                # The network selection has been successsfully applied (does not mean a network is selected)
+                main_context = GLib.MainContext.default()
+                timeout_check = 0
+                while supplicantState == [] and timeout_check <= API_TIMEOUT:
+                    time.sleep(1)
+                    timeout_check += 1
+                    debug_print(
+                        f"Select request timeout: {timeout_check} / {API_TIMEOUT}", 2
+                    )
+                    main_context.iteration(False)
+
+                if supplicantState != []:
+                    if supplicantState[0] == "completed":
+                        # Check the current BSSID post connection
+                        bssidPath = if_obj.Get(
+                            WPAS_DBUS_INTERFACES_INTERFACE,
+                            "CurrentBSS",
+                            dbus_interface=dbus.PROPERTIES_IFACE,
+                        )
+                        if bssidPath != "/":
+                            bssidresolution = getBss(bssidPath)
+                            if bssidresolution:
+                                bssid = bssidresolution
+                                debug_print(f"Logged Events: {connectionEvents}", 2)
+                                debug_print("Connected", 1)
+                                status = "connected"
+                            else:
+                                debug_print(f"select error: {selectErr}", 2)
+                                debug_print(f"Logged Events: {connectionEvents}", 2)
+                                debug_print(
+                                    "Connection failed. Post connection check returned no network",
+                                    1,
+                                )
+                                status = "connection_lost"
+                        else:
+                            debug_print(f"select error: {selectErr}", 2)
+                            debug_print(f"Logged Events: {connectionEvents}", 2)
+                            debug_print("Connection failed. Aborting", 1)
+                            status = "connection_lost"
+
+                    elif supplicantState[0] == "fail":
+                        debug_print(f"select error: {selectErr}", 2)
+                        debug_print(f"Logged Events: {connectionEvents}", 2)
+                        debug_print("Connection failed. Aborting", 1)
+                        status = f"connection_failed:{supplicantState[0]}"
+                    else:
+                        debug_print(f"select error: {selectErr}", 2)
+                        debug_print(f"Logged Events: {connectionEvents}", 2)
+                        debug_print("Connection failed. Aborting", 1)
+                        status = f"connection failed:{supplicantState[0]}"
+                else:
+                    debug_print(f"select error: {selectErr}", 2)
+                    debug_print(f"Logged Events: {connectionEvents}", 2)
+                    debug_print(f"No connection", 1)
+                    status = "Network_not_found"
+
+            else:
+                debug_print(f"select error: {selectErr}", 2)
+                debug_print(f"Logged Events: {connectionEvents}", 2)
+                if timeout_check >= API_TIMEOUT:
+                    status = "Connection Timeout"
+                    debug_print("Connection Timeout", 1)
+                else:
+                    status = "Connection Err"
+                    debug_print("Connection Err", 1)
+
+    except DBusException as de:
+        debug_print(f"DBUS Error State: {de}", 0)
+    except ValueError as error:
+        raise ValidationError(f"{error}", status_code=400)
+
+    network_change_handler.remove()
+    properties_change_handler.remove()
+
+    response.eventLog = connectionEvents
+    if selectErr != None:
+        response.selectErr = str(selectErr)
+    else:
+        response.selectErr = ""
+
+    return {
+        "status": status,
+        "response": response,
+        "connectedNet": bssid,
+        "input": netConfig.ssid,
+    }
+
+
+async def get_systemd_network_currentNetwork_details(
+    interface: network.Interface, timeout: network.APIConfig
+):
+    """
+    Queries systemd via dbus to get a scan of the available networks.
+    """
+    try:
+        dbus.mainloop.glib.DBusGMainLoop(set_as_default=True)
+        res = ""
+        setup_DBus_Supplicant_Access(interface)
+        time.sleep(1)
+
+        # res = fetch_currentBSS(interface)
+        bssidPath = if_obj.Get(
+            WPAS_DBUS_INTERFACES_INTERFACE,
+            "CurrentBSS",
+            dbus_interface=dbus.PROPERTIES_IFACE,
+        )
+
+        if bssidPath != "/":
+            res = getBss(bssidPath)
+            return {"connectedStatus": True, "connectedNet": res}
+        else:
+            return {"connectedStatus": False, "connectedNet": None}
+    except DBusException:
+        debug_print("DBUS Error State: {de}", 0)
+    except ValueError as error:
+        raise ValidationError(f"{error}", status_code=400)
+
+
+# async def main():
+#     await get_async_systemd_network_scan('passive', 'wlan0')
+#     # testnet = '{"ssid":"PiAP_6","psk":"wlanpieea","key_mgmt":"SAE","ieee80211w":2}'
+#     # await set_systemd_network_addNetwork('wlan0',testnet,True)
+
+# if __name__ == "__main__":
+# 	asyncio.run(main())
+
+# ### -- Test for printing out the connected network ###
+# if_obj = bus.get_object(WPAS_DBUS_SERVICE, path)
+# res = if_obj.Get(WPAS_DBUS_INTERFACES_INTERFACE, 'CurrentNetwork', dbus_interface=dbus.PROPERTIES_IFACE)
+# # showNetwork(res)
+
+# ### -- Test for printing out the connected network ###
+# if_obj = bus.get_object(WPAS_DBUS_SERVICE, path)
+# res = if_obj.Get(WPAS_DBUS_INTERFACES_INTERFACE, 'CurrentBSS', dbus_interface=dbus.PROPERTIES_IFACE)
+# print(getBss(res))
diff --git a/wlanpi_core/services/system_service.py b/wlanpi_core/services/system_service.py
index 7ce23d4..8331aa3 100644
--- a/wlanpi_core/services/system_service.py
+++ b/wlanpi_core/services/system_service.py
@@ -30,6 +30,8 @@
     "wlanpi-grafana-wipry-lp-5",
     "wlanpi-grafana-wipry-lp-6",
     "wlanpi-grafana-wipry-lp-stop",
+    "wpa_supplicant",
+    "wpa_supplicant@wlan0",
 ]
 
 
diff --git a/wlanpi_core/static/img/apple-icon-144x144.png b/wlanpi_core/static/img/apple-icon-144x144.png
index 16c573f..ce85419 100644
Binary files a/wlanpi_core/static/img/apple-icon-144x144.png and b/wlanpi_core/static/img/apple-icon-144x144.png differ