bmcclure / ha-aquanta

An unofficial Aquanta water heater controller integration for Home Assistant
MIT License
10 stars 3 forks source link

Set Operation Mode and Temperature Target #28

Open Mark612 opened 1 year ago

Mark612 commented 1 year ago

Checklist

Is your feature request related to a problem? Please describe.

Additional control of the API if possible. Setting the modes and temperature target when setpoint is used.

Describe the solution you'd like

Add the ability through service call methods to set the operation mode and target temperature

service: water_heater.set_operation_mode data: entity_id: water_heater.aquanta_a_water_heater operation_mode: setpoint

service: water_heater.set_temperature data: entity_id: water_heater.aquanta_a_water_heater temperature: 130

Describe alternatives you've considered

Exposing a switch would be ideal.

Additional context

May reference: https://github.com/alexanv1/customcomponents/tree/master/aquanta

bmcclure commented 1 year ago

Thanks for the request! I agree this would be a great enhancement.

The challenge with this is that I'm using this package from pypi which doesn't expose these abilities currently. The other aquanta custom component linked to does offer this functionality, but it's doing it by making requests to HTTP endpoints manually instead of using a separate python package to handle it (which is the HA best practice).

I think the best solution will probably be to fork the pypi package, implement the enhancements into that, and either get the original developer to merge the changes in or release a new package on pypi. Then I can update this integration to make use of the new functionality.

I'll plan to work on these changes as time permits, I don't think it should be very difficult as long as the API that the pypi package connects to is either the same or offers similar functionality.

Mark612 commented 1 year ago

Additionally, add set_performance_mode

Mark612 commented 1 year ago

An update that may be useful to those using aquanta.

I have two water heaters. One is in away mode, temperature/manual mode, the other is intelligence with the 'efficient' setting.

I wrote a custom program using http requests for setting temperature or intelligence modes, and performance modes (low, efficient, high). Pushing the button will toggle the modes and set the temperature based on the input select.

Implemented using templates and 'custom:button-card'

One interesting note, is that your implementation doesn't preserve the temperature state, so the history graph is 'unavailable' several times. May need to implement a 'var' or similar to keep the last value then update.

image

bmcclure commented 1 year ago

Thanks for the additional info!

Out of curiosity, and in case it might help with the full implementation, would you be willing to share the details of the HTTP requests you're using to make those changes? I'm curious if they're using the same API endpoint as the integration or not.

It seems like we should make a separate issue for the statistics gaps to keep this one focused on adding the missing API functionality. I'll create one to continue that discussion.

Mark612 commented 1 year ago

Out of curiosity, and in case it might help with the full implementation, would you be willing to share the details of the HTTP requests you're using to make those changes? I'm curious if they're using the same API endpoint as the integration or not.

Will do. I found a couple of bugs, and working through them. I implemented it with HTTP/python, and a combination of input_numbers, automation, command line sensors and scripts. Seeing if I can simplify it a bit more.

Mark612 commented 1 year ago

Out of curiosity, and in case it might help with the full implementation, would you be willing to share the details of the HTTP requests you're using to make those changes? I'm curious if they're using the same API endpoint as the integration or not.

Here is the core code that seems to be working for me. I borrowed quite a bit from Alex's code, he built the original Aquanta API calls. I also 'import dill' to save the entire session then load it again, to reduce the number of login calls. Keeps the session cache.

You would need to get the device IDs for each water heater. I have two, and just hard coded them.

I can set temperature, or use intelligence mode. For the latter, I can set less, efficient, or most efficient. You wouldn't need to update the status again, as you have that in your code.


API_BASE_URL = "https://portal.aquanta.io/portal" API_LOGIN_URL = f"{API_BASE_URL}/login" API_SET_LOCATION_URL = f"{API_BASE_URL}/set/selected_location?locationId="

API_SETTINGS_URL = f"{API_BASE_URL}/get/settings" API_GET_URL = f"{API_BASE_URL}/get"

