diff --git a/homeassistant/components/iron_os/__init__.py b/homeassistant/components/iron_os/__init__.py
index bf3c6c34c83ee0276b1eb5d96f6da892963903b8..11d99a1558a9a12d176eee6ce2ac5b8eb21ddb94 100644
--- a/homeassistant/components/iron_os/__init__.py
+++ b/homeassistant/components/iron_os/__init__.py
@@ -16,7 +16,7 @@ from homeassistant.exceptions import ConfigEntryNotReady
 from .const import DOMAIN
 from .coordinator import IronOSCoordinator
 
-PLATFORMS: list[Platform] = [Platform.SENSOR]
+PLATFORMS: list[Platform] = [Platform.NUMBER, Platform.SENSOR]
 
 type IronOSConfigEntry = ConfigEntry[IronOSCoordinator]
 
diff --git a/homeassistant/components/iron_os/const.py b/homeassistant/components/iron_os/const.py
index 86b7d401f4fce5168bab1fb59a5fbdca484f1bfb..34889636808dcb9dada89c3fb592236d61c5d28e 100644
--- a/homeassistant/components/iron_os/const.py
+++ b/homeassistant/components/iron_os/const.py
@@ -8,3 +8,6 @@ MODEL = "Pinecil V2"
 OHM = "Ω"
 
 DISCOVERY_SVC_UUID = "9eae1000-9d0d-48c5-aa55-33e27f9bc533"
