/*******************************************************************************
* Copyright (c) 2013 Oak Ridge National Laboratory.
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
* http://www.eclipse.org/legal/epl-v10.html
******************************************************************************/
/**
* Extension of WebPDA for control system data protocol such as the one in
* EPICS. WebPAD_CS is the namespace.
*
* @namespace
*
* @version 1.0.0
*
* @author Xihui Chen
*/
WebPDA_CS={};
(function() {
/**
* Create a control system PV.
* @param {string} name name of the PV.
* @param {number} minUpdatePeriodInMs the minimum update period in millisecond.
* @param {boolean} bufferAllValues if all values should be buffered during the update period.
* @returns the pv.
*/
WebPDA.prototype.createPV = function(name, minUpdatePeriodInMs,
bufferAllValues) {
var pvObj = {
pvName : name,
parameters : {
minUpdatePeriodInMs : minUpdatePeriodInMs,
bufferAllValues : bufferAllValues
}
};
var compareFunc = function(src, target) {
if (target == null || target == undefined)
return false;
if (src.pvName != target.pvName)
return false;
if (src.parameters.minUpdatePeriodInMs != target.parameters.minUpdatePeriodInMs)
return false;
if (src.parameters.bufferAllValues != target.parameters.bufferAllValues)
return false;
return true;
};
return this.internalCreatePV(name, pvObj, compareFunc, bufferAllValues);
};
WebPDA.prototype.processJsonForPV = function(internalPV, json) {
switch (json.e) {
case "conn":
internalPV.connected = json.d;
break;
case "val":
internalPV.value = processSingleValueBinary({
binData : json.d,
startIndex : 8
}, internalPV.value);
break;
case "bufVal":
internalPV.allBufferedValues = [];
var wrappedBinData = {
binData : json.d,
startIndex : 8
};
while (wrappedBinData.startIndex<json.d.byteLength-1) {
internalPV.value = processSingleValueBinary(wrappedBinData, internalPV.value);
internalPV.allBufferedValues.push(internalPV.value.clone());
}
break;
case "writePermission":
internalPV.writeAllowed = json.d;
break;
default:
break;
}
if (WebPDA_Debug)
console.log(this);
};
/**
* Convert a json represented value to V... type value
*
* @param wrappedBinData
* single value frame and the start index of this frame.
* The start index will move to next frame after processing.
* @param currentValue
* current value of the PV.
* @returns the converted value.
*/
function processSingleValueBinary(wrappedBinData, currentValue) {
var binData = wrappedBinData.binData;
var start = wrappedBinData.startIndex;
var jsonLength = new Int16Array(binData,start, 1)[0];//new DataView(binData).getInt16(start, true);
var uint8Array = new Uint8Array(binData,start + 4, jsonLength);
var jsonString = WebPDA_Util.decodeUTF8Array(uint8Array);// String.fromCharCode.apply(null,array);
var valueJson = JSON.parse(jsonString);
for ( var prop in valueJson) {
var propValue = valueJson[prop];
switch (prop) {
case "type":
// This is a new type. Start a new value
currentValue = createValue(valueJson.type);
break;
case "t":
currentValue.timestamp = parseTimestamp(propValue);
break;
case "v":
currentValue.parseBinaryValue(propValue);
break;
case "sev":
currentValue.severity = propValue;
break;
case "an":
currentValue.alarmName = propValue;
break;
case "dl":
currentValue.display.displayLow = propValue;
break;
case "dh":
currentValue.display.displayHigh = propValue;
break;
case "wl":
currentValue.display.warnLow = propValue;
break;
case "wh":
currentValue.display.warnHigh = propValue;
break;
case "al":
currentValue.display.alarmLow = propValue;
break;
case "ah":
currentValue.display.alarmHigh = propValue;
break;
case "prec":
currentValue.display.precision = propValue;
break;
case "units":
currentValue.display.units = propValue;
break;
case "labels":
currentValue.labels = propValue;
break;
case "len":
currentValue.length = propValue;
break;
default:
throw new Error("Unkown Json Property: " + prop);
break;
}
}
var nextStart = jsonLength + start + 4 + currentValue.getBinValueLength();
if(currentValue.getBinValueLength()>0){
currentValue.parseBinaryValue(binData,jsonLength + start + 4);
}
wrappedBinData.startIndex=nextStart;
return currentValue;
}
function createValue(type) {
switch (type) {
case "VDouble":
case "VFloat":
case "VLong":
case "VInt":
case "VShort":
case "VByte":
return new WebPDA_CS.VNumber(type);
case "VString":
return new WebPDA_CS.VString(type);
case "VEnum":
return new WebPDA_CS.VEnum(type);
case "VDoubleArray":
case "VFloatArray":
case "VLongArray":
case "VIntArray":
case "VShortArray":
case "VByteArray":
return new WebPDA_CS.VNumberArray(type);
case "VStringArray":
return new WebPDA_CS.VStringArray(type);
case "VEnumArray":
return new WebPDA_CS.VEnumArray(type);
default:
throw new Error("Unknown data type: " + type);
break;
}
}
/**
* Timestamp that represents the time stamp of the pv value.
* @constructor
* @param {number} sec seconds since Unix Epoch (1 January 1970 00:00:00 UTC) .
* @param {number} nanoSec nano second part.
*/
WebPDA_CS.Timestamp = function (sec, nanoSec) {
/** Seconds part.
* @type {number}*/
this.sec = sec;
/** Nanoseconds part.
* @type {number} */
this.nanoSec = nanoSec;
this.toString = function() {
return WebPDA_Util.formatDate(this.getDate());
};
};
/**
* Get {@link Date} representation of the timestamp.
* @returns {Date} the date object.
*/
WebPDA_CS.Timestamp.prototype.getDate = function() {
if (this.date == null) {
this.date = new Date(this.sec * 1000 + this.nanoSec / 1000000);
}
return this.date;
};
function parseTimestamp(timeInJson) {
return new WebPDA_CS.Timestamp(timeInJson.s,timeInJson.ns);
}
/**
* Display related information in a PV value.
* @constructor
*/
WebPDA_CS.Display = function() {
/**Lower display limit.
* @type {number}*/
this.displayLow = null;
/**Upper display limit.
* @type {number}*/
this.displayHigh = null;
/**Lower warning limit.
* @type {number}*/
this.warnLow = null;
/**Upper warning limit.
* @type {number}*/
this.warnHigh = null;
/**Lower alarm limit.
* @type {number}*/
this.alarmLow = null;
/**Upper alarm limit.
* @type {number} */
this.alarmHigh = null;
/**Precision.
* @type {number} */
this.precision = null;
/**Units.
* @type {string}*/
this.units = null;
};
/**
* The basic data type which is the root for all other data types.
* @constructor
*/
WebPDA_CS.VBasicType = function(type) {
/**Timestamp field.
* @type {WebPDA_CS.Timestamp} */
this.timestamp = null;
/** Core value field.
* @type {object} */
this.value = null;
/** severity such as NONE, MINOR, MAJOR.
* @type {string}*/
this.severity = null;
/** alarm name.
* @type {string} */
this.alarmName = null;
/**type string that describes the actual data type.
* @type {string} */
this.type = type;
};
WebPDA_CS.VBasicType.prototype.toString = function() {
return "[" + this.type + "] " + this.timestamp + " " + this.value + " "
+ this.severity + " " + this.alarmName;
};
/**
* Clone this data type object without copying the value field.
* @param obj
* @returns {Object}
* @private
*/
WebPDA_CS.VBasicType.prototype.clone = function(){
var r = new Object();
for ( var i in this) {
if (i!="value" && typeof (this[i]) == "object" && this[i] != null)
r[i] = WebPDA_Util.clone(this[i]);
else
r[i] = this[i];
}
return r;
};
/**
* Get length of the binary presentation of the value.
* @private
*/
WebPDA_CS.VBasicType.prototype.getBinValueLength = function() {
throw new Error("This function must be overriden by subclass");
};
/**
* The data type that has its core value as a number.
* @constructor
* @param {string} type type string that describes the type on server side.
* @extends WebPDA_CS.VBasicType
*/
WebPDA_CS.VNumber = function(type) {
WebPDA_CS.VBasicType.call(this, type);
/**Display field.
* @type {WebPDA_CS.Display}*/
this.display = new WebPDA_CS.Display();
};
WebPDA_CS.VNumber.prototype = new WebPDA_CS.VBasicType;
WebPDA_CS.VNumber.prototype.parseBinaryValue = function(binData, offset) {
switch (this.type) {
case "VDouble":
case "VLong":
/** Number value field.
* @type {number} */
this.value = new Float64Array(binData, offset, 1)[0];
break;
case "VFloat":
this.value = new Float32Array(binData, offset, 1)[0];
break;
case "VInt":
this.value = new Int32Array(binData, offset, 1)[0];
break;
case "VShort":
this.value = new Int16Array(binData, offset, 1)[0];
break;
case "VByte":
this.value = new Int8Array(binData, offset, 1)[0];
break;
default:
throw new Error("This is not a VNumber type: " + type);
break;
}
};
WebPDA_CS.VNumber.prototype.getBinValueLength = function() {
switch (this.type) {
case "VDouble":
case "VLong":
return 8;
case "VFloat":
case "VInt":
return 4;
case "VShort":
return 2;
case "VByte":
return 1;
default:
throw new Error("This is not a VNumber type: " + type);
break;
}
};
/**
* The data type that has its core value as a number array.
* @constructor
* @param {string} type type string that describes the type on server side.
* @extends WebPDA_CS.VNumber
*/
WebPDA_CS.VNumberArray=function (type) {
WebPDA_CS.VNumber.call(this, type);
/** Array length.
* @type {number} */
this.length=null;
};
WebPDA_CS.VNumberArray.prototype = new WebPDA_CS.VNumber;
WebPDA_CS.VNumberArray.prototype.parseBinaryValue = function(binData, offset) {
switch (this.type) {
case "VDoubleArray":
case "VLongArray":
/** Array value field.
* @type {number[]} */
this.value = new Float64Array(binData, offset, this.length);
break;
case "VFloatArray":
this.value = new Float32Array(binData, offset, this.length);
break;
case "VIntArray":
this.value = new Int32Array(binData, offset, this.length);
break;
case "VShortArray":
this.value = new Int16Array(binData, offset, this.length);
break;
case "VByteArray":
this.value = new Int8Array(binData, offset, this.length);
break;
default:
throw new Error("This is not a VNumberArray type: " + type);
break;
}
};
WebPDA_CS.VNumberArray.prototype.getBinValueLength = function() {
switch (this.type) {
case "VDoubleArray":
case "VLongArray":
return 8*this.length;
case "VIntArray":
case "VFloatArray":
return 4*this.length;
case "VShortArray":
return 2*this.length;
case "VByteArray":
return this.length;
default:
throw new Error("This is not a VNumberArray type: " + type);
break;
}
};
WebPDA_CS.VNumberArray.prototype.toString = function() {
return "[" + this.type + "] " + this.timestamp + " ["
+ this.value.length + " " + this.value[0] + "..."
+ this.value[this.value.length - 1] + "] " + this.severity
+ " " + this.alarmName;
};
/**
* The data type that has its core value as a string.
* @constructor
* @param {string} type type string that describes the type on server side.
* @extends WebPDA_CS.VBasicType
*/
WebPDA_CS.VString = function(type) {
WebPDA_CS.VBasicType.call(this, type);
};
WebPDA_CS.VString.prototype = new WebPDA_CS.VBasicType;
WebPDA_CS.VString.prototype.parseBinaryValue = function(jsonValue) {
/** String value field.
* @type {string} */
this.value = jsonValue;
};
WebPDA_CS.VString.prototype.getBinValueLength = function() {
return 0;
};
/**
* The data type that has its core value as a string array.
* @constructor
* @param {string} type type string that describes the type on server side.
* @extends WebPDA_CS.VString
*/
WebPDA_CS.VStringArray = function(type) {
WebPDA_CS.VString.call(this, type);
/** String array value field.
* @type {string[]} */
this.value = jsonValue;
};
WebPDA_CS.VStringArray.prototype = new WebPDA_CS.VString;
/**
* The data type that has its core value as a number,
* which represents a index in <code>labels</code> array.
* @constructor
* @param {string} type type string that describes the type on server side.
* @extends WebPDA_CS.VBasicType
*/
WebPDA_CS.VEnum = function(type) {
WebPDA_CS.VBasicType.call(this, type);
/** All labels for the enum.
* @type {string[]}*/
this.labels = [];
};
WebPDA_CS.VEnum.prototype = new WebPDA_CS.VBasicType;
WebPDA_CS.VEnum.prototype.parseBinaryValue = function(binData, offset) {
/** Index value field.
* @type {number} */
this.value = new Int32Array(binData, offset,1)[0];
};
WebPDA_CS.VEnum.prototype.getBinValueLength = function() {
return 4;
};
WebPDA_CS.VEnum.prototype.toString = function() {
return "[" + this.type + "] " + this.timestamp + " "
+ this.labels[this.value] + " " + this.alarmName;
};
/**
* The data type that has its core value as a number array, in which every
* element represents a index in <code>labels</code> array.
* @constructor
* @param {string} type type string that describes the type on server side.
* @extends WebPDA_CS.VEnum
*/
WebPDA_CS.VEnumArray = function(type) {
WebPDA_CS.VEnum.call(this, type);
};
WebPDA_CS.VEnumArray.prototype = new WebPDA_CS.VEnum;
WebPDA_CS.VEnumArray.prototype.parseBinaryValue = function(binData, offset) {
/** Index array value field.
* @type {number[]} */
this.value = new Int32Array(binData, offset, this.length);
};
WebPDA_CS.VEnumArray.prototype.getBinValueLength = function() {
return this.length*4;
};
WebPDA_CS.VEnumArray.prototype.toString = function() {
return "[" + this.type + "] " + this.timestamp + " ["
+ this.value.length + " " + (this.value) + this.value[0]
+ "..." + this.value[this.value.length - 1] + "] "
+ this.severity + " " + this.alarmName;
};
}());