Working example of HA integration

Yes. Create a method in your api to post data like this.

def post_data(self, path: str, parameter: str, value: Any) -> bool:
    """Post api data."""
     try:
        data = {parameter, value}
        r = requests.post(f"http://{self.host}/{path}", json_data=data, timeout=10)
    except requests.exceptions.ConnectTimeout as err:
        raise APIConnectionError("Timeout connecting to api") from err
    else:
        return True

your required data format maybe different.

Thanks, do I need to use a corodinator or just call the function (method?)

I tried calling the function directly for the switch.py, I added:

from .api import post_data

    def turn_on(self, **_kwargs):
        """Turn on device"""
        api.post_data(self.parameter, "ON")

    def turn_off(self, **_kwargs):
        """Turn off device"""
        api.post_data(self.parameter, "OFF")

But this just broke the switch, so I must have it wrong?

Edit: just found the error, it cant import api.post_data

ImportError: cannot import name ‘post_data’ from ‘custom_components.msp_integration_requests_example.api’ (/config/custom_components/msp_integration_requests_example/api.py)

You cannot just call the post data directly like this when it is part of a class. Your code will be erroring for a slightly different reason in that you have imported post_data but you are calling api.post_ data instead of just post_data. But for the reason above, it would not work anyway and would error when you used your switch.

Access to your instantiated api class exists in your coordinator.

As such, you should use

self.coordinator.api.post_data(self.parameter, "ON")

Assuming your api code does not need a path attribute as in my example.

The post path is the same as the get_api eg “api”, I think that is already set from the config flow right?

I need to send as regular html post eg id=value not Json, but that shouldn’t be too hard once it works.

But so far it doesn’t like my “OFF”, wants value, thought that was my value…hmm

Errors:

File “/config/custom_components/msp_integration_requests_example/switch.py”, line 123, in turn_off
self.coordinator.api.post_data(self.parameter, “OFF”)
TypeError: API.post_data() missing 1 required positional argument: ‘value’

Edit: Figured it out, it required the path…!!! silly me

If you are not sending json then change your requests call in the api to.

r = requests.post(f"http://{self.host}/{path}", data=data, timeout=10)

Ie use the data parameter instead of the json_data parameter.

I’m sending this which seems to work let me test yours:

r = requests.post(f"http://{self.host}/{path}", f"{parameter}={value}", timeout=10)

Doesn’t like your data=data

Errors:
TypeError: a bytes-like object is required, not ‘str’

I do note using my f"{parameter}={value}" the switches works, but they change ON->OFF->ON after toggling to ON

Is there someway to force the API to read after switch toggles?
Tried this, doesn’t fix it, self.coordinator.api.get_data(“api”) the:
ON->OFF->ON after toggling to ON similar for off…

Note the actual device is not doing this, it changes to the correct position immediately, and does not change, it’s just the Home Assistant GUI.

Seems like a timing thing, if I toggle the switch just before the API reads it doesn’t do it.

Need to figure out how to get the coordinator to update its values after a change of switch positions.

Yes, you need to force the coordinator to update.

Use

self.schedule_update_ha_state()

In you turn_on/off functions on your switch

Edit:

I would normally always use the async_turn_on/off functions even if calling a sync api

async def async_turn_on(self, **kwargs):
    """Turn the device on."""
    await self.hass.async_add_executor_job(self.api.post_data, self.parameter, "ON")

and then use

await self.coordinator.async_refresh()

to force an immediate update as some of the other methods try to batch and wait a while before doing an update which gives you the switch behaviour as you see.

I tried but it doesn’t like it, no attribute for the path:

    await self.hass.async_add_executor_job(self.api.post_data, self.parameter, "OFF")
                                           ^^^^^^^^
AttributeError: 'ExampleBaseSwitch' object has no attribute 'api'

If I add the path it still wont have it.

    await self.hass.async_add_executor_job(self.api.post_data, "api", self.parameter, "OFF")
                                           ^^^^^^^^
AttributeError: 'ExampleBaseSwitch' object has no attribute 'api'

EDIT: I got it working using the coordinator, is this ok?

await self.hass.async_add_executor_job(self.coordinator.api.post_data, "api", self.parameter, "ON")

Sorry a typo on my part. Your fix is correct

Thanks for the conformation and help!

On on to my last challenge, a Number Input.

So i have created number.py and I have set it all up it display the number box in the GUI.
Just working out how to send the input_number.

Struggling to workout how I get the input_number to send to the API.

Trying doesn’t work:

    async def async_set_value(self, **kwargs):
        """Turn on device"""
        await self.hass.async_add_executor_job(self.coordinator.api.post_data, "api", self.parameter, self.input_number)
        await self.coordinator.async_refresh()

Not sure where you are getting the self.input_number from.

Try this (forgive any typos)

async def async_set_native_value(self, value: float) -> None:
    await self.hass.async_add_executor_job(self.coordinator.api.post_data, "api", self.parameter, value)
    await self.coordinator.async_refresh()
    