+
+MAX_TEMP: int = 450
+MIN_TEMP: int = 10
diff --git a/homeassistant/components/iron_os/icons.json b/homeassistant/components/iron_os/icons.json
index 0d207607a4f5291312080c591142260461e95426..fa14b8134d0a37c350e953dfea978ee1847bc232 100644
--- a/homeassistant/components/iron_os/icons.json
+++ b/homeassistant/components/iron_os/icons.json
@@ -1,12 +1,14 @@
 {
   "entity": {
+    "number": {
+      "setpoint_temperature": {
+        "default": "mdi:thermometer"
+      }
+    },
     "sensor": {
       "live_temperature": {
         "default": "mdi:soldering-iron"
       },
-      "setpoint_temperature": {
-        "default": "mdi:thermostat"
-      },
       "voltage": {
         "default": "mdi:current-dc"
       },
diff --git a/homeassistant/components/iron_os/number.py b/homeassistant/components/iron_os/number.py
new file mode 100644
index 0000000000000000000000000000000000000000..9230faec1f13ff761ed989abdae2aef544eb4fb1
--- /dev/null
+++ b/homeassistant/components/iron_os/number.py
@@ -0,0 +1,96 @@
+"""Number platform for IronOS integration."""
+
+from __future__ import annotations
+
+from collections.abc import Callable
+from dataclasses import dataclass
+from enum import StrEnum
+
+from pynecil import CharSetting, CommunicationError, LiveDataResponse
+
+from homeassistant.components.number import (
+    NumberDeviceClass,
+    NumberEntity,
+    NumberEntityDescription,
+    NumberMode,
+)
+from homeassistant.const import UnitOfTemperature
+from homeassistant.core import HomeAssistant
+from homeassistant.exceptions import ServiceValidationError
+from homeassistant.helpers.entity_platform import AddEntitiesCallback
+
+from . import IronOSConfigEntry
+from .const import DOMAIN, MAX_TEMP, MIN_TEMP
+from .entity import IronOSBaseEntity
+
+
+@dataclass(frozen=True, kw_only=True)
+class IronOSNumberEntityDescription(NumberEntityDescription):
+    """Describes IronOS number entity."""
+
+    value_fn: Callable[[LiveDataResponse], float | int | None]
+    max_value_fn: Callable[[LiveDataResponse], float | int]
+    set_key: CharSetting
+
+
+class PinecilNumber(StrEnum):
+    """Number controls for Pinecil device."""
+
+    SETPOINT_TEMP = "setpoint_temperature"
+
+
+PINECIL_NUMBER_DESCRIPTIONS: tuple[IronOSNumberEntityDescription, ...] = (
+    IronOSNumberEntityDescription(
+        key=PinecilNumber.SETPOINT_TEMP,
+        translation_key=PinecilNumber.SETPOINT_TEMP,
+        native_unit_of_measurement=UnitOfTemperature.CELSIUS,
+        device_class=NumberDeviceClass.TEMPERATURE,
+        value_fn=lambda data: data.setpoint_temp,
+        set_key=CharSetting.SETPOINT_TEMP,
+        mode=NumberMode.BOX,
+        native_min_value=MIN_TEMP,
+        native_step=5,
+        max_value_fn=lambda data: min(data.max_tip_temp_ability or MAX_TEMP, MAX_TEMP),
+    ),
+)
+
+
+async def async_setup_entry(
+    hass: HomeAssistant,
+    entry: IronOSConfigEntry,
+    async_add_entities: AddEntitiesCallback,
+) -> None:
+    """Set up number entities from a config entry."""
+    coordinator = entry.runtime_data
+
+    async_add_entities(
+        IronOSNumberEntity(coordinator, description)
+        for description in PINECIL_NUMBER_DESCRIPTIONS
+    )
+
+
+class IronOSNumberEntity(IronOSBaseEntity, NumberEntity):
+    """Implementation of a IronOS number entity."""
+
+    entity_description: IronOSNumberEntityDescription
+
+    async def async_set_native_value(self, value: float) -> None:
+        """Update the current value."""
+        try:
+            await self.coordinator.device.write(self.entity_description.set_key, value)
+        except CommunicationError as e:
+            raise ServiceValidationError(
+                translation_domain=DOMAIN,
+                translation_key="submit_setting_failed",
+            ) from e
+        self.async_write_ha_state()
+
+    @property
+    def native_value(self) -> float | int | None:
+        """Return sensor state."""
+        return self.entity_description.value_fn(self.coordinator.data)
+
+    @property
+    def native_max_value(self) -> float:
+        """Return sensor state."""
+        return self.entity_description.max_value_fn(self.coordinator.data)
diff --git a/homeassistant/components/iron_os/strings.json b/homeassistant/components/iron_os/strings.json
index cb95330b7686a33cf3eb3f318d189705016f6f96..75584fe191c37019e1c0827f70718b2a6e397485 100644
--- a/homeassistant/components/iron_os/strings.json
+++ b/homeassistant/components/iron_os/strings.json
@@ -17,6 +17,11 @@
     }
   },
   "entity": {
+    "number": {
+      "setpoint_temperature": {
+        "name": "Setpoint temperature"
+      }
+    },
     "sensor": {
       "live_temperature": {
         "name": "Tip temperature"
@@ -79,6 +84,9 @@
     },
     "setup_device_connection_error_exception": {
       "message": "Connection to device {name} failed, try again later"
+    },
+    "submit_setting_failed": {
+      "message": "Failed to submit setting to device, try again later"
     }
   }
 }
diff --git a/tests/components/iron_os/snapshots/test_number.ambr b/tests/components/iron_os/snapshots/test_number.ambr
new file mode 100644
index 0000000000000000000000000000000000000000..2f5ee62e37e1b705557331647f2397c19edf4cc5
--- /dev/null
+++ b/tests/components/iron_os/snapshots/test_number.ambr
@@ -0,0 +1,58 @@
+# serializer version: 1
+# name: test_state[number.pinecil_setpoint_temperature-entry]
+  EntityRegistryEntrySnapshot({
+    'aliases': set({
+    }),
+    'area_id': None,
+    'capabilities': dict({
+      'max': 450,
+      'min': 10,
+      'mode': <NumberMode.BOX: 'box'>,
+      'step': 5,
+    }),
+    'config_entry_id': <ANY>,
+    'device_class': None,
+    'device_id': <ANY>,
+    'disabled_by': None,
+    'domain': 'number',
+    'entity_category': None,
+    'entity_id': 'number.pinecil_setpoint_temperature',
+    'has_entity_name': True,
+    'hidden_by': None,
+    'icon': None,
+    'id': <ANY>,
+    'labels': set({
+    }),
+    'name': None,
+    'options': dict({
+    }),
+    'original_device_class': <NumberDeviceClass.TEMPERATURE: 'temperature'>,
+    'original_icon': None,
+    'original_name': 'Setpoint temperature',
+    'platform': 'iron_os',
+    'previous_unique_id': None,
+    'supported_features': 0,
+    'translation_key': <PinecilNumber.SETPOINT_TEMP: 'setpoint_temperature'>,
+    'unique_id': 'c0:ff:ee:c0:ff:ee_setpoint_temperature',
+    'unit_of_measurement': <UnitOfTemperature.CELSIUS: '°C'>,
+  })
+# ---
+# name: test_state[number.pinecil_setpoint_temperature-state]
+  StateSnapshot({
+    'attributes': ReadOnlyDict({
+      'device_class': 'temperature',
+      'friendly_name': 'Pinecil Setpoint temperature',
+      'max': 450,
+      'min': 10,
+      'mode': <NumberMode.BOX: 'box'>,
+      'step': 5,
+      'unit_of_measurement': <UnitOfTemperature.CELSIUS: '°C'>,
+    }),
+    'context': <ANY>,
+    'entity_id': 'number.pinecil_setpoint_temperature',
+    'last_changed': <ANY>,
+    'last_reported': <ANY>,
+    'last_updated': <ANY>,
+    'state': '300',
+  })
+# ---
diff --git a/tests/components/iron_os/test_number.py b/tests/components/iron_os/test_number.py
new file mode 100644
index 0000000000000000000000000000000000000000..c091040668c300ba5b5312d285c473d3b05954f0
--- /dev/null
+++ b/tests/components/iron_os/test_number.py
@@ -0,0 +1,104 @@
+"""Tests for the IronOS number platform."""
+
+from collections.abc import AsyncGenerator
+from unittest.mock import AsyncMock, patch
+
+from pynecil import CharSetting, CommunicationError
+import pytest
+from syrupy.assertion import SnapshotAssertion
+
+from homeassistant.components.number import (
+    ATTR_VALUE,
+    DOMAIN as NUMBER_DOMAIN,
+    SERVICE_SET_VALUE,
+)
+from homeassistant.config_entries import ConfigEntryState
+from homeassistant.const import ATTR_ENTITY_ID, Platform
+from homeassistant.core import HomeAssistant
+from homeassistant.exceptions import ServiceValidationError
+from homeassistant.helpers import entity_registry as er
+
+from tests.common import MockConfigEntry, snapshot_platform
+
+
+@pytest.fixture(autouse=True)
+async def sensor_only() -> AsyncGenerator[None, None]:
+    """Enable only the number platform."""
+    with patch(
+        "homeassistant.components.iron_os.PLATFORMS",
+        [Platform.NUMBER],
+    ):
+        yield
+
+
+@pytest.mark.usefixtures(
+    "entity_registry_enabled_by_default", "mock_pynecil", "ble_device"
+)
+async def test_state(
+    hass: HomeAssistant,
+    config_entry: MockConfigEntry,
+    snapshot: SnapshotAssertion,
+    entity_registry: er.EntityRegistry,
+) -> None:
+    """Test the IronOS number platform states."""
+    config_entry.add_to_hass(hass)
+    await hass.config_entries.async_setup(config_entry.entry_id)
+    await hass.async_block_till_done()
+
+    assert config_entry.state is ConfigEntryState.LOADED
+
+    await snapshot_platform(hass, entity_registry, snapshot, config_entry.entry_id)
+
+
+@pytest.mark.usefixtures("entity_registry_enabled_by_default", "ble_device")
+async def test_set_value(
+    hass: HomeAssistant,
+    config_entry: MockConfigEntry,
+    mock_pynecil: AsyncMock,
+) -> None:
+    """Test the IronOS number platform set value service."""
+
+    config_entry.add_to_hass(hass)
+    await hass.config_entries.async_setup(config_entry.entry_id)
+    await hass.async_block_till_done()
+
+    assert config_entry.state is ConfigEntryState.LOADED
+
+    await hass.services.async_call(
+        NUMBER_DOMAIN,
+        SERVICE_SET_VALUE,
+        service_data={ATTR_VALUE: 300},
+        target={ATTR_ENTITY_ID: "number.pinecil_setpoint_temperature"},
+        blocking=True,
+    )
+    assert len(mock_pynecil.write.mock_calls) == 1
+    mock_pynecil.write.assert_called_once_with(CharSetting.SETPOINT_TEMP, 300)
+
+
+@pytest.mark.usefixtures("entity_registry_enabled_by_default", "ble_device")
+async def test_set_value_exception(
+    hass: HomeAssistant,
+    config_entry: MockConfigEntry,
+    mock_pynecil: AsyncMock,
+) -> None:
+    """Test the IronOS number platform set value service with exception."""
+
+    config_entry.add_to_hass(hass)
+    await hass.config_entries.async_setup(config_entry.entry_id)
+    await hass.async_block_till_done()
+
+    assert config_entry.state is ConfigEntryState.LOADED
+
+    mock_pynecil.write.side_effect = CommunicationError
+
+    with pytest.raises(
+        ServiceValidationError,
+        match="Failed to submit setting to device, try again later",
+    ):
+        await hass.services.async_call(
+            NUMBER_DOMAIN,
+            SERVICE_SET_VALUE,
+            service_data={ATTR_VALUE: 300},
+            target={ATTR_ENTITY_ID: "number.pinecil_setpoint_temperature"},
+            blocking=True,
+        )