Writing a driver to interface with a Honeywell Tuxedo Touch and need some help with headers

Working on a driver to interface with a Honeywell Tuxedo Touch running on ver5.3.21.0 firmware with api and having some trouble figuring out how to send header info the auth token. Keep in mind this is all rough code as I'm building it out. I'll clean it up once I get it working.

The default data is just garbage I stuck in there so it has the correct character length.

Raw code:
https://raw.githubusercontent.com/heidrickla/Hubitat/main/Drivers/HoneywellTuxedoTouchAPI/HoneywellTuxedoTouchAPI.groovy

Example :
http://:/system_http_api/API_REV01/AdvancedSecurity/ArmWithCode?arming=AWAY,STAY,NIGHT&pID=1 or 2 or 3...&ucode=Valid User Code&operation=set

Authentication token should be added as part of authtoken http header (Authentication token recieved during registeration operation. Not applicable for browser clients)

def armStay() {
getHmac(privateKey)
def apiRev = "API_REV01"
def apiBasePath = "/system_http_api/${apiRev}"
def postParams = [
    uri: "http://${tuxedoTouchIP}:${tuxedoTouchPort}${apiBasePath}/AdvancedSecurity/ArmWithCode?arming=STAY@pID=${partitionNumber}&ucode=${userPin}&operation=set",
	requestContentType: 'application/json',
	contentType: 'application/json',
    headers: ['authtoken':"${authorizationToken}",'MACID':"${hubitatMac}"]//,
	//body : ["name": "value"]
]
log.debug postParams
log.debug "http://${tuxedoTouchIP}:${tuxedoTouchPort}${apiBasePath}/AdvancedSecurity/ArmWithCode?arming=STAY@pID=${partitionNumber}&ucode=${userPin}&operation=set"
asynchttpPost('myCallbackMethod', postParams, [dataitem1: "datavalue1"])

}

def getHmac(authenticationToken) {
String result
String key = "${authenticationToken}"
String data = "${authenticationToken}"

try {

    // get an hmac_sha1 key from the raw key bytes
    SecretKeySpec signingKey = new SecretKeySpec(key.getBytes(), "HmacSHA1");

    // get an hmac_sha1 Mac instance and initialize with the signing key
    Mac mac = Mac.getInstance("HmacSHA1");
    mac.init(signingKey);

    // compute the hmac on input data bytes
    byte[] rawHmac = mac.doFinal(data.getBytes());
    result= rawHmac.encodeHex()
	log.debug "HMAC_SHA1 result: ${result}"

} catch (Exception e) {
    log.debug("Failed to generate HMAC : " + e.getMessage())
}
}

Here's the API documentation:

Summary

-- GetSecurityStatus

-- Get the default home partition status, Partition ID is optional.
Example : http://:/system_http_api/API_REV01/GetSecurityStatus?operation=get
Authentication token should be added as part of authtoken http header (Authentication token recieved during registeration operation. Not applicable for browser clients)

-- SetSecurityArm

-- Use this service to change the partition status.Default is AWAY and partition 1.
Example : http://:/system_http_api/API_REV01/SetSecurityArm?arming=[AWAY,STAY,NIGHT]&pID=[Partition ID (1-8)]&operation=set
Authentication token should be added as part of authtoken http header (Authentication token recieved during registeration operation. Not applicable for browser clients)

-- GetOccupancyMode

-- Get the occupancy status from automation mode.
Example : http://:/system_http_api/API_REV01/GetOccupancyMode?operation=get
Authentication token should be added as part of authtoken http header (Authentication token recieved during registeration operation. Not applicable for browser clients)

-- SetOccupancyMode

-- This Service is used to set the occupancy status for automation mode
Example : http://:/system_http_api/API_REV01/SetOccupancyMode?omode=[HOME,AWAY/CLOSE,NIGHT]&operation=set
Authentication token should be added as part of authtoken http header (Authentication token recieved during registeration operation. Not applicable for browser clients)

-- GetLightStatus

-- This Service will get the status off a particular binary light (Nodeid,Device Name,Device Type and Status [Binary Switch 0-OFF,255-ON][Multilevel Switch 0-OFF, 1 to 99-ON)
Example : http://:/system_http_api/API_REV01/GetLightStatus?nodeID=[Device ID assigned in Tuxedo Home Automation]&operation=set
Authentication token should be added as part of authtoken http header (Authentication token recieved during registeration operation. Not applicable for browser clients)

-- SetLight

-- This Service will set the status off a particular binary or dimmer light
Example : http://:/system_http_api/API_REV01/SetLight?nodeID=[Device ID assigned in Tuxedo Home Automation]&percent=[percent=ON, OFF, DIM1,…
DIM10]&operation=set
Where DIM1-DIM10 equal to 10 to 99.

-- GetThermostatMode

