diff --git a/.coveragerc b/.coveragerc
index 2aabc0d80281d1b3a4ec9349cc836af2e982ef10..cf9fa59397da42f517872911c8c2f5090c29f903 100644
--- a/.coveragerc
+++ b/.coveragerc
@@ -311,7 +311,10 @@ omit =
     homeassistant/components/huawei_lte/*
     homeassistant/components/huawei_router/device_tracker.py
     homeassistant/components/hue/light.py
+    homeassistant/components/hunterdouglas_powerview/__init__.py
     homeassistant/components/hunterdouglas_powerview/scene.py
+    homeassistant/components/hunterdouglas_powerview/cover.py
+    homeassistant/components/hunterdouglas_powerview/entity.py
     homeassistant/components/hydrawise/*
     homeassistant/components/hyperion/light.py
     homeassistant/components/ialarm/alarm_control_panel.py
diff --git a/CODEOWNERS b/CODEOWNERS
index f60da8494d79865303b93fb73c7ddc2f8c0b2d9f..51ce87e702f15cab18cab237c69e89f24376b901 100644
--- a/CODEOWNERS
+++ b/CODEOWNERS
@@ -174,6 +174,7 @@ homeassistant/components/http/* @home-assistant/core
 homeassistant/components/huawei_lte/* @scop
 homeassistant/components/huawei_router/* @abmantis
 homeassistant/components/hue/* @balloob
+homeassistant/components/hunterdouglas_powerview/* @bdraco
 homeassistant/components/iammeter/* @lewei50
 homeassistant/components/iaqualink/* @flz
 homeassistant/components/icloud/* @Quentame
diff --git a/homeassistant/components/hunterdouglas_powerview/__init__.py b/homeassistant/components/hunterdouglas_powerview/__init__.py
index 14ede545576898dad49a1a1eaf39a95bdc6bd83c..44ebf25a4f4c19bb788fb88d167228818ec2e972 100644
--- a/homeassistant/components/hunterdouglas_powerview/__init__.py
+++ b/homeassistant/components/hunterdouglas_powerview/__init__.py
@@ -1 +1,194 @@
-"""The hunterdouglas_powerview component."""
+"""The Hunter Douglas PowerView integration."""
+import asyncio
+from datetime import timedelta
+import logging
+
+from aiopvapi.helpers.aiorequest import AioRequest
+from aiopvapi.helpers.constants import ATTR_ID
+from aiopvapi.helpers.tools import base64_to_unicode
+from aiopvapi.rooms import Rooms
+from aiopvapi.scenes import Scenes
+from aiopvapi.shades import Shades
+from aiopvapi.userdata import UserData
+import async_timeout
+import voluptuous as vol
+
+from homeassistant.config_entries import SOURCE_IMPORT, ConfigEntry
+from homeassistant.const import CONF_HOST
+from homeassistant.core import HomeAssistant, callback
+from homeassistant.exceptions import ConfigEntryNotReady
+from homeassistant.helpers.aiohttp_client import async_get_clientsession
+import homeassistant.helpers.config_validation as cv
+from homeassistant.helpers.update_coordinator import DataUpdateCoordinator, UpdateFailed
+
+from .const import (
+    COORDINATOR,
+    DEVICE_FIRMWARE,
+    DEVICE_INFO,
+    DEVICE_MAC_ADDRESS,
+    DEVICE_MODEL,
+    DEVICE_NAME,
+    DEVICE_REVISION,
+    DEVICE_SERIAL_NUMBER,
+    DOMAIN,
+    FIRMWARE_IN_USERDATA,
+    HUB_EXCEPTIONS,
+    HUB_NAME,
+    MAC_ADDRESS_IN_USERDATA,
+    MAINPROCESSOR_IN_USERDATA_FIRMWARE,
+    MODEL_IN_MAINPROCESSOR,
+    PV_API,
+    PV_ROOM_DATA,
+    PV_SCENE_DATA,
+    PV_SHADE_DATA,
+    PV_SHADES,
+    REVISION_IN_MAINPROCESSOR,
+    ROOM_DATA,
+    SCENE_DATA,
+    SERIAL_NUMBER_IN_USERDATA,
+    SHADE_DATA,
+    USER_DATA,
+)
+
+DEVICE_SCHEMA = vol.Schema(
+    {DOMAIN: vol.Schema({vol.Required(CONF_HOST): cv.string})}, extra=vol.ALLOW_EXTRA
+)
+
+
+def _has_all_unique_hosts(value):
+    """Validate that each hub configured has a unique host."""
+    hosts = [device[CONF_HOST] for device in value]
+    schema = vol.Schema(vol.Unique())
+    schema(hosts)
+    return value
+
+
+CONFIG_SCHEMA = vol.Schema(
+    {DOMAIN: vol.All(cv.ensure_list, [DEVICE_SCHEMA], _has_all_unique_hosts)},
+    extra=vol.ALLOW_EXTRA,
+)
+
+
+PLATFORMS = ["cover", "scene"]
+_LOGGER = logging.getLogger(__name__)
+
+
+async def async_setup(hass: HomeAssistant, hass_config: dict):
+    """Set up the Hunter Douglas PowerView component."""
+    hass.data.setdefault(DOMAIN, {})
+
+    if DOMAIN not in hass_config:
+        return True
+
+    for conf in hass_config[DOMAIN]:
+        hass.async_create_task(
+            hass.config_entries.flow.async_init(
+                DOMAIN, context={"source": SOURCE_IMPORT}, data=conf
+            )
+        )
+
+    return True
+
+
+async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry):
+    """Set up Hunter Douglas PowerView from a config entry."""
+
+    config = entry.data
+
+    hub_address = config.get(CONF_HOST)
+    websession = async_get_clientsession(hass)
+
+    pv_request = AioRequest(hub_address, loop=hass.loop, websession=websession)
+
+    try:
+        async with async_timeout.timeout(10):
+            device_info = await async_get_device_info(pv_request)
+    except HUB_EXCEPTIONS:
+        _LOGGER.error("Connection error to PowerView hub: %s", hub_address)
+        raise ConfigEntryNotReady
+    if not device_info:
+        _LOGGER.error("Unable to initialize PowerView hub: %s", hub_address)
+        raise ConfigEntryNotReady
+
+    rooms = Rooms(pv_request)
+    room_data = _async_map_data_by_id((await rooms.get_resources())[ROOM_DATA])
+
+    scenes = Scenes(pv_request)
+    scene_data = _async_map_data_by_id((await scenes.get_resources())[SCENE_DATA])
+
+    shades = Shades(pv_request)
+    shade_data = _async_map_data_by_id((await shades.get_resources())[SHADE_DATA])
+
+    async def async_update_data():
+        """Fetch data from shade endpoint."""
+        async with async_timeout.timeout(10):
+            shade_entries = await shades.get_resources()
+        if not shade_entries:
+            raise UpdateFailed(f"Failed to fetch new shade data.")
+        return _async_map_data_by_id(shade_entries[SHADE_DATA])
+
+    coordinator = DataUpdateCoordinator(
+        hass,
+        _LOGGER,
+        name="powerview hub",
+        update_method=async_update_data,
+        update_interval=timedelta(seconds=60),
+    )
+
+    hass.data[DOMAIN][entry.entry_id] = {
+        PV_API: pv_request,
+        PV_ROOM_DATA: room_data,
+        PV_SCENE_DATA: scene_data,
+        PV_SHADES: shades,
+        PV_SHADE_DATA: shade_data,
+        COORDINATOR: coordinator,
+        DEVICE_INFO: device_info,
+    }
+
+    for component in PLATFORMS:
+        hass.async_create_task(
+            hass.config_entries.async_forward_entry_setup(entry, component)
+        )
+
+    return True
+
+
+async def async_get_device_info(pv_request):
+    """Determine device info."""
+    userdata = UserData(pv_request)
+    resources = await userdata.get_resources()
+    userdata_data = resources[USER_DATA]
+
+    main_processor_info = userdata_data[FIRMWARE_IN_USERDATA][
+        MAINPROCESSOR_IN_USERDATA_FIRMWARE
+    ]
+    return {
+        DEVICE_NAME: base64_to_unicode(userdata_data[HUB_NAME]),
+        DEVICE_MAC_ADDRESS: userdata_data[MAC_ADDRESS_IN_USERDATA],
+        DEVICE_SERIAL_NUMBER: userdata_data[SERIAL_NUMBER_IN_USERDATA],
+        DEVICE_REVISION: main_processor_info[REVISION_IN_MAINPROCESSOR],
+        DEVICE_FIRMWARE: main_processor_info,
+        DEVICE_MODEL: main_processor_info[MODEL_IN_MAINPROCESSOR],
+    }
+
+
+@callback
+def _async_map_data_by_id(data):
+    """Return a dict with the key being the id for a list of entries."""
+    return {entry[ATTR_ID]: entry for entry in data}
+
+
+async def async_unload_entry(hass: HomeAssistant, entry: ConfigEntry):
+    """Unload a config entry."""
+    unload_ok = all(
+        await asyncio.gather(
+            *[
+                hass.config_entries.async_forward_entry_unload(entry, component)
+                for component in PLATFORMS
+            ]
+        )
+    )
+    if unload_ok:
+        hass.data[DOMAIN].pop(entry.entry_id)
+
+    return unload_ok
diff --git a/homeassistant/components/hunterdouglas_powerview/config_flow.py b/homeassistant/components/hunterdouglas_powerview/config_flow.py
new file mode 100644
index 0000000000000000000000000000000000000000..52a70b85d2e0dc58ec50a1391922fd95f9d4289c
--- /dev/null
+++ b/homeassistant/components/hunterdouglas_powerview/config_flow.py
@@ -0,0 +1,135 @@
+"""Config flow for Hunter Douglas PowerView integration."""
+import logging
+
+from aiopvapi.helpers.aiorequest import AioRequest
+import async_timeout
+import voluptuous as vol
+
+from homeassistant import config_entries, core, exceptions
+from homeassistant.const import CONF_HOST, CONF_NAME
+from homeassistant.helpers.aiohttp_client import async_get_clientsession
+
+from . import async_get_device_info
+from .const import DEVICE_NAME, DEVICE_SERIAL_NUMBER, HUB_EXCEPTIONS
+from .const import DOMAIN  # pylint:disable=unused-import
+
+_LOGGER = logging.getLogger(__name__)
+
+DATA_SCHEMA = vol.Schema({vol.Required(CONF_HOST): str})
+HAP_SUFFIX = "._hap._tcp.local."
+
+
+async def validate_input(hass: core.HomeAssistant, data):
+    """Validate the user input allows us to connect.
+
+    Data has the keys from DATA_SCHEMA with values provided by the user.
+    """
+
+    hub_address = data[CONF_HOST]
+    websession = async_get_clientsession(hass)
+
+    pv_request = AioRequest(hub_address, loop=hass.loop, websession=websession)
+
+    try:
+        async with async_timeout.timeout(10):
+            device_info = await async_get_device_info(pv_request)
+    except HUB_EXCEPTIONS:
+        raise CannotConnect
+    if not device_info:
+        raise CannotConnect
+
+    # Return info that you want to store in the config entry.
+    return {
+        "title": device_info[DEVICE_NAME],
+        "unique_id": device_info[DEVICE_SERIAL_NUMBER],
+    }
+
+
+class ConfigFlow(config_entries.ConfigFlow, domain=DOMAIN):
+    """Handle a config flow for Hunter Douglas PowerView."""
+
+    VERSION = 1
+    CONNECTION_CLASS = config_entries.CONN_CLASS_LOCAL_POLL
+
+    def __init__(self):
+        """Initialize the powerview config flow."""
+        self.powerview_config = {}
+
+    async def async_step_user(self, user_input=None):
+        """Handle the initial step."""
+        errors = {}
+        if user_input is not None:
+            if self._host_already_configured(user_input[CONF_HOST]):
+                return self.async_abort(reason="already_configured")
+            try:
+                info = await validate_input(self.hass, user_input)
+            except CannotConnect:
+                errors["base"] = "cannot_connect"
+            except Exception:  # pylint: disable=broad-except
+                _LOGGER.exception("Unexpected exception")
+                errors["base"] = "unknown"
+
+            if not errors:
+                await self.async_set_unique_id(info["unique_id"])
+                return self.async_create_entry(
+                    title=info["title"], data={CONF_HOST: user_input[CONF_HOST]}
+                )
+
+        return self.async_show_form(
+            step_id="user", data_schema=DATA_SCHEMA, errors=errors
+        )
+
+    async def async_step_import(self, user_input=None):
+        """Handle the initial step."""
+        return await self.async_step_user(user_input)
+
+    async def async_step_homekit(self, homekit_info):
+        """Handle HomeKit discovery."""
+
+        # If we already have the host configured do
+        # not open connections to it if we can avoid it.
+        if self._host_already_configured(homekit_info[CONF_HOST]):
+            return self.async_abort(reason="already_configured")
+
+        try:
+            info = await validate_input(self.hass, homekit_info)
+        except CannotConnect:
+            return self.async_abort(reason="cannot_connect")
+        except Exception:  # pylint: disable=broad-except
+            return self.async_abort(reason="unknown")
+
+        await self.async_set_unique_id(info["unique_id"], raise_on_progress=False)
+        self._abort_if_unique_id_configured({CONF_HOST: homekit_info["host"]})
+
+        name = homekit_info["name"]
+        if name.endswith(HAP_SUFFIX):
+            name = name[: -len(HAP_SUFFIX)]
+
+        self.powerview_config = {
+            CONF_HOST: homekit_info["host"],
+            CONF_NAME: name,
+        }
+        return await self.async_step_link()
+
+    async def async_step_link(self, user_input=None):
+        """Attempt to link with Powerview."""
+        if user_input is not None:
+            return self.async_create_entry(
+                title=self.powerview_config[CONF_NAME],
+                data={CONF_HOST: self.powerview_config[CONF_HOST]},
+            )
+
+        return self.async_show_form(
+            step_id="link", description_placeholders=self.powerview_config
+        )
+
+    def _host_already_configured(self, host):
+        """See if we already have a hub with the host address configured."""
+        existing_hosts = {
+            entry.data[CONF_HOST] for entry in self._async_current_entries()
+        }
+        return host in existing_hosts
+
+
+class CannotConnect(exceptions.HomeAssistantError):
+    """Error to indicate we cannot connect."""
diff --git a/homeassistant/components/hunterdouglas_powerview/const.py b/homeassistant/components/hunterdouglas_powerview/const.py
new file mode 100644
index 0000000000000000000000000000000000000000..9979cfb186c82eb07fe2b1162be8291e7c2f1c86
--- /dev/null
+++ b/homeassistant/components/hunterdouglas_powerview/const.py
@@ -0,0 +1,65 @@
+"""Support for Powerview scenes from a Powerview hub."""
+
+import asyncio
+
+from aiopvapi.helpers.aiorequest import PvApiConnectionError
+
+DOMAIN = "hunterdouglas_powerview"
+
+
+MANUFACTURER = "Hunter Douglas"
+
+HUB_ADDRESS = "address"
+
+SCENE_DATA = "sceneData"
+SHADE_DATA = "shadeData"
+ROOM_DATA = "roomData"
+USER_DATA = "userData"
+
+MAC_ADDRESS_IN_USERDATA = "macAddress"
+SERIAL_NUMBER_IN_USERDATA = "serialNumber"
+FIRMWARE_IN_USERDATA = "firmware"
+MAINPROCESSOR_IN_USERDATA_FIRMWARE = "mainProcessor"
+REVISION_IN_MAINPROCESSOR = "revision"
+MODEL_IN_MAINPROCESSOR = "name"
+HUB_NAME = "hubName"
+
+FIRMWARE_IN_SHADE = "firmware"
+
+FIRMWARE_REVISION = "revision"
+FIRMWARE_SUB_REVISION = "subRevision"
+FIRMWARE_BUILD = "build"
+
+DEVICE_NAME = "device_name"
+DEVICE_MAC_ADDRESS = "device_mac_address"
+DEVICE_SERIAL_NUMBER = "device_serial_number"
+DEVICE_REVISION = "device_revision"
+DEVICE_INFO = "device_info"
+DEVICE_MODEL = "device_model"
+DEVICE_FIRMWARE = "device_firmware"
+
+SCENE_NAME = "name"
+SCENE_ID = "id"
+ROOM_ID_IN_SCENE = "roomId"
+
+SHADE_NAME = "name"
+SHADE_ID = "id"
+ROOM_ID_IN_SHADE = "roomId"
+
+ROOM_NAME = "name"
+ROOM_NAME_UNICODE = "name_unicode"
+ROOM_ID = "id"
+
+SHADE_RESPONSE = "shade"
+
+STATE_ATTRIBUTE_ROOM_NAME = "roomName"
+
+PV_API = "pv_api"
+PV_HUB = "pv_hub"
+PV_SHADES = "pv_shades"
+PV_SCENE_DATA = "pv_scene_data"
+PV_SHADE_DATA = "pv_shade_data"
+PV_ROOM_DATA = "pv_room_data"
+COORDINATOR = "coordinator"
+
+HUB_EXCEPTIONS = (asyncio.TimeoutError, PvApiConnectionError)
diff --git a/homeassistant/components/hunterdouglas_powerview/cover.py b/homeassistant/components/hunterdouglas_powerview/cover.py
new file mode 100644
index 0000000000000000000000000000000000000000..45fd798238f58eb94d8cf5e708649f3e5f4f952f
--- /dev/null
+++ b/homeassistant/components/hunterdouglas_powerview/cover.py
@@ -0,0 +1,306 @@
+"""Support for hunter douglas shades."""
+import asyncio
+import logging
+
+from aiopvapi.helpers.constants import ATTR_POSITION1, ATTR_POSITION_DATA
+from aiopvapi.resources.shade import (
+    ATTR_POSKIND1,
+    ATTR_TYPE,
+    MAX_POSITION,
+    MIN_POSITION,
+    factory as PvShade,
+)
+import async_timeout
+
+from homeassistant.components.cover import (
+    ATTR_POSITION,
+    DEVICE_CLASS_SHADE,
+    SUPPORT_CLOSE,
+    SUPPORT_OPEN,
+    SUPPORT_SET_POSITION,
+    SUPPORT_STOP,
+    CoverEntity,
+)
+from homeassistant.core import callback
+from homeassistant.helpers.event import async_call_later
+
+from .const import (
+    COORDINATOR,
+    DEVICE_INFO,
+    DEVICE_MODEL,
+    DEVICE_SERIAL_NUMBER,
+    DOMAIN,
+    FIRMWARE_BUILD,
+    FIRMWARE_IN_SHADE,
+    FIRMWARE_REVISION,
+    FIRMWARE_SUB_REVISION,
+    MANUFACTURER,
+    PV_API,
+    PV_ROOM_DATA,
+    PV_SHADE_DATA,
+    ROOM_ID_IN_SHADE,
+    ROOM_NAME_UNICODE,
+    SHADE_RESPONSE,
+    STATE_ATTRIBUTE_ROOM_NAME,
+)
+from .entity import HDEntity
+
+_LOGGER = logging.getLogger(__name__)
+
+# Estimated time it takes to complete a transition
+# from one state to another
+TRANSITION_COMPLETE_DURATION = 30
+
+
+async def async_setup_entry(hass, entry, async_add_entities):
+    """Set up the hunter douglas shades."""
+
+    pv_data = hass.data[DOMAIN][entry.entry_id]
+    room_data = pv_data[PV_ROOM_DATA]
+    shade_data = pv_data[PV_SHADE_DATA]
+    pv_request = pv_data[PV_API]
+    coordinator = pv_data[COORDINATOR]
+    device_info = pv_data[DEVICE_INFO]
+
+    entities = []
+    for raw_shade in shade_data.values():
+        # The shade may be out of sync with the hub
+        # so we force a refresh when we add it if
+        # possible
+        shade = PvShade(raw_shade, pv_request)
+        name_before_refresh = shade.name
+        try:
+            async with async_timeout.timeout(1):
+                await shade.refresh()
+        except asyncio.TimeoutError:
+            # Forced refresh is not required for setup
+            pass
+        entities.append(
+            PowerViewShade(
+                shade, name_before_refresh, room_data, coordinator, device_info
+            )
+        )
+    async_add_entities(entities)
+
+
+def hd_position_to_hass(hd_position):
+    """Convert hunter douglas position to hass position."""
+    return round((hd_position / MAX_POSITION) * 100)
+
+
+def hass_position_to_hd(hass_positon):
+    """Convert hass position to hunter douglas position."""
+    return int(hass_positon / 100 * MAX_POSITION)
+
+
+class PowerViewShade(HDEntity, CoverEntity):
+    """Representation of a powerview shade."""
+
+    def __init__(self, shade, name, room_data, coordinator, device_info):
+        """Initialize the shade."""
+        room_id = shade.raw_data.get(ROOM_ID_IN_SHADE)
+        super().__init__(coordinator, device_info, shade.id)
+        self._shade = shade
+        self._device_info = device_info
+        self._is_opening = False
+        self._is_closing = False
+        self._room_name = None
+        self._last_action_timestamp = 0
+        self._scheduled_transition_update = None
+        self._name = name
+        self._room_name = room_data.get(room_id, {}).get(ROOM_NAME_UNICODE, "")
+        self._current_cover_position = MIN_POSITION
+        self._coordinator = coordinator
+
+    @property
+    def device_state_attributes(self):
+        """Return the state attributes."""
+        return {STATE_ATTRIBUTE_ROOM_NAME: self._room_name}
+
+    @property
+    def supported_features(self):
+        """Flag supported features."""
+        supported_features = SUPPORT_OPEN | SUPPORT_CLOSE | SUPPORT_SET_POSITION
+        if self._device_info[DEVICE_MODEL] != "1":
+            supported_features |= SUPPORT_STOP
+        return supported_features
+
+    @property
+    def is_closed(self):
+        """Return if the cover is closed."""
+        return self._current_cover_position == MIN_POSITION
+
+    @property
+    def is_opening(self):
+        """Return if the cover is opening."""
+        return self._is_opening
+
+    @property
+    def is_closing(self):
+        """Return if the cover is closing."""
+        return self._is_closing
+
+    @property
+    def current_cover_position(self):
+        """Return the current position of cover."""
+        return hd_position_to_hass(self._current_cover_position)
+
+    @property
+    def device_class(self):
+        """Return device class."""
+        return DEVICE_CLASS_SHADE
+
+    @property
+    def name(self):
+        """Return the name of the shade."""
+        return self._name
+
+    async def async_close_cover(self, **kwargs):
+        """Close the cover."""
+        await self._async_move(0)
+
+    async def async_open_cover(self, **kwargs):
+        """Open the cover."""
+        await self._async_move(100)
+
+    async def async_stop_cover(self, **kwargs):
+        """Stop the cover."""
+        # Cancel any previous updates
+        self._async_cancel_scheduled_transition_update()
+        self._async_update_from_command(await self._shade.stop())
+        await self._async_force_refresh_state()
+
+    async def set_cover_position(self, **kwargs):
+        """Move the shade to a specific position."""
+        if ATTR_POSITION not in kwargs:
+            return
+        await self._async_move(kwargs[ATTR_POSITION])
+
+    async def _async_move(self, target_hass_position):
+        """Move the shade to a position."""
+        current_hass_position = hd_position_to_hass(self._current_cover_position)
+        steps_to_move = abs(current_hass_position - target_hass_position)
+        if not steps_to_move:
+            return
+        self._async_schedule_update_for_transition(steps_to_move)
+        self._async_update_from_command(
+            await self._shade.move(
+                {
+                    ATTR_POSITION1: hass_position_to_hd(target_hass_position),
+                    ATTR_POSKIND1: 1,
+                }
+            )
+        )
+        self._is_opening = False
+        self._is_closing = False
+        if target_hass_position > current_hass_position:
+            self._is_opening = True
+        elif target_hass_position < current_hass_position:
+            self._is_closing = True
+        self.async_write_ha_state()
+
+    @callback
+    def _async_update_from_command(self, raw_data):
+        """Update the shade state after a command."""
+        if not raw_data or SHADE_RESPONSE not in raw_data:
+            return
+        self._async_process_new_shade_data(raw_data[SHADE_RESPONSE])
+
+    @callback
+    def _async_process_new_shade_data(self, data):
+        """Process new data from an update."""
+        self._shade.raw_data = data
+        self._async_update_current_cover_position()
+
+    @callback
+    def _async_update_current_cover_position(self):
+        """Update the current cover position from the data."""
+        _LOGGER.debug("Raw data update: %s", self._shade.raw_data)
+        position_data = self._shade.raw_data[ATTR_POSITION_DATA]
+        if ATTR_POSITION1 in position_data:
+            self._current_cover_position = position_data[ATTR_POSITION1]
+        self._is_opening = False
+        self._is_closing = False
+
+    @callback
+    def _async_cancel_scheduled_transition_update(self):
+        """Cancel any previous updates."""
+        if not self._scheduled_transition_update:
+            return
+        self._scheduled_transition_update()
+        self._scheduled_transition_update = None
+
+    @callback
+    def _async_schedule_update_for_transition(self, steps):
+        self.async_write_ha_state()
+
+        # Cancel any previous updates
+        self._async_cancel_scheduled_transition_update()
+
+        est_time_to_complete_transition = 1 + int(
+            TRANSITION_COMPLETE_DURATION * (steps / 100)
+        )
+
+        _LOGGER.debug(
+            "Estimated time to complete transition of %s steps for %s: %s",
+            steps,
+            self.name,
+            est_time_to_complete_transition,
+        )
+
+        # Schedule an update for when we expect the transition
+        # to be completed.
+        self._scheduled_transition_update = async_call_later(
+            self.hass,
+            est_time_to_complete_transition,
+            self._async_complete_schedule_update,
+        )
+
+    async def _async_complete_schedule_update(self, _):
+        """Update status of the cover."""
+        _LOGGER.debug("Processing scheduled update for %s", self.name)
+        self._scheduled_transition_update = None
+        await self._async_force_refresh_state()
+
+    async def _async_force_refresh_state(self):
+        """Refresh the cover state and force the device cache to be bypassed."""
+        await self._shade.refresh()
+        self._async_update_current_cover_position()
+        self.async_write_ha_state()
+
+    @property
+    def device_info(self):
+        """Return the device_info of the device."""
+        firmware = self._shade.raw_data[FIRMWARE_IN_SHADE]
+        sw_version = f"{firmware[FIRMWARE_REVISION]}.{firmware[FIRMWARE_SUB_REVISION]}.{firmware[FIRMWARE_BUILD]}"
+        model = self._shade.raw_data[ATTR_TYPE]
+        for shade in self._shade.shade_types:
+            if shade.shade_type == model:
+                model = shade.description
+                break
+
+        return {
+            "identifiers": {(DOMAIN, self.unique_id)},
+            "name": self.name,
+            "model": str(model),
+            "sw_version": sw_version,
+            "manufacturer": MANUFACTURER,
+            "via_device": (DOMAIN, self._device_info[DEVICE_SERIAL_NUMBER]),
+        }
+
+    async def async_added_to_hass(self):
+        """When entity is added to hass."""
+        self._async_update_current_cover_position()
+        self.async_on_remove(
+            self._coordinator.async_add_listener(self._async_update_shade_from_group)
+        )
+
+    @callback
+    def _async_update_shade_from_group(self):
+        """Update with new data from the coordinator."""
+        if self._scheduled_transition_update:
+            # If a transition in in progress
+            # the data will be wrong
+            return
+        self._async_process_new_shade_data(self._coordinator.data[self._shade.id])
+        self.async_write_ha_state()
diff --git a/homeassistant/components/hunterdouglas_powerview/entity.py b/homeassistant/components/hunterdouglas_powerview/entity.py
new file mode 100644
index 0000000000000000000000000000000000000000..03d20e027b8bd4ed2b1144e77265b72bd972acd1
--- /dev/null
+++ b/homeassistant/components/hunterdouglas_powerview/entity.py
@@ -0,0 +1,59 @@
+"""The nexia integration base entity."""
+
+import homeassistant.helpers.device_registry as dr
+from homeassistant.helpers.entity import Entity
+
+from .const import (
+    DEVICE_FIRMWARE,
+    DEVICE_MAC_ADDRESS,
+    DEVICE_MODEL,
+    DEVICE_NAME,
+    DEVICE_SERIAL_NUMBER,
+    DOMAIN,
+    FIRMWARE_BUILD,
+    FIRMWARE_REVISION,
+    FIRMWARE_SUB_REVISION,
+    MANUFACTURER,
+)
+
+
+class HDEntity(Entity):
+    """Base class for hunter douglas entities."""
+
+    def __init__(self, coordinator, device_info, unique_id):
+        """Initialize the entity."""
+        super().__init__()
+        self._coordinator = coordinator
+        self._unique_id = unique_id
+        self._device_info = device_info
+
+    @property
+    def available(self):
+        """Return True if entity is available."""
+        return self._coordinator.last_update_success
+
+    @property
+    def unique_id(self):
+        """Return the unique id."""
+        return self._unique_id
+
+    @property
+    def should_poll(self):
+        """Return False, updates are controlled via coordinator."""
+        return False
+
+    @property
+    def device_info(self):
+        """Return the device_info of the device."""
+        firmware = self._device_info[DEVICE_FIRMWARE]
+        sw_version = f"{firmware[FIRMWARE_REVISION]}.{firmware[FIRMWARE_SUB_REVISION]}.{firmware[FIRMWARE_BUILD]}"
+        return {
+            "identifiers": {(DOMAIN, self._device_info[DEVICE_SERIAL_NUMBER])},
+            "connections": {
+                (dr.CONNECTION_NETWORK_MAC, self._device_info[DEVICE_MAC_ADDRESS])
+            },
+            "name": self._device_info[DEVICE_NAME],
+            "model": self._device_info[DEVICE_MODEL],
+            "sw_version": sw_version,
+            "manufacturer": MANUFACTURER,
+        }
diff --git a/homeassistant/components/hunterdouglas_powerview/manifest.json b/homeassistant/components/hunterdouglas_powerview/manifest.json
index 68fc6118a34e84d3e72137639da11d38e64ae44d..b68ec02d3f689578e9fb0452176d16af53603ae1 100644
--- a/homeassistant/components/hunterdouglas_powerview/manifest.json
+++ b/homeassistant/components/hunterdouglas_powerview/manifest.json
@@ -2,6 +2,12 @@
   "domain": "hunterdouglas_powerview",
   "name": "Hunter Douglas PowerView",
   "documentation": "https://www.home-assistant.io/integrations/hunterdouglas_powerview",
-  "requirements": ["aiopvapi==1.6.14"],
-  "codeowners": []
-}
+  "requirements": [
+    "aiopvapi==1.6.14"
+  ],
+  "codeowners": ["@bdraco"],
+  "config_flow": true,
+  "homekit": {
+    "models": ["PowerView"]
+  }  
+}
\ No newline at end of file
diff --git a/homeassistant/components/hunterdouglas_powerview/scene.py b/homeassistant/components/hunterdouglas_powerview/scene.py
index b73ce8fd7d5ee99c231d519e6966f0d1ff6dfba4..0e98ce0448d14831faccccb85cec2f664744ee12 100644
--- a/homeassistant/components/hunterdouglas_powerview/scene.py
+++ b/homeassistant/components/hunterdouglas_powerview/scene.py
@@ -2,86 +2,73 @@
 import logging
 from typing import Any
 
-from aiopvapi.helpers.aiorequest import AioRequest
 from aiopvapi.resources.scene import Scene as PvScene
-from aiopvapi.rooms import Rooms
-from aiopvapi.scenes import Scenes
 import voluptuous as vol
 
-from homeassistant.components.scene import DOMAIN, Scene
-from homeassistant.const import CONF_PLATFORM
-from homeassistant.helpers.aiohttp_client import async_get_clientsession
+from homeassistant.components.scene import Scene
+from homeassistant.config_entries import SOURCE_IMPORT
+from homeassistant.const import CONF_HOST, CONF_PLATFORM
 import homeassistant.helpers.config_validation as cv
-from homeassistant.helpers.entity import async_generate_entity_id
+
+from .const import (
+    COORDINATOR,
+    DEVICE_INFO,
+    DOMAIN,
+    HUB_ADDRESS,
+    PV_API,
+    PV_ROOM_DATA,
+    PV_SCENE_DATA,
+    ROOM_NAME_UNICODE,
+    STATE_ATTRIBUTE_ROOM_NAME,
+)
+from .entity import HDEntity
 
 _LOGGER = logging.getLogger(__name__)
-ENTITY_ID_FORMAT = DOMAIN + ".{}"
-HUB_ADDRESS = "address"
 
 PLATFORM_SCHEMA = vol.Schema(
-    {
-        vol.Required(CONF_PLATFORM): "hunterdouglas_powerview",
-        vol.Required(HUB_ADDRESS): cv.string,
-    }
+    {vol.Required(CONF_PLATFORM): DOMAIN, vol.Required(HUB_ADDRESS): cv.string}
 )
 
 
-SCENE_DATA = "sceneData"
-ROOM_DATA = "roomData"
-SCENE_NAME = "name"
-ROOM_NAME = "name"
-SCENE_ID = "id"
-ROOM_ID = "id"
-ROOM_ID_IN_SCENE = "roomId"
-STATE_ATTRIBUTE_ROOM_NAME = "roomName"
-
+def setup_platform(hass, config, add_entities, discovery_info=None):
+    """Import platform from yaml."""
 
-async def async_setup_platform(hass, config, async_add_entities, discovery_info=None):
-    """Set up Home Assistant scene entries."""
+    hass.async_create_task(
+        hass.config_entries.flow.async_init(
+            DOMAIN,
+            context={"source": SOURCE_IMPORT},
+            data={CONF_HOST: config[HUB_ADDRESS]},
+        )
+    )
 
-    hub_address = config.get(HUB_ADDRESS)
-    websession = async_get_clientsession(hass)
 
-    pv_request = AioRequest(hub_address, loop=hass.loop, websession=websession)
+async def async_setup_entry(hass, entry, async_add_entities):
+    """Set up powerview scene entries."""
 
-    _scenes = await Scenes(pv_request).get_resources()
-    _rooms = await Rooms(pv_request).get_resources()
+    pv_data = hass.data[DOMAIN][entry.entry_id]
+    room_data = pv_data[PV_ROOM_DATA]
+    scene_data = pv_data[PV_SCENE_DATA]
+    pv_request = pv_data[PV_API]
+    coordinator = pv_data[COORDINATOR]
+    device_info = pv_data[DEVICE_INFO]
 
-    if not _scenes or not _rooms:
-        _LOGGER.error("Unable to initialize PowerView hub: %s", hub_address)
-        return
     pvscenes = (
-        PowerViewScene(hass, PvScene(_raw_scene, pv_request), _rooms)
-        for _raw_scene in _scenes[SCENE_DATA]
+        PowerViewScene(
+            PvScene(raw_scene, pv_request), room_data, coordinator, device_info
+        )
+        for scene_id, raw_scene in scene_data.items()
     )
     async_add_entities(pvscenes)
 
 
-class PowerViewScene(Scene):
+class PowerViewScene(HDEntity, Scene):
     """Representation of a Powerview scene."""
 
-    def __init__(self, hass, scene, room_data):
+    def __init__(self, scene, room_data, coordinator, device_info):
         """Initialize the scene."""
+        super().__init__(coordinator, device_info, scene.id)
         self._scene = scene
-        self.hass = hass
-        self._room_name = None
-        self._sync_room_data(room_data)
-        self.entity_id = async_generate_entity_id(
-            ENTITY_ID_FORMAT, str(self._scene.id), hass=hass
-        )
-
-    def _sync_room_data(self, room_data):
-        """Sync room data."""
-        room = next(
-            (
-                room
-                for room in room_data[ROOM_DATA]
-                if room[ROOM_ID] == self._scene.room_id
-            ),
-            {},
-        )
-
-        self._room_name = room.get(ROOM_NAME, "")
+        self._room_name = room_data.get(scene.room_id, {}).get(ROOM_NAME_UNICODE, "")
 
     @property
     def name(self):
diff --git a/homeassistant/components/hunterdouglas_powerview/strings.json b/homeassistant/components/hunterdouglas_powerview/strings.json
new file mode 100644
index 0000000000000000000000000000000000000000..61bc0735613dd7095ca21223ac23577694100831
--- /dev/null
+++ b/homeassistant/components/hunterdouglas_powerview/strings.json
@@ -0,0 +1,24 @@
+{
+  "title": "Hunter Douglas PowerView",
+  "config": {
+    "step": {
+      "user": {
+        "title": "Connect to the PowerView Hub",
+        "data": {
+          "host": "IP Address"
+        }
+      },
+      "link": {
+        "title": "Connect to the PowerView Hub",
+        "description": "Do you want to setup {name} ({host})?"
+      } 
+    },
+    "error": {
+      "cannot_connect": "Failed to connect, please try again",
+      "unknown": "Unexpected error"
+    },
+    "abort": {
+      "already_configured": "Device is already configured"
+    }
+  }
+}
\ No newline at end of file
diff --git a/homeassistant/components/hunterdouglas_powerview/translations/en.json b/homeassistant/components/hunterdouglas_powerview/translations/en.json
new file mode 100644
index 0000000000000000000000000000000000000000..b2e9c1f207e65e8493e3d667eb86f2440039eacc
--- /dev/null
+++ b/homeassistant/components/hunterdouglas_powerview/translations/en.json
@@ -0,0 +1,25 @@
+{
+  "config": {
+    "step": {
+      "user": {
+        "title": "Connect to the PowerView Hub",
+        "data": {
+          "host": "IP Address"
+        }
+      },
+      "link": {
+        "title": "Connect to the PowerView Hub",
+        "description": "Do you want to setup {name} ({host})?"
+      } 
+    },
+    "error": {
+      "cannot_connect": "Failed to connect, please try again",
+      "unknown": "Unexpected error"
+    },
+    "abort": {
+      "already_configured": "Device is already configured",
+      "cannot_connect": "Failed to connect, please try again",
+      "unknown": "Unexpected error"
+    }
+  }
+}
\ No newline at end of file
diff --git a/homeassistant/generated/config_flows.py b/homeassistant/generated/config_flows.py
index 636dab4de2709e2cd58f98957b26f6d5c524df72..6e259c2bf84afbb0cd798a1277a389732034cf96 100644
--- a/homeassistant/generated/config_flows.py
+++ b/homeassistant/generated/config_flows.py
@@ -54,6 +54,7 @@ FLOWS = [
     "homematicip_cloud",
     "huawei_lte",
     "hue",
+    "hunterdouglas_powerview",
     "iaqualink",
     "icloud",
     "ifttt",
diff --git a/homeassistant/generated/zeroconf.py b/homeassistant/generated/zeroconf.py
index fa0c5ad593aa34975b91000bc6de7c07f19b872b..ddf9b2cdb6756c30cd3165294c5384da31d3a05e 100644
--- a/homeassistant/generated/zeroconf.py
+++ b/homeassistant/generated/zeroconf.py
@@ -49,6 +49,7 @@ HOMEKIT = {
     "Healty Home Coach": "netatmo",
     "LIFX": "lifx",
     "Netatmo Relay": "netatmo",
+    "PowerView": "hunterdouglas_powerview",
     "Presence": "netatmo",
     "Rachio": "rachio",
     "TRADFRI": "tradfri",
diff --git a/requirements_test_all.txt b/requirements_test_all.txt
index f02da029de75926f0769fefcb6dc0f9c1ed5de46..47362e55aee8f8b9a01b7782595b7db7fcc53a07 100644
--- a/requirements_test_all.txt
+++ b/requirements_test_all.txt
@@ -85,6 +85,9 @@ aiohue==2.1.0
 # homeassistant.components.notion
 aionotion==1.1.0
 
+# homeassistant.components.hunterdouglas_powerview
+aiopvapi==1.6.14
+
 # homeassistant.components.pvpc_hourly_pricing
 aiopvpc==1.0.2
 
diff --git a/tests/components/hunterdouglas_powerview/__init__.py b/tests/components/hunterdouglas_powerview/__init__.py
new file mode 100644
index 0000000000000000000000000000000000000000..034d845b1109f5cf7aa8dc4085f956a0e3f4b7ff
--- /dev/null
+++ b/tests/components/hunterdouglas_powerview/__init__.py
@@ -0,0 +1 @@
+"""Tests for the Hunter Douglas PowerView integration."""
diff --git a/tests/components/hunterdouglas_powerview/test_config_flow.py b/tests/components/hunterdouglas_powerview/test_config_flow.py
new file mode 100644
index 0000000000000000000000000000000000000000..67b2c2dc954b9192e7f98aab1436d3f185b27485
--- /dev/null
+++ b/tests/components/hunterdouglas_powerview/test_config_flow.py
@@ -0,0 +1,220 @@
+"""Test the Logitech Harmony Hub config flow."""
+import asyncio
+import json
+
+from asynctest import CoroutineMock, MagicMock, patch
+
+from homeassistant import config_entries, setup
+from homeassistant.components.hunterdouglas_powerview.const import DOMAIN
+
+from tests.common import load_fixture
+
+
+def _get_mock_powerview_userdata(userdata=None, get_resources=None):
+    mock_powerview_userdata = MagicMock()
+    if not userdata:
+        userdata = json.loads(load_fixture("hunterdouglas_powerview/userdata.json"))
+    if get_resources:
+        type(mock_powerview_userdata).get_resources = CoroutineMock(
+            side_effect=get_resources
+        )
+    else:
+        type(mock_powerview_userdata).get_resources = CoroutineMock(
+            return_value=userdata
+        )
+    return mock_powerview_userdata
+
+
+async def test_user_form(hass):
+    """Test we get the user form."""
+    await setup.async_setup_component(hass, "persistent_notification", {})
+    result = await hass.config_entries.flow.async_init(
+        DOMAIN, context={"source": config_entries.SOURCE_USER}
+    )
+    assert result["type"] == "form"
+    assert result["errors"] == {}
+
+    mock_powerview_userdata = _get_mock_powerview_userdata()
+    with patch(
+        "homeassistant.components.hunterdouglas_powerview.UserData",
+        return_value=mock_powerview_userdata,
+    ), patch(
+        "homeassistant.components.hunterdouglas_powerview.async_setup",
+        return_value=True,
+    ) as mock_setup, patch(
+        "homeassistant.components.hunterdouglas_powerview.async_setup_entry",
+        return_value=True,
+    ) as mock_setup_entry:
+        result2 = await hass.config_entries.flow.async_configure(
+            result["flow_id"], {"host": "1.2.3.4"},
+        )
+
+    assert result2["type"] == "create_entry"
+    assert result2["title"] == "AlexanderHD"
+    assert result2["data"] == {
+        "host": "1.2.3.4",
+    }
+    await hass.async_block_till_done()
+    assert len(mock_setup.mock_calls) == 1
+    assert len(mock_setup_entry.mock_calls) == 1
+
+    result3 = await hass.config_entries.flow.async_init(
+        DOMAIN, context={"source": config_entries.SOURCE_USER}
+    )
+    assert result3["type"] == "form"
+    assert result3["errors"] == {}
+
+    result4 = await hass.config_entries.flow.async_configure(
+        result3["flow_id"], {"host": "1.2.3.4"},
+    )
+    assert result4["type"] == "abort"
+
+
+async def test_form_import(hass):
+    """Test we get the form with import source."""
+    await setup.async_setup_component(hass, "persistent_notification", {})
+
+    mock_powerview_userdata = _get_mock_powerview_userdata()
+    with patch(
+        "homeassistant.components.hunterdouglas_powerview.UserData",
+        return_value=mock_powerview_userdata,
+    ), patch(
+        "homeassistant.components.hunterdouglas_powerview.async_setup",
+        return_value=True,
+    ) as mock_setup, patch(
+        "homeassistant.components.hunterdouglas_powerview.async_setup_entry",
+        return_value=True,
+    ) as mock_setup_entry:
+        result = await hass.config_entries.flow.async_init(
+            DOMAIN,
+            context={"source": config_entries.SOURCE_IMPORT},
+            data={"host": "1.2.3.4"},
+        )
+
+    assert result["type"] == "create_entry"
+    assert result["title"] == "AlexanderHD"
+    assert result["data"] == {
+        "host": "1.2.3.4",
+    }
+    await hass.async_block_till_done()
+    assert len(mock_setup.mock_calls) == 1
+    assert len(mock_setup_entry.mock_calls) == 1
+
+
+async def test_form_homekit(hass):
+    """Test we get the form with homekit source."""
+    await setup.async_setup_component(hass, "persistent_notification", {})
+
+    mock_powerview_userdata = _get_mock_powerview_userdata()
+    with patch(
+        "homeassistant.components.hunterdouglas_powerview.UserData",
+        return_value=mock_powerview_userdata,
+    ):
+        result = await hass.config_entries.flow.async_init(
+            DOMAIN,
+            context={"source": "homekit"},
+            data={
+                "host": "1.2.3.4",
+                "properties": {"id": "AA::BB::CC::DD::EE::FF"},
+                "name": "PowerViewHub._hap._tcp.local.",
+            },
+        )
+
+    assert result["type"] == "form"
+    assert result["step_id"] == "link"
+    assert result["errors"] is None
+    assert result["description_placeholders"] == {
+        "host": "1.2.3.4",
+        "name": "PowerViewHub",
+    }
+
+    with patch(
+        "homeassistant.components.hunterdouglas_powerview.UserData",
+        return_value=mock_powerview_userdata,
+    ), patch(
+        "homeassistant.components.hunterdouglas_powerview.async_setup",
+        return_value=True,
+    ) as mock_setup, patch(
+        "homeassistant.components.hunterdouglas_powerview.async_setup_entry",
+        return_value=True,
+    ) as mock_setup_entry:
+        result2 = await hass.config_entries.flow.async_configure(result["flow_id"], {})
+
+    assert result2["type"] == "create_entry"
+    assert result2["title"] == "PowerViewHub"
+    assert result2["data"] == {"host": "1.2.3.4"}
+    assert result2["result"].unique_id == "ABC123"
+
+    await hass.async_block_till_done()
+    assert len(mock_setup.mock_calls) == 1
+    assert len(mock_setup_entry.mock_calls) == 1
+
+    result3 = await hass.config_entries.flow.async_init(
+        DOMAIN,
+        context={"source": "homekit"},
+        data={
+            "host": "1.2.3.4",
+            "properties": {"id": "AA::BB::CC::DD::EE::FF"},
+            "name": "PowerViewHub._hap._tcp.local.",
+        },
+    )
+    assert result3["type"] == "abort"
+
+
+async def test_form_cannot_connect(hass):
+    """Test we handle cannot connect error."""
+    result = await hass.config_entries.flow.async_init(
+        DOMAIN, context={"source": config_entries.SOURCE_USER}
+    )
+
+    mock_powerview_userdata = _get_mock_powerview_userdata(
+        get_resources=asyncio.TimeoutError
+    )
+    with patch(
+        "homeassistant.components.hunterdouglas_powerview.UserData",
+        return_value=mock_powerview_userdata,
+    ):
+        result2 = await hass.config_entries.flow.async_configure(
+            result["flow_id"], {"host": "1.2.3.4"},
+        )
+
+    assert result2["type"] == "form"
+    assert result2["errors"] == {"base": "cannot_connect"}
+
+
+async def test_form_no_data(hass):
+    """Test we handle no data being returned from the hub."""
+    result = await hass.config_entries.flow.async_init(
+        DOMAIN, context={"source": config_entries.SOURCE_USER}
+    )
+
+    mock_powerview_userdata = _get_mock_powerview_userdata(userdata={"userData": {}})
+    with patch(
+        "homeassistant.components.hunterdouglas_powerview.UserData",
+        return_value=mock_powerview_userdata,
+    ):
+        result2 = await hass.config_entries.flow.async_configure(
+            result["flow_id"], {"host": "1.2.3.4"},
+        )
+
+    assert result2["type"] == "form"
+    assert result2["errors"] == {"base": "unknown"}
+
+
+async def test_form_unknown_exception(hass):
+    """Test we handle unknown exception."""
+    result = await hass.config_entries.flow.async_init(
+        DOMAIN, context={"source": config_entries.SOURCE_USER}
+    )
+
+    mock_powerview_userdata = _get_mock_powerview_userdata(userdata={"userData": {}})
+    with patch(
+        "homeassistant.components.hunterdouglas_powerview.UserData",
+        return_value=mock_powerview_userdata,
+    ):
+        result2 = await hass.config_entries.flow.async_configure(
+            result["flow_id"], {"host": "1.2.3.4"},
+        )
+
+    assert result2["type"] == "form"
+    assert result2["errors"] == {"base": "unknown"}
diff --git a/tests/fixtures/hunterdouglas_powerview/userdata.json b/tests/fixtures/hunterdouglas_powerview/userdata.json
new file mode 100644
index 0000000000000000000000000000000000000000..ca5eea73f7b7864650acaa92647e263c58160c2c
--- /dev/null
+++ b/tests/fixtures/hunterdouglas_powerview/userdata.json
@@ -0,0 +1,50 @@
+{
+	"userData": {
+		"_id": "abc",
+		"color": {
+			"green": 0,
+			"blue": 255,
+			"brightness": 5,
+			"red": 0
+		},
+		"autoBackup": false,
+		"ip": "192.168.1.72",
+		"macAddress": "aa:bb:cc:dd:ee:ff",
+		"mask": "255.255.255.0",
+		"gateway": "192.168.1.1",
+		"dns": "192.168.1.3",
+		"firmware": {
+			"mainProcessor": {
+				"name": "PV Hub2.0",
+				"revision": 2,
+				"subRevision": 0,
+				"build": 1024
+			},
+			"radio": {
+				"revision": 2,
+				"subRevision": 0,
+				"build": 2610
+			}
+		},
+		"serialNumber": "ABC123",
+		"rfIDInt": 64789,
+		"rfID": "0xFD15",
+		"rfStatus": 0,
+		"brand": "HD",
+		"wireless": false,
+		"hubName": "QWxleGFuZGVySEQ=",
+		"localTimeDataSet": true,
+		"enableScheduledEvents": true,
+		"editingEnabled": true,
+		"setupCompleted": false,
+		"staticIp": false,
+		"times": {
+			"timezone": "America/Chicago",
+			"localSunriseTimeInMinutes": 0,
+			"localSunsetTimeInMinutes": 0,
+			"currentOffset": -18000
+		},
+		"rcUp": true,
+		"remoteConnectEnabled": true
+	}
+}