Skip to content

Commit

Permalink
Refactor
Browse files Browse the repository at this point in the history
Adds Config flow
renames `icon_tomorrow` to `icon_soon`
adds `days_as_soon` config option
  • Loading branch information
pinkywafer committed Nov 18, 2019
1 parent cb042d0 commit 05bbeb6
Show file tree
Hide file tree
Showing 10 changed files with 466 additions and 52 deletions.
32 changes: 32 additions & 0 deletions .github/workflows/main.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
name: Release

on:
release:
types: [published]

jobs:
release:
name: Prepare release
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v1
- name: "Set version numbmer"
run: |
sed -i '/VERSION = /c\VERSION = "${{ github.ref }}"' custom_components/anniversaries/const.py
sed -i 's|refs/heads/||' custom_components/anniversaries/const.py
sed -i 's|refs/tags/||' custom_components/anniversaries/const.py
# Pack the HACS dir as a zip and upload to the release
- name: ZIP Anniversaries Dir
run: |
cd /home/runner/work/Anniversaries/Anniversaries/custom_components/anniversaries
zip anniversaries.zip -r ./
- name: Upload zip to release
uses: svenstaro/upload-release-action@v1-release

with:
repo_token: ${{ secrets.GITHUB_TOKEN }}
file: /home/runner/work/Anniversaries/Anniversaries/custom_components/anniversaries/anniversaries.zip
asset_name: anniversaries.zip
tag: ${{ github.ref }}
overwrite: true
17 changes: 13 additions & 4 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,8 @@

The 'anniversaries' component is a Home Assistant custom sensor which counts down to a recurring date such as birthdays, but can be used for any anniversary which occurs annually on the same date.