-- This service will return the mode of particular thermostat(Nodeid,Device Name,Device Type and Mode [OFF,HEAT,COOL,AUTO,SAVECOOL,SAVEHEAT])
Example : http://:/system_http_api/API_REV01/GetThermostatMode?nodeID=[Device ID assigned in Tuxedo Home Automation]&operation=set
Authentication token should be added as part of authtoken http header (Authentication token recieved during registeration operation. Not applicable for browser clients)

-- GetThermostatSetPoint

-- This service will give Heat and Cool Setpoint of particular thermostat(Nodeid,Device Name,Device Type,Heat Setpoint,Cool Setpoint,Save Heat,Save Cool)
Example : http://:/system_http_api/API_REV01/GetThermostatSetPoint?nodeID=[Device ID assigned in Tuxedo Home Automation]&operation=set
Authentication token should be added as part of authtoken http header (Authentication token recieved during registeration operation. Not applicable for browser clients)

-- GetThermostatEnergyMode

-- This service will give the thermostat mode,Energy Save or Normal
Example : http://:/system_http_api/API_REV01/GetThermostatEnergyMode?nodeID=[Device ID assigned in Tuxedo Home Automation]&operation=set
Authentication token should be added as part of authtoken http header (Authentication token recieved during registeration operation. Not applicable for browser clients)

-- SetThermostatMode

-- Use this service to change the thermostat mode [mode=OFF,HEAT,COOL,AUTO].Modes 0-OFF,1-HEAT,2-COOL,3-AUTO,11-SAVEHEAT,12-SAVECOOL.
Example : http://:/system_http_api/API_REV01/SetThermostatMode?nodeID=[Device ID assigned in Tuxedo Home Automation]&mode=
[mode=OFF,HEAT,COOL,AUTO]&operation=set
Authentication token should be added as part of authtoken http header (Authentication token recieved during registeration operation. Not applicable for browser clients)

-- SetThermostatSetPoint

-- Use this service to change the Heat and Cool Setpoint of particular thermostat [mode=HEAT,COOL,SAVEHEAT,SAVECOOL].Modes 1-HEAT,2-COOL,11-SAVEHEAT,12-SAVECOOL.
Example : http://:/system_http_api/API_REV01/SetThermostatSetPoint?nodeID=[Device ID assigned in Tuxedo Home Automation]&mode=
[mode=HEAT,COOL,SAVEHEAT,SAVECOOL]&setPoint=[setpoint=temp_value]&operation=set
Authentication token should be added as part of authtoken http header (Authentication token recieved during registeration operation. Not applicable for browser clients)

-- SetThermostatEnergyMode

-- Use this service to change the thermostat mode to Energy Save or Normal[dev_id=node_ID], [energy_mode=NORMAL,ECO]
Authentication token should be added as part of authtoken http header (Authentication token recieved during registeration operation. Not applicable for browser clients)

-- GetDoorLockStatus

-- Use this service to get the status of a particular doorlock
Example : http://:/system_http_api/API_REV01/GetDoorLockStatus?nodeID=[Device ID assigned in Tuxedo Home Automation]&operation=set
Authentication token should be added as part of authtoken http header (Authentication token recieved during registeration operation. Not applicable for browser clients)

-- SetDoorLock

-- Use this service to set the status of a particular doorlock[LOCK or 1]
Example : http://:/system_http_api/API_REV01/SetDoorLock?nodeID=[Device ID assigned in Tuxedo Home Automation]&cntrl=[LOCK,UNLOCK ]&operation=set
Authentication token should be added as part of authtoken http header (Authentication token recieved during registeration operation. Not applicable for browser clients)

-- GetSceneList

-- Use this command to retrieve all the Scenes
Example : http://:/system_http_api/API_REV01/GetSceneList?operation=get
Authentication token should be added as part of authtoken http header (Authentication token recieved during registeration operation. Not applicable for browser clients)

-- ExecuteScene

-- Use this command to execute the scene
Example : http://:/system_http_api/API_REV01/ExecuteScene?sceneID=[SceneID=1]&operation=set
Authentication token should be added as part of authtoken http header (Authentication token recieved during registeration operation. Not applicable for browser clients)

-- GetThermostatTemperature

-- This Service will retrieve the current temperature from the thermostat
Example : http://:/system_http_api/API_REV01/GetThermostatTemperature?nodeID=[Device Node ID]&operation=set
Authentication token should be added as part of authtoken http header (Authentication token recieved during registeration operation. Not applicable for browser clients)

-- GetThermostatFanMode

-- This Service will retrieve the current temperature from the thermostat
Example : http://:/system_http_api/API_REV01/GetThermostatTemperature?nodeID=[Device Node ID]&operation=set
Authentication token should be added as part of authtoken http header (Authentication token recieved during registeration operation. Not applicable for browser clients)

-- GetThermostatFullStatus

