Skip to content

Commit

Permalink
WIP ⭐
Browse files Browse the repository at this point in the history
  • Loading branch information
Florian Wartner committed Jul 23, 2024
1 parent b33a1cd commit 9f893a1
Show file tree
Hide file tree
Showing 4 changed files with 126 additions and 25 deletions.
13 changes: 10 additions & 3 deletions custom_components/openwakeword-installer/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,14 +18,16 @@ async def async_setup(hass: HomeAssistant, config: ConfigType) -> bool:
"""Set up the Wakeword Installer component."""
await hass.async_add_executor_job(create_directory, '/share/openwakeword')

# Register the service
async def handle_update_wakewords_service(call: ServiceCall):
repositories = call.data.get(CONF_REPOSITORIES, [])
for repository in repositories:
repository_url = repository.get(CONF_REPOSITORY_URL)
folder_path = repository.get(CONF_FOLDER_PATH, '')
await hass.async_add_executor_job(update_wakewords, repository_url, folder_path)
_LOGGER.info(f"Wakewords updated from {repository_url} (folder: {folder_path})")
success = await hass.async_add_executor_job(update_wakewords, repository_url, folder_path)
if success:
_LOGGER.info(f"Wakewords updated from {repository_url} (folder: {folder_path})")
else:
_LOGGER.error(f"Failed to update wakewords from {repository_url} (folder: {folder_path})")

hass.services.async_register(DOMAIN, "update_wakewords", handle_update_wakewords_service)

Expand Down Expand Up @@ -62,6 +64,11 @@ async def async_unload_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:

return True

async def async_reload_entry(hass: HomeAssistant, entry: ConfigEntry) -> None:
"""Reload config entry."""
await async_unload_entry(hass, entry)
await async_setup_entry(hass, entry)

def create_directory(path: str):
"""Create directory if it does not exist."""
if not os.path.exists(path):
Expand Down
38 changes: 31 additions & 7 deletions custom_components/openwakeword-installer/config_flow.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,20 +2,24 @@
from homeassistant import config_entries
from homeassistant.core import callback

from .const import DOMAIN, CONF_REPOSITORY_URL, CONF_FOLDER_PATH, CONF_SCAN_INTERVAL
from .const import DOMAIN, CONF_REPOSITORIES, CONF_REPOSITORY_URL, CONF_FOLDER_PATH, CONF_SCAN_INTERVAL

@config_entries.HANDLERS.register(DOMAIN)
class WakewordInstallerConfigFlow(config_entries.ConfigFlow, domain=DOMAIN):
"""Handle a config flow for Wakeword Installer."""

VERSION = 1
CONNECTION_CLASS = config_entries.CONN_CLASS_CLOUD_POLL

def __init__(self):
"""Initialize."""
self.repositories = []

async def async_step_user(self, user_input=None):
"""Handle a flow initialized by the user."""
errors = {}
if user_input is not None:
return self.async_create_entry(title="Wakeword Installer", data=user_input)
self.repositories.append(user_input)
return await self.async_step_add_another()

