Skip to content
Merged
Show file tree
Hide file tree
Changes from 6 commits
Commits
Show all changes
27 commits
Select commit Hold shift + click to select a range
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
37 changes: 23 additions & 14 deletions homeassistant/components/updater/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,14 +21,13 @@
_LOGGER = logging.getLogger(__name__)

ATTR_RELEASE_NOTES = "release_notes"
ATTR_NEWEST_VERSION = "newest_version"

CONF_REPORTING = "reporting"
CONF_COMPONENT_REPORTING = "include_used_components"

DOMAIN = "updater"

ENTITY_ID = "updater.updater"

UPDATER_URL = "https://updater.home-assistant.io/"
UPDATER_UUID_FILE = ".uuid"

Expand All @@ -47,6 +46,14 @@
)


class Updater:
"""Updater class."""

update_available = None
release_notes = None
newest_version = None


def _create_uuid(hass, filename=UPDATER_UUID_FILE):
"""Create UUID and save it in a file."""
with open(hass.config.path(filename), "w") as fptr:
Expand Down Expand Up @@ -81,14 +88,16 @@ async def async_setup(hass, config):

include_components = config.get(CONF_COMPONENT_REPORTING)

updater = hass.data[DOMAIN] = Updater()

async def check_new_version(now):
"""Check if a new version is available and report if one is."""
result = await get_newest_version(hass, huuid, include_components)

if result is None:
return

newest, releasenotes = result
newest, release_notes = result

# Skip on dev
if newest is None or "dev" in current_version:
Expand All @@ -99,18 +108,18 @@ async def check_new_version(now):
newest = hass.components.hassio.get_homeassistant_version()

# Validate version
update_available = False
if StrictVersion(newest) > StrictVersion(current_version):
_LOGGER.info("The latest available version is %s", newest)
hass.states.async_set(
ENTITY_ID,
newest,
{
ATTR_FRIENDLY_NAME: "Update Available",
ATTR_RELEASE_NOTES: releasenotes,
},
)
_LOGGER.info("The latest available version of Home Assistant is %s", newest)
update_available = True
elif StrictVersion(newest) == StrictVersion(current_version):
_LOGGER.info("You are on the latest version (%s) of Home Assistant", newest)
elif StrictVersion(newest) < StrictVersion(current_version):
_LOGGER.debug("Local version is newer than the latest version (%s)", newest)

updater.update_available = update_available
updater.release_notes = release_notes
updater.newest_version = newest

# Update daily, start 1 hour after startup
_dt = dt_util.utcnow() + timedelta(hours=1)
Expand Down Expand Up @@ -145,13 +154,13 @@ async def get_newest_version(hass, huuid, include_components):
req = await session.post(UPDATER_URL, json=info_object)
_LOGGER.info(
(
"Submitted analytics to Home Assistant servers. "
"Submitted analytics to Home Assistant servers."
"Information submitted includes %s"
),
info_object,
)
except (asyncio.TimeoutError, aiohttp.ClientError):
_LOGGER.error("Could not contact Home Assistant Update to check " "for updates")
_LOGGER.error("Could not contact Home Assistant Update to check for updates")
return None

try:
Expand Down
62 changes: 62 additions & 0 deletions homeassistant/components/updater/binary_sensor.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
"""Support for Home Assistant Updater binary sensors."""

from homeassistant.components.binary_sensor import BinarySensorDevice

from . import ATTR_NEWEST_VERSION, ATTR_RELEASE_NOTES, DOMAIN


async def async_setup_platform(hass, config, async_add_entities, discovery_info=None):
"""Set up the updater binary sensors."""
updater = hass.data[DOMAIN]

async_add_entities([UpdaterBinary(updater)])


class UpdaterBinary(BinarySensorDevice):
"""Representation of an updater binary sensor."""

def __init__(self, updater):
"""Initialize the binary sensor."""
self._updater = updater

@property
def name(self) -> str:
"""Return the name of the binary sensor, if any."""
return "Updater"

@property
def unique_id(self) -> str:
"""Return a unique ID."""
return "updater"

@property
def is_on(self) -> bool:
"""Return true if the binary sensor is on."""
return "on" if self._updater.update_available else "off"
Comment thread
Santobert marked this conversation as resolved.
Outdated

