Source: webpda-cs.js

/*******************************************************************************
 * 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;
	};

}());