Skip to content

Add Dynamic IP Update Logic for openHASP Plate#160

Open
wjnelson78 wants to merge 2 commits intoHASwitchPlate:mainfrom
wjnelson78:main
Open

Add Dynamic IP Update Logic for openHASP Plate#160
wjnelson78 wants to merge 2 commits intoHASwitchPlate:mainfrom
wjnelson78:main

Conversation

@wjnelson78
Copy link
Contributor

Pull Request Description:

This PR enables the openHASP integration to detect and update the device’s IP address whenever the plate reports a new one in its statusupdate message. Specifically:

  1. Checks for "ip" in the MQTT payload

• If found, stores the new IP in Home Assistant’s config entry data (async_update_entry)

• Also updates the plate’s device registry entry (async_update_device), so the device “Link” in HA points to the current IP address

  1. Prevents stale IP references

• Ensures the plate entity, device registry link, and UI references are always in sync with the actual device IP

These changes improve reliability in environments where the plate’s IP might change over time (e.g., DHCP lease expirations, new IP assignments), eliminating the need for manual URL updates.

…r_job

In `__init__.py`, direct open() calls were being made on the Home Assistant event loop, leading to “Detected blocking call to open()” warnings and potential performance issues.

To address this:
- Moved the reading of `pages_schema.json` out of the constructor and into `async_added_to_hass`, wrapping the file read with `hass.async_add_executor_job()`.
- Introduced small synchronous helper functions (`_sync_read_json`, `_sync_read_text`, etc.) for file I/O. These are called inside `async_add_executor_job()`, preventing the main loop from blocking.
- Updated `async_load_page` to use the same pattern for JSON or text lines, validating JSON data against the pre-loaded schema.
- Ensures compliance with Home Assistant’s async guidelines and avoids blocking the event loop with disk I/O.
- Improves overall responsiveness and prevents related warnings in the HA logs.
Copy link
Collaborator

@dgomes dgomes left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

lets leave .gitignore alone ;)

# Define minimal schemas so that each service is recognized as an entity service
WAKEUP_SCHEMA = cv.make_entity_service_schema({})
PAGE_NEXT_SCHEMA = cv.make_entity_service_schema({})
PAGE_PREV_SCHEMA = cv.make_entity_service_schema({})
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this is already in a different PR

# If "ip" is reported, update config entry + device registry
if "ip" in message:
self._statusupdate["ip"] = message["ip"]
new_url = f"http://{message['ip']}/"
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

we should use a function to create the url and not write the url template in 2 different locations

if new_url != old_url:
_LOGGER.debug("IP changed to %s, updating config entry URL...", new_url)
new_data = dict(self._entry.data)
new_data[DISCOVERED_URL] = new_url
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

why not edit self._entry.data directly ?

@wjnelson78
Copy link
Contributor Author

Hi @dgomes, apologies for the delayed response! I had applied the fix to my local codebase and didn't notice the review feedback until recently.

I've addressed your feedback in a new, cleaner branch:

  1. Removed duplicate entity service schema changes - Those are handled separately in PR Use proper entity service schemas & replace async_forward_entry_setup with async_forward_entry_setups #159
  2. Added make_plate_url() helper function - URL construction is now in a single location
  3. Simplified config entry update - Using dict spread syntax {**self._entry.data, DISCOVERED_URL: new_url} instead of creating a copy and modifying it

The updated changes are minimal and focused solely on the dynamic IP update feature. Would you prefer I close this PR and open a new one from the fix/dynamic-ip-update branch, or can I force-push to update this PR?

Thanks for your patience!

@fvanroie
Copy link
Collaborator

This branch has conflicts that must be resolved, please review and dix this PR.
If it is reviewed in a timely fashion, I can add it to the pending 0.7.8 release.

