APIs

Show:
"use strict";

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

var path = require("path");
var fs = require("fs");

var resolveNodeId = require("node-opcua-nodeid").resolveNodeId;
var AddressSpace = require("node-opcua-address-space").AddressSpace;
var UADataType = require("node-opcua-address-space").UADataType;

var normalize_require_file = require("node-opcua-utils").normalize_require_file;
var LineFile = require("node-opcua-utils/src/linefile").LineFile;
var lowerFirstLetter = require("node-opcua-utils").lowerFirstLetter;

var hasConstructor = require("node-opcua-factory").hasConstructor;
var getConstructor = require("node-opcua-factory").getConstructor;
var hasEnumeration  =require("node-opcua-factory").hasEnumeration;
var getEnumeration  =require("node-opcua-factory").getEnumeration;

var crypto = require("crypto");

function hashNamespace(namespaceUri) {
    var hash =  crypto.createHash("sha1").update(namespaceUri).digest("hex");
    return hash;
}


/**
 * returns the location of the  javascript version of the schema  corresponding to schemaName
 * @method getSchemaSourceFile
 * @param namespace {String}
 * @param schemaName {String}
 * @param schema_type {String}  "enum" | "schema"
 * @param schema_folder {String}
 * @return {string}
 * @private
 */
function getSchemaSourceFile(namespace, schemaName, schema_type,schema_folder) {

    assert(schemaName.match(/[a-zA-Z]+/));
    if(!fs.existsSync(schema_folder)){
        throw new Error("Cannot find schema folder  " + schema_folder);
    }
    if (!(schema_type === "enum" || schema_type === "schema" || schema_type === "")) {
        throw new Error(" unexpected schema_type" + schema_type);
    }
    var subfolder = hashNamespace(namespace);

    var root = path.join(schema_folder,"schemas");
    var folder = path.normalize(path.join(root,subfolder));

    if (!fs.existsSync(folder)){
        fs.mkdirSync(folder);
    }
    if (schema_type === "") {
        return path.join(folder, schemaName + ".js");
    } else {
        return path.join(folder, schemaName + "_" + schema_type + ".js");
    }
}

/**
 * convert a nodeset enumeration into a javascript script enumeration code
 * @method generateEnumerationCode
 * @param dataType
 * @param filename {string} the output filename
 *
 */
function generateEnumerationCode(dataType, filename) {
    assert(typeof filename === "string");

    var dataTypeName = dataType.browseName.name.toString();
    assert(!hasEnumeration(dataTypeName));


    // create the enumeration file
    var f = new LineFile();
    f.write("// namespace " + dataType.namespaceUri.toString());
    f.write("var factories  = require(\"node-opcua-factory\");");
    f.write("var makeNodeId = require(\"node-opcua-nodeid\").makeNodeId;");


    f.write("var " + dataTypeName + "_Schema = {");
    f.write("  id:  makeNodeId(" + dataType.nodeId.value + "," + dataType.nodeId.namespace + "),");
    f.write("  name: '" + dataTypeName + "',");
    f.write("  namespace: '" + dataType.nodeId.namespace + "',");

    f.write("  enumValues: {");
    dataType.definition.forEach(function (pair) {
        f.write("     " + pair.name + ": " + pair.value + ",");
    });
    f.write("  }");
    f.write("};");
    f.write("exports." + dataTypeName + "_Schema = " + dataTypeName + "_Schema;");
    f.write("exports." + dataTypeName + " = factories.registerEnumeration(" + dataTypeName + "_Schema);");
    f.save(filename);
}

var QualifiedName = require("node-opcua-data-model").QualifiedName;
/**
 * var dataType = {
 *    browseName: "Color",
 *    definition: [
 *      { name: "Red",  value: 12},
 *      { name: "Blue", value: 11}
 *    ]
 * };
 *
 * makeEnumeration(dataType);
 *
 * @method makeEnumeration
 *
 * @param dataType {Object}
 * @return {*}
 */
