diff --git a/.coveragerc b/.coveragerc
index 237e676c9ac2256ad73b4605915be8d93d89b6d0..9c27fd4c7289c7d2221de338389a386a639d3207 100644
--- a/.coveragerc
+++ b/.coveragerc
@@ -317,6 +317,9 @@ omit =
     homeassistant/components/firmata/switch.py
     homeassistant/components/fitbit/*
     homeassistant/components/fixer/sensor.py
+    homeassistant/components/fjaraskupan/__init__.py
+    homeassistant/components/fjaraskupan/const.py
+    homeassistant/components/fjaraskupan/fan.py
     homeassistant/components/fleetgo/device_tracker.py
     homeassistant/components/flexit/climate.py
     homeassistant/components/flic/binary_sensor.py
diff --git a/CODEOWNERS b/CODEOWNERS
index 6c6a248cdc9790bad6a0904b3daf23590b239a53..62b5b70648bb2e1369e0fd41456a58b28f16e469 100644
--- a/CODEOWNERS
+++ b/CODEOWNERS
@@ -163,6 +163,7 @@ homeassistant/components/filter/* @dgomes
 homeassistant/components/fireservicerota/* @cyberjunky
 homeassistant/components/firmata/* @DaAwesomeP
 homeassistant/components/fixer/* @fabaff
+homeassistant/components/fjaraskupan/* @elupus
 homeassistant/components/flick_electric/* @ZephireNZ
 homeassistant/components/flipr/* @cnico
 homeassistant/components/flo/* @dmulcahey
diff --git a/homeassistant/components/fjaraskupan/__init__.py b/homeassistant/components/fjaraskupan/__init__.py
new file mode 100644
index 0000000000000000000000000000000000000000..598fefe30c834df829de2f5c4a9be8148fa2fe66
--- /dev/null
+++ b/homeassistant/components/fjaraskupan/__init__.py
@@ -0,0 +1,143 @@
+"""The Fjäråskupan integration."""
+from __future__ import annotations
+
+from dataclasses import dataclass
+from datetime import timedelta
+import logging
+from typing import Callable
+
+from bleak import BleakScanner
+from bleak.backends.device import BLEDevice
+from bleak.backends.scanner import AdvertisementData
+from fjaraskupan import Device, State, device_filter
+
+from homeassistant.config_entries import ConfigEntry
+from homeassistant.core import HomeAssistant, callback
+from homeassistant.helpers.dispatcher import (
+    async_dispatcher_connect,
+    async_dispatcher_send,
+)
+from homeassistant.helpers.entity import DeviceInfo, Entity
+from homeassistant.helpers.entity_platform import AddEntitiesCallback
+from homeassistant.helpers.update_coordinator import DataUpdateCoordinator
+
+from .const import DISPATCH_DETECTION, DOMAIN
+
+PLATFORMS = ["fan"]
+
+_LOGGER = logging.getLogger(__name__)
+
+
+@dataclass
+class DeviceState:
+    """Store state of a device."""
+
+    device: Device
+    coordinator: DataUpdateCoordinator[State]
+    device_info: DeviceInfo
+
+
+@dataclass
+class EntryState:
+    """Store state of config entry."""
+
+    scanner: BleakScanner
+    devices: dict[str, DeviceState]
+
+
+async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
+    """Set up Fjäråskupan from a config entry."""
+
+    scanner = BleakScanner()
+
+    state = EntryState(scanner, {})
+    hass.data.setdefault(DOMAIN, {})
+    hass.data[DOMAIN][entry.entry_id] = state
+
+    async def detection_callback(
+        ble_device: BLEDevice, advertisement_data: AdvertisementData
+    ) -> None:
+        if not device_filter(ble_device, advertisement_data):
+            return
+
+        _LOGGER.debug(
+            "Detection: %s %s - %s", ble_device.name, ble_device, advertisement_data
+        )
+
+        data = state.devices.get(ble_device.address)
+
+        if data:
+            data.device.detection_callback(ble_device, advertisement_data)
+            data.coordinator.async_set_updated_data(data.device.state)
+        else:
+
+            device = Device(ble_device)
+            device.detection_callback(ble_device, advertisement_data)
+
+            async def async_update_data():
+                """Handle an explicit update request."""
+                await device.update()
+                return device.state
+
+            coordinator: DataUpdateCoordinator[State] = DataUpdateCoordinator(
+                hass,
+                logger=_LOGGER,
+                name="Fjaraskupan Updater",
+                update_interval=timedelta(seconds=120),
+                update_method=async_update_data,
+            )
+            coordinator.async_set_updated_data(device.state)
+
+            device_info: DeviceInfo = {
+                "identifiers": {(DOMAIN, ble_device.address)},
+                "manufacturer": "Fjäråskupan",
+                "name": "Fjäråskupan",
+            }
+            device_state = DeviceState(device, coordinator, device_info)
+            state.devices[ble_device.address] = device_state
+            async_dispatcher_send(
+                hass, f"{DISPATCH_DETECTION}.{entry.entry_id}", device_state
+            )
+
+    scanner.register_detection_callback(detection_callback)
+    await scanner.start()
+
+    hass.config_entries.async_setup_platforms(entry, PLATFORMS)
+    return True
+
+
+@callback
+def async_setup_entry_platform(
+    hass: HomeAssistant,
+    entry: ConfigEntry,
+    async_add_entities: AddEntitiesCallback,
+    constructor: Callable[[DeviceState], list[Entity]],
+) -> None:
+    """Set up a platform with added entities."""
+
+    entry_state: EntryState = hass.data[DOMAIN][entry.entry_id]
+    async_add_entities(
+        entity
+        for device_state in entry_state.devices.values()
+        for entity in constructor(device_state)
+    )
+
+    @callback
+    def _detection(device_state: DeviceState) -> None:
+        async_add_entities(constructor(device_state))
+
+    entry.async_on_unload(
+        async_dispatcher_connect(
+            hass, f"{DISPATCH_DETECTION}.{entry.entry_id}", _detection
+        )
+    )
+
+
+async def async_unload_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
+    """Unload a config entry."""
+    unload_ok = await hass.config_entries.async_unload_platforms(entry, PLATFORMS)
+    if unload_ok:
+        entry_state: EntryState = hass.data[DOMAIN].pop(entry.entry_id)
+        await entry_state.scanner.stop()
+
+    return unload_ok
diff --git a/homeassistant/components/fjaraskupan/config_flow.py b/homeassistant/components/fjaraskupan/config_flow.py
new file mode 100644
index 0000000000000000000000000000000000000000..9b82ae1199bd99c17e3956b6b97db9babeba4e31
--- /dev/null
+++ b/homeassistant/components/fjaraskupan/config_flow.py
@@ -0,0 +1,38 @@
+"""Config flow for Fjäråskupan integration."""
+from __future__ import annotations
+
+import asyncio
+
+import async_timeout
+from bleak import BleakScanner
+from bleak.backends.device import BLEDevice
+from bleak.backends.scanner import AdvertisementData
+from fjaraskupan import device_filter
+
+from homeassistant.helpers.config_entry_flow import register_discovery_flow
+
+from .const import DOMAIN
+
+CONST_WAIT_TIME = 5.0
+
+
+async def _async_has_devices(hass) -> bool:
+    """Return if there are devices that can be discovered."""
+
+    event = asyncio.Event()
+
+    def detection(device: BLEDevice, advertisement_data: AdvertisementData):
+        if device_filter(device, advertisement_data):
+            event.set()
+
+    async with BleakScanner(detection_callback=detection):
+        try:
+            async with async_timeout.timeout(CONST_WAIT_TIME):
+                await event.wait()
+        except asyncio.TimeoutError:
+            return False
+
+    return True
+
+
+register_discovery_flow(DOMAIN, "Fjäråskupan", _async_has_devices)
diff --git a/homeassistant/components/fjaraskupan/const.py b/homeassistant/components/fjaraskupan/const.py
new file mode 100644
index 0000000000000000000000000000000000000000..957ac518293afcb0db13511e055639cea27f001a
--- /dev/null
+++ b/homeassistant/components/fjaraskupan/const.py
@@ -0,0 +1,5 @@
+"""Constants for the Fjäråskupan integration."""
+
+DOMAIN = "fjaraskupan"
+
+DISPATCH_DETECTION = f"{DOMAIN}.detection"
diff --git a/homeassistant/components/fjaraskupan/fan.py b/homeassistant/components/fjaraskupan/fan.py
new file mode 100644
index 0000000000000000000000000000000000000000..4a81e70b848124befc0b11c74b2406f3e144b0d8
--- /dev/null
+++ b/homeassistant/components/fjaraskupan/fan.py
@@ -0,0 +1,192 @@
+"""Support for Fjäråskupan fans."""
+from __future__ import annotations
+
+from fjaraskupan import (
+    COMMAND_AFTERCOOKINGTIMERAUTO,
+    COMMAND_AFTERCOOKINGTIMERMANUAL,
+    COMMAND_AFTERCOOKINGTIMEROFF,
+    COMMAND_STOP_FAN,
+    Device,
+    State,
+)
+
+from homeassistant.components.fan import (
+    SUPPORT_PRESET_MODE,
+    SUPPORT_SET_SPEED,
+    FanEntity,
+)
+from homeassistant.config_entries import ConfigEntry
+from homeassistant.core import HomeAssistant, callback
+from homeassistant.helpers.entity import DeviceInfo
+from homeassistant.helpers.entity_platform import AddEntitiesCallback
+from homeassistant.helpers.update_coordinator import (
+    CoordinatorEntity,
+    DataUpdateCoordinator,
+)
+from homeassistant.util.percentage import (
+    ordered_list_item_to_percentage,
+    percentage_to_ordered_list_item,
+)
+
+from . import DeviceState, async_setup_entry_platform
+
+ORDERED_NAMED_FAN_SPEEDS = ["1", "2", "3", "4", "5", "6", "7", "8"]
+
+PRESET_MODE_NORMAL = "normal"
+PRESET_MODE_AFTER_COOKING_MANUAL = "after_cooking_manual"
+PRESET_MODE_AFTER_COOKING_AUTO = "after_cooking_auto"
+PRESET_MODES = [
+    PRESET_MODE_NORMAL,
+    PRESET_MODE_AFTER_COOKING_AUTO,
+    PRESET_MODE_AFTER_COOKING_MANUAL,
+]
+
+PRESET_TO_COMMAND = {
+    PRESET_MODE_AFTER_COOKING_MANUAL: COMMAND_AFTERCOOKINGTIMERMANUAL,
+    PRESET_MODE_AFTER_COOKING_AUTO: COMMAND_AFTERCOOKINGTIMERAUTO,
+    PRESET_MODE_NORMAL: COMMAND_AFTERCOOKINGTIMEROFF,
+}
+
+
+async def async_setup_entry(
+    hass: HomeAssistant,
+    config_entry: ConfigEntry,
+    async_add_entities: AddEntitiesCallback,
+) -> None:
+    """Set up sensors dynamically through discovery."""
+
+    def _constructor(device_state: DeviceState):
+        return [
+            Fan(device_state.coordinator, device_state.device, device_state.device_info)
+        ]
+
+    async_setup_entry_platform(hass, config_entry, async_add_entities, _constructor)
+
+
+class Fan(CoordinatorEntity[State], FanEntity):
+    """Fan entity."""
+
+    def __init__(
+        self,
+        coordinator: DataUpdateCoordinator[State],
+        device: Device,
+        device_info: DeviceInfo,
+    ) -> None:
+        """Init fan entity."""
+        super().__init__(coordinator)
+        self._device = device
+        self._default_on_speed = 25
+        self._attr_name = device_info["name"]
+        self._attr_unique_id = device.address
+        self._attr_device_info = device_info
+        self._percentage = 0
+        self._preset_mode = PRESET_MODE_NORMAL
+        self._update_from_device_data(coordinator.data)
+
+    async def async_set_percentage(self, percentage: int) -> None:
+        """Set speed."""
+        new_speed = percentage_to_ordered_list_item(
+            ORDERED_NAMED_FAN_SPEEDS, percentage
+        )
+        await self._device.send_fan_speed(int(new_speed))
+        self.coordinator.async_set_updated_data(self._device.state)
+
+    async def async_turn_on(
+        self,
+        speed: str = None,
+        percentage: int = None,
+        preset_mode: str = None,
+        **kwargs,
+    ) -> None:
+        """Turn on the fan."""
+
+        if preset_mode is None:
+            preset_mode = self._preset_mode
+
+        if percentage is None:
+            percentage = self._default_on_speed
+
+        new_speed = percentage_to_ordered_list_item(
+            ORDERED_NAMED_FAN_SPEEDS, percentage
+        )
+
+        async with self._device:
+            if preset_mode != self._preset_mode:
+                await self._device.send_command(PRESET_TO_COMMAND[preset_mode])
+
+            if preset_mode == PRESET_MODE_NORMAL:
+                await self._device.send_fan_speed(int(new_speed))
+            elif preset_mode == PRESET_MODE_AFTER_COOKING_MANUAL:
+                await self._device.send_after_cooking(int(new_speed))
+            elif preset_mode == PRESET_MODE_AFTER_COOKING_AUTO:
+                await self._device.send_after_cooking(0)
+
+        self.coordinator.async_set_updated_data(self._device.state)
+
+    async def async_set_preset_mode(self, preset_mode: str) -> None:
+        """Set new preset mode."""
+        await self._device.send_command(PRESET_TO_COMMAND[preset_mode])
+        self.coordinator.async_set_updated_data(self._device.state)
+
+    async def async_turn_off(self, **kwargs) -> None:
+        """Turn the entity off."""
+        await self._device.send_command(COMMAND_STOP_FAN)
+        self.coordinator.async_set_updated_data(self._device.state)
+
+    @property
+    def speed_count(self) -> int:
+        """Return the number of speeds the fan supports."""
+        return len(ORDERED_NAMED_FAN_SPEEDS)
+
+    @property
+    def percentage(self) -> int | None:
+        """Return the current speed."""
+        return self._percentage
+
+    @property
+    def supported_features(self) -> int:
+        """Flag supported features."""
+        return SUPPORT_SET_SPEED | SUPPORT_PRESET_MODE
+
+    @property
+    def is_on(self) -> bool:
+        """Return true if fan is on."""
+        return self._percentage != 0
+
+    @property
+    def preset_mode(self) -> str | None:
+        """Return the current preset mode."""
+        return self._preset_mode
+
+    @property
+    def preset_modes(self) -> list[str] | None:
+        """Return a list of available preset modes."""
+        return PRESET_MODES
+
+    def _update_from_device_data(self, data: State | None) -> None:
+        """Handle data update."""
+        if not data:
+            self._percentage = 0
+            return
+
+        if data.fan_speed:
+            self._percentage = ordered_list_item_to_percentage(
+                ORDERED_NAMED_FAN_SPEEDS, str(data.fan_speed)
+            )
+        else:
+            self._percentage = 0
+
+        if data.after_cooking_on:
+            if data.after_cooking_fan_speed:
+                self._preset_mode = PRESET_MODE_AFTER_COOKING_MANUAL
+            else:
+                self._preset_mode = PRESET_MODE_AFTER_COOKING_AUTO
+        else:
+            self._preset_mode = PRESET_MODE_NORMAL
+
+    @callback
+    def _handle_coordinator_update(self) -> None:
+        """Handle data update."""
+
+        self._update_from_device_data(self.coordinator.data)
+        self.async_write_ha_state()
diff --git a/homeassistant/components/fjaraskupan/manifest.json b/homeassistant/components/fjaraskupan/manifest.json
new file mode 100644
index 0000000000000000000000000000000000000000..68158776afe2638fc5dc5b09b6ec10de26f2706f
--- /dev/null
+++ b/homeassistant/components/fjaraskupan/manifest.json
@@ -0,0 +1,13 @@
+{
+  "domain": "fjaraskupan",
+  "name": "Fj\u00e4r\u00e5skupan",
+  "config_flow": true,
+  "documentation": "https://www.home-assistant.io/integrations/fjaraskupan",
+  "requirements": [
+    "fjaraskupan==1.0.0"
+  ],
+  "codeowners": [
+    "@elupus"
+  ],
+  "iot_class": "local_polling"
+}
\ No newline at end of file
diff --git a/homeassistant/components/fjaraskupan/strings.json b/homeassistant/components/fjaraskupan/strings.json
new file mode 100644
index 0000000000000000000000000000000000000000..c72fc777772b6ea0dab2b8c89a048a18ea74492e
--- /dev/null
+++ b/homeassistant/components/fjaraskupan/strings.json
@@ -0,0 +1,13 @@
+{
+  "config": {
+    "step": {
+      "confirm": {
+        "description": "Do you want to set up Fjäråskupan?"
+      }
+    },
+    "abort": {
+      "single_instance_allowed": "[%key:common::config_flow::abort::single_instance_allowed%]",
+      "no_devices_found": "[%key:common::config_flow::abort::no_devices_found%]"
+    }
+  }
+}
\ No newline at end of file
diff --git a/homeassistant/components/fjaraskupan/translations/en.json b/homeassistant/components/fjaraskupan/translations/en.json
new file mode 100644
index 0000000000000000000000000000000000000000..206d0c9cbdb7116adf12a2b6a7781033ec6d7234
--- /dev/null
+++ b/homeassistant/components/fjaraskupan/translations/en.json
@@ -0,0 +1,13 @@
+{
+    "config": {
+        "abort": {
+            "no_devices_found": "No devices found on the network",
+            "single_instance_allowed": "Already configured. Only a single configuration possible."
+        },
+        "step": {
+            "confirm": {
+                "description": "Do you want to set up Fjäråskupan?"
+            }
+        }
+    }
+}
\ No newline at end of file
diff --git a/homeassistant/generated/config_flows.py b/homeassistant/generated/config_flows.py
index 339bbb1ede3a648ce3359e6ce2d8a5e384ab2773..3d43e4cbccb18dca9af03e6c545703ccab0acba7 100644
--- a/homeassistant/generated/config_flows.py
+++ b/homeassistant/generated/config_flows.py
@@ -77,6 +77,7 @@ FLOWS = [
     "ezviz",
     "faa_delays",
     "fireservicerota",
+    "fjaraskupan",
     "flick_electric",
     "flipr",
     "flo",
diff --git a/requirements_all.txt b/requirements_all.txt
index 8ddac2bd76bcd7217b0f9a8f535d83450e31573b..a1b9869e1261c503fe08c113b6e58f6dbdddc156 100644
--- a/requirements_all.txt
+++ b/requirements_all.txt
@@ -625,6 +625,9 @@ fitbit==0.3.1
 # homeassistant.components.fixer
 fixerio==1.0.0a0
 
+# homeassistant.components.fjaraskupan
+fjaraskupan==1.0.0
+
 # homeassistant.components.flipr
 flipr-api==1.4.1
 
diff --git a/requirements_test_all.txt b/requirements_test_all.txt
index 5a03aaca96f16f36699e439131e16701aa1dde7f..cacc3ed3f26667a81326cad8ade5711d11872e65 100644
--- a/requirements_test_all.txt
+++ b/requirements_test_all.txt
@@ -345,6 +345,9 @@ faadelays==0.0.7
 # homeassistant.components.feedreader
 feedparser==6.0.2
 
+# homeassistant.components.fjaraskupan
+fjaraskupan==1.0.0
+
 # homeassistant.components.flipr
 flipr-api==1.4.1
 
diff --git a/tests/components/fjaraskupan/__init__.py b/tests/components/fjaraskupan/__init__.py
new file mode 100644
index 0000000000000000000000000000000000000000..26a5ecd660590a9b49e74e829ab5aefffbaf69e4
--- /dev/null
+++ b/tests/components/fjaraskupan/__init__.py
@@ -0,0 +1 @@
+"""Tests for the Fjäråskupan integration."""
diff --git a/tests/components/fjaraskupan/conftest.py b/tests/components/fjaraskupan/conftest.py
new file mode 100644
index 0000000000000000000000000000000000000000..d60abcdb9ad35faa7ad70ac4e1fbc41dbe7a5f1d
--- /dev/null
+++ b/tests/components/fjaraskupan/conftest.py
@@ -0,0 +1,41 @@
+"""Standard fixtures for the Fjäråskupan integration."""
+from __future__ import annotations
+
+from unittest.mock import patch
+
+from bleak.backends.device import BLEDevice
+from bleak.backends.scanner import AdvertisementData, BaseBleakScanner
+from pytest import fixture
+
+
+@fixture(name="scanner", autouse=True)
+def fixture_scanner(hass):
+    """Fixture for scanner."""
+
+    devices = [BLEDevice("1.1.1.1", "COOKERHOOD_FJAR")]
+
+    class MockScanner(BaseBleakScanner):
+        """Mock Scanner."""
+
+        async def start(self):
+            """Start scanning for devices."""
+            for device in devices:
+                self._callback(device, AdvertisementData())
+
+        async def stop(self):
+            """Stop scanning for devices."""
+
+        @property
+        def discovered_devices(self) -> list[BLEDevice]:
+            """Return discovered devices."""
+            return devices
+
+        def set_scanning_filter(self, **kwargs):
+            """Set the scanning filter."""
+
+    with patch(
+        "homeassistant.components.fjaraskupan.config_flow.BleakScanner", new=MockScanner
+    ), patch(
+        "homeassistant.components.fjaraskupan.config_flow.CONST_WAIT_TIME", new=0.01
+    ):
+        yield devices
diff --git a/tests/components/fjaraskupan/test_config_flow.py b/tests/components/fjaraskupan/test_config_flow.py
new file mode 100644
index 0000000000000000000000000000000000000000..7244042d35666538701faac37387ea08853ac507
--- /dev/null
+++ b/tests/components/fjaraskupan/test_config_flow.py
@@ -0,0 +1,59 @@
+"""Test the Fjäråskupan config flow."""
+from __future__ import annotations
+
+from unittest.mock import patch
+
+from bleak.backends.device import BLEDevice
+from pytest import fixture
+
+from homeassistant import config_entries, setup
+from homeassistant.components.fjaraskupan.const import DOMAIN
+from homeassistant.core import HomeAssistant
+from homeassistant.data_entry_flow import (
+    RESULT_TYPE_ABORT,
+    RESULT_TYPE_CREATE_ENTRY,
+    RESULT_TYPE_FORM,
+)
+
+
+@fixture(name="mock_setup_entry", autouse=True)
+async def fixture_mock_setup_entry(hass):
+    """Fixture for config entry."""
+    await setup.async_setup_component(hass, "persistent_notification", {})
+
+    with patch(
+        "homeassistant.components.fjaraskupan.async_setup_entry", return_value=True
+    ) as mock_setup_entry:
+        yield mock_setup_entry
+
+
+async def test_configure(hass: HomeAssistant, mock_setup_entry) -> None:
+    """Test we get the form."""
+    result = await hass.config_entries.flow.async_init(
+        DOMAIN, context={"source": config_entries.SOURCE_USER}
+    )
+
+    assert result["type"] == RESULT_TYPE_FORM
+    result = await hass.config_entries.flow.async_configure(result["flow_id"], {})
+
+    assert result["type"] == RESULT_TYPE_CREATE_ENTRY
+    assert result["title"] == "Fjäråskupan"
+    assert result["data"] == {}
+
+    await hass.async_block_till_done()
+    assert len(mock_setup_entry.mock_calls) == 1
+
+
+async def test_scan_no_devices(hass: HomeAssistant, scanner: list[BLEDevice]) -> None:
+    """Test we get the form."""
+    scanner.clear()
+
+    result = await hass.config_entries.flow.async_init(
+        DOMAIN, context={"source": config_entries.SOURCE_USER}
+    )
+
+    assert result["type"] == RESULT_TYPE_FORM
+    result = await hass.config_entries.flow.async_configure(result["flow_id"], {})
+
+    assert result["type"] == RESULT_TYPE_ABORT
+    assert result["reason"] == "no_devices_found"