Please Note This forum exists for community support for the Mango product family and the Radix IoT Platform. Although Radix IoT employees participate in this forum from time to time, there is no guarantee of a response to anything posted here, nor can Radix IoT, LLC guarantee the accuracy of any information expressed or conveyed. Specific project questions from customers with active support contracts are asked to send requests to support@radixiot.com.

Radix IoT Website Mango 3 Documentation Website Mango 4 Documentation Website

  • Hello
    I'm wondering how to integrate air conditioners connected with Intesis adapters.
    They provide this REST API documentation:
    https://www.intesishome.com/developers/rest-api-documentation/

    Can you give some tips or even better examples of how to do it with Mango?

    Sincerely,
    Dovydas


  • Hi Dovydas,

    Here's a script I wrote to integrate with a REST API, stripped down a little to make it more generic. It's a token authenticated service, where if you set a value to the API key it will login and request a token it can use to refresh. I think that's similar to what you would need to do, or you could manually provide the token to the script.

    You will want to be using the latest version of Mango. We recently added some HTTP utilities to the scripting data source that this script hinges on, and we just made the release that contains them. Here's the git issue describing the feature (it may not be fully documented yet) https://github.com/infiniteautomation/ma-core-public/issues/1094

    var baseUrl = "sptth://api.com";
    var createdPoints = [];
    var createdFolders = {};
    
    //We will use this to see if points have already been defined in the context.
    var global = this;
    
    function refreshSessionToken() {
    	if(typeof sessionToken === 'undefined') {
    		var sessionInfo = refresh.value.split("~");
    		sessionToken = sessionInfo[0];
    		refreshToken = sessionInfo[1];
    		session = {"Session-Token": sessionToken};
    	}
    	return HttpBuilder.post(baseUrl + "/refresh", session, {"RefreshToken": refreshToken})
    		.err(function(status, headers, content) {
    			//print("Refresh Login failure!" + content);
    			return false;
    		}).resp(function(status, headers, content) {
    			var login = JSON.parse(content);
    			sessionToken = login.SessionToken;
    			refreshToken = login.RefreshToken;
    			refresh.set(sessionToken + "~" + refreshToken);
    			session = {"Session-Token": sessionToken};
    			//print("Refresh Login success!" + content);
    			RuntimeManager.sleep(100);
    			return true;
    		}).execute();
    }
    
    function createApiPoint(deviceName, name, varName, isBool) {
    	var basePoint = JSON.parse(JsonEmport.dataPointQuery("eq(xid,DP_API_Example_BaseNumeric)")).dataPoints[0];
    	basePoint.xid = "DP_API_Example_"+varName;
    	basePoint.pointLocator.varName = varName;
    	basePoint.deviceName = deviceName;
    	basePoint.name = name;
    	basePoint.enabled = true;
    	if(isBool) {
    		basePoint.pointLocator.dataType = "BINARY";
    		basePoint.templateXid = "BINARY_DEFAULT";
    		//You may need to set some other properties to get it to work....
    		/*
    		basePoint.plotType = "STEP";
    		basePoint.textRenderer = {
                "type":"BINARY",
                "oneColour":"black",
                "oneLabel":"one",
                "zeroColour":"blue",
                "zeroLabel":"zero"
             };
    		basePoint.chartRenderer = {
                "type":"TABLE",
                "limit":10
             };*/
    	}
    	// This is a great place to build the point hierarchy too!
    	/*
    	if(!(basePoint.deviceName in createdFolders) {
    		newFolder = {"name":basePoint.deviceName,"subfolders":[],"points":[]};
    		newFolder.points.push(basePoint.xid);
    		createdFolders[newFolder.deviceName] = newFolder;
    	} else
    		createdFolders[basePoint.deviceName].points.push(basePoint.xid);
    	*/
    	createdPoints.push(basePoint);
    }
    
    var errReconnect = function(status, headers, content) {
    							if(status == 401 && refreshSessionToken()) {
    							    //print("Re-requested!");
    								return true;
    							} else if(status != 401) {
    							    //print("Non-authorization error: " + status + " " + content);
    							    //LOG.error("Non-authorization error: " + status + " " + content);
    							}
    							return false;
    			};
    			
    var mainResponse = function(status, headers, content) {
    				//Do what needs doing as far as querying data or parsing the content response
    				/*
    				var body = JSON.parse(content);
    				for(int k = 0; k < body.length; k++) {
    					var info = body[k];
    					if(typeof global[info.variableName] === 'undefined') //we're using global to identify if that variable name already exists
    						createApiPoint(info.deviceName, info.name, info.variableName, info.isBool);
    					else
    						global[info.variableName].set(info.value);
    				}
    				*/
    				return false; //return false so we know we didn't reconnect and need to poll again
    			};
    			
    if(apiKey.value !=="") {
    	HttpBuilder.post(baseUrl + "/tokens", {"Content-Type":"application/json"},
    	{
    		"Key" : apiKey.value,
    		"Remember" : true,
    		"Device" : "Example API Script"
    	}).err(function(status, headers, content) {
    		if(status == -1) { /* an exception was probably thrown */ }
    		//else print(content);
    	}).resp(function(status, headers, content) {
    		var response = JSON.parse(content);
    		sessionToken = response.SessionToken;
    		refreshToken = response.RefreshToken;
    		session = {"Session-Token": sessionToken};
    		if(typeof refreshToken !== 'undefined')
    			refresh.set(sessionToken + "~" + refreshToken);
    	}).execute();
    	apiKey.set("");
    } else { //Wrap in else so API key context update executes this from set to ""
    	while(true) {
    		if(typeof session !== 'undefined') {
    			if(HttpBuilder.get(baseUrl + "/beginning/request", session, null)
    			    .err(errReconnect).resp(mainResponse).execute())
    		    	continue;
    			break;
    		} else if(refreshSessionToken()) {
                //print("Else if main clause reconnect");
    			continue;
    		} else //Failed to authenticate for some reason
    			break;
    	}
    }
    
    ph = [];
    for(var k in createdFolders)
        ph.push(createdFolders[k]);
    //print(JSON.stringify(ph))
    if(createdPoints.length > 0) {
        JsonEmport.doImport(JSON.stringify({"dataPoints":createdPoints, "pointHierarchy":ph}));
    }
    

    So we've got a script to reconnect and get a new session, and it creates the points for you!

    Here's the JSON for my example (not tested, but derived from a working scripting data source), which includes the apiKey, refresh, and DP_API_Example_BaseNumeric points referenced in the script.

    {
       "dataSources":[
          {
             "xid":"DS_a42f2287-aecc-4ad9-8f3c-97a2acddc0d8",
             "name":"Generic Rest Script",
             "enabled":false,
             "type":"SCRIPTING",
             "alarmLevels":{
                "SCRIPT_ERROR":"URGENT",
                "DATA_TYPE_ERROR":"URGENT",
                "POLL_ABORTED":"URGENT",
                "LOG_ERROR":"URGENT"
             },
             "purgeType":"YEARS",
             "updateEvent":"CONTEXT_UPDATE",
             "context":[
             ],
             "logLevel":"NONE",
             "cronPattern":"0 * * * * ?",
             "executionDelaySeconds":0,
             "historicalSetting":false,
             "script":"var baseUrl = \"sptth:\/\/api.com\";\r\nvar createdPoints = [];\r\nvar createdFolders = {};\r\n\r\n\/\/We will use this to see if points have already been defined in the context.\r\nvar global = this;\r\n\r\nfunction refreshSessionToken() {\r\n\tif(typeof sessionToken === 'undefined') {\r\n\t\tvar sessionInfo = refresh.value.split(\"~\");\r\n\t\tsessionToken = sessionInfo[0];\r\n\t\trefreshToken = sessionInfo[1];\r\n\t\tsession = {\"Session-Token\": sessionToken};\r\n\t}\r\n\treturn HttpBuilder.post(baseUrl + \"\/refresh\", session, {\"RefreshToken\": refreshToken})\r\n\t\t.err(function(status, headers, content) {\r\n\t\t\t\/\/print(\"Refresh Login failure!\" + content);\r\n\t\t\treturn false;\r\n\t\t}).resp(function(status, headers, content) {\r\n\t\t\tvar login = JSON.parse(content);\r\n\t\t\tsessionToken = login.SessionToken;\r\n\t\t\trefreshToken = login.RefreshToken;\r\n\t\t\trefresh.set(sessionToken + \"~\" + refreshToken);\r\n\t\t\tsession = {\"Session-Token\": sessionToken};\r\n\t\t\t\/\/print(\"Refresh Login success!\" + content);\r\n\t\t\tRuntimeManager.sleep(100);\r\n\t\t\treturn true;\r\n\t\t}).execute();\r\n}\r\n\r\nfunction createApiPoint(deviceName, name, varName, isBool) {\r\n\tvar basePoint = JSON.parse(JsonEmport.dataPointQuery(\"eq(xid,DP_API_Example_BaseNumeric)\")).dataPoints[0];\r\n\tbasePoint.xid = \"DP_API_Example_\"+varName;\r\n\tbasePoint.pointLocator.varName = varName;\r\n\tbasePoint.deviceName = deviceName;\r\n\tbasePoint.name = name;\r\n\tbasePoint.enabled = true;\r\n\tif(isBool) {\r\n\t\tbasePoint.pointLocator.dataType = \"BINARY\";\r\n\t\tbasePoint.templateXid = \"BINARY_DEFAULT\";\r\n\t\t\/\/You may need to set some other properties to get it to work....\r\n\t\t\/*\r\n\t\tbasePoint.plotType = \"STEP\";\r\n\t\tbasePoint.textRenderer = {\r\n            \"type\":\"BINARY\",\r\n            \"oneColour\":\"black\",\r\n            \"oneLabel\":\"one\",\r\n            \"zeroColour\":\"blue\",\r\n            \"zeroLabel\":\"zero\"\r\n         };\r\n\t\tbasePoint.chartRenderer = {\r\n            \"type\":\"TABLE\",\r\n            \"limit\":10\r\n         };*\/\r\n\t}\r\n\t\/\/ This is a great place to build the point hierarchy too!\r\n\t\/*\r\n\tif(!(basePoint.deviceName in createdFolders) {\r\n\t\tnewFolder = {\"name\":basePoint.deviceName,\"subfolders\":[],\"points\":[]};\r\n\t\tnewFolder.points.push(basePoint.xid);\r\n\t\tcreatedFolders[newFolder.deviceName] = newFolder;\r\n\t}\r\n\t*\/\r\n\tcreatedPoints.push(basePoint);\r\n}\r\n\r\nvar errReconnect = function(status, headers, content) {\r\n\t\t\t\t\t\t\tif(status == 401 && refreshSessionToken()) {\r\n\t\t\t\t\t\t\t    \/\/print(\"Re-requested!\");\r\n\t\t\t\t\t\t\t\treturn true;\r\n\t\t\t\t\t\t\t} else if(status != 401) {\r\n\t\t\t\t\t\t\t    \/\/print(\"Non-authorization error: \" + status + \" \" + content);\r\n\t\t\t\t\t\t\t    \/\/LOG.error(\"Non-authorization error: \" + status + \" \" + content);\r\n\t\t\t\t\t\t\t}\r\n\t\t\t\t\t\t\treturn false;\r\n\t\t\t};\r\n\t\t\t\r\nvar mainResponse = function(status, headers, content) {\r\n\t\t\t\t\/\/Do what needs doing as far as querying data or parsing the content response\r\n\t\t\t\t\/*\r\n\t\t\t\tvar body = JSON.parse(content);\r\n\t\t\t\tfor(int k = 0; k < body.length; k++) {\r\n\t\t\t\t\tvar info = body[k];\r\n\t\t\t\t\tif(typeof global[info.variableName] === 'undefined') \/\/we're using global to identify if that variable name already exists\r\n\t\t\t\t\t\tcreateApiPoint(info.deviceName, info.name, info.variableName, info.isBool);\r\n\t\t\t\t\telse\r\n\t\t\t\t\t\tglobal[info.variableName].set(info.value);\r\n\t\t\t\t}\r\n\t\t\t\t*\/\r\n\t\t\t\treturn false; \/\/return false so we know we didn't reconnect and need to poll again\r\n\t\t\t};\r\n\t\t\t\r\nif(apiKey.value !==\"\") {\r\n\tHttpBuilder.post(baseUrl + \"\/tokens\", {\"Content-Type\":\"application\/json\"},\r\n\t{\r\n\t\t\"Key\" : apiKey.value,\r\n\t\t\"Remember\" : true,\r\n\t\t\"Device\" : \"Example API Script\"\r\n\t}).err(function(status, headers, content) {\r\n\t\tif(status == -1) { \/* an exception was probably thrown *\/ }\r\n\t\t\/\/else print(content);\r\n\t}).resp(function(status, headers, content) {\r\n\t\tvar response = JSON.parse(content);\r\n\t\tsessionToken = response.SessionToken;\r\n\t\trefreshToken = response.RefreshToken;\r\n\t\tsession = {\"Session-Token\": sessionToken};\r\n\t\tif(typeof refreshToken !== 'undefined')\r\n\t\t\trefresh.set(sessionToken + \"~\" + refreshToken);\r\n\t}).execute();\r\n\tapiKey.set(\"\");\r\n} else { \/\/Wrap in else so API key context update executes this from set to \"\"\r\n\twhile(true) {\r\n\t\tif(typeof session !== 'undefined') {\r\n\t\t\tif(HttpBuilder.get(baseUrl + \"\/beginning\/request\", session, null)\r\n\t\t\t    .err(errReconnect).resp(mainResponse).execute())\r\n\t\t    \tcontinue;\r\n\t\t\tbreak;\r\n\t\t} else if(refreshSessionToken()) {\r\n            \/\/print(\"Else if main clause reconnect\");\r\n\t\t\tcontinue;\r\n\t\t} else \/\/Failed to authenticate for some reason\r\n\t\t\tbreak;\r\n\t}\r\n}\r\n\r\nph = [];\r\nfor(var k in createdFolders)\r\n    ph.push(createdFolders[k]);\r\n\/\/print(JSON.stringify(ph))\r\nif(createdPoints.length > 0) {\r\n    JsonEmport.doImport(JSON.stringify({\"dataPoints\":createdPoints, \"pointHierarchy\":ph}));\r\n}\r\n",
             "scriptPermissions":{
                "customPermissions":"",
                "dataPointReadPermissions":"superadmin",
                "dataPointSetPermissions":"superadmin",
                "dataSourcePermissions":"superadmin"
             },
             "editPermission":"",
             "purgeOverride":false,
             "purgePeriod":1
          }
       ],
       "dataPoints":[
          {
             "xid":"DP_API_Example_ApiKey",
             "name":"Api Key",
             "enabled":true,
             "loggingType":"ON_CHANGE",
             "intervalLoggingPeriodType":"MINUTES",
             "intervalLoggingType":"INSTANT",
             "purgeType":"YEARS",
             "pointLocator":{
                "dataType":"ALPHANUMERIC",
                "contextUpdate":true,
                "settable":true,
                "varName":"apiKey"
             },
             "eventDetectors":[
             ],
             "plotType":"STEP",
             "rollup":"NONE",
             "unit":"",
             "path":"",
             "templateXid":"Alphanumeric_Default",
             "chartColour":"",
             "chartRenderer":{
                "type":"TABLE",
                "limit":10
             },
             "dataSourceXid":"DS_a42f2287-aecc-4ad9-8f3c-97a2acddc0d8",
             "defaultCacheSize":1,
             "deviceName":"Generic Rest Script",
             "discardExtremeValues":false,
             "discardHighLimit":1.7976931348623157E308,
             "discardLowLimit":-1.7976931348623157E308,
             "intervalLoggingPeriod":15,
             "intervalLoggingSampleWindowSize":0,
             "overrideIntervalLoggingSamples":false,
             "preventSetExtremeValues":false,
             "purgeOverride":false,
             "purgePeriod":1,
             "readPermission":"",
             "setExtremeHighLimit":1.7976931348623157E308,
             "setExtremeLowLimit":-1.7976931348623157E308,
             "setPermission":"",
             "textRenderer":{
                "type":"PLAIN",
                "useUnitAsSuffix":true,
                "unit":"",
                "renderedUnit":"",
                "suffix":""
             },
             "tolerance":0.0
          },
          {
             "xid":"DP_API_Example_RefreshTokens",
             "name":"Refresh Tokens",
             "enabled":true,
             "loggingType":"ON_CHANGE",
             "intervalLoggingPeriodType":"MINUTES",
             "intervalLoggingType":"INSTANT",
             "purgeType":"YEARS",
             "pointLocator":{
                "dataType":"ALPHANUMERIC",
                "contextUpdate":false,
                "settable":true,
                "varName":"refresh"
             },
             "eventDetectors":[
             ],
             "plotType":"STEP",
             "rollup":"NONE",
             "unit":"",
             "path":"",
             "templateXid":"Alphanumeric_Default",
             "chartColour":"",
             "chartRenderer":{
                "type":"TABLE",
                "limit":10
             },
             "dataSourceXid":"DS_a42f2287-aecc-4ad9-8f3c-97a2acddc0d8",
             "defaultCacheSize":1,
             "deviceName":"Generic Rest Script",
             "discardExtremeValues":false,
             "discardHighLimit":1.7976931348623157E308,
             "discardLowLimit":-1.7976931348623157E308,
             "intervalLoggingPeriod":15,
             "intervalLoggingSampleWindowSize":0,
             "overrideIntervalLoggingSamples":false,
             "preventSetExtremeValues":false,
             "purgeOverride":false,
             "purgePeriod":1,
             "readPermission":"",
             "setExtremeHighLimit":1.7976931348623157E308,
             "setExtremeLowLimit":-1.7976931348623157E308,
             "setPermission":"",
             "textRenderer":{
                "type":"PLAIN",
                "useUnitAsSuffix":true,
                "unit":"",
                "renderedUnit":"",
                "suffix":""
             },
             "tolerance":0.0
          },
          {
             "xid":"DP_API_Example_BaseNumeric",
             "name":"BaseNumeric",
             "enabled":false,
             "loggingType":"INTERVAL",
             "intervalLoggingPeriodType":"MINUTES",
             "intervalLoggingType":"AVERAGE",
             "purgeType":"YEARS",
             "pointLocator":{
                "dataType":"NUMERIC",
                "contextUpdate":false,
                "settable":true,
                "varName":"baseNumericPoint"
             },
             "eventDetectors":[
             ],
             "plotType":"SPLINE",
             "rollup":"NONE",
             "unit":"",
             "path":"",
             "templateXid":"Numeric_Default",
             "chartColour":"",
             "chartRenderer":{
                "type":"IMAGE",
                "timePeriodType":"DAYS",
                "numberOfPeriods":1
             },
             "dataSourceXid":"DS_a42f2287-aecc-4ad9-8f3c-97a2acddc0d8",
             "defaultCacheSize":1,
             "deviceName":"Generic Rest Script",
             "discardExtremeValues":false,
             "discardHighLimit":1.7976931348623157E308,
             "discardLowLimit":-1.7976931348623157E308,
             "intervalLoggingPeriod":1,
             "intervalLoggingSampleWindowSize":0,
             "overrideIntervalLoggingSamples":false,
             "preventSetExtremeValues":false,
             "purgeOverride":false,
             "purgePeriod":1,
             "readPermission":"",
             "setExtremeHighLimit":1.7976931348623157E308,
             "setExtremeLowLimit":-1.7976931348623157E308,
             "setPermission":"",
             "textRenderer":{
                "type":"ANALOG",
                "useUnitAsSuffix":true,
                "unit":"",
                "renderedUnit":"",
                "format":"0.00"
             },
             "tolerance":0.0
          }
       ]
    }
    

    Alternatively, it's possible you may be able to get what you want out of an HTTP Json Retriever if you can set your session token such that it doesn't time out and is a static header for the data source.


  • Hello Phillip,
    thanks for your answer!

    I updated my Mango to newest version 3.2.2+20171009170034 but unfortunately still have this error:
    ReferenceError: "HttpBuilder" is not defined in at line number 15

    Should I install this somehow manually?

    Dovydas


  • I wonder if something has gone awry in your upgrade process. I just updated a Mango to the version you have and when I run print(HttpBuilder) in a Scripting data source I get a description of the utility.

    Is it possible that you have more than one mango-X.Y.Z.jar in your Mango/lib/ directory, or have one elsewhere on the classpath? How did you update?


  • There were some errors (can't find jar or smth) on updating process so I just extracted m2m2-core-3.2.2.zip into my Mango directory. And yes, there were two files - mango-3.2.1.jar and mango-3.2.2.jar, so I deleted 3.2.1 and now it started with HttpBuilder.
    Thanks! Going to analyze your script..


  • Great!

    You may consider placing the m2m2-core-3.2.2.zip into your Mango/ directory again and then simply restart Mango. It will automatically perform an upgrade if there is a new core zip in the Mango/ directory. It's possible there were other JARs update and you'd have duplicates of those too, which would produce uncertain results.