function makeEnumeration(dataType,bForce) {

    assert(dataType);
    assert(dataType.hasOwnProperty("browseName"));
    assert(dataType.browseName instanceof QualifiedName);
    assert(_.isArray(dataType.definition));

    var dataTypeName = dataType.browseName.name.toString();
    if (hasEnumeration(dataTypeName)) {
        return getEnumeration(dataTypeName).typedEnum;
    }
    //var Enumeration_Schema = {
    //    id: dataType.nodeId,
    //    name: dataType.browseName.toString(),
    //    enumValues: {}
    //};
    //
    //dataType.definition.forEach(function (pair) {
    //    Enumeration_Schema.enumValues[pair.name] = parseInt(pair.value, 10);
    //});

    var namespace = dataType.namespaceUri;
    var filename = getSchemaSourceFile(namespace,dataType.browseName.toString(), "enum");


    generateEnumerationCode(dataType, filename);

    var relative_filename = normalize_require_file(__dirname, filename);

    return require(relative_filename)[dataType.browseName.toString()];
}

exports.makeEnumeration = makeEnumeration;




function generateStructureCode(namespace,schema,schema_folder) {

    assert(fs.existsSync(schema_folder,"schema folder must exist"));
    var name = schema.name;

    var f = new LineFile();

    f.write("var factories  = require(\"node-opcua-factory\");");
    f.write("var coerceNodeId = require(\"node-opcua-nodeid\").coerceNodeId;");
    f.write("var " + schema.name + "_Schema = {");
    f.write("    id:  coerceNodeId(\'" + schema.id.toString() + "\'),");
    f.write("    name: \"" + name + "\",");
    f.write("    fields: [");
    schema.fields.forEach(function (field) {
        f.write("       {");
        f.write("           name: \"" + field.name + "\",");
        f.write("           fieldType: \"" + field.fieldType + "\"");
        if (field.isArray) {
            f.write("         ,   isArray:" + (field.isArray ? "true" : false));
        }
        if (field.description) {
            f.write("          , documentation:"  +  " \"" + (field.description) + "\" ");
        }
        f.write("       },");
    });
    f.write("        ]");
    f.write("    };");
    f.write("exports." + name + "_Schema = " + name + "_Schema;");
    //xx write("exports."+name+" = factories.registerObject(" + name+"_Schema);");

    var filename = getSchemaSourceFile(namespace,name, "schema",schema_folder);
    f.save(filename);

}

function generateFileCode(namespace,schema,schema_folder) {

    assert(fs.existsSync(schema_folder,"schema folder must exist"));
    assert(typeof namespace === "string");

    var f = new LineFile();

    var name = schema.name;
    var hint = "$node-opcua/schemas/"+hashNamespace(namespace);

    f.write("// namespace " + namespace.toString());
    f.write("var  registerObject = require(\"node-opcua-factory\").registerObject;");
    // f.write("registerObject('_generated_schemas|"+ name + "','_generated_schemas');");
    f.write("registerObject('" + hint + "|" +  name + "');");

    //
    f.write("require(\"" + hint + "/" +  name + "_schema\");");

    // var filename = "../_generated_schemas/_auto_generated_"+ name;
    var filename = "$node-opcua/generated/_auto_generated_" + name;
    f.write("var " + name + " = require(\"" + filename + "\")." + name + ";");
    f.write("exports." + name + " = " + name + ";");

    filename = getSchemaSourceFile(namespace,name, "",schema_folder);
    f.save(filename);
}


