private static void AddRelationshipsBetweenInterfaces() { foreach (Tuple <DtdlInterface, string, string> dtdlInterfaceTuple in _interfaceList) { // find the interface to add a relationship to foreach (Tuple <DtdlInterface, string, string> dtdlInterfaceTupleRelationship in _interfaceList) { if (dtdlInterfaceTuple.Item3 == dtdlInterfaceTupleRelationship.Item2) { DtdlContents dtdlRelationship = new DtdlContents { Type = "Relationship", Name = "Parent", Target = "dtmi:" + dtdlInterfaceTupleRelationship.Item1.DisplayName + ";1", }; if (!dtdlInterfaceTuple.Item1.Contents.Contains(dtdlRelationship)) { dtdlInterfaceTuple.Item1.Contents.Add(dtdlRelationship); } break; } } } }
// OPC UA defines variables, views and objects, as well as associated variabletypes, datatypes, referencetypes and objecttypes // In addition, OPC UA defines methods and properties public static void Generate(UANodeSet nodeSet) { // clear previously generated DTDL _map.Clear(); _interfaceList.Clear(); _contentsList.Clear(); _nodeList.Clear(); _nodesetNamespaceURI = nodeSet.NamespaceUris[0]; CreateSchemaMap(); // create DTDL interfaces and their contents foreach (UANode uaNode in nodeSet.Items) { UAVariable variable = uaNode as UAVariable; if (variable != null) { if (uaNode.BrowseName.ToString() == "InputArguments") { continue; } // check if this node is part of the model bool isPartOfModel = false; foreach (Reference reference in variable.References) { if (reference.ReferenceType == "HasModellingRule") { isPartOfModel = true; break; } } if (isPartOfModel) { // ignore this node continue; } DtdlContents dtdlTelemetry = new DtdlContents { Type = "Telemetry", Name = Regex.Replace(uaNode.BrowseName.ToString().Trim(), "[^A-Za-z]+", ""), Schema = GetDtdlDataType(variable.DataType) }; Tuple <DtdlContents, string> newTuple = new Tuple <DtdlContents, string>(dtdlTelemetry, variable.ParentNodeId); if (!_contentsList.Contains(newTuple)) { _contentsList.Add(newTuple); } Tuple <string, string, string> newNodeTuple; if (variable.BrowseName.Length > 0) { newNodeTuple = new Tuple <string, string, string>(variable.BrowseName, GetDtdlDataType(variable.DataType.ToString()), variable.ParentNodeId ?? ""); } else { newNodeTuple = new Tuple <string, string, string>(variable.NodeId.ToString(), GetDtdlDataType(variable.DataType.ToString()), variable.ParentNodeId ?? ""); } string key = nodeSet.NamespaceUris[0] + "#" + variable.NodeId.ToString().Substring(variable.NodeId.ToString().IndexOf(';') + 1); if (!_nodeList.ContainsKey(key)) { _nodeList.Add(key, newNodeTuple); } continue; } UAMethod method = uaNode as UAMethod; if (method != null) { // check if this node is part of the model bool isPartOfModel = false; foreach (Reference reference in method.References) { if (reference.ReferenceType == "HasModellingRule") { isPartOfModel = true; break; } } if (isPartOfModel) { // ignore this node continue; } DtdlContents dtdlCommand = new DtdlContents { Type = "Command", Name = Regex.Replace(uaNode.BrowseName.ToString().Trim(), "[^A-Za-z]+", "") }; Tuple <DtdlContents, string> newTuple = new Tuple <DtdlContents, string>(dtdlCommand, method.ParentNodeId); if (!_contentsList.Contains(newTuple)) { _contentsList.Add(newTuple); } Tuple <string, string, string> newNodeTuple; if (method.BrowseName.Length > 0) { newNodeTuple = new Tuple <string, string, string>(method.BrowseName, "command", method.ParentNodeId ?? ""); } else { newNodeTuple = new Tuple <string, string, string>(method.NodeId.ToString(), "command", method.ParentNodeId ?? ""); } string key = nodeSet.NamespaceUris[0] + "#" + method.NodeId.ToString().Substring(method.NodeId.ToString().IndexOf(';') + 1); if (!_nodeList.ContainsKey(key)) { _nodeList.Add(key, newNodeTuple); } continue; } UAObject uaObject = uaNode as UAObject; if (uaObject != null) { // check if this node is part of the model bool isPartOfModel = false; foreach (Reference reference in uaObject.References) { if (reference.ReferenceType == "HasModellingRule") { isPartOfModel = true; break; } } if (isPartOfModel) { // ignore this node continue; } DtdlInterface dtdlInterface = new DtdlInterface { Id = "dtmi:" + Regex.Replace(uaNode.BrowseName.ToString().Trim(), "[^A-Za-z]+", "") + ";1", Type = "Interface", DisplayName = Regex.Replace(uaNode.BrowseName.ToString().Trim(), "[^A-Za-z]+", ""), Contents = new List <DtdlContents>() }; Tuple <DtdlInterface, string, string> newTuple = new Tuple <DtdlInterface, string, string>(dtdlInterface, uaObject.NodeId, uaObject.ParentNodeId); if (!_interfaceList.Contains(newTuple)) { _interfaceList.Add(newTuple); } Tuple <string, string, string> newNodeTuple; if (uaObject.BrowseName.Length > 0) { newNodeTuple = new Tuple <string, string, string>(uaObject.BrowseName, "object", uaObject.ParentNodeId ?? ""); } else { newNodeTuple = new Tuple <string, string, string>(uaObject.NodeId.ToString(), "object", uaObject.ParentNodeId ?? ""); } string key = nodeSet.NamespaceUris[0] + "#" + uaObject.NodeId.ToString().Substring(uaObject.NodeId.ToString().IndexOf(';') + 1); if (!_nodeList.ContainsKey(key)) { _nodeList.Add(key, newNodeTuple); } continue; } UAView view = uaNode as UAView; if (view != null) { // we don't map views since DTDL has no such concept continue; } UAVariableType variableType = uaNode as UAVariableType; if (variableType != null) { // we don't map UA variable types, only instances. DTDL only has a limited set of built-in types. continue; } UADataType dataType = uaNode as UADataType; if (dataType != null) { // we don't map UA data types, only instances. DTDL only has a limited set of built-in types. continue; } UAReferenceType referenceType = uaNode as UAReferenceType; if (referenceType != null) { // we don't map UA reference types, only instances. DTDL only has a limited set of built-in types. continue; } UAObjectType objectType = uaNode as UAObjectType; if (objectType != null) { // we don't map UA object (custom) types, only instances. DTDL only has a limited set of built-in types. continue; } throw new ArgumentException("Unknown UA node detected!"); } AddComponentsToInterfaces(); AddRelationshipsBetweenInterfaces(); // generate JSON files foreach (Tuple <DtdlInterface, string, string> dtdlInterfaceTuple in _interfaceList) { string generatedDTDL = JsonConvert.SerializeObject(dtdlInterfaceTuple.Item1, Formatting.Indented); string dtdlPath = Path.Combine(Directory.GetCurrentDirectory(), "JSON", Path.GetFileNameWithoutExtension(dtdlInterfaceTuple.Item1.DisplayName) + ".dtdl.json"); System.IO.File.WriteAllText(dtdlPath, generatedDTDL); } }
/// <summary> /// Generates a DTDL JSON file from an OWL-based RDF file. /// </summary> /// <param name="rdfFile">RDF input file</param> /// <param name="dtdlFile">DTDL output file</param> private static void GenerateDTDL(FileInfo rdfFile, FileInfo dtdlFile) { try { Console.WriteLine("Reading file..."); FileLoader.Load(_ontologyGraph, rdfFile.FullName); // Start looping through for each owl:Class foreach (OntologyClass owlClass in _ontologyGraph.OwlClasses) { // Generate a DTMI for the owl:Class string Id = GenerateDTMI(owlClass); if (!String.IsNullOrEmpty(Id)) { Console.WriteLine($"{owlClass.Resource.ToString()} -> {Id}"); // Create Interface DtdlInterface dtdlInterface = new DtdlInterface { Id = Id, Type = "Interface", DisplayName = GetInterfaceDisplayName(owlClass), Comment = GetInterfaceComment(owlClass), Description = "", Contents = new List <DtdlContents>() }; // Use DTDL 'extends' for super classes IEnumerable <OntologyClass> foundSuperClasses = owlClass.DirectSuperClasses; if (foundSuperClasses.Any()) { List <string> extendsList = new List <string>(); int extendsMax = 0; foreach (var superClass in foundSuperClasses) { // DTDL v2 allows for a maximum of 2 extends. We ignore the other super classes if (extendsMax < 2) { string superClassId = GenerateDTMI(superClass); if (superClassId != null) { extendsList.Add(superClassId); extendsMax++; } } } dtdlInterface.Extends = extendsList; } List <OntologyProperty> properties; // Get list of properties which have this class as a domain properties = owlClass.IsDomainOf.ToList(); foreach (var property in properties) { // An owl:ObjectProperty is used to create a DTDL relationship. if (property.Types.First().ToString() == "http://www.w3.org/2002/07/owl#ObjectProperty") { Console.WriteLine($" Found relationship: {property}"); // If IRI, parse out relationship name from IRI if (property.ToString().Contains("#")) { int index = property.ToString().LastIndexOf("#"); property.AddLabel(property.ToString().Substring(index + 1)); } else if (property.ToString().Contains("/")) { int index = property.ToString().LastIndexOf("/"); property.AddLabel(property.ToString().Substring(index + 1)); } // Create relationship DtdlContents dtdlRelationship = new DtdlContents { Name = Trim(property.ToString()), Type = "Relationship", DisplayName = GetRelationshipDisplayName(property), Comment = GetComment(property) }; // DTDL only supports a single target Id. var range = property.Ranges.FirstOrDefault(); if (range == null) { // If no range is found, we omit the DTDL target property. // This allows any Interface to be the target. Console.WriteLine(" No target found."); } else { Console.WriteLine($" Found target: {range}"); // Convert range to DTMI and add to DTDL relationship target. string target = GenerateDTMI(range); dtdlRelationship.Target = target; } // Add relationship to the Interface dtdlInterface.Contents.Add(dtdlRelationship); } // An owl:DatatypeProperty is used to create a DTDL property. if (property.Types.First().ToString() == "http://www.w3.org/2002/07/owl#DatatypeProperty") { Console.WriteLine($" Found property: {property}"); // Create property DtdlContents dtdlProperty = new DtdlContents { Name = Trim(property.ToString()), Type = "Property", Schema = _map[property.Ranges.FirstOrDefault().ToString()], Comment = GetComment(property), Writable = true }; // Add the Property to the Interface dtdlInterface.Contents.Add(dtdlProperty); } // An owl:AnnotationProperty is used to create a DTDL property. if (property.Types.First().ToString() == "http://www.w3.org/2002/07/owl#AnnotationProperty") { Console.WriteLine($" Found property: {property}"); // Create property DtdlContents dtdlProperty = new DtdlContents { Name = Trim(property.ToString()), Type = "Property", // TODO: Lookup actual data type and create complex DTDL schema or map to DTDL semantic type Schema = "float", Comment = GetComment(property), Writable = true }; // Add the Property to the Interface dtdlInterface.Contents.Add(dtdlProperty); } } // Add the DTDL context to the Interface dtdlInterface.Context = _context; // Add interface to the list of interfaces _interfaceList.Add(dtdlInterface); } } if (_interfaceList.Count == 0) { throw new Exception("No OWL:Classes found."); } // Serialize to JSON var json = JsonConvert.SerializeObject(_interfaceList); // Save to file System.IO.File.WriteAllText(dtdlFile.ToString(), json); Console.WriteLine($"DTDL written to: {dtdlFile}"); // Run DTDL validation Console.WriteLine("Validating DTDL..."); ModelParser modelParser = new ModelParser(); List <string> modelJson = new List <string>(); modelJson.Add(json); IReadOnlyDictionary <Dtmi, DTEntityInfo> parseTask = modelParser.ParseAsync(modelJson).GetAwaiter().GetResult(); } catch (ParsingException pe) { Console.WriteLine($"*** Error parsing models"); int errCount = 1; foreach (ParsingError err in pe.Errors) { Console.WriteLine($"Error {errCount}:"); Console.WriteLine($"{err.Message}"); Console.WriteLine($"Primary ID: {err.PrimaryID}"); Console.WriteLine($"Secondary ID: {err.SecondaryID}"); Console.WriteLine($"Property: {err.Property}\n"); errCount++; } } catch (Exception e) { Console.WriteLine($"{e.Message}"); } Console.WriteLine($"Finished!"); }