API_SET_SETTINGS_URL = f"{API_BASE_URL}/set/advancedSettings" API_EFFICIENCY_SELECTION = f"{API_BASE_URL}/set/efficiencySelection"

CONTROL_MODE_INTELLIGENCE = "Aquanta Intelligence" CONTROL_MODE_TEMPERATURE = "Set Temperature"

WATER_HEATER_ID_A = 1 WATER_HEATER_ID_B = 2

VERBOSITY = 0

###################

FUNCTIONS

###################

def check_response(response: requests.Response): if response.status_code != 200: logPrint(f'Operation failed, status = {response.status_code}, response = {response.text}', 1)

return

def login(username: str, password: str):

session = requests.Session()

login_data = dict(email=username, password=password, returnSecureToken=True)
verifyPasswordResponse = session.post("https://www.googleapis.com/identitytoolkit/v3/relyingparty/verifyPassword?key=AIzaSyBHWHB8Org9BWCH-YFzis-8oEbMaKmI2Tw", data=login_data)
check_response(verifyPasswordResponse)
logPrint(f'Received VerifyPassword response, status = {verifyPasswordResponse.status_code}, json = {verifyPasswordResponse.json()}')

idToken = dict(idToken = verifyPasswordResponse.json()["idToken"])
loginResponse = session.post(f"{API_BASE_URL}/login", data=idToken)
check_response(loginResponse)
logPrint(f'Received login response, status = {loginResponse.status_code}, json = {loginResponse.json()}')

return session

class Aquanta():

def __init__(self, username, password, location):
    self._username = username
    self._password = password
    self._location = location

    self._session = None
    self._login_time = None
    self._session = None
    self._data = {}
    self._settings = {}

def check_login(self):

   if self._session is None or (datetime.now() - self._login_time > timedelta(minutes = 30)):
      # Login again after 30 minutes
      self._session = login(self._username, self._password)
      self._login_time = datetime.now()
      self.set_location()
      logPrint("new session",0)
   else:
      logPrint("session continues!",0)

def update_location(self, location):
    self._location = location

def set_location(self):
    response = self._session.put(f"{API_SET_LOCATION_URL}{self._location}")
    logPrint(f'Set location to {self._location}, status = {response.status_code}')

def update(self):

    self.check_login()

    try:
        response = self._session.get(API_GET_URL)
        check_response(response)
        logPrint(f'Received {API_GET_URL} response, status = {response.status_code}, json = {response.json()}')
        self._data = response.json()

        response = self._session.get(API_SETTINGS_URL)
        check_response(response)

        logPrint(f'Received {API_SETTINGS_URL} response, status = {response.status_code}, json = {response.json()}')
        self._settings = response.json()

    except:
        logPrint(f'Update error, will try to login and retry on the next update interval.')
        self._session = None

@property
def state(self):
    return self._data

@property
def performance_mode(self):
    return self.state["efficiencySelection"]

@property
def aquanta_intelligence_active(self):
    return not self.settings["aquantaIntel"]

@property
def settings(self):
    return self._settings

@property
def target_temperature(self):
    return round(float(self.settings["setPoint"]) * 1.8 + 32)

def set_performance_mode(self, performance_mode: str):
    self.check_login()
    json = {"efficiencySelection": performance_mode}
    response = self._session.put(API_EFFICIENCY_SELECTION, json=json)
    logPrint(f'Performance mode set to {performance_mode}. Received {API_SET_SETTINGS_URL} response, status = {response.status_code}')
    check_response(response)

    self.update()

set target temperature or intelactive where temperature is ignored

intelactive: True sets aquaintelligence active, False sets temperature control

def set_target_temperature(self, target_temperature, intelactive: bool):
    self.check_login()

    json = {
                "aquantaIntel": not intelactive,
                "aquantaSystem": False,
                "setPoint": (target_temperature - 32) / 1.8
            }

    response = self._session.put(API_SET_SETTINGS_URL, json=json)
    logPrint(f'Target temperature set to {target_temperature}. Received {API_SET_SETTINGS_URL} response, status = {response.status_code}')
    check_response(response)

    self.update()