function makeStructure(dataType,bForce,schema_folder) {

    assert(fs.existsSync(schema_folder)," schema_folder must exist");

    bForce = !!bForce;

    assert(dataType instanceof UADataType);

    var addressSpace = dataType.addressSpace;
    assert(addressSpace.constructor.name === "AddressSpace");
    assert(addressSpace instanceof AddressSpace);

    var namespaceUri = addressSpace.getNamespaceUri(dataType.nodeId.namespace);

    // istanbul ignore next
    if (!dataType.binaryEncodingNodeId) {
        throw new Error("DataType with name " + dataType.browseName.toString() + " has no binaryEncoding node\nplease check your nodeset file");
    }

    // if binaryEncodingNodeId is in the standard factory => no need to overwrite

    if (!bForce && (hasConstructor(dataType.binaryEncodingNodeId) ||   dataType.binaryEncodingNodeId.namespace === 0)) {
        //xx console.log("Skipping standard constructor".bgYellow ," for dataType" ,dataType.browseName.toString());
        return getConstructor(dataType.binaryEncodingNodeId);
    }

    var schema = constructSchema(addressSpace, dataType);

    generateFileCode(namespaceUri,schema, schema_folder);

    generateStructureCode(namespaceUri,schema);

    var filename = getSchemaSourceFile(namespaceUri,schema.name, "");

    var relative_filename = normalize_require_file(__dirname, filename);

    //xx console.log("xxxxxxxxxxxxxxxxxx => ".green,schema.name,filename.cyan,relative_filename.yellow);

    var constructor = require(relative_filename)[schema.name];
    assert(_.isFunction(constructor), "expecting a constructor here");

    return constructor;
}

exports.makeStructure = makeStructure;

/*= private
 *
 * @example:
 * @example:
 *    var dataType =  {
 *       browseName: "ServerStatusDataType",
 *       definition: [
 *           { name "timeout", dataType: "UInt32" }
 *       ]
 *    };
 * @param dataType {Object}
 * @return {*}
 */
function constructSchema(addressSpace, dataType) {

    var dataTypeName = dataType.browseName.name.toString();
    // remove DataType to get the name of the class
    dataTypeName = dataTypeName.replace(/DataType/, "");

    var schema = {
        id: dataType.binaryEncodingNodeId,
        name: dataTypeName,
        namespace: dataType.nodeId.namespace,
        fields: [
            // { name: "title", fieldType: "UAString" , isArray: false , documentation: "some text"},
        ]
    };
    var enumeration = addressSpace.findDataType("Enumeration");
    assert(enumeration,"Enumeration Type not found: please check your nodeset file");
    var structure = addressSpace.findDataType("Structure");
    assert(structure,"Structure Type not found: please check your nodeset file");

    // construct the fields
    dataType.definition.forEach(function (pair) {

        var dataTypeId = resolveNodeId(pair.dataType);

        var fieldDataType = addressSpace.findNode(dataTypeId);

        if (!fieldDataType) {
            throw new Error(" cannot find description for object " + dataTypeId +
                ". Check that this node exists in the nodeset.xml file");
        }

        //xx console.log("xxxxx dataType",dataType.toString());

        // check if  dataType is an enumeration or a structure or  a basic type
        if (fieldDataType.isSupertypeOf(enumeration)) {
            makeEnumeration(fieldDataType);
        }
        if (fieldDataType.isSupertypeOf(structure)) {
            makeStructure(fieldDataType);
        }

        var dataTypeName = fieldDataType.browseName.toString();
        dataTypeName = dataTypeName.replace(/DataType/, "");

        schema.fields.push({
            name: lowerFirstLetter(pair.name),
            fieldType: dataTypeName,
            isArray: false,
            description: (pair.description ? pair.description.text : "")
        });
    });
    return schema;
}


var nodeset = {
    ServerState: null,
    ServerStatus: null,
    ServiceCounter: null,
    SessionDiagnostics: null
};
exports.nodeset = nodeset;

function registerDataTypeEnum(addressSpace, dataTypeName,bForce) {

    var dataType = addressSpace.findDataType(dataTypeName);
    assert(dataType);
    var superType = addressSpace.findNode(dataType.subtypeOf);
    assert(superType.browseName.toString() === "Enumeration");
    return makeEnumeration(dataType,bForce);
}