I was looking at some template examples and took a punt.

Yep thats got it working.

Thanks so much for all the help, can I buy you a beer or coffee if you don’t drink?
Do you have paypal donations link or similar?

No problem, glad you have it all working.

Dont have any of those as do this just for the love of it. If you want to donate to a charity, that would be nice.

Just doing some clean up and wondering why the sensors are not getting created with correct unique name, they get the name like:

sensor.battery_soh

Shouldn’t they have a unique name like below?
eg sensor.DOMAIN-Master-battery_soh

    @property
    def unique_id(self) -> str:
        """Return unique id."""
        # All entities must have a unique id.  Think carefully what you want this to be as
        # changing it later will cause HA to create new entities.
        return (f"{DOMAIN}-{self.coordinator.get_api_data_value("role")}-{self.parameter}")

You can do this how you think and is very much based on what your device/api is.

I wouldn’t necessarily include your domain name but often it makes sense to include the device name. I assume you have amended the device_info property or are at least happy with it as it is?

There is actually a nice function in HA to add the device name to your entity name by setting

_attr_has_entity_name = True

in your entity class. Ie.

class ExampleBaseSensor(CoordinatorEntity, SensorEntity):
    """Implementation of a sensor."""

    _attr_has_entity_name = True

    def __init__(self, coordinator: ExampleCoordinator, parameter_name: str) -> None:
        """Initialise sensor."""
        super().__init__(coordinator)
        self.parameter = parameter_name
        self.parameter_value = coordinator.get_api_data_value(self.parameter)

I think you may need to remove and re-add for it to recreate these entities with their new ids.

Also, you have not changed the unique id property, which is fine, but if you have multiple devices you want to add, this will not generate unique ids. Again, as per the comments, think about this and structure how you see fit.

Edit: sorry was having a thick moment and now get what youre asking. No, unique_id does not define the entity_id. I know! Unique id is a hidden id in the entity registry. Entity id is generated based on the entity name with the caveat that HA will also do some other things re generating the entity id based on the attribute mentioned above and also translations keys and adding numbers to the end if not unique etc.

Just to say, there is now an example with services.

Nice to see some more basic examples out there.

I was browsing them and have some suggestions/questions. Not sure if this is the best place to mention them, but anyways

There is this comment:

#  If you have created any custom services, they need to be removed here too.

I understood from some discussions on Discord a while back that nowadays it is preferred to just register the services when the integration gets loaded (I think it was in async_setup) and not remove them. Unfortunately I don’t recall the details of the reason for this.


A bit of a nitpick, but there are some comments in the fan.py that mention lights which is a bit confusing.


Is there a specific reason to use requests in the examples?
When using aiohttp or httpx would avoid the need for wrapping the calls in async_add_executor_job all the time.


I think you can simplify the async_unload_entry by adding the updatelistener like below so it will be unloaded with the entry without additional code in the async_unload_entry.

    config_entry.async_on_unload(config_entry.add_update_listener(_async_update_listener))
1 Like

Thanks for the feesback.

I’ll lok into the custom services comment as not aware of this. I do know however, that the example doesn’t actually unload them anyway. It was on my to fix list.

Great, will fix fan comments.

In terms of using requests, it was more about using a sync library as many libraries out there for integration to IOT are sync and it demonstrates how to use these in HA. Obviously, it doesnt actually use the requests methods and uses non blocking mock api methods so desnt need to run on a thread.

And yes, good point about unloading listeners, will amend that.

Thanks.

Can you please provide some guidance or an example that uses a serial connection to talk to the device? I have a large number of devices that communicate with serial that I have written commercial drivers for other automation platforms in the past and I would like to take a stab at porting those over to home assistant but the documentation (in general, not yours) is very limited, very confusing, seems to assume you’re a 12th tier python ninja (sorry, I’m on programming language number 35 or 40 with python, the lines get blurry and its hard to be that in depth). I have serial controlled PDUs for example, that seems like an incredibly easy integration to write. I have a python class built that will authenticate to the PDUs and grab sensor data and control the state of the outlets. I was expecting it to be trivial to integrate but I don’t even know where to get started. I have written it with queues and asyncio (aioserial) so its fully non blocking but I don’t know where to insert my event loop. Looking at the main docs it seems to indicate that IO should only occur in the update function. Specifically it says “All data has to be fetched inside the update method and cached on the entity”, does that mean I launch a loop and serial read/write coroutines from within the update? I am very confused. I have only found two serial integrations and I’m struggling to reverse engineer them as they are written completely differently from each other and from the way you have written these examples. They also seem to require serial to ethernet adapters. It kind of astounds me that there are so few examples given the massive number of serial controlled av devices out there. A fully commented working example would go miles to help me out. I can provide my python code but didn’t want to dump a big chunk of code that’s off topic into the thread.

Thanks

Sure. Let me look at this for you. If you could post a link to your api that would really help (as guessing your api is handling all serial io and this is more about using that in HA?).

Edit: if its just the code, PM me it.