Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Phasechange #23

Merged
merged 5 commits into from Jun 3, 2023
Merged

Phasechange #23

merged 5 commits into from Jun 3, 2023

Conversation

gustafssone
Copy link

Added service calls.
Not at all familiar with python so this is a trial and error, might need help with the correct syntax.
Will the available_current arguments pass correct in json?
I.e. will:
return await acc.map[installation_id].limit_amps(available_current)
pass as "AvailableCurrent": 16 or similar?
Also, not sure if the strings are correct, I wonder if it is case sensitive, availableCurrent instead of AvailableCurrent

_LOGGER.debug("update current single phase")
installation_id = service_call.data["installation_id"]
available_current = service_call.data["available_current"]
return await acc.map[installation_id].limit_amps(available_current)
Copy link
Collaborator

Choose a reason for hiding this comment

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

limit_amps uses keyword arguments so the correct way to call this would be.
limit_amps(**{"keyword": value}) or limit_amps(keyword=value, keyword2=value)

@gustafssone
Copy link
Author

Thanks for the feedback. Does it look better now?

I'm not sure how to read the loop that you are doing:
kwargs.pop("availableCurrent")
at

kwargs.pop("AvailableCurrent")

Will I able to set the three phases differently, e.g 16, 12, 4?

Because as mentioned, the API says "Provide either AvailableCurrent or AvailableCurrentPhase1, AvailableCurrentPhase2 and AvailableCurrentPhase3." @ https://api.zaptec.com/help/index.html#/Installation/post_api_installation__id__update

@Hellowlol
Copy link
Collaborator

I'm not sure how to read the loop that you are doing: kwargs.pop("availableCurrent") at

kwargs.pop("AvailableCurrent")

We are checking what keyword arguments that is used. If if one of AvailableCurrentPhase1, AvailableCurrentPhase2, AvailableCurrentPhase3 and availableCurrent. We remove availableCurrent.

So to all 3 phase: limit_amps(availableCurrent=20)
To use 1 phase: limit_amps(AvailableCurrentPhase3=20) # Only draw power from P3.

Will I able to set the three phases differently, e.g 16, 12, 4?
Because as mentioned, the API says "Provide either AvailableCurrent or AvailableCurrentPhase1, AvailableCurrentPhase2 and AvailableCurrentPhase3." @ https://api.zaptec.com/help/index.html#/Installation/post_api_installation__id__update

@gustafssone
Copy link
Author

Ah ok, thanks.
So I tried it out, but got the following error, both on update single or 3 phases, any ideas?


Logger: homeassistant.helpers.script.websocket_api_script
Source: custom_components/zaptec/api.py:390
Integration: zaptec (documentation)
First occurred: 8:44:30 AM (1 occurrences)
Last logged: 8:44:30 AM