function registerDataType(addressSpace, dataTypeName,schema_folder, bForce) {

    if (!fs.existsSync(schema_folder)) {
        throw new Error("schema_folder must exist : " + dataTypeName + " " + schema_folder);
    }
    var dataType = addressSpace.findDataType(dataTypeName + "DataType");
    if (!dataType) {
        dataType = addressSpace.findDataType(dataTypeName);
    }

    // istanbul ignore next
    if (!dataType) {
        console.log("registerDataType: warning : Cannot find DataType " + dataTypeName);
        return null;
        //xx throw new Error(" Cannot find DataType " + dataTypeName);
    }

    var superType = addressSpace.findNode(dataType.subtypeOf);
    assert(superType.browseName.toString() === "Structure");

    // finding object with encoding
    //
    //   <UAObject NodeId="i=864" BrowseName="Default Binary" SymbolicName="DefaultBinary">
    //   <DisplayName>Default Binary</DisplayName>
    //   <References>
    //       <Reference ReferenceType="HasEncoding" IsForward="false">i=862</Reference>
    return makeStructure(dataType,bForce,schema_folder);
}


/**
 * creates the requested data structure and javascript objects for the OPCUA objects
 * @method createExtensionObjectDefinition
 * @param addressSpace {AddressSpace}
 */
var createExtensionObjectDefinition = function (addressSpace) {

    assert(addressSpace instanceof AddressSpace);
    var force = true;
    // nodeset.ApplicationDescription = nodeset.ApplicationDescription || registerDataType(addressSpace, "ApplicationDescription",force);
    nodeset.ServerState = nodeset.ServerState || registerDataTypeEnum(addressSpace, "ServerState", force);
    nodeset.ServerStatus = nodeset.ServerStatus || registerDataType(addressSpace, "ServerStatus", force);
    //xx nodeset.ServiceCounter = nodeset.ServiceCounter || registerDataType(addressSpace, "ServiceCounter", force);
    //xx nodeset.SessionDiagnostics = nodeset.SessionDiagnostics || registerDataType(addressSpace, "SessionDiagnostics",force);
    //xx nodeset.ServerDiagnosticsSummary = nodeset.ServerDiagnosticsSummary || registerDataType(addressSpace, "ServerDiagnosticsSummary",force);
    //xx nodeset.SubscriptionDiagnostics = nodeset.SubscriptionDiagnostics || registerDataType(addressSpace,"SubscriptionDiagnostics",force);
    //xx nodeset.ModelChangeStructure = nodeset.ModelChangeStructure || registerDataType(addressSpace,"ModelChangeStructure",force);
    //xx nodeset.SemanticChangeStructure = nodeset.SemanticChangeStructure || registerDataType(addressSpace,"SemanticChangeStructure",force);
    //xx nodeset.RedundantServer = nodeset.RedundantServer || registerDataType(addressSpace,"RedundantServer",force);
    //xx nodeset.SamplingIntervalDiagnostics = nodeset.SamplingIntervalDiagnostics || registerDataType(addressSpace,"SamplingIntervalDiagnostics",force);
    //xx nodeset.SessionSecurityDiagnostics = nodeset.SessionSecurityDiagnostics || registerDataType(addressSpace,"SessionSecurityDiagnostics",force);

    /**
     *  This Structured DataType defines the local time that may or may not take daylight saving time
     *  into account. Its elements are described in Table 24.
     *  Table 24 – TimeZoneDataType Definition
     *  Name                       Type       Description
     *  TimeZoneDataType structure
     *  offset                     Int16     The offset in minutes from UtcTime
     *  daylightSavingInOffset     Boolean   If TRUE, then daylight saving time (DST) is in effect and offset
     *                                       includes the DST correction. If FALSE then the offset does not
     *                                       include the DST correction and DST may or may not have
     *                                       been in effect.
     */
    //xx nodeset.TimeZoneDataType = nodeset.TimeZoneDataType || registerDataType(addressSpace, "TimeZoneDataType", force);
};

exports.createExtensionObjectDefinition = createExtensionObjectDefinition;