-- This Service will retrieve the current temperature from the thermostat
Example : http://:/system_http_api/API_REV01/GetThermostatTemperature?nodeID=[Device Node ID]&operation=set
Authentication token should be added as part of authtoken http header (Authentication token recieved during registeration operation. Not applicable for browser clients)

-- GetWaterValveStatus

-- Use this service to get the status of a particular water valve control
Example : http://:/system_http_api/API_REV01/GetWaterValveStatus?nodeID=[node_ID=dev_id]

-- SetWaterValveStatus

-- Use this service to set the status of a particular water valve control
Example : http://:/system_http_api/API_REV01/SetWaterValveStatus?nodeID=[node_ID=dev_id]&cntrl=[[Close or 1] or [Open or 255]]&operation=set

-- GetGarageDoorStatus

-- Use this service to get the status of a particular garage door control
Example : http://:/system_http_api/API_REV01/GetGarageDoorStatus?nodeID=[node_ID=dev_id]

-- SetGarageDoorStatus

-- Use this service to set the status of a particular garage door control
Example : http://:/system_http_api/API_REV01/SetGarageDoorStatus?nodeID=[node_ID=dev_id]&cntrl=[[Close or 1] or [Open or 255]]&operation=set

This one looks like kind of a doozy.

Here's an implementation for HA. Assuming it works, it should be a good reference: home-assistant/alarm_control_panel.py at dev · hinzo/home-assistant · GitHub

Where do you calculate authorizationToken for your requests?

That and how to insert it into the header are what I am struggling with. From what you linked this looks useful.

def _api_request(self, api_name, params):
    uri_params = urllib.parse.urlencode(params)
    _LOGGER.info("api_name: %s, uri_params: %s", api_name, uri_params)
    uri_params_encrypted = _encrypt_data(
        bytes(uri_params, "utf-8"), self._api_key_enc, self._api_iv_enc
    )

    full_url = self._url + API_BASE_PATH + api_name
    header = "MACID:" + self._mac + ",Path:" + API_REV + api_name
    authtoken = _sign_string(header, self._api_key_enc)

    response = requests.post(
        full_url,
        data={
            "param": uri_params_encrypted,
            "len": len(urllib.parse.quote_plus(uri_params_encrypted)),
            "tstamp": random.random(),
        },
        headers={
            "authtoken": authtoken,
            "identity": self._api_iv_enc,
            PRAGMA: "no-cache",
            CACHE_CONTROL: "no-cache",
        },
        verify=False,
    )

    result = None
    if response.status_code == 200:
        content = json.loads(response.content)
        result = _decrypt_data(
            content["Result"], self._api_key_enc, self._api_iv_enc
        )
        result = json.loads(result)
    elif response.status_code == 401:
        # Unauthorized
        _LOGGER.error("Unauthorized, API: %s", api_name)
    elif response.status_code == 405:
        # Method Not Allowed
        _LOGGER.error("Method Not Allowed, API: %s", api_name)
    else:
        # Unknown
        _LOGGER.error(
            "Unknown error, status_code: %s, API: %s",
            response.status_code,
            api_name,
        )
    return result

Yea, it's a tough one and I'm way out of my league but I've been asked about this for a while now so figured I'd take a stab at it. I've never really dealt with tokens or auth encryption of any kind while programming. If anything I usually find a way around it.

Nothing is out of your league when you have an API doc and example. :sunglasses:

True, I'll just spend the next week banging my head against it and someone will come along and say hey dummy do this. I've only been programming with groovy for about a month so there's a lot to learn.

If I could figure out how to get it to pass the auth info correctly the rest is super easy.

hard to help when i dont have one of the therms..

This is actually the alarm keypad not the thermostat. Lets you control the panel from a web interface on the keypad or api calls which is what I am trying to figure out.

These are the parts you need:

data={
"param": uri_params_encrypted,
"len": len(urllib.parse.quote_plus(uri_params_encrypted)),
"tstamp": random.random(),
},
headers={
"authtoken": authtoken,
"identity": self._api_iv_enc,
PRAGMA: "no-cache",
CACHE_CONTROL: "no-cache",
}

data is a JSON body for the POST. Headers are a map just like that for the POST.

What about the _encrypt_data and _decrypt_data method?

These helpers are in the HA code:

_encrypt_data (for the "param" member in body)

_sign_string (for calculating the "authtoken" member in headers)

I guess I don't even need the public key.

I don't think I need the PRAGMA and CACHE_CONTROL stuff here.

Alright, I'm giving up for the night... well day now... whatever. Going to bed. Frustrating with it being so close to working. Latest version published on my github.

Inspected the code, maybe I can get some direction from that...

image

What about the substring here? Do I need to change the api_key_enc and api_iv_enc byte sizes?

The code that I shared by PM does the string-byte conversion first, so the counts are divided by two (32 bytes for the key and 16 bytes for the iv). This one is slicing it at strings first, so that's why they're doubled in size. Essentially, they're both right.

1 Like