Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
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
15 changes: 15 additions & 0 deletions .github/workflows/hassfest.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
name: Validate with hassfest

on:
push:
pull_request:
schedule:
- cron: "0 1 * * SUN"
workflow_dispatch:

jobs:
validate:
runs-on: "ubuntu-latest"
steps:
- uses: "actions/checkout@v6"
- uses: home-assistant/actions/hassfest@master
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ All communication is via WebSockets. I've managed to reliably automate:
Any feedback is welcome, this is my first integration with HomeAssistant.

# How
When adding the integration, you will need to know your Jung Home gateway address and get an API token from https://<JUNGHOME>/api/junghome/swagger/ (using User Registration action).
When adding the integration, you will need to know your Jung Home gateway address and get an API token from `https://<JUNGHOME>/api/junghome/swagger/` (using User Registration action).

You will need to confirm the token in the Jung Home mobile app.

Expand Down
34 changes: 23 additions & 11 deletions custom_components/junghome/__init__.py
Original file line number Diff line number Diff line change
@@ -1,13 +1,22 @@
"""
Junghome integration package.

Provides integration setup and entry load/unload helpers.
"""

import logging

from homeassistant.config_entries import ConfigEntry
from homeassistant.core import HomeAssistant
from homeassistant.helpers.typing import ConfigType
from .coordinator import JungHomeDataUpdateCoordinator

from .const import DOMAIN
from .coordinator import JungHomeDataUpdateCoordinator

_LOGGER = logging.getLogger(__name__)

async def async_setup(hass: HomeAssistant, config: ConfigType) -> bool:

async def async_setup(_hass: HomeAssistant, _config: ConfigType) -> bool:
"""Set up the Jung Home integration."""
return True

Expand All @@ -31,7 +40,10 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
await coordinator.start()

# Forward the setup to the appropriate platforms
await hass.config_entries.async_forward_entry_setups(entry, ["light", "switch", "sensor", "event"])
await hass.config_entries.async_forward_entry_setups(
entry,
["light", "switch", "sensor", "event"],
)

return True

Expand All @@ -40,15 +52,15 @@ async def async_unload_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
if DOMAIN in hass.data and entry.entry_id in hass.data[DOMAIN]:
del hass.data[DOMAIN][entry.entry_id]

# Unload platforms
unload_ok = await hass.config_entries.async_forward_entry_unload(entry, "light")
unload_ok = unload_ok and await hass.config_entries.async_forward_entry_unload(entry, "switch")
unload_ok = unload_ok and await hass.config_entries.async_forward_entry_unload(entry, "sensor")
unload_ok = unload_ok and await hass.config_entries.async_forward_entry_unload(entry, "event")

return unload_ok
# Unload platforms sequentially and return combined result
return (
await hass.config_entries.async_forward_entry_unload(entry, "light")
and await hass.config_entries.async_forward_entry_unload(entry, "switch")
and await hass.config_entries.async_forward_entry_unload(entry, "sensor")
and await hass.config_entries.async_forward_entry_unload(entry, "event")
)

async def async_reload_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
"""Reload a config entry."""
await async_unload_entry(hass, entry)
return await async_setup_entry(hass, entry)
return await async_setup_entry(hass, entry)
53 changes: 42 additions & 11 deletions custom_components/junghome/button.py
Original file line number Diff line number Diff line change
@@ -1,35 +1,66 @@
"""
Button event forwarding for the Jung Home integration.

This module listens for internal dispatcher events and forwards
stateless button presses to the Home Assistant event bus.
"""

from __future__ import annotations

import logging
from typing import TYPE_CHECKING, Any

from homeassistant.helpers.dispatcher import async_dispatcher_connect
from .const import DOMAIN

if TYPE_CHECKING:
from collections.abc import Callable, Mapping

from homeassistant.config_entries import ConfigEntry
from homeassistant.core import HomeAssistant

_LOGGER = logging.getLogger(__name__)

SIGNAL_JUNG_BUTTON_EVENT = "jung_home_button_event"
SIGNAL_JUNG_BUTTON_EVENT: str = "jung_home_button_event"

async def async_setup_entry(hass, entry, async_add_entities):

