/**
* @copyright 2015 Tridium, Inc. All Rights Reserved.
* @author Gareth Johnson
*/
/**
* Defines {@link baja.Status}.
* @module baja/obj/Status
*/
define([ "bajaScript/sys",
"bajaScript/baja/obj/Facets",
"bajaScript/baja/obj/Simple",
"bajaScript/baja/obj/objUtil",
"lex!" ], function (
baja,
Facets,
Simple,
objUtil,
lexjs) {
"use strict";
var subclass = baja.subclass,
callSuper = baja.callSuper,
objectify = baja.objectify,
bajaDef = baja.def,
bajaHasType = baja.hasType,
strictArg = baja.strictArg,
strictAllArgs = baja.strictAllArgs,
cacheDecode = objUtil.cacheDecode,
cacheEncode = objUtil.cacheEncode,
facetsDefault = Facets.DEFAULT,
statusNullLookup;
// Revisit after NCCB-24257 with a type extension on control:PriorityLevel
/**
* Converts the given control:PriorityLevel to a string representation.
*
* @param {baja.FrozenEnum} priorityLevel a `control:PriorityLevel`
* @param {String} fallbackStr used if the PriorityLevel is the fallback slot
* @inner
* @returns {String|Promise<String>}
*/
function toPriorityLevelString(priorityLevel, fallbackStr) {
if (!baja.hasType(priorityLevel, 'baja:DynamicEnum')) { return null; }
var frozen = priorityLevel.getRange().getFrozenType();
if (frozen && frozen.is('control:PriorityLevel')) {
var frozenEnum = baja.$('control:PriorityLevel').get(priorityLevel.getOrdinal());
if (frozenEnum.getTag() !== 'fallback') {
return frozenEnum.getDisplayTag();
}
return fallbackStr || "def";
}
}
function toStringSync(str, facets, fallbackStr) {
var keys = facets.getKeys(),
i;
for (i = 0; i < keys.length; i++) {
var key = keys[i],
val = facets.get(key);
if (key === 'activeLevel') {
str += ' @ ' + (toPriorityLevelString(val, fallbackStr) || val);
} else if (val === true) {
str += ' ' + key;
} else {
str += ' ' + key + '=' + val;
}
}
return str;
}
/**
* Represents a `baja:Status` in BajaScript.
*
* `Status` provides a bit mask for various standardized
* status flags in the Baja control architecture. Plus
* it provides for arbitrary extensions using `BFacets`.
*
* When creating a `Simple`, always use the `make()` method instead of
* creating a new Object.
*
* @class
* @alias baja.Status
* @extends baja.Simple
*/
var Status = function Status(bits, facets) {
// Constructor should be considered private
callSuper(Status, this, arguments);
this.$bits = bits;
this.$facets = facets;
};
subclass(Status, Simple);
/**
* Make a `Status`.
*
* @param {Object|Number} obj the Object Literal that specifies the method's arguments or Status bits.
* @param {Number} obj.bits the Status bits.
* @param {baja.Facets} [obj.facets] the facets for the Status.
* @param {baja.Status} [obj.orig] if defined, obj.state must also be defined. This is used to create
* a new Status with one of it's bit states changed (see example above).
* @param {Boolean} [obj.state] the state of the bit to change (used in conjunction with obj.orig).
* @returns {baja.Status} the status.
*
* @example
* //The bits (Number) or (for more arguments) an Object Literal can be
* //used to specify the method's arguments.
* var st1 = baja.Status.make(baja.Status.DOWN | baja.Status.FAULT);
*
* // ... or for more arguments...
*
* var st2 = baja.Status.make({
* bits: baja.Status.DOWN,
* facets: facets
* });
*
* //The make() method can also be used to create a new status with its
* //state changed...
*
* var newStatus = baja.Status.make({
* orig: oldStatus,
* bits: baja.Status.OVERRIDDEN,
* state: true
* }};
*/
Status.make = function (obj) {
obj = objectify(obj, "bits");
var orig = obj.orig,
bits = obj.bits,
state = obj.state,
facets = obj.facets,
nullLookup = Status.$nullLookup;
// If the bits are a baja.Status then get the bits
if (bajaHasType(bits) && bits.getType().equals(Status.DEFAULT.getType())) {
bits = bits.getBits();
}
strictArg(bits, Number);
// Make with original bits...
if (orig !== undefined) {
strictAllArgs([ orig, state ], [ Status, Boolean ]);
bits = state ? (orig.$bits | bits) : (orig.$bits & ~bits);
if (bits === 0 && orig.$facets === facetsDefault) {
return Status.ok;
}
if (orig.$bits === bits) {
return orig;
}
facets = orig.$facets;
}
// Standard make...
facets = bajaDef(facets, facetsDefault);
strictArg(facets, Facets);
if (facets === facetsDefault) {
if (bits === 0) {
return Status.DEFAULT;
}
if (bits <= 256) {
if (!(nullLookup.hasOwnProperty(bits))) {
nullLookup[bits] = new Status(bits, facets);
}
return nullLookup[bits];
}
}
return new Status(bits, facets);
};
/**
* Make a `Status`.
*
* @param {Object|Number} obj the Object Literal that specifies the method's arguments or Status bits.
* @param {Number} obj.bits the Status bits.
* @param {baja.Facets} [obj.facets] the facets for the Status.
* @param {baja.Status} [obj.orig] if defined, obj.state must also be defined. This is used to create
* a new Status with one of it's bit states changed (see example above).
* @param {Boolean} [obj.state] the state of the bit to change (used in conjunction with obj.orig).
* @returns {baja.Status} the status.
*
* @example
* //The bits (Number) or (for more arguments) an Object Literal can be
* //used to specify the method's arguments.
*
* var st1 = baja.Status.make(baja.Status.DOWN | baja.Status.FAULT);
*
* // ... or for more arguments...
*
* var st2 = baja.$("baja:Status").make({
* bits: baja.Status.DOWN,
* facets: facets
* });
*
* //The make method can also be used to create a new status with its
* //state changed...
*
* var newStatus = baja.$("baja:Status").make({
* orig: oldStatus,
* bits: baja.Status.OVERRIDDEN,
* state: true
* }};
*/
Status.prototype.make = function (obj) {
return Status.make.apply(Status, arguments);
};
/**
* Decode a `Status` from a `String`.
*
* @method
* @param {String} str
* @returns {baja.Status}
*/
Status.prototype.decodeFromString = cacheDecode(function (str) {
var x,
semi = str.indexOf(';'),
bits;
if (semi < 0) {
x = Status.make(parseInt(str, 16));
} else {
bits = parseInt(str.substring(0, semi), 16);
x = Status.make({ "bits": bits, "facets": facetsDefault.decodeFromString(str.substring(semi + 1), baja.Simple.$unsafeDecode) });
}
return x;
});
/**
* Encode the `Status` to a `String`.
*
* @method
* @returns {String}
*/
Status.prototype.encodeToString = cacheEncode(function () {
var s = this.$bits.toString(16);
if (this.$facets !== facetsDefault) {
s += ";" + this.$facets.encodeToString();
}
return s;
});
/**
* Returns a string of just the flags which are set or
* returns ok if none are set.
*
* @returns {String}
*/
Status.prototype.flagsToString = function (obj) {
var lex = obj && obj.lex,
flags = [],
bits = this.$bits;
if (!lex || lex.getModuleName() !== 'baja') {
lex = lexjs.getLexiconFromCache('baja');
}
var getDisplayTag = function (tag) {
return lex ? lex.get('Status.' + tag) : tag;
};
// flags to String
if (!bits) { return getDisplayTag('ok'); }
if (bits & Status.DISABLED) { flags.push(getDisplayTag('disabled')); }
if (bits & Status.FAULT) { flags.push(getDisplayTag('fault')); }
if (bits & Status.DOWN) { flags.push(getDisplayTag('down')); }
if (bits & Status.ALARM) { flags.push(getDisplayTag('alarm')); }
if (bits & Status.STALE) { flags.push(getDisplayTag('stale')); }
if (bits & Status.OVERRIDDEN) { flags.push(getDisplayTag('overridden')); }
if (bits & Status.NULL) { flags.push(getDisplayTag('null')); }
if (bits & Status.UNACKED_ALARM) { flags.push(getDisplayTag('unackedAlarm')); }
return flags.join(',');
};
/**
* Returns the string representation of the 'Status'.
*
* This method is invoked synchronously. The string result will be returned
* directly from this function.
*
* **Notes on lexicons:**
*
* * A lexicon will be used if it is passed in.
* * If no lexicon is passed in, the baja lexicon will be used if it has been
* cached locally.
* * If the baja lexicon has not been cached, strings units will be
* represented by their internal tag names (which are in English).
*
* @param {Object} obj the Object Literal for the method's arguments.
* @param [obj.lex] the 'baja' lexicon
*
* @returns {String|Promise.<String>}
*/
Status.prototype.toString = function (obj) {
var str = '{' + this.flagsToString(obj) + '}',
facets = this.$facets;
// facets to String
if (facets !== facetsDefault) {
if (!obj) {
return toStringSync(str, facets, "def");
}
return baja.lex({ module: 'control' })
.then(function (controlLex) {
return toStringSync(str, facets, controlLex.get('def'));
});
}
return str;
};
/**
* Equality test.
*
* @param obj
* @returns {Boolean}
*/
Status.prototype.equals = function (obj) {
if (bajaHasType(obj) && obj.getType().equals(this.getType())) {
if (this.$facets === facetsDefault) {
return obj.$bits === this.$bits;
}
return obj.$bits === this.$bits && this.$facets.equals(obj.$facets);
}
return false;
};
/**
* Default `Status` instance.
* @type {baja.Status}
*/
Status.DEFAULT = new Status(0, facetsDefault);
// If the facets are null then the Status is interned into this Object map
statusNullLookup = Status.$nullLookup = {};
/**
* Bit for disabled.
* @type {Number}
*/
Status.DISABLED = 0x0001;
/**
* Bit for fault.
* @type {Number}
*/
Status.FAULT = 0x0002;
/**
* Bit for down.
* @type {Number}
*/
Status.DOWN = 0x0004;
/**
* Bit for alarm.
* @type {Number}
*/
Status.ALARM = 0x0008;
/**
* Bit for stale.
* @type {Number}
*/
Status.STALE = 0x0010;
/**
* Bit for overridden.
* @type {Number}
*/
Status.OVERRIDDEN = 0x0020;
/**
* Bit for null.
* @type {Number}
*/
Status.NULL = 0x0040;
/**
* Bit for unacked alarm.
* @type {Number}
*/
Status.UNACKED_ALARM = 0x0080;
/**
* String used in a `Facets` for identifying the active priority level of a
* writable point.
* @type {String}
*/
Status.ACTIVE_LEVEL = "activeLevel";
/**
* `Status` for ok (null facets).
* @type {baja.Status}
*/
Status.ok = Status.DEFAULT;
/**
* `Status` for disabled (null facets).
* @type {baja.Status}
*/
Status.disabled = statusNullLookup[Status.DISABLED] = new Status(Status.DISABLED, facetsDefault);
/**
* `Status` for fault (null facets).
* @type {baja.Status}
*/
Status.fault = statusNullLookup[Status.FAULT] = new Status(Status.FAULT, facetsDefault);
/**
* `Status` for down (null facets).
* @type {baja.Status}
*/
Status.down = statusNullLookup[Status.DOWN] = new Status(Status.DOWN, facetsDefault);
/**
* `Status` for alarm (null facets).
* @type {baja.Status}
*/
Status.alarm = statusNullLookup[Status.ALARM] = new Status(Status.ALARM, facetsDefault);
/**
* `Status` for stale (null facets).
* @type {baja.Status}
*/
Status.stale = statusNullLookup[Status.STALE] = new Status(Status.STALE, facetsDefault);
/**
* `Status` for overridden (null facets).
* @type {baja.Status}
*/
Status.overridden = statusNullLookup[Status.OVERRIDDEN] = new Status(Status.OVERRIDDEN, facetsDefault);
/**
* `Status` for null status (null facets).
* @type {baja.Status}
*/
Status.nullStatus = statusNullLookup[Status.NULL] = new Status(Status.NULL, facetsDefault);
/**
* `Status` for unacked alarm (null facets).
* @type {baja.Status}
*/
Status.unackedAlarm = statusNullLookup[Status.UNACKED_ALARM] = new Status(Status.UNACKED_ALARM, facetsDefault);
/**
* Return the facets for the `Status`.
*
* @returns {baja.Facets} status facets
*/
Status.prototype.getFacets = function () {
return this.$facets;
};
/**
* Return a value from the status facets.
*
* @param {String} name the name of the value to get from the status facets.
* @param [def] if defined, this value is returned if the name can't be found in
* the status facets.
* @returns the value from the status facets (null if def is undefined and name can't be found).
*/
Status.prototype.get = function (name, def) {
return this.$facets.get(name, def);
};
/**
* Return the `Status` bits.
*
* @returns {Number} status bits.
*/
Status.prototype.getBits = function () {
return this.$bits;
};
/**
* Return true if the `Status` is not disabled, fault, down
* stale or null.
*
* @returns {Boolean} true if valid.
*/
Status.prototype.isValid = function () {
return (((this.$bits & Status.DISABLED) === 0) &&
((this.$bits & Status.FAULT) === 0) &&
((this.$bits & Status.DOWN) === 0) &&
((this.$bits & Status.STALE) === 0) &&
((this.$bits & Status.NULL) === 0));
};
/**
* Return true if the `Status` is ok.
*
* @returns {Boolean}
*/
Status.prototype.isOk = function () {
return this.$bits === 0;
};
/**
* Return true if the `Status` is disabled.
*
* @returns {Boolean}
*/
Status.prototype.isDisabled = function () {
return (this.$bits & Status.DISABLED) !== 0;
};
/**
* Return true if the `Status` is in fault.
*
* @returns {Boolean}
*/
Status.prototype.isFault = function () {
return (this.$bits & Status.FAULT) !== 0;
};
/**
* Return true if the `Status` is down.
*
* @returns {Boolean}
*/
Status.prototype.isDown = function () {
return (this.$bits & Status.DOWN) !== 0;
};
/**
* Return true if the `Status` is in alarm.
*
* @returns {Boolean}
*/
Status.prototype.isAlarm = function () {
return (this.$bits & Status.ALARM) !== 0;
};
/**
* Return true if the `Status` is stale.
*
* @returns {Boolean}
*/
Status.prototype.isStale = function () {
return (this.$bits & Status.STALE) !== 0;
};
/**
* Return true if the `Status` is overridden.
*
* @returns {Boolean}
*/
Status.prototype.isOverridden = function () {
return (this.$bits & Status.OVERRIDDEN) !== 0;
};
/**
* Return true if the `Status` is null.
*
* @returns {Boolean}
*/
Status.prototype.isNull = function () {
return (this.$bits & Status.NULL) !== 0;
};
/**
* Return true if the `Status` is unacked alarm.
*
* @returns {Boolean}
*/
Status.prototype.isUnackedAlarm = function () {
return (this.$bits & Status.UNACKED_ALARM) !== 0;
};
/**
* Return the status (itself).
*
* @returns {baja.Status} the status (itself).
*/
Status.prototype.getStatus = function () {
return this;
};
/**
* @param {number} ordinal
* @returns {boolean} true if this ordinal is set
* @since Niagara 4.8
*/
Status.prototype.getBit = function (ordinal) {
return !!(this.$bits & ordinal);
};
/**
* @returns {Array.<number>} all known Status ordinals
* @since Niagara 4.8
*/
Status.prototype.getOrdinals = function () {
return [
Status.DISABLED,
Status.FAULT,
Status.DOWN,
Status.ALARM,
Status.STALE,
Status.OVERRIDDEN,
Status.NULL,
Status.UNACKED_ALARM
];
};
/**
* @returns {baja.EnumSet} an `EnumSet` representing which bits are selected
* out of the available bits for a `Status`
* @since Niagara 4.8
*/
Status.prototype.toEnumSet = function () {
var that = this,
ordinals = that.getOrdinals(),
range = baja.EnumRange.make({
ordinals: ordinals,
tags: [
'disabled', 'fault', 'down', 'alarm',
'stale', 'overridden', 'null', 'unackedAlarm'
]
});
return baja.EnumSet.make({
ordinals: ordinals.filter(function (ordinal) { return that.getBit(ordinal); }),
range: range
});
};
/**
* Return the status from a `BIStatus`.
*
* @returns {baja.Status} resolved status value
*/
Status.getStatusFromIStatus = function (statusVal) {
var status = statusVal,
type = statusVal.getType(),
out,
hasOut = false;
if (type.isComplex()) {
out = statusVal.get("out");
if (out && out.getType().is("baja:StatusValue")) {
status = out.getStatus();
hasOut = true;
}
}
if (!hasOut && (type.is("baja:StatusValue") || typeof statusVal.getStatus === "function")) {
status = statusVal.getStatus();
}
return status instanceof Status ? status : Status.DEFAULT;
};
return Status;
});