**1.0.0 includes BREAKING CHANGES** read the [release notes](https://github.com/pinkywafer/anniversaries/releases/latest).

State Returned:
* The number of days remaining to the next occurance.

Expand All @@ -30,7 +32,7 @@ Attributes:
## Installation

### MANUAL INSTALLATION
1. Download the
1. Download the `anniversaries.zip` file from the
[latest release](https://github.com/pinkywafer/anniversaries/releases/latest).
2. Unpack the release and copy the `custom_components/anniversaries` directory
into the `custom_components` directory of your Home Assistant
Expand All @@ -45,10 +47,16 @@ Attributes:
4. Restart Home Assistant.

## Configuration
Anniversaries can be configured on the integrations menu or in configuration.yaml
### Config Flow
In Configuration/Integrations click on the + button, select Anniversaries and configure the options on the form.
### configuration.yaml
Add `anniversaries` sensor in your `configuration.yaml`. The following example adds two sensors - Shakespeare's birthday and wedding anniversary!
```yaml
# Example configuration.yaml entry
sensor:

anniversaries:
sensors:
- platform: anniversaries
name: Shakespeare's Birthday
date: '1564-04-23'
Expand All @@ -60,9 +68,10 @@ sensor:
### CONFIGURATION PARAMETERS
|Attribute |Optional|Description
|:----------|----------|------------
|`platform` | No |`anniversaries`
| `name` | No | Friendly name
|`date` | No | date in format `'YYYY-MM-DD'`
| `icon_normal` | Yes | Default icon **Default**: `mdi:calendar-blank`
| `icon_today` | Yes | Icon if the anniversary is today **Default**: `mdi:calendar-star`
| `icon_tomorrow` | Yes | Icon if the anniversary is tomorrow **Default**: `mdi:calendar`
| `days_as_soon` Yes | Days in advance to display the icon defined in `icon_soon` **Default**: 1
| `icon_soon` | Yes | Icon if the anniversary is tomorrow **Default**: `mdi:calendar`
| `date_format` | Yes | formats the returned date **Default**: '%Y-%m-%d' _for reference, see [http://strftime.org/](http://strftime.org/)_
42 changes: 42 additions & 0 deletions custom_components/anniversaries/.translations/en.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
{
"config": {
"title": "Anniversaries",
"step": {
"user": {
"title": "Anniversaries",
"description": "Enter the sensor name and configure sensor parameters. More info on https://github.com/pinkywafer/Anniversaries",
"data": {
"name": "Friendly name",
"date": "First Date (yyyy-mm-dd)",
"icon_normal": "Icon",
"icon_today": "Icon when anniversary is today",
"days_as_soon": "Number of Days to consider soon",
"icon_soon": "Icon when anniversary is soon",
"date_format": "Date format (see http://strftime.org/)"
}
}
},
"error": {
"invalid_date": "The date is not valid. Please enter a valid 'YYYY-MM-DD' date"
}
},
"options": {
"step": {
"init": {
"title": "Anniversaries",
"description": "Change sensor parameters. More info on https://github.com/pinkywafer/Anniversaries",
"data": {
"date": "First Date (yyyy-mm-dd)",
"icon_normal": "Icon",
"icon_today": "Icon when anniversary is today",
"days_as_soon": "Number of Days to consider soon",
"icon_soon": "Icon when anniversary is soon",
"date_format": "Date format (see http://strftime.org/)"
}
}
},
"error": {
"invalid_date": "The date is not valid. Please enter a valid 'YYYY-MM-DD' date"
}
}
}
96 changes: 95 additions & 1 deletion custom_components/anniversaries/__init__.py
Original file line number Diff line number Diff line change
@@ -1 +1,95 @@
"""Anniversaries Sensor"""
"""Anniversaries Platform"""
import os
from datetime import timedelta
import logging
from homeassistant import config_entries
import voluptuous as vol
import homeassistant.helpers.config_validation as cv
from homeassistant.helpers import discovery
from homeassistant.util import Throttle
from .sensor import anniversaries

from integrationhelper.const import CC_STARTUP_VERSION

from homeassistant.const import CONF_NAME

from .const import (
CONF_SENSORS,
CONF_ENABLED,
DEFAULT_NAME,
DOMAIN_DATA,
DOMAIN,
ISSUE_URL,
PLATFORM,
VERSION,
CONFIG_SCHEMA,
)

#MIN_TIME_BETWEEN_UPDATES = timedelta(seconds=30)

_LOGGER = logging.getLogger(__name__)


async def async_setup(hass, config):
"""Set up this component using YAML."""
if config.get(DOMAIN) is None:
# config flow setup
return True

# log startup message
_LOGGER.info(
CC_STARTUP_VERSION.format(name=DOMAIN, version=VERSION, issue_link=ISSUE_URL)
)
platform_config = config[DOMAIN].get(CONF_SENSORS, {})

# If platform is not enabled, skip.
if not platform_config:
return False

for entry in platform_config:
hass.async_create_task(
discovery.async_load_platform(hass, PLATFORM, DOMAIN, entry, config)
)
hass.async_create_task(
hass.config_entries.flow.async_init(
DOMAIN, context={"source": config_entries.SOURCE_IMPORT}, data={}
)
)
return True


async def async_setup_entry(hass, config_entry):
"""Set up this integration using UI."""
if config_entry.source == config_entries.SOURCE_IMPORT:
# set up using YAML
hass.async_create_task(hass.config_entries.async_remove(config_entry.entry_id))
return False
# log startup message
_LOGGER.info(
CC_STARTUP_VERSION.format(name=DOMAIN, version=VERSION, issue_link=ISSUE_URL)
)
config_entry.options = config_entry.data
config_entry.add_update_listener(update_listener)
# Add sensor
hass.async_add_job(
hass.config_entries.async_forward_entry_setup(config_entry, PLATFORM)
)
return True


async def async_remove_entry(hass, config_entry):
"""Handle removal of an entry."""
try:
await hass.config_entries.async_forward_entry_unload(config_entry, PLATFORM)
_LOGGER.info(
"Successfully removed sensor from the Anniversaries integration"
)
except ValueError:
pass


async def update_listener(hass, entry):
"""Update listener."""
entry.data = entry.options
await hass.config_entries.async_forward_entry_unload(entry, PLATFORM)
hass.async_add_job(hass.config_entries.async_forward_entry_setup(entry, PLATFORM))
138 changes: 138 additions & 0 deletions custom_components/anniversaries/config_flow.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,138 @@
""" config flow """
from collections import OrderedDict
import logging
from homeassistant.core import callback
import voluptuous as vol
import homeassistant.helpers.config_validation as cv
from homeassistant import config_entries
from datetime import datetime
import uuid

from .const import (
DOMAIN,
DEFAULT_ICON_NORMAL,
DEFAULT_ICON_SOON,
DEFAULT_ICON_TODAY,
DEFAULT_DATE_FORMAT,
DEFAULT_SOON,
CONF_SENSOR,
CONF_ENABLED,
CONF_ICON_NORMAL,
CONF_ICON_TODAY,
CONF_ICON_SOON,
CONF_DATE,
CONF_DATE_FORMAT,
CONF_SENSORS,
CONF_SOON,
)

from homeassistant.const import CONF_NAME

_LOGGER = logging.getLogger(__name__)


@config_entries.HANDLERS.register(DOMAIN)
class AnniversariesFlowHandler(config_entries.ConfigFlow):
CONNECTION_CLASS = config_entries.CONN_CLASS_LOCAL_POLL

def __init__(self):
self._errors = {}
self._data = {}
self._data["unique_id"] = str(uuid.uuid4())

async def async_step_user(self, user_input=None): # pylint: disable=unused-argument
self._errors = {}
if user_input is not None:
self._data.update(user_input)
if is_not_date(user_input[CONF_DATE]):
self._errors["base"] = "invalid_date"
if self._errors == {}:
return self.async_create_entry(title=self._data["name"], data=self._data)
return await self._show_user_form(user_input)

async def _show_user_form(self, user_input):
name = ""
date = ""
icon_normal = DEFAULT_ICON_NORMAL
icon_soon = DEFAULT_ICON_SOON
icon_today = DEFAULT_ICON_TODAY
date_format = DEFAULT_DATE_FORMAT
days_as_soon = DEFAULT_SOON
if user_input is not None:
if CONF_NAME in user_input:
name = user_input[CONF_NAME]
if CONF_DATE in user_input:
date = user_input[CONF_DATE]
if CONF_ICON_NORMAL in user_input:
icon_normal = user_input[CONF_ICON_NORMAL]
if CONF_ICON_SOON in user_input:
icon_soon = user_input[CONF_ICON_SOON]
if CONF_ICON_TODAY in user_input:
icon_today = user_input[CONF_ICON_TODAY]
if CONF_DATE_FORMAT in user_input:
date_format = user_input[CONF_DATE_FORMAT]
data_schema = OrderedDict()
data_schema[vol.Required(CONF_NAME, default=name)] = str
data_schema[vol.Required(CONF_DATE, default=date)] = str
data_schema[vol.Required(CONF_ICON_NORMAL, default=icon_normal)] = str
data_schema[vol.Required(CONF_ICON_TODAY, default=icon_today)] = str
data_schema[vol.Required(CONF_SOON, default=days_as_soon)] = int
data_schema[vol.Required(CONF_ICON_SOON, default=icon_soon)] = str
data_schema[vol.Required(CONF_DATE_FORMAT, default=date_format)] = str
return self.async_show_form(step_id="user", data_schema=vol.Schema(data_schema), errors=self._errors)

async def async_step_import(self, user_input): # pylint: disable=unused-argument
"""Import a config entry.
Special type of import, we're not actually going to store any data.
Instead, we're going to rely on the values that are in config file.
"""
if self._async_current_entries():
return self.async_abort(reason="single_instance_allowed")

return self.async_create_entry(title="configuration.yaml", data={})

@staticmethod
@callback
def async_get_options_flow(config_entry):
if config_entry.options.get("unique_id", None) is not None:
return OptionsFlowHandler(config_entry)
else:
return EmptyOptions(config_entry)

def is_not_date(date):
try:
datetime.strptime(date, "%Y-%m-%d")
return False
except ValueError:
return True

class OptionsFlowHandler(config_entries.OptionsFlow):
def __init__(self, config_entry):
self.config_entry = config_entry
self._data = config_entry.options

async def async_step_init(self, user_input=None):
self._errors = {}
if user_input is not None:
self._data.update(user_input)
if is_not_date(user_input[CONF_DATE]):
self._errors["base"] = "invalid_date"
if self._errors == {}:
return self.async_create_entry(title="", data=self._data)
return await self._show_init_form(user_input)

async def _show_init_form(self, user_input):
data_schema = OrderedDict()
data_schema[vol.Required(CONF_DATE, default=self.config_entry.options.get(CONF_DATE),)] = str
data_schema[vol.Required(CONF_ICON_NORMAL,default=self.config_entry.options.get(CONF_ICON_NORMAL),)] = str
data_schema[vol.Required(CONF_ICON_TODAY,default=self.config_entry.options.get(CONF_ICON_SOON),)] = str
data_schema[vol.Required(CONF_SOON,default=self.config_entry.options.get(CONF_SOON),)] = int
data_schema[vol.Required(CONF_ICON_SOON,default=self.config_entry.options.get(CONF_ICON_SOON),)] = str
data_schema[vol.Required(CONF_DATE_FORMAT,default=self.config_entry.options.get(CONF_DATE_FORMAT),)] = str
return self.async_show_form(
step_id="init", data_schema=vol.Schema(data_schema), errors=self._errors
)

class EmptyOptions(config_entries.OptionsFlow):
def __init__(self, config_entry):
self.config_entry = config_entry
Loading

0 comments on commit 05bbeb6

Please sign in to comment.