Wont using virtual serial port limit this connection to 1 stream, ie 1 PI ?
Yes, good suspicion!
Yes I have control over the HTTP messages, so I can add anything to that,
Great! So I would set up an HTTP receiver with only one point, Let's say its parameter name is 'data' (or maybe just d, for short). Then I'll publish to the receiver messages like
POST / HTTP/1.1
User-Agent: Mango M2M2 HTTP Sender publisher
Content-Length: 31
Content-Type: application/x-www-form-urlencoded
Host: 127.0.0.1
Connection: Keep-Alive
Expect: 100-continue
Accept-Encoding: gzip,deflate
data=s1z56.78+s2z78.90+s3z12.34
And put the http receiver point with Parameter name "data" into the context of a script like,
var sensorTransforms = this.sensorTransforms;
if(typeof this.sensorTransforms === 'undefined') {
this.sensorTransforms = sensorTransforms = {
//Use this map of sensor identifiers to transformation functions to
// manipulate the value of the data
s1: function(val) { return val*2 + 3; },
s2: function(val) { return val*6 - 12; },
s3: function(val) { return Math.pow(val, 2); }
};
}
function createPoint(identifier) {
var newDp = JSON.parse(JsonEmport.dataPointQuery(
'eq(xid,DP_BaseQueueProcessorNumeric)')).dataPoints[0];
newDp.enabled = true;
newDp.pointLocator.varName = identifier;
newDp.name = "Sensor " + identifier;
delete newDp.xid; //To generate a new XID,
//or encode your own XID for reference elsewhere
JsonEmport.doImport( JSON.stringify({"dataPoints": [newDp]}) );
}
//Because there is a slight asynchrony, we need to track where we got to in the data
// queue, so we'll use a context point for that.
var unprocessedMessages = data.pointValuesSince(tracker.time);
var lastTs = 0;
for(var k = 0; k < unprocessedMessages.length; k+=1) {
var sensorData = unprocessedMessages[k].stringValue.split(" ");
for(var j = 0; j < sensorData.length; j+=1) {
var pointData = sensorData[j].split("z");
if(typeof this[pointData[0]] === 'undefined' ) {
createPoint(pointData[0]);
}
if( pointData[0] in sensorTransforms )
this[pointData[0]].set( sensorTransforms[pointData[0]](Number(pointData[1])) );
else
this[pointData[0]].set( Number(pointData[1]) );
}
lastTs = unprocessedMessages[k].time;
}
//Update the timestamp we have processed unto
if(lastTs !== 0)
tracker.set(!tracker.value, lastTs);
And here's the JSON for the script and the base points (you will probably need to define a cron for the scripting data source, as the feature to have a scripting data source only be driven by context point updates has been added to 3.5 (which should see some sort of alpha release today or very soon). You would probably also need to create a "Numeric_All-Data" data point template. It also has the JSON for the HTTP receiver I tested with
{
"dataSources":[
{
"xid":"DS_306b3fdc-b914-4187-9a4c-0024be71d52e",
"name":"DataQueueProcessor",
"enabled":false,
"type":"SCRIPTING",
"alarmLevels":{
"SCRIPT_ERROR":"URGENT",
"DATA_TYPE_ERROR":"URGENT",
"POLL_ABORTED":"URGENT",
"LOG_ERROR":"URGENT"
},
"purgeType":"YEARS",
"updateEvent":"CONTEXT_UPDATE",
"context":[
{
"varName":"data",
"dataPointXid":"DP_97ba3ae9-f9ca-4b36-a557-b07fc89824f1",
"updateContext":true
}
],
"logLevel":"NONE",
"cronPattern":"",
"executionDelaySeconds":0,
"historicalSetting":false,
"script":"var sensorTransforms = this.sensorTransforms;\nif(typeof this.sensorTransforms === 'undefined') {\n this.sensorTransforms = sensorTransforms = {\n \/\/Use this map of sensor identifiers to transformation functions to\n \/\/ manipulate the value of the data\n s1: function(val) { return val*2 + 3; },\n s2: function(val) { return val*6 - 12; },\n s3: function(val) { return Math.pow(val, 2); }\n };\n}\n\nfunction createPoint(identifier) {\n var newDp = JSON.parse(JsonEmport.dataPointQuery(\n 'eq(xid,DP_BaseQueueProcessorNumeric)')).dataPoints[0];\n newDp.enabled = true;\n newDp.pointLocator.varName = identifier;\n newDp.name = \"Sensor \" + identifier;\n delete newDp.xid; \/\/To generate a new XID,\n \/\/or encode your own XID for reference elsewhere\n JsonEmport.doImport( JSON.stringify({\"dataPoints\": [newDp]}) );\n}\n\n\/\/Because there is a slight asynchrony, we need to track where we got to in the data\n\/\/ queue, so we'll use a context point for that.\nvar unprocessedMessages = data.pointValuesSince(tracker.time);\nvar lastTs = 0;\nfor(var k = 0; k < unprocessedMessages.length; k+=1) {\n var sensorData = unprocessedMessages[k].stringValue.split(\" \");\n for(var j = 0; j < sensorData.length; j+=1) {\n var pointData = sensorData[j].split(\"z\");\n if(typeof this[pointData[0]] === 'undefined' ) {\n createPoint(pointData[0]);\n }\n if( pointData[0] in sensorTransforms )\n this[pointData[0]].set( sensorTransforms[pointData[0]](Number(pointData[1])) );\n else\n this[pointData[0]].set( Number(pointData[1]) );\n }\n lastTs = unprocessedMessages[k].time;\n}\n\n\/\/Update the timestamp we have processed unto\nif(lastTs !== 0)\n tracker.set(!tracker.value, lastTs);\n",
"scriptPermissions":{
"customPermissions":"",
"dataPointReadPermissions":"superadmin,superadmin",
"dataPointSetPermissions":"superadmin,superadmin",
"dataSourcePermissions":"superadmin,superadmin"
},
"editPermission":"",
"purgeOverride":false,
"purgePeriod":1
},
{
"xid":"DS_bcd20bf3-a723-4631-a5ce-77f409e54a46",
"name":"DataQueueReceiver",
"enabled":true,
"type":"HTTP_RECEIVER",
"alarmLevels":{
"SET_POINT_FAILURE":"URGENT"
},
"purgeType":"YEARS",
"setType":"PUBLISHER",
"dateFormat":"DATE_FORMAT_BASIC",
"deviceIdWhiteList":[
"*"
],
"ipWhiteList":[
"*.*.*.*"
],
"setPointUrl":"",
"editPermission":"",
"purgeOverride":false,
"purgePeriod":1
}
],
"dataPoints":[
{
"xid":"DP_97ba3ae9-f9ca-4b36-a557-b07fc89824f1",
"name":"Data Message",
"enabled":true,
"loggingType":"ALL",
"intervalLoggingPeriodType":"MINUTES",
"intervalLoggingType":"INSTANT",
"purgeType":"YEARS",
"pointLocator":{
"dataType":"ALPHANUMERIC",
"binary0Value":"",
"includeTimestamp":true,
"parameterName":"data",
"setPointName":"",
"settable":false
},
"eventDetectors":[
],
"plotType":"STEP",
"rollup":"NONE",
"unit":"",
"simplifyType":"NONE",
"chartColour":"",
"chartRenderer":{
"type":"TABLE",
"limit":10
},
"dataSourceXid":"DS_bcd20bf3-a723-4631-a5ce-77f409e54a46",
"defaultCacheSize":1,
"deviceName":"DataQueueReceiver",
"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":"",
"tags":{
},
"textRenderer":{
"type":"PLAIN",
"useUnitAsSuffix":true,
"unit":"",
"renderedUnit":"",
"suffix":""
},
"tolerance":0.0
},
{
"xid":"DP_60527aba-6569-451a-91a7-9b1c1c940811",
"name":"Queue Timestamp Tracker",
"enabled":true,
"loggingType":"ON_CHANGE",
"intervalLoggingPeriodType":"MINUTES",
"intervalLoggingType":"INSTANT",
"purgeType":"YEARS",
"pointLocator":{
"dataType":"BINARY",
"contextUpdate":false,
"settable":true,
"varName":"tracker"
},
"eventDetectors":[
],
"plotType":"STEP",
"rollup":"NONE",
"unit":"",
"templateXid":"Binary_Default",
"simplifyType":"NONE",
"chartColour":"",
"chartRenderer":{
"type":"TABLE",
"limit":10
},
"dataSourceXid":"DS_306b3fdc-b914-4187-9a4c-0024be71d52e",
"defaultCacheSize":1,
"deviceName":"DataQueueProcessor",
"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":"",
"tags":{
},
"textRenderer":{
"type":"BINARY",
"oneColour":"black",
"oneLabel":"one",
"zeroColour":"blue",
"zeroLabel":"zero"
},
"tolerance":0.0
},
{
"xid":"DP_BaseQueueProcessorNumeric",
"name":"Base Numeric Sensor Point",
"enabled":false,
"loggingType":"ALL",
"intervalLoggingPeriodType":"MINUTES",
"intervalLoggingType":"AVERAGE",
"purgeType":"YEARS",
"pointLocator":{
"dataType":"NUMERIC",
"contextUpdate":false,
"settable":true,
"varName":"notEnabledBaseDataPoint"
},
"eventDetectors":[
],
"plotType":"SPLINE",
"rollup":"NONE",
"unit":"",
"templateXid":"Numeric_All_Data",
"simplifyType":"NONE",
"chartColour":"",
"chartRenderer":{
"type":"IMAGE",
"timePeriodType":"DAYS",
"numberOfPeriods":1
},
"dataSourceXid":"DS_306b3fdc-b914-4187-9a4c-0024be71d52e",
"defaultCacheSize":1,
"deviceName":"DataQueueProcessor",
"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":"",
"tags":{
},
"textRenderer":{
"type":"ANALOG",
"useUnitAsSuffix":true,
"unit":"",
"renderedUnit":"",
"format":"0.00"
},
"tolerance":0.0
}
]
}
Choosing space and the letter z for my value delimiters was arbitrary (but saved me some HTTP encoding in my nc testing by hand). You can use anything properly query-string-encoded as your delimiters except the @ which is used when timestamps are transmitted. You would just need to adjust the script accordingly.
Also I should mention there's an outstanding issue where HTTP receivers don't get messages coming in over IPv6. This means prefer 127.0.0.1 to localhost, as localhost may be ::1 and get bounced by the whitelist. https://github.com/infiniteautomation/ma-core-public/issues/107
Sounding like a reasonable direction?