diff --git a/components/micropython/vendor/ports/esp32/machine_pin.c b/components/micropython/vendor/ports/esp32/machine_pin.c
index b3c0783c79f2aa62310dc69daff33d61aa06e8c6..89515976e4993d88d00c07f114f33ab4965cf573 100644
--- a/components/micropython/vendor/ports/esp32/machine_pin.c
+++ b/components/micropython/vendor/ports/esp32/machine_pin.c
@@ -155,7 +155,7 @@ STATIC const machine_pin_obj_t machine_pin_obj[GPIO_NUM_MAX] = {
     {{&machine_pin_type}, GPIO_NUM_6}, // Badgelink
     {{&machine_pin_type}, GPIO_NUM_7}, // Badgelink
     {{NULL}, -1}, // 8
-    {{NULL}, -1}, // 9
+    {{&machine_pin_type}, GPIO_NUM_9}, // Battery ADC
     {{NULL}, -1}, // 10
     {{NULL}, -1}, // 11
     {{NULL}, -1}, // 12
diff --git a/python_payload/mypystubs/machine.pyi b/python_payload/mypystubs/machine.pyi
index 4e527e1d56f69cdd720f53c286fce2cf5e94a8d6..5f1a7adbde03a02ec785825cb5f408c521cac864 100644
--- a/python_payload/mypystubs/machine.pyi
+++ b/python_payload/mypystubs/machine.pyi
@@ -1,30 +1,36 @@
-try:
-    from typing import Protocol
-except ImportError:
-    from typing_extensions import Protocol  # type: ignore
+from typing import Any
 
-class Pin(Protocol):
+class Pin:
     """
     See the official micropython docs for more details:
 
     https://docs.micropython.org/en/latest/library/machine.Pin.html
     """
 
-    IN: int
-    OUT: int
-    OPEN_DRAIN: int
-    PULL_UP: int
-    PULL_DOWN: int
-    IRQ_RISING: int = 1
-    IRQ_FALLING: int = 2
-    WAKE_LOW: int
-    WAKE_HIGH: int
-    DRIVE_0: int
-    DRIVE_1: int
-    DRIVE_2: int
-    DRIVE_3: int
+    IN: int = 1
+    OUT: int = 2
+    OPEN_DRAIN: int = 3
+    PULL_UP: int = 4
+    PULL_DOWN: int = 5
+    IRQ_RISING: int = 6
+    IRQ_FALLING: int = 7
+    WAKE_LOW: int = 8
+    WAKE_HIGH: int = 9
+    DRIVE_0: int = 10
+    DRIVE_1: int = 11
+    DRIVE_2: int = 12
+    DRIVE_3: int = 13
 
-    def __init__(self, id, mode=-1, pull=-1, *, value=None, drive=0, alt=-1):
+    def __init__(
+        self,
+        id: int,
+        mode: int = -1,
+        pull: int = -1,
+        *,
+        value: Any = None,
+        drive: int = 0,
+        alt: int = -1
+    ) -> None:
         """
         Available pins for the flow3r badge are:
 
@@ -35,17 +41,30 @@ class Pin(Protocol):
         * 17 (QWIIC SDA)
         * 45 (QWIIC SCL)
         """
-    def init(self, mode=-1, pull=-1, *, value=None, drive=0, alt=-1): ...
-    def value(self, x=None): ...
-    def __call__(self, x=None): ...
-    def on(self): ...
-    def off(self): ...
+    def init(
+        self,
+        mode: int = -1,
+        pull: int = -1,
+        *,
+        value: Any = None,
+        drive: int = 0,
+        alt: int = -1
+    ) -> None: ...
+    def value(self, x: Any = None) -> int: ...
+    def __call__(self, x: Any = None) -> None: ...
+    def on(self) -> None: ...
+    def off(self) -> None: ...
     def irq(
         self,
-        handler=None,
-        trigger=IRQ_FALLING | IRQ_RISING,
+        handler: Any = None,
+        trigger: Any = IRQ_FALLING | IRQ_RISING,
         *,
-        priority=1,
-        wake=None,
-        hard=False
-    ): ...
+        priority: int = 1,
+        wake: Any = None,
+        hard: bool = False
+    ) -> None: ...
+
+class ADC:
+    ATTN_11DB: int = 1
+    def __init__(self, p: Pin, atten: int = 0): ...
+    def read_uv(self) -> float: ...
diff --git a/python_payload/st3m/input.py b/python_payload/st3m/input.py
index 17f6b78cd8da60dab38f5b8f1b9f723c92b87372..06a2660073e8cdfe210ac078097bc76669342b6e 100644
--- a/python_payload/st3m/input.py
+++ b/python_payload/st3m/input.py
@@ -4,7 +4,7 @@ import hardware
 import captouch
 import imu
 
-import math
+import machine
 
 
 class InputState:
@@ -458,6 +458,24 @@ class IMUState:
         self.pressure, self.temperature = imu.pressure_read()
 
 
+class PowerState:
+    __slots__ = ("_ts", "_adc_pin", "_adc", "battery_voltage")
+
+    def __init__(self) -> None:
+        self._adc_pin = machine.Pin(9, machine.Pin.IN)
+        self._adc = machine.ADC(self._adc_pin, atten=machine.ADC.ATTN_11DB)
+        self.battery_voltage = self._battery_voltage_sample()
+        self._ts = 0
+
+    def _battery_voltage_sample(self) -> float:
+        return self._adc.read_uv() * 2 / 1e6
+
+    def _update(self, ts: int, hr: InputState) -> None:
+        if ts >= self._ts + 1000:
+            self.battery_voltage = self._battery_voltage_sample()
+            self._ts = ts
+
+
 class InputController:
     """
     A stateful input controller. It accepts InputState updates from the Reactor
@@ -475,6 +493,7 @@ class InputController:
         "left_shoulder",
         "right_shoulder",
         "imu",
+        "power",
         "_ts",
     )
 
@@ -483,6 +502,7 @@ class InputController:
         self.left_shoulder = TriSwitchState(TriSwitchHandedness.left)
         self.right_shoulder = TriSwitchState(TriSwitchHandedness.right)
         self.imu = IMUState()
+        self.power = PowerState()
         self._ts = 0
 
     def think(self, hr: InputState, delta_ms: int) -> None:
@@ -491,6 +511,7 @@ class InputController:
         self.left_shoulder._update(self._ts, hr)
         self.right_shoulder._update(self._ts, hr)
         self.imu._update(self._ts, hr)
+        self.power._update(self._ts, hr)
 
     def _ignore_pressed(self) -> None:
         """
diff --git a/sim/fakes/machine.py b/sim/fakes/machine.py
new file mode 100644
index 0000000000000000000000000000000000000000..77b2272a1ec254b6a7f5da0b15f3140fb3c47edd
--- /dev/null
+++ b/sim/fakes/machine.py
@@ -0,0 +1,16 @@
+class Pin:
+    IN = None
+
+    def __init__(self, _1, _2):
+        pass
+
+
+class ADC:
+    ATTN_11DB = None
+
+    def __init__(self, _1, atten):
+        pass
+
+    def read_uv(self):
+        # A half full battery as seen by the ADC
+        return 3.8e6 / 2