Copy link

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR's title and description claim to add dynamic IP update logic for openHASP plates, but the actual code changes do NOT implement this feature. The PR description mentions checking for "ip" in MQTT payload, calling async_update_entry, and async_update_device, but none of this logic is present in the diff.

Instead, this PR primarily contains code refactoring and cleanup work:

  • Code formatting and comment improvements
  • Migration from Script to async_call_from_config for event handling
  • Removal of the http_proxy parameter from push_image service
  • Refactored file I/O operations to use dedicated helper functions
  • Simplified hash generation for image IDs
Comments suppressed due to low confidence (1)

custom_components/openhasp/init.py:159

  • The removal of ATTR_PROXY (http_proxy) parameter from PUSH_IMAGE_SCHEMA is a breaking API change. Users who were passing the http_proxy parameter to the push_image service will now get schema validation errors. Consider deprecating this parameter first with a warning, or document this as a breaking change in release notes.
PUSH_IMAGE_SCHEMA = vol.Schema(
    {
        vol.Required(ATTR_IMAGE): vol.Any(cv.url, cv.isfile),
        vol.Required(ATTR_OBJECT): hasp_object,
        vol.Optional(ATTR_WIDTH): cv.positive_int,
        vol.Optional(ATTR_HEIGHT): cv.positive_int,
        vol.Optional(ATTR_FORCE_FITSCREEN): cv.boolean,
    },

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment on lines +789 to +792
for service in self.event_services[event]:
await async_call_from_config(
self.hass, service, validate_config=False, variables=message
)
Copy link

Copilot AI Dec 18, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The change from Script to async_call_from_config changes the event handling behavior. The Script class provided run context management and proper variable scoping. With async_call_from_config, there's no explicit context passed (the Context import was removed), which may affect service call tracing and automation debugging in Home Assistant. Additionally, validate_config=False bypasses safety checks that could catch configuration errors early.

Copilot uses AI. Check for mistakes.
Comment on lines +279 to +280
for domain in PLATFORMS:
hass.async_create_task(hass.config_entries.async_forward_entry_setup(entry, domain))
Copy link

Copilot AI Dec 18, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Using async_forward_entry_setup in a loop with async_create_task can lead to race conditions and is deprecated. The modern Home Assistant best practice is to use async_forward_entry_setups which properly handles multiple platforms concurrently and awaits all setups to complete before returning.

Suggested change
for domain in PLATFORMS:
hass.async_create_task(hass.config_entries.async_forward_entry_setup(entry, domain))
await hass.config_entries.async_forward_entry_setups(entry, PLATFORMS)

Copilot uses AI. Check for mistakes.
).hexdigest()
async def async_push_image(self, image, obj, width=None, height=None, fitscreen=False):
"""Push an image to the plate object."""
image_id = hashlib.md5(image.encode("utf-8")).hexdigest()
Copy link

Copilot AI Dec 18, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The image_id hash calculation has been weakened. Previously it included both the image URL and the plate name in the hash (making each plate have unique image IDs), but now it only hashes the image URL. This means if multiple plates use the same image URL, they will share the same cached image in hass.data[DOMAIN][DATA_IMAGES], which could cause issues if plates have different display resolutions or if the image gets removed from the cache while still in use by another plate.

Suggested change
image_id = hashlib.md5(image.encode("utf-8")).hexdigest()
image_key = f"{self._topic}|{image}"
image_id = hashlib.md5(image_key.encode("utf-8")).hexdigest()

Copilot uses AI. Check for mistakes.
_LOGGER.error(
"Can't change to %s, available pages are 1 to %s", page, num_pages
)
if page <= 0 or page > num_pages:
Copy link

Copilot AI Dec 18, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Missing validation of page parameter type. The removed type checks ensured page was an integer and num_pages was an integer before performing comparisons. Now if page is passed as a string or other type, the comparison 'page <= 0 or page > num_pages' may not work correctly or could raise a TypeError at runtime.

Copilot uses AI. Check for mistakes.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

5 participants