APIs

Show:

const assert = require("node-opcua-assert").assert;
const _ = require("underscore");

const DataType = require("node-opcua-variant").DataType;
const Variant = require("node-opcua-variant").Variant;
const VariantArrayType = require("node-opcua-variant").VariantArrayType;

const add_dataItem_stuff = require("./UADataItem").add_dataItem_stuff;

const coerceLocalizedText = require("node-opcua-data-model").coerceLocalizedText;
const EnumValueType  = require("node-opcua-data-model").EnumValueType;
const Namespace = require("../namespace").Namespace;


function coerceEnumValues(enumValues) {

    if (_.isArray(enumValues)) {

        //
        return _.map(enumValues, function (en) {
            assert(en.hasOwnProperty("value"));
            assert(en.hasOwnProperty("displayName"));
            return new EnumValueType({
                value: en.value,
                displayName: coerceLocalizedText(en.displayName)
            });
        })
    } else {
        return coerceEnumValues(_.map(enumValues, function (value, key) {

            return new EnumValueType({
                value: value,
                displayName: coerceLocalizedText(key),
                description: coerceLocalizedText(key)
            });
        }));
    }
}

module.exports.install = function (AddressSpace) {

    /**
     *
     * @method addMultiStateValueDiscrete
     * @param options {Object}
     * @param options.browseName {String}
     * @param [options.nodeId  {NodeId}]
     * @param [options.value {UInt32} = 0 }
     * @param options.enumValues { EnumValueType[]| {Key,Value} }
     * @return {Object|UAVariable}
     *
     * @example
     *
     *
     *      namespace.addMultiStateValueDiscrete({
     *          componentOf:parentObj,
     *          browseName: "myVar",
     *          enumValues: {
     *              "Red":    0xFF0000,
     *              "Green":  0x00FF00,
     *              "Blue":   0x0000FF
     *          }
     *      });
     *      addMultiStateValueDiscrete(parentObj,{
     *          browseName: "myVar",
     *          enumValues: [
     *              {
     *                 value: 0xFF0000,
     *                 displayName: "Red",
     *                 description: " The color Red"
     *              },
     *              {
     *                 value: 0x00FF000,
     *                 displayName: "Green",
     *                 description: " The color Green"
     *              },
     *              {
     *                 value: 0x0000FF,
     *                 displayName: "Blue",
     *                 description: " The color Blue"
     *              }
     *
     *          ]
     *      });
     */

    AddressSpace.prototype.addMultiStateValueDiscrete = function (options) {
        return this._resolveRequestedNamespace(options).addMultiStateValueDiscrete(options);
    };
    Namespace.prototype.addMultiStateValueDiscrete = function(options) {

        assert(options.hasOwnProperty("enumValues"));
        assert(!options.hasOwnProperty("ValuePrecision"));

        const namespace = this;
        const addressSpace = namespace.addressSpace;

        const multiStateValueDiscreteType = addressSpace.findVariableType("MultiStateValueDiscreteType");
        assert(multiStateValueDiscreteType, "expecting MultiStateValueDiscreteType to be defined , check nodeset xml file");

        // todo : if options.typeDefinition is specified, check that type is SubTypeOf MultiStateDiscreteType

        // EnumValueType
        //   value: Int64, displayName: LocalizedText, Description: LocalizedText
        const enumValues = coerceEnumValues(options.enumValues);


        options.value = ( options.value === undefined) ? enumValues[0].value : options.value;

        let cloned_options = _.clone(options);
        cloned_options = _.extend(cloned_options,{
            typeDefinition: multiStateValueDiscreteType.nodeId,
            dataType:       "Number",
            // valueRank:
            // note : OPCUA Spec 1.03 specifies -1:Scalar (part 8 page 8) but nodeset file specifies -2:Any
            valueRank:       -1, // -1 : Scalar
            value:           new Variant({dataType: DataType.UInt32, value: options.value})
        });

        const variable = namespace.addVariable(cloned_options);

        add_dataItem_stuff(variable, options);


        namespace.addVariable({
            propertyOf: variable,
            typeDefinition: "PropertyType",
            browseName: {name:"EnumValues",namespaceIndex:0},
            dataType: "EnumValueType",
            accessLevel: "CurrentRead",
            userAccessLevel: "CurrentRead",
            minimumSamplingInterval: 0,
            value: new Variant({
                dataType: DataType.ExtensionObject,
                arrayType: VariantArrayType.Array,
                value: enumValues
            }),
            modellingRule: options.modellingRule ? "Mandatory" : undefined

        });

        // construct an index to quickly find a EnumValue from a value
        const enumValueIndex = {};
        enumValues.forEach(function (e) {
            enumValueIndex[e.value[1]] = e;
        });


        variable.enumValues._index = enumValueIndex;

        function findValueAsText(value) {
            assert(!(value instanceof Variant));
            let valueAsText = "Invalid";
            if (enumValueIndex[value]) {
                valueAsText = enumValueIndex[value].displayName;
            }
            const result = new Variant({
                dataType: DataType.LocalizedText,
                value: coerceLocalizedText(valueAsText)
            });
            return result;

        }

        const valueAsText = findValueAsText(options.value);

        namespace.addVariable({
            propertyOf: variable,
            typeDefinition: "PropertyType",
            browseName: {name:"ValueAsText",namespaceIndex: 0},
            dataType: "LocalizedText",
            accessLevel: "CurrentRead",
            userAccessLevel: "CurrentRead",
            minimumSamplingInterval: 0,
            value: valueAsText,
            modellingRule: options.modellingRule ? "Mandatory" : undefined
        });

        // install additional helpers methods
        variable.install_extra_properties();


        function install_synchronisation(variable) {
            variable.on("value_changed", function (value, indexRange) {
                const valueAsText = findValueAsText(value.value.value);
                variable.valueAsText.setValueFromSource(valueAsText);
            });
        }

        install_synchronisation(variable);

        // replace clone
        const old_clone = variable.clone;
        assert(_.isFunction(old_clone));

        function new_clone() {
            const self = this;
            const variable = old_clone.apply(self, arguments);
            install_synchronisation(variable);
            return variable;
        }

        variable.clone = new_clone;

        assert(variable.enumValues.browseName.toString() === "EnumValues");
        assert(variable.valueAsText.browseName.toString() === "ValueAsText");
        return variable;

    };
};