websocket_api script: Error executing script. Unexpected error for call_service at pos 1: name 'partial' is not defined
Traceback (most recent call last):
  File "/usr/src/homeassistant/homeassistant/helpers/script.py", line 447, in _async_step
    await getattr(self, handler)()
  File "/usr/src/homeassistant/homeassistant/helpers/script.py", line 680, in _async_call_service_step
    await service_task
  File "/usr/src/homeassistant/homeassistant/core.py", line 1704, in async_call
    task.result()
  File "/usr/src/homeassistant/homeassistant/core.py", line 1741, in _execute_service
    await cast(Callable[[ServiceCall], Awaitable[None]], handler.job.target)(
  File "/config/custom_components/zaptec/services.py", line 72, in service_handle_update_installation_3
    return await acc.map[installation_id].limit_amps(availableCurrentPhase1=available_current_phase1, availableCurrentPhase2=available_current_phase2, availableCurrentPhase3=available_current_phase3)
  File "/config/custom_components/zaptec/api.py", line 189, in limit_amps
    return await self._account._request(
  File "/config/custom_components/zaptec/api.py", line 390, in _request
    call = partial(call, data=data)
NameError: name 'partial' is not defined

And:

Source: custom_components/zaptec/api.py:390
Integration: Home Assistant WebSocket API (documentation, issues)
First occurred: 8:44:30 AM (1 occurrences)
Last logged: 8:44:30 AM

[140099440827648] Error handling message: Unknown error (unknown_error)
Traceback (most recent call last):
  File "/usr/src/homeassistant/homeassistant/components/websocket_api/decorators.py", line 27, in _handle_async_response
    await func(hass, connection, msg)
  File "/usr/src/homeassistant/homeassistant/components/websocket_api/commands.py", line 636, in handle_execute_script
    await script_obj.async_run(msg.get("variables"), context=context)
  File "/usr/src/homeassistant/homeassistant/helpers/script.py", line 1513, in async_run
    await asyncio.shield(run.async_run())
  File "/usr/src/homeassistant/homeassistant/helpers/script.py", line 405, in async_run
    await self._async_step(log_exceptions=False)
  File "/usr/src/homeassistant/homeassistant/helpers/script.py", line 449, in _async_step
    self._handle_exception(
  File "/usr/src/homeassistant/homeassistant/helpers/script.py", line 472, in _handle_exception
    raise exception
  File "/usr/src/homeassistant/homeassistant/helpers/script.py", line 447, in _async_step
    await getattr(self, handler)()
  File "/usr/src/homeassistant/homeassistant/helpers/script.py", line 680, in _async_call_service_step
    await service_task
  File "/usr/src/homeassistant/homeassistant/core.py", line 1704, in async_call
    task.result()
  File "/usr/src/homeassistant/homeassistant/core.py", line 1741, in _execute_service
    await cast(Callable[[ServiceCall], Awaitable[None]], handler.job.target)(
  File "/config/custom_components/zaptec/services.py", line 72, in service_handle_update_installation_3
    return await acc.map[installation_id].limit_amps(availableCurrentPhase1=available_current_phase1, availableCurrentPhase2=available_current_phase2, availableCurrentPhase3=available_current_phase3)
  File "/config/custom_components/zaptec/api.py", line 189, in limit_amps
    return await self._account._request(
  File "/config/custom_components/zaptec/api.py", line 390, in _request
    call = partial(call, data=data)
NameError: name 'partial' is not defined

@gustafssone
Copy link
Author

Other service calls (but those are two the charger, not installation) seems to be working. Could it have to do with that?

@Hellowlol
Copy link
Collaborator

Its because partial isn’t defined. I have forgotten to add the import:

from functools import partial

@gustafssone
Copy link
Author

Seems that, that did the trick. No errors now, but it is not working. I checked through the Swagger UI at https://api.zaptec.com/help/index.html#/Installation/get_api_installation__id_ and it appears to be updated when I use it there. But something in the code is off.
How can I verify what is being sent through debugging?
E.g.:
curl -X POST "https://api.zaptec.com/api/installation/123/update" -H "accept: /" -H "Authorization: Bearer 123" -H "Content-Type: application/json-patch+json" -d "{"availableCurrentPhase1":16,"availableCurrentPhase2":16,"availableCurrentPhase3":16}"

@elden1337
Copy link

Hi, will this PR be merged? I've started to implement Zaptec as chargertype in my peak-avoiding integration Peaqev and even if i can do it without letting the charger update its amps, the experience is greatly improved if the possibility exists.

Also, I see that you've made two separate calls for one and three-phase. Shouldn't it just be possible to have one call as user and not having to know what it currently is charging at? Given the comment here stating Use availableCurrent for 3phase I think it might?

@Hellowlol
Copy link
Collaborator

Not in its current state no, i dont want to have two different api service calls. I cant remember what was needed but i think its just to register the service call and its ready as it is. I need to test it

@gustafssone
Copy link
Author

I don't have the technical skills to do development. Needs some basics in python - so I am pursuing a python course currently so I hopefully can continue it. I intend to solve the amps feature.

@elden1337
Copy link

Understood. I'll implement it without amp-changes for now then (and if i'm not mistaken you are contributor on that branch of mine as well) and upgrade later.
Gl with the Python course!

@Hellowlol
Copy link
Collaborator

@gustafssone I think you have done a really good job! . Basically, you just remove one of the service calls. If you want to finish it, I can do a quick code review so you can get it merged?

@elden1337
Copy link

Is this pr-change in the planning being released soon?

@Hellowlol
Copy link
Collaborator

@elden1337 not until the pr is updated. Logging needs to be cleaned up, i want one service call and somebody needs to test it. (I dont have a zaptec charger)

@elden1337
Copy link

Got it! I dont have one myself either. But maybe I can help pointing a user in this direction if @gustafssone isnt available.

@dsmagghe
Copy link

I have a couple of them so i'm willing to test.

@elden1337
Copy link

Sent it out among my peaqev-users. Perhaps someone volunteers to help testing it. We'll see.

@gustafssone
Copy link
Author

So much to do, so little time. If you have any testers/developers please go forward with it.

@gustafssone
Copy link
Author

Hello again, I got around to try to fix this. Perhaps @Hellowlol have some feedback? Before changing no of servicecalls and logging, need to get the thing working at all, and seems to be communication issues towards their API. Verified that it still works with their own Zaptec.ZapCloud.WebAPI which it does.
Anyhow, without "Content-type":"application/json" I got http code 415, Unsupported Media Type when sending this type of data. I believe this is the only service call that I make that use a body, all the post ones (send command) just use the URL.
Adding the "Content-type" here https://github.com/gustafssone/zaptec/blob/2c40dc127b4390f4a03371583d0240d90ad576ea/custom_components/zaptec/api.py#L386 , I get error code 400 instead, "One or more validation errors occurred".
I'm guessing formatting of body/data, but not sure how to validate. Tried
call = partial(call, data=json.dumps(data))
here: https://github.com/gustafssone/zaptec/blob/2c40dc127b4390f4a03371583d0240d90ad576ea/custom_components/zaptec/api.py#L396 to no result. Some googling indicated that might be the cause. Using the JSON dump at https://github.com/gustafssone/zaptec/blob/2c40dc127b4390f4a03371583d0240d90ad576ea/custom_components/zaptec/api.py#L408 to get the response. So some progress, I guess it's just not sure how to receive the data/body.

Some debug logging and responses:
2023-03-30 22:32:37.294 DEBUG (MainThread) [custom_components.zaptec.api] calling https://api.zaptec.com/api/installation/xx/update 2023-03-30 22:32:37.294 DEBUG (MainThread) [custom_components.zaptec.api] header is {'Authorization': 'Bearer xx', 'Accept': 'application/json'} 2023-03-30 22:32:37.294 DEBUG (MainThread) [custom_components.zaptec.api] data is {'availableCurrentPhase1': 15, 'availableCurrentPhase2': 16, 'availableCurrentPhase3': 16} 2023-03-30 22:32:37.294 DEBUG (MainThread) [custom_components.zaptec.api] method is post 2023-03-30 22:32:37.294 DEBUG (MainThread) [custom_components.zaptec.api] hheader is {'Authorization': 'Bearer xx', 'Accept': 'application/json', 'Content-type': 'application/json-patch+json'} 2023-03-30 22:32:37.576 DEBUG (MainThread) [custom_components.zaptec.api] json dump 2023-03-30 22:32:37.577 DEBUG (MainThread) [custom_components.zaptec.api] { "errors": { "": [ "Unexpected character encountered while parsing value: a. Path '', line 0, position 0." ] }, "type": "https://tools.ietf.org/html/rfc7231#section-6.5.1", "title": "One or more validation errors occurred.", "status": 400, "traceId": "xx" }

@gustafssone
Copy link
Author

@Hellowlol great that you cleaned up the code. I still get the parent error when actually trying to change the amps. Perhaps with your experience you have some insight on where to look?
Formatting/body data?
JSON handling?

@Hellowlol
Copy link
Collaborator

Hellowlol commented May 12, 2023

Hi,

Unfortunately, I only cleaned it up. I didn’t have a charger to test with.

Can you try to edit

call = partial(call, data=data)

Replace data=data with json=data

Let me know if works

@gustafssone
Copy link
Author

gustafssone commented May 16, 2023

I got it to work 😃

    try:
        with async_timeout.timeout(30):
            call = getattr(self._client, method)
            if data is not None and method == "post":
                #data=data didn't work, but json=data did.
                call = partial(call, json=data)
            async with call(full_url, headers=header) as resp:
                if resp.status == 401:
                    await self._refresh_token()
                    return await self._request(url)
                elif resp.status == 204:
                    content = await resp.read()
                    return content
                else:
                   #content_type=None was important, otherwise issues with MIME type
                    json_result = await resp.json(content_type=None)
                    _LOGGER.debug("json dump")
                    #note that this returns null
                    _LOGGER.debug(json.dumps(json_result, indent=4))
                    return json_result

A few points:
I got error "Attempt to decode JSON with unexpected mimetype: '" if I didn't put content_type=None in resp.json - should be changed?
Response body is none/null, but there are some headers - relevant?
MaxCurrent defines that maximum amps, irrelevant of what we set here. Should it be noted somewhere?
Within the swagger UI at https://api.zaptec.com/ the curl post suggests:
-H "accept: */*" -H "Authorization: Bearer ey.." -H "Content-Type: application/json-patch+json"
It appears to be working without the different accept and Content-Type - should be skip it?
Let me know your thoughts and I'll update the rest of the code with your suggestions.

@gustafssone
Copy link
Author

@Hellowlol any input on above?

@Hellowlol
Copy link
Collaborator

Just check the path and set the correct headerd json + patch, before you attemot to load rhe result you should checl the path again so you use the correct way for loading the response

@Hellowlol Hellowlol merged commit 02912bd into custom-components:master Jun 3, 2023
1 check passed
@elden1337
Copy link

💪🏼💪🏼

@Hellowlol
Copy link
Collaborator

It’s released as a prelease as I didn’t have time to do proper testing. If ppl test and it’s ok I’ll promote it to a normal version

@elden1337
Copy link

@gustafssone great work! can you ping me somehow once you feel it's good and i'll start updating peaqev to accommodate it asap.

@gustafssone
Copy link
Author

Lets continue discussion on #35 .

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.

None yet

4 participants