data_schema = vol.Schema({
vol.Required(CONF_REPOSITORY_URL): str,
Expand All @@ -27,6 +31,20 @@ async def async_step_user(self, user_input=None):
step_id="user", data_schema=data_schema, errors=errors
)

async def async_step_add_another(self, user_input=None):
"""Handle adding another repository."""
if user_input is not None:
if user_input["add_another"]:
return await self.async_step_user()
return self.async_create_entry(title="Wakeword Installer", data={CONF_REPOSITORIES: self.repositories})

return self.async_show_form(
step_id="add_another",
data_schema=vol.Schema({
vol.Required("add_another"): bool,
}),
)

@staticmethod
@callback
def async_get_options_flow(config_entry):
Expand All @@ -43,11 +61,17 @@ async def async_step_init(self, user_input=None):
if user_input is not None:
return self.async_create_entry(title="", data=user_input)

repositories = self.config_entry.data.get(CONF_REPOSITORIES, [])
options = {}

for i, repo in enumerate(repositories):
options[f"{CONF_REPOSITORY_URL}_{i}"] = repo.get(CONF_REPOSITORY_URL, "")
options[f"{CONF_FOLDER_PATH}_{i}"] = repo.get(CONF_FOLDER_PATH, "")
options[f"{CONF_SCAN_INTERVAL}_{i}"] = repo.get(CONF_SCAN_INTERVAL, 3600)

data_schema = vol.Schema({
vol.Optional(CONF_REPOSITORY_URL, default=self.config_entry.options.get(CONF_REPOSITORY_URL, self.config_entry.data.get(CONF_REPOSITORY_URL, ''))): str,
vol.Optional(CONF_FOLDER_PATH, default=self.config_entry.options.get(CONF_FOLDER_PATH, self.config_entry.data.get(CONF_FOLDER_PATH, ''))): str,
vol.Optional(CONF_SCAN_INTERVAL, default=3600): int,
vol.Optional("update_wakewords", default=False): bool,
vol.Optional(key): (str if "url" in key or "path" in key else int)
for key in options
})

return self.async_show_form(step_id="init", data_schema=data_schema)
96 changes: 81 additions & 15 deletions custom_components/openwakeword-installer/sensor.py
Original file line number Diff line number Diff line change
@@ -1,28 +1,69 @@
import logging
from homeassistant.helpers.entity import Entity
from homeassistant.helpers.update_coordinator import DataUpdateCoordinator, UpdateFailed
from homeassistant.exceptions import ConfigEntryNotReady
from datetime import timedelta

from .const import DOMAIN, CONF_REPOSITORY_URL, CONF_FOLDER_PATH, CONF_SCAN_INTERVAL
from .const import DOMAIN, CONF_REPOSITORIES, CONF_REPOSITORY_URL, CONF_FOLDER_PATH, CONF_SCAN_INTERVAL
from .update import update_wakewords

_LOGGER = logging.getLogger(__name__)

async def async_setup_entry(hass, entry, async_add_entities):
"""Set up the Wakeword Installer sensor."""
repository_url = entry.data.get(CONF_REPOSITORY_URL)
folder_path = entry.data.get(CONF_FOLDER_PATH)
scan_interval = entry.data.get(CONF_SCAN_INTERVAL)
repositories = entry.data.get(CONF_REPOSITORIES, [])

async_add_entities([WakewordUpdateStatusSensor(repository_url, folder_path, scan_interval)], True)
coordinator = WakewordUpdateCoordinator(hass, repositories)

await coordinator.async_refresh()

if not coordinator.last_update_success:
raise ConfigEntryNotReady

async_add_entities(
[WakewordUpdateStatusSensor(coordinator, repo) for repo in repositories],
True
)

class WakewordUpdateCoordinator(DataUpdateCoordinator):
"""Class to manage fetching Wakeword update status."""

def __init__(self, hass, repositories):
"""Initialize."""
self.repositories = repositories
update_interval = min(repo.get(CONF_SCAN_INTERVAL, 3600) for repo in repositories)

super().__init__(
hass,
_LOGGER,
name="Wakeword Update Status",
update_interval=timedelta(seconds=update_interval),
)

async def _async_update_data(self):
"""Fetch data from API endpoint."""
results = {}
for repo in self.repositories:
try:
success = await self.hass.async_add_executor_job(
update_wakewords,
repo[CONF_REPOSITORY_URL],
repo.get(CONF_FOLDER_PATH, '')
)
results[repo[CONF_REPOSITORY_URL]] = "Up to date" if success else "Update failed"
except Exception as err:
raise UpdateFailed(f"Error communicating with API: {err}")
return results

class WakewordUpdateStatusSensor(Entity):
"""Representation of a Wakeword Update Status sensor."""

def __init__(self, repository_url, folder_path, scan_interval):
def __init__(self, coordinator, repository):
"""Initialize the sensor."""
self._state = None
self._repository_url = repository_url
self._folder_path = folder_path
self._scan_interval = scan_interval
self._name = "Wakeword Installer Update Status"
self.coordinator = coordinator
self._repository_url = repository[CONF_REPOSITORY_URL]
self._folder_path = repository.get(CONF_FOLDER_PATH, '')
self._name = f"Wakeword Update Status: {self._repository_url}"

@property
def name(self):
Expand All @@ -32,9 +73,34 @@ def name(self):
@property
def state(self):
"""Return the state of the sensor."""
return self._state
return self.coordinator.data.get(self._repository_url, "Unknown")

@property
def extra_state_attributes(self):
"""Return the state attributes."""
return {
"repository_url": self._repository_url,
"folder_path": self._folder_path,
}

@property
def should_poll(self):
"""No need to poll. Coordinator notifies entity of updates."""
return False

@property
def available(self):
"""Return if entity is available."""
return self.coordinator.last_update_success

async def async_added_to_hass(self):
"""When entity is added to hass."""
self.async_on_remove(
self.coordinator.async_add_listener(
self.async_write_ha_state
)
)

async def async_update(self):
"""Fetch new state data for the sensor."""
# Here you would implement the logic to check for updates
self._state = "idle"
"""Update the entity."""
await self.coordinator.async_request_refresh()
4 changes: 4 additions & 0 deletions custom_components/openwakeword-installer/update.py
Original file line number Diff line number Diff line change
Expand Up @@ -30,8 +30,11 @@ def update_wakewords(repository_url, folder='', base_directory='/share/openwakew
target_path = os.path.join(target_repo_dir, folder) if folder else target_repo_dir
process_files(target_path, base_directory)

return True # Return True if update is successful

except Exception as e:
_LOGGER.error(f"Error updating wakewords from repository {repository_url} to {target_repo_dir}: {e}")
return False # Return False if update fails

def process_files(source_directory, target_directory):
"""Process .tflite files from the source directory to the target directory."""
Expand All @@ -47,3 +50,4 @@ def process_files(source_directory, target_directory):
_LOGGER.info(f"Copied {file} to {target_directory}")
except Exception as e:
_LOGGER.error(f"Error processing files from {source_directory} to {target_directory}: {e}")
raise # Re-raise the exception to be caught by the calling function

0 comments on commit 9f893a1

Please sign in to comment.