Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
23 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
75 changes: 63 additions & 12 deletions homeassistant/components/counter/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,9 @@

import voluptuous as vol

from homeassistant.const import ATTR_ENTITY_ID, CONF_ICON, CONF_NAME
from homeassistant.const import ATTR_ENTITY_ID, CONF_ICON, CONF_NAME,\
CONF_MAXIMUM, CONF_MINIMUM

import homeassistant.helpers.config_validation as cv
from homeassistant.helpers.entity_component import EntityComponent
from homeassistant.helpers.restore_state import RestoreEntity
Expand All @@ -12,6 +14,8 @@

ATTR_INITIAL = 'initial'
ATTR_STEP = 'step'
ATTR_MINIMUM = 'minimum'
ATTR_MAXIMUM = 'maximum'

CONF_INITIAL = 'initial'
CONF_RESTORE = 'restore'
Expand All @@ -26,18 +30,30 @@
SERVICE_DECREMENT = 'decrement'
SERVICE_INCREMENT = 'increment'
SERVICE_RESET = 'reset'
SERVICE_CONFIGURE = 'configure'

SERVICE_SCHEMA = vol.Schema({
SERVICE_SCHEMA_SIMPLE = vol.Schema({
vol.Optional(ATTR_ENTITY_ID): cv.comp_entity_ids,
})

SERVICE_SCHEMA_CONFIGURE = vol.Schema({
ATTR_ENTITY_ID: cv.comp_entity_ids,
vol.Optional(ATTR_MINIMUM): vol.Any(None, vol.Coerce(int)),
vol.Optional(ATTR_MAXIMUM): vol.Any(None, vol.Coerce(int)),
vol.Optional(ATTR_STEP): cv.positive_int,
})

CONFIG_SCHEMA = vol.Schema({
DOMAIN: cv.schema_with_slug_keys(
vol.Any({
vol.Optional(CONF_ICON): cv.icon,
vol.Optional(CONF_INITIAL, default=DEFAULT_INITIAL):
cv.positive_int,
vol.Optional(CONF_NAME): cv.string,
vol.Optional(CONF_MAXIMUM, default=None):
vol.Any(None, vol.Coerce(int)),
vol.Optional(CONF_MINIMUM, default=None):
vol.Any(None, vol.Coerce(int)),
vol.Optional(CONF_RESTORE, default=True): cv.boolean,
vol.Optional(CONF_STEP, default=DEFAULT_STEP): cv.positive_int,
}, None)
Expand All @@ -60,21 +76,27 @@ async def async_setup(hass, config):
restore = cfg.get(CONF_RESTORE)
step = cfg.get(CONF_STEP)
icon = cfg.get(CONF_ICON)
minimum = cfg.get(CONF_MINIMUM)
maximum = cfg.get(CONF_MAXIMUM)

entities.append(Counter(object_id, name, initial, restore, step, icon))
entities.append(Counter(object_id, name, initial, minimum, maximum,
restore, step, icon))

if not entities:
return False

component.async_register_entity_service(
SERVICE_INCREMENT, SERVICE_SCHEMA,
SERVICE_INCREMENT, SERVICE_SCHEMA_SIMPLE,
'async_increment')
component.async_register_entity_service(
SERVICE_DECREMENT, SERVICE_SCHEMA,
SERVICE_DECREMENT, SERVICE_SCHEMA_SIMPLE,
'async_decrement')
component.async_register_entity_service(
SERVICE_RESET, SERVICE_SCHEMA,
SERVICE_RESET, SERVICE_SCHEMA_SIMPLE,
'async_reset')
component.async_register_entity_service(
SERVICE_CONFIGURE, SERVICE_SCHEMA_CONFIGURE,
'async_configure')

await component.async_add_entities(entities)
return True
Expand All @@ -83,13 +105,16 @@ async def async_setup(hass, config):
class Counter(RestoreEntity):
"""Representation of a counter."""

def __init__(self, object_id, name, initial, restore, step, icon):
def __init__(self, object_id, name, initial, minimum, maximum,
restore, step, icon):
"""Initialize a counter."""
self.entity_id = ENTITY_ID_FORMAT.format(object_id)
self._name = name
self._restore = restore
self._step = step
self._state = self._initial = initial
self._min = minimum
self._max = maximum
self._icon = icon

@property
Expand All @@ -115,10 +140,24 @@ def state(self):
@property
def state_attributes(self):
"""Return the state attributes."""
return {
ret = {
ATTR_INITIAL: self._initial,
ATTR_STEP: self._step,
}
if self._min is not None:
ret[CONF_MINIMUM] = self._min
if self._max is not None:
ret[CONF_MAXIMUM] = self._max
return ret

def compute_next_state(self, state):
"""Keep the state within the range of min/max values."""
if self._min is not None:
state = max(self._min, state)
if self._max is not None:
state = min(self._max, state)

return state

async def async_added_to_hass(self):
"""Call when entity about to be added to Home Assistant."""
Expand All @@ -128,19 +167,31 @@ async def async_added_to_hass(self):
if self._restore:
state = await self.async_get_last_state()
if state is not None:
self._state = int(state.state)
self._state = self.compute_next_state(int(state.state))

async def async_decrement(self):
"""Decrement the counter."""
self._state -= self._step
self._state = self.compute_next_state(self._state - self._step)
await self.async_update_ha_state()

async def async_increment(self):
"""Increment a counter."""
self._state += self._step
self._state = self.compute_next_state(self._state + self._step)
await self.async_update_ha_state()

async def async_reset(self):
"""Reset a counter."""
self._state = self._initial
self._state = self.compute_next_state(self._initial)
await self.async_update_ha_state()

async def async_configure(self, **kwargs):
"""Change the counter's settings with a service."""
if CONF_MINIMUM in kwargs:
self._min = kwargs[CONF_MINIMUM]
if CONF_MAXIMUM in kwargs:
self._max = kwargs[CONF_MAXIMUM]
if CONF_STEP in kwargs:
self._step = kwargs[CONF_STEP]

self._state = self.compute_next_state(self._state)
await self.async_update_ha_state()
17 changes: 16 additions & 1 deletion homeassistant/components/counter/services.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -17,4 +17,19 @@ reset:
fields:
entity_id:
description: Entity id of the counter to reset.
example: 'counter.count0'
example: 'counter.count0'
configure:
description: Change counter parameters
fields:
entity_id:
description: Entity id of the counter to change.
example: 'counter.count0'
minimum:
description: New minimum value for the counter or None to remove minimum
example: 0
maximum:
description: New maximum value for the counter or None to remove maximum
example: 100
step:
description: New value for step
example: 2
165 changes: 159 additions & 6 deletions tests/components/counter/test_init.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,13 +3,13 @@
import asyncio
import logging

from homeassistant.core import CoreState, State, Context
from homeassistant.components.counter import (CONF_ICON, CONF_INITIAL,
CONF_NAME, CONF_RESTORE,
CONF_STEP, DOMAIN)
from homeassistant.const import (ATTR_FRIENDLY_NAME, ATTR_ICON)
from homeassistant.core import Context, CoreState, State
from homeassistant.setup import async_setup_component
from homeassistant.components.counter import (
DOMAIN, CONF_INITIAL, CONF_RESTORE, CONF_STEP, CONF_NAME, CONF_ICON)
from homeassistant.const import (ATTR_ICON, ATTR_FRIENDLY_NAME)

from tests.common import mock_restore_cache
from tests.common import (mock_restore_cache)
from tests.components.counter.common import (
async_decrement, async_increment, async_reset)

Expand Down Expand Up @@ -243,3 +243,156 @@ async def test_counter_context(hass, hass_admin_user):
assert state2 is not None
assert state.state != state2.state
assert state2.context.user_id == hass_admin_user.id


async def test_counter_min(hass, hass_admin_user):
"""Test that min works."""
assert await async_setup_component(hass, 'counter', {
'counter': {
'test': {
'minimum': '0',
'initial': '0'
}
}
})

state = hass.states.get('counter.test')
assert state is not None
assert state.state == '0'

await hass.services.async_call('counter', 'decrement', {
'entity_id': state.entity_id,
}, True, Context(user_id=hass_admin_user.id))

state2 = hass.states.get('counter.test')
assert state2 is not None
assert state2.state == '0'

await hass.services.async_call('counter', 'increment', {
'entity_id': state.entity_id,
}, True, Context(user_id=hass_admin_user.id))

state2 = hass.states.get('counter.test')
assert state2 is not None
assert state2.state == '1'


async def test_counter_max(hass, hass_admin_user):
"""Test that max works."""
assert await async_setup_component(hass, 'counter', {
'counter': {
'test': {
'maximum': '0',
'initial': '0'
}
}
})

state = hass.states.get('counter.test')
assert state is not None
assert state.state == '0'

await hass.services.async_call('counter', 'increment', {
'entity_id': state.entity_id,
}, True, Context(user_id=hass_admin_user.id))

state2 = hass.states.get('counter.test')
assert state2 is not None
assert state2.state == '0'

await hass.services.async_call('counter', 'decrement', {
'entity_id': state.entity_id,
}, True, Context(user_id=hass_admin_user.id))

state2 = hass.states.get('counter.test')
assert state2 is not None
assert state2.state == '-1'


async def test_configure(hass, hass_admin_user):
"""Test that setting values through configure works."""
assert await async_setup_component(hass, 'counter', {
'counter': {
'test': {
'maximum': '10',
'initial': '10'
}
}
})

state = hass.states.get('counter.test')
assert state is not None
assert state.state == '10'
assert 10 == state.attributes.get('maximum')

# update max
await hass.services.async_call('counter', 'configure', {
'entity_id': state.entity_id,
'maximum': 0,
}, True, Context(user_id=hass_admin_user.id))

state = hass.states.get('counter.test')
assert state is not None
assert state.state == '0'
assert 0 == state.attributes.get('maximum')

# disable max
await hass.services.async_call('counter', 'configure', {
'entity_id': state.entity_id,
'maximum': None,
}, True, Context(user_id=hass_admin_user.id))

state = hass.states.get('counter.test')
assert state is not None
assert state.state == '0'
assert state.attributes.get('maximum') is None

# update min
assert state.attributes.get('minimum') is None
await hass.services.async_call('counter', 'configure', {
'entity_id': state.entity_id,
'minimum': 5,
}, True, Context(user_id=hass_admin_user.id))

state = hass.states.get('counter.test')
assert state is not None
assert state.state == '5'
assert 5 == state.attributes.get('minimum')

# disable min
await hass.services.async_call('counter', 'configure', {
'entity_id': state.entity_id,
'minimum': None,
}, True, Context(user_id=hass_admin_user.id))

state = hass.states.get('counter.test')
assert state is not None
assert state.state == '5'
assert state.attributes.get('minimum') is None

# update step
assert 1 == state.attributes.get('step')
await hass.services.async_call('counter', 'configure', {
'entity_id': state.entity_id,
'step': 3,
}, True, Context(user_id=hass_admin_user.id))

state = hass.states.get('counter.test')
assert state is not None
assert state.state == '5'
assert 3 == state.attributes.get('step')

# update all
await hass.services.async_call('counter', 'configure', {
'entity_id': state.entity_id,
'step': 5,
'minimum': 0,
'maximum': 9,
}, True, Context(user_id=hass_admin_user.id))

state = hass.states.get('counter.test')
assert state is not None
assert state.state == '5'
assert 5 == state.attributes.get('step')
assert 0 == state.attributes.get('minimum')
assert 9 == state.attributes.get('maximum')