Hacksore / bluelinky

An unofficial nodejs API wrapper for Hyundai bluelink and Kia UVO
https://bluelinky.readme.io
MIT License
341 stars 76 forks source link

Support for getting/setting charging schedule #227

Open kalleguld opened 2 years ago

kalleguld commented 2 years ago

Some EVs (eg. the Hyundai Ioniq 5) have the option of setting a charging schedule from the BlueLink App. Will it be possible to view and edit this schedule from BlueLinky?

Alternatively, if someone has an API description from Hyundai, I'd be happy to make a a pull request.

Hacksore commented 2 years ago

Possible but it would require someone to find the endpoints 😬.

kalleguld commented 2 years ago

Do you have a good way of finding the endpoint? Decompilation? Wireshark?

PierreLevres commented 2 years ago

Some python code I had working before I switched to Bluelinky and node-red completely

` def api_get_chargeschedule(self): if not self.check_control_token(): return False

location

    url = BaseURL[self.userConfig.brand] + '/api/v2/spa/vehicles/' + self.vehicleConfig.id + '/reservation/charge'
    headers = {
        'Host': BaseHost[self.userConfig.brand], 'Accept': Accept,
        'Authorization': self.controller.session.controlToken,
        'ccsp-application-id': CcspApplicationId,
        'Accept-Language': AcceptLanguage, 'Accept-Encoding': AcceptEncoding, 'offset': '2',
        'stamp': self.controller.session.stamp,
        'User-Agent': UserAgent, 'Connection': Connection, 'Content-Type': ContentJSON,
        'ccsp-device-id': self.controller.session.deviceId
    }
    response = requests.get(url, headers=headers)
    if response.status_code == 200:
        try:
            response = json.loads(response.text)
            # TODO get schedule response
            return response['resMsg']
        except ValueError:
            raise ApiError('NOK Parsing charge schedule: ' + response)
    else:
        raise ApiError('NOK requesting charge schedule. Error: ' + str(response.status_code) + response.text)

def api_set_chargeschedule(self, schedule1, schedule2, tempset, chargeschedule):
    # todo get input from formatted @dataclass
    '''
     Parameters Schedule1, Schedule2 = timeschedules for the temperature set
       first array [x,y,z, etc] with x,y,z, days that need to be set, where sunday = 0 and saturday = 6
       second the time and timesection in 12h notation, int or string, plus 0 or 1 (=AM or PM), int or string
       eg [[2,5,6],["1040","0"]] means 10:40 AM on Tuesday, Friday, Saturday

     Param Tempset
      first the temp to be set (in celcius), float or string
      second True or False for defrost on or off
      [23.0, True] means a temperature of 23 celsius and defrosting on

     ChargeSchedule
       first starttime = array time (int or string) and timesection 0 or 1 (AM or PM)
       then  endtime = array time (int or string) and timesection 0 or 1 (AM or PM)
       prio = 1 (only off-peak times) or 2 (prio to off-peak times)
     [["1100","1"],["0700","0"], 1 ] means off peak times are from 23:00 to 07:00 and car should only charge during these off peak times

     if any parameter = True or None, then ignore values. If parameter = False then disable the schedule, else set the values.
    '''
    if not self.check_control_token(): return False

    # first get the currens settings
    data = self.api_get_chargeschedule()
    olddata = data
    if not (not data):
        try:
            # now enter the new settings

            if not (schedule1 is None or schedule1 is True):  # ignore it of True or None
                if schedule1 is False:  # turn the schedule off
                    data['reservChargeInfo']['reservChargeInfoDetail']['reservChargeSet'] = False
                else:
                    data['reservChargeInfo']['reservChargeInfoDetail']['reservChargeSet'] = True
                    schedule = {"day": schedule1[0],
                                "time": {"time": str(schedule1[1][0]), "timeSection": int(schedule1[2][1])}}
                    data['reservChargeInfo']['reservChargeInfoDetail']['reservInfo'] = schedule
            if not (schedule2 is None or schedule2 is True):  # ignore it of True or None
                if schedule2 is False:  # turn the schedule off
                    data['reserveChargeInfo2']['reservChargeInfoDetail']['reservChargeSet'] = False
                else:
                    data['reserveChargeInfo2']['reservChargeInfoDetail']['reservChargeSet'] = True
                    schedule = {"day": schedule2[0],
                                "time": {"time": str(schedule2[1][0]), "timeSection": int(schedule2[2][1])}}
                    data['reserveChargeInfo2']['reservChargeInfoDetail']['reservInfo'] = schedule

            if not (tempset is None or tempset is True):  # ignore it of True or None
                if tempset is False:  # turn the schedule off
                    data['reservChargeInfo']['reservChargeInfoDetail']['reservFatcSet']['airCtrl'] = 0
                    data['reserveChargeInfo2']['reservChargeInfoDetail']['reservFatcSet']['airCtrl'] = 0
                else:
                    data['reservChargeInfo']['reservChargeInfoDetail']['reservFatcSet']['airCtrl'] = 1
                    data['reserveChargeInfo2']['reservChargeInfoDetail']['reservFatcSet']['airCtrl'] = 1
                    data['reservChargeInfo']['reservChargeInfoDetail']['reservFatcSet']['airTemp'][
                        'value'] = temp2hex(str(tempset[0]))
                    data['reserveChargeInfo2']['reservChargeInfoDetail']['reservFatcSet']['airTemp'][
                        'value'] = temp2hex(str(tempset[0]))
                    data['reservChargeInfo']['reservChargeInfoDetail']['reservFatcSet']['defrost'] = tempset[1]
                    data['reserveChargeInfo2']['reservChargeInfoDetail']['reservFatcSet']['defrost'] = tempset[1]

            if not (chargeschedule is None or chargeschedule is True):  # ignore it of True or None
                if chargeschedule is False:  # turn the schedule off
                    data['reservFlag'] = 0
                else:
                    data['reservFlag'] = 1
                    data['offPeakPowerInfo']['offPeakPowerTime1'] = {
                        "starttime": {"time": str(chargeschedule[0][1]), "timeSection": int(chargeschedule[0][1])},
                        "endtime": {"time": str(chargeschedule[1][1]), "timeSection": int(chargeschedule[1][1])}}
                    data['offPeakPowerInfo']['offPeakPowerFlag'] = int(chargeschedule[2])
            if data == olddata: return True  # nothing changed

            url = BaseURL[
                      self.userConfig.brand] + '/api/v2/spa/vehicles/' + self.vehicleConfig.id + '/reservation/charge'
            headers = {
                'Host': BaseHost[self.userConfig.brand], 'Accept': Accept,
                'Authorization': self.controller.session.controlToken,
                'ccsp-application-id': CcspApplicationId,
                'Accept-Language': AcceptLanguage, 'Accept-Encoding': AcceptEncoding, 'offset': '2',
                'User-Agent': UserAgent, 'Connection': Connection, 'Content-Type': ContentJSON,
                'stamp': self.controller.session.stamp,
                'ccsp-device-id': self.controller.session.deviceId
            }
            data['self.controller.session.deviceId'] = self.controller.session.deviceId
            response = requests.post(url, json=data, headers=headers)
            if response.status_code == 200: return True
        except:
            raise ApiError('NOK setting charge schedule.')
    raise ApiError('NOK setting charge schedule.')

`

Hacksore commented 2 years ago

This is what I've written in the past https://docs.google.com/document/d/1q0Pz2t5Gmt-vv8z-2Yswhvg2x9B2f3d1vjpkIjt9EfQ/edit?usp=sharing

kalleguld commented 2 years ago

GETting the /reservation/charge endpoint as @PierreLevres suggested works fine. But POSTing to that endpoint results in a 503 Service Unavailable message. Anyone got a clue?