async def async_setup_entry(
hass: HomeAssistant,
entry: ConfigEntry,
_async_add_entities: Callable[..., None] | None = None,
) -> None:
"""Set up Jung Home button event forwarding (no entities)."""
def handle_button_event(message):
_LOGGER.debug("[JUNGHOME] handle_button_event called with message: %s", message)

def handle_button_event(message: Mapping[str, Any]) -> None:
"""Handle a dispatcher message and fire a HA event on button press."""
_LOGGER.debug("handle_button_event called with message: %s", message)
data = message.get("data")
if not data:
return

device_id = data.get("id")
values = data.get("values", [])

# Look for stateless press requests
for value in values:
if value["key"] in {"up_request", "down_request", "trigger_request"} and value["value"] == "1":
_LOGGER.debug("Stateless button event detected for device %s", device_id)
if (
value.get("key")
in {"up_request", "down_request", "trigger_request"}
and value.get("value") == "1"
):
_LOGGER.debug(
"Stateless button event detected for device %s", device_id
)
hass.bus.fire(
"jung_home_button_press",
{
"device_id": device_id,
"datapoint_type": data.get("type"),
}
},
)
break

entry.async_on_unload(
async_dispatcher_connect(hass, SIGNAL_JUNG_BUTTON_EVENT, handle_button_event)
)
# No entities to add
return

# Remove button.py as event.py now handles all button logic
# No entities to add; event forwarding only.
43 changes: 29 additions & 14 deletions custom_components/junghome/config_flow.py
Original file line number Diff line number Diff line change
@@ -1,13 +1,23 @@
from homeassistant import config_entries
"""Config flow for the Junghome integration."""

from __future__ import annotations

from typing import Any

import voluptuous as vol
from .const import DOMAIN # Define DOMAIN in const.py (e.g., DOMAIN = "junghome")
from homeassistant.core import HomeAssistant
from homeassistant import config_entries

from .const import DOMAIN

# Expected token length used for simple validation
TOKEN_EXPECTED_LENGTH = 95
TOKEN_FIELD = "token"

# Example validation schema
STEP_USER_DATA_SCHEMA = vol.Schema(
{
vol.Required("host"): str,
vol.Required("token"): str,
vol.Required(TOKEN_FIELD): str,
}
)

Expand All @@ -17,37 +27,42 @@ class JungHomeConfigFlow(config_entries.ConfigFlow, domain=DOMAIN):
VERSION = 1
CONNECTION_CLASS = config_entries.CONN_CLASS_LOCAL_PUSH

async def async_step_user(self, user_input=None):
async def async_step_user(
self, user_input: dict[str, Any] | None = None
) -> config_entries.FlowResult:
"""Handle the initial step."""
errors = {}
errors: dict[str, str] = {}

if user_input is not None:
# Validate user input here
host = user_input["host"]
token = user_input["token"]
token_value = user_input[TOKEN_FIELD]

if not self._validate_host(host):
errors["host"] = "invalid_host"

if not self._validate_token(token):
errors["token"] = "invalid_token"
if not self._validate_token(token_value):
errors[TOKEN_FIELD] = "invalid_token"

if not errors:
# Save configuration if validation passes
return self.async_create_entry(title="Jung Home", data=user_input)
return self.async_create_entry(
title="Jung Home",
data={"host": host, TOKEN_FIELD: token_value},
)

return self.async_show_form(
step_id="user",
data_schema=STEP_USER_DATA_SCHEMA,
errors=errors,
)

def _validate_host(self, host):
def _validate_host(self, host: str) -> bool:
"""Validate the host (e.g., check format or connectivity)."""
# Example: Ensure the host is a valid IP address or hostname
return isinstance(host, str) and len(host) > 0

def _validate_token(self, token):
def _validate_token(self, token: str) -> bool:
"""Validate the API token."""
# Example: Ensure the token has a minimum length
return isinstance(token, str) and len(token) == 95
# Example: Check token is a string and matches expected length.
return isinstance(token, str) and len(token) == TOKEN_EXPECTED_LENGTH
2 changes: 2 additions & 0 deletions custom_components/junghome/const.py
Original file line number Diff line number Diff line change
@@ -1 +1,3 @@
"""Integration constants for the Junghome integration."""

DOMAIN = "junghome"
Loading
Loading