@property
def device_class(self) -> str:
"""Return the class of this device, from component DEVICE_CLASSES."""
return "connectivity"
Comment thread
Santobert marked this conversation as resolved.
Outdated

@property
def available(self) -> bool:
"""Return True if entity is available."""
return True if self._updater.update_available is not None else False
Comment thread
Santobert marked this conversation as resolved.
Outdated

@property
def should_poll(self) -> bool:
"""Return True if entity has to be polled for state."""
return True
Comment thread
Santobert marked this conversation as resolved.
Outdated

@property
def device_state_attributes(self) -> dict:
"""Return the optional state attributes."""
data = super().device_state_attributes
if data is None:
data = {}
if self._updater.release_notes:
data[ATTR_RELEASE_NOTES] = self._updater.release_notes
if self._updater.newest_version:
data[ATTR_NEWEST_VERSION] = self._updater.newest_version
return data
21 changes: 13 additions & 8 deletions tests/components/updater/test_init.py
Original file line number Diff line number Diff line change
Expand Up @@ -45,9 +45,7 @@ def mock_get_uuid():


@asyncio.coroutine
def test_new_version_shows_entity_after_hour(
hass, mock_get_uuid, mock_get_newest_version
):
def test_new_version_shows_entity_true(hass, mock_get_uuid, mock_get_newest_version):
"""Test if new entity is created if new version is available."""
Comment thread
Santobert marked this conversation as resolved.
Outdated
mock_get_uuid.return_value = MOCK_HUUID
mock_get_newest_version.return_value = mock_coro((NEW_VERSION, ""))
Expand All @@ -59,11 +57,14 @@ def test_new_version_shows_entity_after_hour(
async_fire_time_changed(hass, dt_util.utcnow() + timedelta(hours=1))
Comment thread
Santobert marked this conversation as resolved.
Outdated
yield from hass.async_block_till_done()

assert hass.states.is_state(updater.ENTITY_ID, NEW_VERSION)
assert hass.states.is_state(updater.ENTITY_ID, "on")
assert (
hass.states.get(updater.ENTITY_ID).attributes["newest_version"] == NEW_VERSION
)
Comment thread
Santobert marked this conversation as resolved.


@asyncio.coroutine
def test_same_version_not_show_entity(hass, mock_get_uuid, mock_get_newest_version):
def test_same_version_shows_entity_false(hass, mock_get_uuid, mock_get_newest_version):
"""Test if new entity is created if new version is available."""
mock_get_uuid.return_value = MOCK_HUUID
mock_get_newest_version.return_value = mock_coro((MOCK_VERSION, ""))
Expand All @@ -75,7 +76,10 @@ def test_same_version_not_show_entity(hass, mock_get_uuid, mock_get_newest_versi
async_fire_time_changed(hass, dt_util.utcnow() + timedelta(hours=1))
yield from hass.async_block_till_done()

assert hass.states.get(updater.ENTITY_ID) is None
assert hass.states.is_state(updater.ENTITY_ID, "off")
assert (
hass.states.get(updater.ENTITY_ID).attributes["newest_version"] == MOCK_VERSION
)


@asyncio.coroutine
Expand All @@ -93,7 +97,7 @@ def test_disable_reporting(hass, mock_get_uuid, mock_get_newest_version):
async_fire_time_changed(hass, dt_util.utcnow() + timedelta(hours=1))
yield from hass.async_block_till_done()

assert hass.states.get(updater.ENTITY_ID) is None
assert hass.states.is_state(updater.ENTITY_ID, "off")
res = yield from updater.get_newest_version(hass, MOCK_HUUID, MOCK_CONFIG)
call = mock_get_newest_version.mock_calls[0][1]
assert call[0] is hass
Expand Down Expand Up @@ -185,4 +189,5 @@ def test_new_version_shows_entity_after_hour_hassio(
async_fire_time_changed(hass, dt_util.utcnow() + timedelta(hours=1))
yield from hass.async_block_till_done()

assert hass.states.is_state(updater.ENTITY_ID, "999.0")
assert hass.states.is_state(updater.ENTITY_ID, "on")
assert hass.states.get(updater.ENTITY_ID).attributes["newest_version"] == "999.0"