portal = "portal.aquanta.io/views/" if setting == "state": if value == "efficiency": if my_device.aquanta_intelligence_active: print(my_device.state["efficiencySelection"]) else: print(my_device.target_temperature) else: print("Not implemented")

elif setting == "efficiency": my_device.set_target_temperature(target_temperature, True) if value == "Less": my_device.set_performance_mode("Less Efficient") elif value == "Most": my_device.set_performance_mode("Most Efficient") else: my_device.set_performance_mode("Efficient")

elif setting == "temperature": target_temperature = float(value) my_device.set_target_temperature(target_temperature, False)

mluckolson commented 3 months ago

Found my way here from the Home Assistant forums. Implemented @bmcclure's awesome integration via HACS and noticed two interesting things: (1) the frequent "unavailable" interrupts (mentioned above); and (2) a good surprise that it appears I can control the thermostat/setpoint from the device (see control immediately below the boost/away toggles) even though Ben mentioned in the OP that only boost and away could be controlled: Screenshot 2024-08-13 at 4 44 42 PM

I'm using the latest version from HACS -- what would you guys recommend to address these two issues? @Mark612's implementation above is square on what I'm trying to do, but I'm not sure if that code has been pulled in, or is available as a separate fork somehow?

Appreciate any guidance -- and again, thanks for the awesome work so far!

Mark612 commented 2 months ago

Found my way here from the Home Assistant forums. Implemented @bmcclure's awesome integration via HACS and noticed Appreciate any guidance -- and again, thanks for the awesome work so far!

I use the process I described above. Var variables to solve the issue of the Aquanta api timing out, to save the last known good value. I also used my own code, independently, to set other parameters not provided in the integration.

I hope to help with the integration to merge my code in the future.

mluckolson commented 2 months ago

@Mark612 nicely done with your app. Is your app an HA custom component or a freestanding app or? Willing to share?

Also, I’ve been scouring the web trying to find the http syntax to control setpoint and other parameters. Sounds like you coded to the http calls — can you point me to a link or something where the syntax is documented? TIA…

Mark612 commented 2 months ago

@Mark612 HA custom component or a freestanding app or? Willing to share?

Also, I’ve been scouring the web trying to find the http syntax to control setpoint and other parameters. Sounds like you coded to the http calls — can you point me to a link or something where the syntax is documented? TIA…

See my posts above, you can see I use a https and sessions library to access the API portal. I use this as a freestanding app, called 'aquanta' in my coding folder, then integrate the commands into home assistant.

command_line:

And as a command.yaml to set the efficiency or change the temperature. I have 2 water heaters:

aquanta_a_intelligence: /config/coding/aquanta a efficiency {{variables}} aquanta_b_intelligence: /config/coding/aquanta b efficiency {{variables}} aquanta_a_settemp: /config/coding/aquanta a temperature {{variables}} aquanta_b_settemp: /config/coding/aquanta b temperature {{variables}}

mluckolson commented 2 months ago

Ah I think I may have misunderstood. I thought you were saying that Aquanta supports http calls directly (e.g., http:/aquanta.io/user=xyz&tank=xyz&setpoint=125, or whatever syntax). Guess this explains why I couldn’t find documentation anywhere!

Since I’m an HA noob, I was going to hack a solution via http calls directly at least temporarily for setpoint temp and efficiency. Candidly I’m not sure where to start with your app code earlier in the thread, nor the commands immediately above. I will happily take any direction and aggressively try to push forward on my own if you’re willing to provide a startpoint?

Regardless, very much appreciate the reply.

Mark612 commented 2 months ago

...Candidly I’m not sure where to start with your app code earlier in the thread, nor the commands immediately above. I will happily take any direction and aggressively try to push forward on my own if you’re willing to provide a startpoint?

Regardless, very much appreciate the reply.

I'll see if I can post my code up, and you can get it going. Even better, work with bmcclure to merge in the additional functionality needed, as described in this open issue.