diff --git a/semantic-model/datamodel/tools/getSubcomponents.js b/semantic-model/datamodel/tools/getSubcomponents.js
index fe98f858..838a174a 100644
--- a/semantic-model/datamodel/tools/getSubcomponents.js
+++ b/semantic-model/datamodel/tools/getSubcomponents.js
@@ -44,6 +44,11 @@ const argv = yargs
description: 'Entity Files containing description of attributes',
type: 'array'
})
+ .option('shacl', {
+ alias: 's',
+ description: 'SHACL File for the object model',
+ type: 'string'
+ })
.option('token', {
alias: 't',
description: 'Token for rest call',
@@ -60,16 +65,16 @@ function namespace (baseIRI) {
const NGSILD = namespace('https://uri.etsi.org/ngsi-ld/');
// Read in all the entity files
-const entitiesstore = new N3.Store();
+const ontstore = new N3.Store();
-const loadEntities = async (entities) => {
- if (entities && entities.length > 0) {
- for (const entity of entities) {
+const loadOntologies = async (ont) => {
+ if (ont && ont.length > 0) {
+ for (const entity of ont) {
const parser = new N3.Parser();
const ttlContent = fs.readFileSync(entity, 'utf8');
parser.parse(ttlContent, (error, quad) => {
if (quad) {
- entitiesstore.addQuad(quad);
+ ontstore.addQuad(quad);
} else if (error) {
console.error('Parsing error:', error);
}
@@ -157,38 +162,47 @@ const analyseNgsildObject = async (id, brokerUrl, token) => {
return;
}
- const quadsFromEntitiesStore = entitiesstore.getQuads(null, null, null, null);
+ const quadsFromEntitiesStore = ontstore.getQuads(null, null, null, null);
store.addQuads(quadsFromEntitiesStore);
const bindingsStream = await myEngine.queryBindings(`
- PREFIX base:
- PREFIX ngsild:
- SELECT ?s ?id
+ PREFIX base:
+ PREFIX ngsild:
+ PREFIX sh:
+ SELECT ?id ?type ?attribute
WHERE {
- ?b a ngsild:Relationship .
- ?s a base:SubComponentRelationship .
- ?id ?s ?b .
+ ?id a ?type .
+ ?id ?attribute ?blank .
+ ?blank a ngsild:Relationship .
+ ?shape a sh:NodeShape .
+ ?shape sh:property ?property .
+ ?property sh:path ?attribute .
+ ?property a base:SubComponentRelationship .
}`,
{ sources: [store] }
);
const bindings = await bindingsStream.toArray();
for (const binding of bindings) {
- const s = binding.get('s').value;
+ const s = binding.get('attribute').value;
const triples = store.getQuads(null, s, null, null);
for (const quad of triples) {
const ngsildObjects = store.getQuads(quad.object, NGSILD('hasObject'), null, null);
for (const ngsildObject of ngsildObjects) {
const subId = ngsildObject.object.value;
- subids.push(subId);
- await analyseNgsildObject(subId, brokerUrl, token);
+ if (!subids.includes(subId)) {
+ subids.push(subId);
+ await analyseNgsildObject(subId, brokerUrl, token);
+ }
}
}
}
};
(async () => {
- await loadEntities(argv.entities);
+ const ontologies = argv.entities;
+ ontologies.push(argv.shacl);
+ await loadOntologies(ontologies);
await analyseNgsildObject(argv._[0], argv['broker-url'], argv.token);
let cmdlineargs = '';
diff --git a/semantic-model/datamodel/tools/lib/owlUtils.js b/semantic-model/datamodel/tools/lib/owlUtils.js
index 0f90a7de..34966e1b 100644
--- a/semantic-model/datamodel/tools/lib/owlUtils.js
+++ b/semantic-model/datamodel/tools/lib/owlUtils.js
@@ -21,7 +21,6 @@ const RDF = $rdf.Namespace('http://www.w3.org/1999/02/22-rdf-syntax-ns#');
const RDFS = $rdf.Namespace('http://www.w3.org/2000/01/rdf-schema#');
const OWL = $rdf.Namespace('http://www.w3.org/2002/07/owl#');
const NGSILD = $rdf.Namespace('https://uri.etsi.org/ngsi-ld/');
-const BASE = $rdf.Namespace('https://industryfusion.github.io/contexts/ontology/v0/base/');
const globalAttributes = [];
const globalEntities = [];
@@ -81,11 +80,6 @@ function dumpAttribute (attribute, entity, store) {
store.add($rdf.sym(attribute.attributeName), RDFS('range'), NGSILD('Property'));
} else {
store.add($rdf.sym(attribute.attributeName), RDFS('range'), NGSILD('Relationship'));
- if (attribute.isSubcomponent) {
- store.add($rdf.sym(attribute.attributeName), RDF('type'), BASE('SubComponentRelationship'));
- } else {
- store.add($rdf.sym(attribute.attributeName), RDF('type'), BASE('PeerRelationship'));
- }
}
}
}
diff --git a/semantic-model/datamodel/tools/lib/shaclUtils.js b/semantic-model/datamodel/tools/lib/shaclUtils.js
index 755dc5f0..950b52d7 100644
--- a/semantic-model/datamodel/tools/lib/shaclUtils.js
+++ b/semantic-model/datamodel/tools/lib/shaclUtils.js
@@ -25,6 +25,7 @@ const myParser = new ContextParser();
const RDF = $rdf.Namespace('http://www.w3.org/1999/02/22-rdf-syntax-ns#');
const SHACL = $rdf.Namespace('http://www.w3.org/ns/shacl#');
const IFFK = $rdf.Namespace('https://industry-fusion.org/knowledge/v0.1/');
+const BASE = $rdf.Namespace('https://industryfusion.github.io/contexts/ontology/v0/base/');
let globalContext;
let globalPrefixHash;
@@ -49,13 +50,14 @@ class NodeShape {
}
class PropertyShape {
- constructor (mincount, maxcount, nodeKind, path, isProperty) {
+ constructor (mincount, maxcount, nodeKind, path, isProperty, isSubComponent) {
this.mincount = mincount;
this.maxcount = maxcount;
this.nodeKind = nodeKind;
this.path = path;
this.constraints = [];
this.isProperty = isProperty;
+ this.isSubComponent = isSubComponent;
}
addConstraint (constraint) {
@@ -83,6 +85,13 @@ function dumpPropertyShape (propertyShape, store) {
store.add(propNode, SHACL('minCount'), propertyShape.mincount);
store.add(propNode, SHACL('maxCount'), propertyShape.maxcount);
store.add(propNode, SHACL('nodeKind'), SHACL('BlankNode'));
+ if (propertyShape.isSubComponent && !propertyShape.isProperty) {
+ store.add(propNode, RDF('type'), BASE('SubComponentRelationship'));
+ } else if (!propertyShape.isSubComponent && !propertyShape.isProperty) {
+ store.add(propNode, RDF('type'), BASE('PeerRelationship'));
+ } else {
+ store.add(propNode, RDF('type'), BASE('Property'));
+ }
store.add(propNode, SHACL('path'), propertyShape.path);
const attributeNode = $rdf.blankNode();
store.add(propNode, SHACL('property'), attributeNode);
@@ -143,12 +152,16 @@ function scanProperties (nodeShape, typeschema) {
let nodeKind = SHACL('Literal');
let klass = null;
let isProperty = true;
+ let isSubComponent = false;
if ('relationship' in typeschema.properties[property]) {
nodeKind = SHACL('IRI');
klass = typeschema.properties[property].relationship;
klass = globalContext.expandTerm(klass, true);
isProperty = false;
}
+ if ('relationship_type' in typeschema.properties[property] && typeschema.properties[property].relationship_type === 'subcomponent') {
+ isSubComponent = true;
+ }
let mincount = 0;
const maxcount = 1;
if (required.includes(property)) {
@@ -158,7 +171,7 @@ function scanProperties (nodeShape, typeschema) {
if (!ContextUtil.isValidIri(path)) {
path = globalContext.expandTerm(path, true);
}
- const propertyShape = new PropertyShape(mincount, maxcount, nodeKind, $rdf.sym(path), isProperty);
+ const propertyShape = new PropertyShape(mincount, maxcount, nodeKind, $rdf.sym(path), isProperty, isSubComponent);
nodeShape.addPropertyShape(propertyShape);
if (klass !== null) {
propertyShape.addConstraint(new Constraint(SHACL('class'), $rdf.sym(klass)));
diff --git a/semantic-model/datamodel/tools/tests/schema2owl/schema3_c0.json_result b/semantic-model/datamodel/tools/tests/schema2owl/schema3_c0.json_result
index db62cd49..eabb3a7d 100644
--- a/semantic-model/datamodel/tools/tests/schema2owl/schema3_c0.json_result
+++ b/semantic-model/datamodel/tools/tests/schema2owl/schema3_c0.json_result
@@ -1,11 +1,10 @@
@prefix owl: .
@prefix iffb: .
@prefix rdfs: .
-@prefix base: .
@prefix ngsi-ld: .
iffb:hasFilter
- a owl:Property, base:PeerRelationship;
+ a owl:Property;
rdfs:domain ;
rdfs:range ngsi-ld:Relationship.
iffb:machine_state
diff --git a/semantic-model/datamodel/tools/tests/schema2owl/schema4_c0.json_result b/semantic-model/datamodel/tools/tests/schema2owl/schema4_c0.json_result
index 439d1a67..5f378f64 100644
--- a/semantic-model/datamodel/tools/tests/schema2owl/schema4_c0.json_result
+++ b/semantic-model/datamodel/tools/tests/schema2owl/schema4_c0.json_result
@@ -1,11 +1,10 @@
@prefix owl: .
@prefix iffb: .
@prefix rdfs: .
-@prefix base: .
@prefix ngsi-ld: .
iffb:hasCartridge
- a owl:Property, base:SubComponentRelationship;
+ a owl:Property;
rdfs:domain ;
rdfs:range ngsi-ld:Relationship.
iffb:machine_state
diff --git a/semantic-model/datamodel/tools/tests/schema2shacl/c0 b/semantic-model/datamodel/tools/tests/schema2shacl/c0
index 6d7e122e..b21818d9 100644
--- a/semantic-model/datamodel/tools/tests/schema2shacl/c0
+++ b/semantic-model/datamodel/tools/tests/schema2shacl/c0
@@ -33,6 +33,10 @@
"sh": {
"@id": "http://www.w3.org/ns/shacl#",
"@prefix": true
+ },
+ "base": {
+ "@id": "https://industryfusion.github.io/contexts/ontology/v0/base/",
+ "@prefix": true
}
}
]
diff --git a/semantic-model/datamodel/tools/tests/schema2shacl/schema3_c0.json_result b/semantic-model/datamodel/tools/tests/schema2shacl/schema3_c0.json_result
index 4e5cf580..2486ac72 100644
--- a/semantic-model/datamodel/tools/tests/schema2shacl/schema3_c0.json_result
+++ b/semantic-model/datamodel/tools/tests/schema2shacl/schema3_c0.json_result
@@ -1,11 +1,13 @@
@prefix iffb: .
@prefix sh: .
+@prefix base: .
@prefix ngsi-ld: .
a sh:NodeShape;
sh:property
[
+ a base:PeerRelationship;
sh:maxCount 1;
sh:minCount 0;
sh:nodeKind sh:BlankNode;
@@ -21,6 +23,7 @@
]
],
[
+ a base:Property;
sh:maxCount 1;
sh:minCount 1;
sh:nodeKind sh:BlankNode;
diff --git a/semantic-model/datamodel/tools/tests/schema2shacl/schema4_c0.json_result b/semantic-model/datamodel/tools/tests/schema2shacl/schema4_c0.json_result
index 1c610ff3..0a336567 100644
--- a/semantic-model/datamodel/tools/tests/schema2shacl/schema4_c0.json_result
+++ b/semantic-model/datamodel/tools/tests/schema2shacl/schema4_c0.json_result
@@ -1,37 +1,40 @@
@prefix iffb: .
@prefix sh: .
+@prefix base: .
@prefix ngsi-ld: .
a sh:NodeShape;
sh:property
[
+ a base:Property;
sh:maxCount 1;
- sh:minCount 0;
+ sh:minCount 1;
sh:nodeKind sh:BlankNode;
- sh:path iffb:hasIdentification;
+ sh:path iffb:waste_class;
sh:property
[
- sh:class
- ;
+ sh:in ( "WC0" "WC1" "WC2" "WC3" );
sh:maxCount 1;
sh:minCount 1;
- sh:nodeKind sh:IRI;
- sh:path ngsi-ld:hasObject
+ sh:nodeKind sh:Literal;
+ sh:path ngsi-ld:hasValue
]
],
[
+ a base:SubComponentRelationship;
sh:maxCount 1;
- sh:minCount 1;
+ sh:minCount 0;
sh:nodeKind sh:BlankNode;
- sh:path iffb:waste_class;
+ sh:path iffb:hasIdentification;
sh:property
[
- sh:in ( "WC0" "WC1" "WC2" "WC3" );
+ sh:class
+ ;
sh:maxCount 1;
sh:minCount 1;
- sh:nodeKind sh:Literal;
- sh:path ngsi-ld:hasValue
+ sh:nodeKind sh:IRI;
+ sh:path ngsi-ld:hasObject
]
];
sh:targetClass .
diff --git a/semantic-model/datamodel/tools/tests/schema2shacl/schema5_c0.json_result b/semantic-model/datamodel/tools/tests/schema2shacl/schema5_c0.json_result
index 68840a6b..4b9da72b 100644
--- a/semantic-model/datamodel/tools/tests/schema2shacl/schema5_c0.json_result
+++ b/semantic-model/datamodel/tools/tests/schema2shacl/schema5_c0.json_result
@@ -1,10 +1,12 @@
@prefix sh: .
+@prefix base: .
@prefix ngsi-ld: .
a sh:NodeShape;
sh:property
[
+ a base:Property;
sh:maxCount 1;
sh:minCount 0;
sh:nodeKind sh:BlankNode;
@@ -19,6 +21,7 @@
]
],
[
+ a base:Property;
sh:maxCount 1;
sh:minCount 1;
sh:nodeKind sh:BlankNode;
diff --git a/semantic-model/datamodel/tools/tests/testOwlUtils.js b/semantic-model/datamodel/tools/tests/testOwlUtils.js
index 20d3afd3..ea469727 100644
--- a/semantic-model/datamodel/tools/tests/testOwlUtils.js
+++ b/semantic-model/datamodel/tools/tests/testOwlUtils.js
@@ -26,7 +26,6 @@ const RDF = $rdf.Namespace('http://www.w3.org/1999/02/22-rdf-syntax-ns#');
const RDFS = $rdf.Namespace('http://www.w3.org/2000/01/rdf-schema#');
const OWL = $rdf.Namespace('http://www.w3.org/2002/07/owl#');
const NGSILD = $rdf.Namespace('https://uri.etsi.org/ngsi-ld/');
-const BASE = $rdf.Namespace('https://industryfusion.github.io/contexts/ontology/v0/base/');
describe('Test class Entity', function () {
it('Should create Entity with IRI', function () {
@@ -76,8 +75,7 @@ describe('Test dumpAttribute', function () {
const expected = [
[$rdf.namedNode(attributeName), RDF('type'), OWL('Property')],
[$rdf.namedNode(attributeName), RDFS('domain'), $rdf.namedNode(entityName)],
- [$rdf.namedNode(attributeName), RDFS('range'), NGSILD('Relationship')],
- [$rdf.namedNode(attributeName), RDF('type'), BASE('PeerRelationship')]
+ [$rdf.namedNode(attributeName), RDFS('range'), NGSILD('Relationship')]
];
const store = {
add: (s, p, o) => { added.push([s, p, o]); }
diff --git a/semantic-model/datamodel/tools/tests/testShaclUtils.js b/semantic-model/datamodel/tools/tests/testShaclUtils.js
index 5a600aa1..14e127ab 100644
--- a/semantic-model/datamodel/tools/tests/testShaclUtils.js
+++ b/semantic-model/datamodel/tools/tests/testShaclUtils.js
@@ -71,8 +71,12 @@ describe('Test dumpPropertyShape', function () {
Namespace: () => (x) => 'ngsild:' + x
};
const SHACL = (x) => 'shacl:' + x;
+ const BASE = (x) => 'base:' + x;
+ const RDF = (x) => 'rdf:' + x;
const revert = ToTest.__set__('$rdf', $rdf);
ToTest.__set__('SHACL', SHACL);
+ ToTest.__set__('BASE', BASE);
+ ToTest.__set__('RDF', RDF);
ToTest.__set__('globalPrefixHash', { 'ngsi-ld': 'ngsi-ld' });
const dumpPropertyShape = ToTest.__get__('dumpPropertyShape');
dumpPropertyShape(propertyShape, store);
@@ -80,6 +84,7 @@ describe('Test dumpPropertyShape', function () {
['propertyNode', 'shacl:minCount', 1],
['propertyNode', 'shacl:maxCount', 2],
['propertyNode', 'shacl:nodeKind', 'shacl:BlankNode'],
+ ['propertyNode', 'rdf:type', 'base:Property'],
['propertyNode', 'shacl:path', 'path'],
['propertyNode', 'shacl:property', {}],
[{}, 'shacl:path', 'ngsild:hasValue'],
@@ -102,8 +107,12 @@ describe('Test dumpPropertyShape', function () {
Namespace: () => (x) => 'ngsild:' + x
};
const SHACL = (x) => 'shacl:' + x;
+ const BASE = (x) => 'base:' + x;
+ const RDF = (x) => 'rdf:' + x;
const revert = ToTest.__set__('$rdf', $rdf);
ToTest.__set__('SHACL', SHACL);
+ ToTest.__set__('BASE', BASE);
+ ToTest.__set__('RDF', RDF);
ToTest.__set__('globalPrefixHash', { 'ngsi-ld': 'ngsi-ld' });
const dumpPropertyShape = ToTest.__get__('dumpPropertyShape');
dumpPropertyShape(propertyShape, store);
@@ -111,6 +120,7 @@ describe('Test dumpPropertyShape', function () {
['propertyNode', 'shacl:minCount', 0],
['propertyNode', 'shacl:maxCount', 2],
['propertyNode', 'shacl:nodeKind', 'shacl:BlankNode'],
+ ['propertyNode', 'rdf:type', 'base:Property'],
['propertyNode', 'shacl:path', 'path'],
['propertyNode', 'shacl:property', {}],
[{}, 'shacl:path', 'ngsild:hasValue'],
@@ -122,7 +132,7 @@ describe('Test dumpPropertyShape', function () {
revert();
});
it('Should dump relationship without constraints', function () {
- const propertyShape = new ToTest.PropertyShape(1, 1, 'nodeKind', 'relationship', false);
+ const propertyShape = new ToTest.PropertyShape(1, 1, 'nodeKind', 'relationship', false, true);
propertyShape.propertyNode = 'propertyNode';
const storeAdds = [];
const store = {
@@ -142,6 +152,7 @@ describe('Test dumpPropertyShape', function () {
['propertyNode', 'shacl:minCount', 1],
['propertyNode', 'shacl:maxCount', 1],
['propertyNode', 'shacl:nodeKind', 'shacl:BlankNode'],
+ ['propertyNode', 'rdf:type', 'base:SubComponentRelationship'],
['propertyNode', 'shacl:path', 'relationship'],
['propertyNode', 'shacl:property', {}],
[{}, 'shacl:path', 'ngsild:hasObject'],
@@ -313,7 +324,8 @@ describe('Test scanProperties', function () {
params: [
'Testing']
}],
- isProperty: true
+ isProperty: true,
+ isSubComponent: false
}
]
};
@@ -327,6 +339,7 @@ describe('Test scanProperties', function () {
properties: {
hasFilter: {
relationship: 'eclass:0173-1#01-ACK991#016',
+ relationship_type: 'subcomponent',
$ref: 'https://industry-fusion.org/base-objects/v0.1/link'
}
}
@@ -345,7 +358,8 @@ describe('Test scanProperties', function () {
params: 'sym:eclass:0173-1#01-ACK991#016'
}
],
- isProperty: false
+ isProperty: false,
+ isSubComponent: true
}]
};
const nodeShape = new ToTest.NodeShape('targetClass');
@@ -389,7 +403,8 @@ describe('Test scanProperties', function () {
]
}
],
- isProperty: true
+ isProperty: true,
+ isSubComponent: false
}
]
};
diff --git a/semantic-model/dataservice/README.md b/semantic-model/dataservice/README.md
index 11cd54ef..4f6c9a33 100644
--- a/semantic-model/dataservice/README.md
+++ b/semantic-model/dataservice/README.md
@@ -26,6 +26,7 @@ direction TB:
`#colon;BoundMap` "1" -- "1" `rdfs#colon;Datatype` : #colon;bindsMapDatatype
`#colon;Binding` "1" -- "1" `#colon;BoundMap` : #colon;bindsMap
`#colon;Binding` "1" -- "1" `owl#colon;Thing` : #colon;bindsEntity
+ `#colon;Binding` "1" -- "1" `ngsi-ld#colon;AttributeType` : #colon;bindsAttributeType
`#colon;Binding`: bindingVersion xsd#colon;string
`#colon;Binding`: bindsContext xsd#colon;string
`#colon;Binding`: bindsFirmware xsd#colon;string
@@ -49,7 +50,8 @@ Example:
ex:myBinding :bindsMap ex:map2 .
ex:myBinding :bindingVersion "0.1"
ex:myBinding :bindsFirmware "myFirmware"
- ex:myBinding:bindsLogic "WHERE { BIND(?var1) as ?value}"
+ ex:myBinding :bindsLogic "WHERE { BIND(?var1) as ?value}"
+ ex:myBinding :bindsAttributeType ngsi-ld:Property
:BoundMap
A Bound Map contains a single map to define mapping from metrics to a model property. Several maps can be used to provide metrics for model properties. For instance it provides the rules to calculate the property ex:state with two maps:
@@ -90,9 +92,9 @@ Start dataservice with `startDataservice.py`:
*\* is supposed to be downloadable from a directory containing different *.ttl files, *\* is the (in the ontology context) namespaced class (e.g. `ex:MyClass` if `ex:` is defined in the ontologies context) and *\* is the name of a *.ttl file in the *bindings* subdirectory of the ontoloy.
-Example:
+Example (note `-d` for dry-run):
- python3 ./startDataservice.py https://industryfusion.github.io/contexts/example/v0.1 iffBaseEntity:Cutter base_test.ttl
+ python3 startDataservice.py -d -e filter_and_cartridge_subcomponent_entities.ttl examples/ urn:iff:filter1 examples/bindings.ttl
## Example Setup
diff --git a/semantic-model/dataservice/examples/bindings.ttl b/semantic-model/dataservice/examples/bindings.ttl
index 9849f6b8..2b4b7bc2 100644
--- a/semantic-model/dataservice/examples/bindings.ttl
+++ b/semantic-model/dataservice/examples/bindings.ttl
@@ -7,8 +7,10 @@
@prefix rdfs: .
@prefix binding: .
@prefix entities: .
+@prefix ngsi-ld: .
@base .
+
[ rdf:type owl:Ontology ;
owl:imports
] .
@@ -37,6 +39,7 @@ iffb:wasteClass rdf:type owl:ObjectProperty .
binding:binding_cartridge_wasteClass rdf:type owl:NamedIndividual ,
base:Binding ;
base:bindsEntity ;
+ base:bindsAttributeType ngsi-ld:Property ;
base:bindsMap binding:map_wasteclass ;
base:bindingVersion "0.9" ;
base:bindsFirmware "abc.1" ;
@@ -55,6 +58,7 @@ binding:binding_cartridge_wasteClass rdf:type owl:NamedIndividual ,
binding:binding_filter_machineState rdf:type owl:NamedIndividual ,
base:Binding ;
base:bindsEntity ;
+ base:bindsAttributeType ngsi-ld:Property ;
base:bindsMap binding:map_machineState ;
base:bindingVersion "0.9" ;
base:bindsFirmware "abc.1" ;
@@ -74,6 +78,7 @@ binding:binding_filter_machineState rdf:type owl:NamedIndividual ,
binding:binding_filter_strength rdf:type owl:NamedIndividual ,
base:Binding ;
base:bindsEntity ;
+ base:bindsAttributeType ngsi-ld:Property ;
base:bindsMap binding:map_strength ;
base:bindingVersion "0.9" ;
base:bindsFirmware "abc.1" .
diff --git a/semantic-model/dataservice/external_services/opcuaConnector.py b/semantic-model/dataservice/external_services/opcuaConnector.py
index 80bc9b66..71d9c1ad 100644
--- a/semantic-model/dataservice/external_services/opcuaConnector.py
+++ b/semantic-model/dataservice/external_services/opcuaConnector.py
@@ -28,12 +28,11 @@
asyncua_client = None
print("The 'asyncua' library is not installed. Please install it separately to use this functionality.")
-
url = os.environ.get('OPCUA_TCP_CONNECT') or "opc.tcp://localhost:4840/"
-async def update_namespace_parameter(client, connector_attribute_dict):
- nodeid = connector_attribute_dict['connectorAttribute']
+async def get_node(client, map):
+ nodeid = map['connectorAttribute']
if 'nsu=' not in nodeid:
return nodeid
try:
@@ -44,8 +43,11 @@ async def update_namespace_parameter(client, connector_attribute_dict):
if ns_part is not None:
namespace = ns_part.split('=')[1]
nsidx = await client.get_namespace_index(namespace)
+ print(f"Retrieved namespace idx {nsidx}")
nodeid = f'ns={nsidx};{i_part}'
- return nodeid
+ print(f"Requesting {nodeid}")
+ var = client.get_node(nodeid)
+ return var
##########################################################################################
@@ -53,18 +55,24 @@ async def update_namespace_parameter(client, connector_attribute_dict):
# to read out data from machines or databases.
# It will update the values in regular intervals
##########################################################################################
-async def subscribe(connector_attribute_dict, firmware, sleeptime=5):
+async def subscribe(map, firmware, sleeptime=5):
if asyncua_client is None:
raise ImportError("The 'asyncua' library is required for this function. Please install it separately.")
async with asyncua_client(url=url) as client:
- # first resolve explicit namespace when given
- connector_attribute_dict['connectorAttribute'] = \
- await update_namespace_parameter(client, connector_attribute_dict)
+ try:
+ var = await get_node(client, map)
+ except:
+ print(f"Warning. Namespace or node in {map['connectorAttribute']} not found. Not providing values for \
+this attribute.")
+ return
while True:
- nodeset = connector_attribute_dict['connectorAttribute']
- var = client.get_node(nodeset)
- value = await var.get_value()
- print(f'In OPCUA Connector with parameters {nodeset} and value {value}')
- connector_attribute_dict['value'] = Literal(value)
- connector_attribute_dict['updated'] = True
+ print(f"Get value for {map['connectorAttribute']}")
+ try:
+ value = await var.get_value()
+ except:
+ print(f"Warning Could not retrieve data for nodeid {map['connectorAttribute']}.")
+ value = None
+ print(f"Value {value} received")
+ map['value'] = Literal(value)
+ map['updated'] = True
await asyncio.sleep(sleeptime)
diff --git a/semantic-model/dataservice/services/testConnector.py b/semantic-model/dataservice/services/testConnector.py
index 365b909d..1bedb110 100644
--- a/semantic-model/dataservice/services/testConnector.py
+++ b/semantic-model/dataservice/services/testConnector.py
@@ -38,23 +38,22 @@ def split_params(param):
# to read out data from machines or databases.
# It will update the values in regular intervals
##########################################################################################
-async def subscribe(connector_attribute_dict, firmware):
+async def subscribe(map, firmware):
while True:
- logic_var_type = connector_attribute_dict['logicVarType']
+ logic_var_type = map['logicVarType']
try:
- connector_attr = connector_attribute_dict['connectorAttribute']
+ connector_attr = map['connectorAttribute']
except:
pass
-
if logic_var_type == XSD.boolean:
- connector_attribute_dict['value'] = Literal(random.choice([True, False]))
+ map['value'] = Literal(random.choice([True, False]))
elif logic_var_type == XSD.integer:
lower, upper = split_params(connector_attr)
- connector_attribute_dict['value'] = Literal(randint(lower, upper))
+ map['value'] = Literal(randint(lower, upper))
elif logic_var_type == XSD.decimal or logic_var_type == XSD.float or logic_var_type == XSD.double:
lower, upper = split_params(connector_attr)
- connector_attribute_dict['value'] = Literal(float(randint(lower, upper)) / 100.0)
- connector_attribute_dict['updated'] = True
- connector_attribute_dict['firmware'] = firmware
+ map['value'] = Literal(float(randint(lower, upper)) / 100.0)
+ map['updated'] = True
+ map['firmware'] = firmware
await asyncio.sleep(1)
diff --git a/semantic-model/dataservice/startDataservice.py b/semantic-model/dataservice/startDataservice.py
index dc8934e9..6cf1e37f 100644
--- a/semantic-model/dataservice/startDataservice.py
+++ b/semantic-model/dataservice/startDataservice.py
@@ -29,7 +29,7 @@
opcuaConnector = None
get_maps_query = """
-SELECT ?map ?attribute ?connectorAttribute ?logicVar ?logicVarType ?connector ?firmwareVersion WHERE {
+SELECT ?map ?binding ?attribute ?connectorAttribute ?logicVar ?logicVarType ?connector ?firmwareVersion WHERE {
?attribute base:boundBy ?binding .
?binding base:bindsEntity ?entityId .
?binding base:bindsMap ?map .
@@ -39,25 +39,26 @@
?map base:bindsConnector ?connector .
?map base:bindsMapDatatype ?logicVarType .
}
+
"""
get_attributes_query = """
-SELECT ?attribute ?attributeType ?entityId ?apiVersion ?firmwareVersion ?logic WHERE {
+SELECT ?attribute ?binding ?attributeType ?entityId ?apiVersion ?firmwareVersion ?logic WHERE {
?attribute base:boundBy ?binding .
?binding base:bindsEntity ?entityId .
?binding base:bindsMap ?map .
OPTIONAL {?binding base:bindsLogic ?logic . } .
?binding base:bindingVersion ?apiVersion .
?binding base:bindsFirmware ?firmwareVersion .
- ?attribute rdfs:range ?attributeType .
+ ?binding base:bindsAttributeType ?attributeType .
}
"""
def parse_args(args=sys.argv[1:]):
parser = argparse.ArgumentParser(description='Start a Dataservice based on ontology and binding information.')
- parser.add_argument('ontdir',
- help='Directory containing the context.jsonld, entities.ttl, and knowledge.ttl files.')
+ parser.add_argument('ontdir', help='Directory containing the context.jsonld, entities.ttl, and knowledge.ttl \
+files.')
parser.add_argument('entityId', help='ID of entity to start service for, e.g. urn:iff:cutter:1 .')
parser.add_argument('binding', help='Resources which describe the contex binding to the type.')
parser.add_argument('-r', '--resources', help='List of additional knowledge resources from the ontdir directory, \
@@ -67,8 +68,7 @@ def parse_args(args=sys.argv[1:]):
parser.add_argument('-p', '--port', help='TCP port to forward data to device agent', default=7070, type=int)
parser.add_argument('-e', '--entities', help='Name of the entities file', default='entities.ttl', type=str)
parser.add_argument('-d', '--dryrun', help='Do not send data.', action='store_true')
- parser.add_argument('-b', '--baseOntology',
- help='Name of base ontology. Default: \
+ parser.add_argument('-b', '--baseOntology', help='Name of base ontology. Default: \
"https://industryfusion.github.io/contexts/ontology/v0/base/"',
default='https://industryfusion.github.io/contexts/ontology/v0/base/')
parsed_args = parser.parse_args(args)
@@ -81,10 +81,11 @@ def parse_args(args=sys.argv[1:]):
query_prefixes = ''
g = rdflib.Graph()
supported_versions = ["0.1", "0.9"]
+owl_bindings = {}
-async def main(entityId, ontdir, entitiesfile, binding_name, entity_id, resources,
- baseOntology, requestedFirmwareVersion, port, dryrun):
+async def main(entityId, ontdir, entitiesfile, binding_name, entity_id, resources, baseOntology,
+ requestedFirmwareVersion, port, dryrun):
global attributes
global prefixes
global query_prefixes
@@ -150,22 +151,24 @@ async def main(entityId, ontdir, entitiesfile, binding_name, entity_id, resource
apiVersion = row.apiVersion.toPython()
firmwareVersion = row.firmwareVersion.toPython()
entityId = row.entityId.toPython()
- if attribute not in attributes.keys():
- attributes[attribute] = {}
- if firmwareVersion not in attributes[attribute]:
- attributes[attribute][firmwareVersion] = {}
- current_attribute = attributes[attribute][firmwareVersion]
- if 'maps' not in current_attribute.keys():
- current_attribute['maps'] = {}
- current_attribute['apiVersion'] = apiVersion
- current_attribute['attributeType'] = attributeType
- current_attribute['logic'] = logic
- current_attribute['entityId'] = entityId
+ binding = str(row.binding)
+ if binding not in owl_bindings.keys():
+ owl_bindings[binding] = {}
+ if firmwareVersion not in owl_bindings[binding]:
+ owl_bindings[binding][firmwareVersion] = {}
+ current_binding = owl_bindings[binding][firmwareVersion]
+ if 'maps' not in current_binding.keys():
+ current_binding['maps'] = {}
+ current_binding['apiVersion'] = apiVersion
+ current_binding['attributeType'] = attributeType
+ current_binding['logic'] = logic
+ current_binding['entityId'] = entityId
+ current_binding['attribute'] = attribute
# Basic checks
if apiVersion not in supported_versions:
- print(f"Error: found binding API version {apiVersion} not in list of \
-supported API versions {supported_versions}")
+ print(f"Error: found binding API version {apiVersion} not in list of supported API versions \
+{supported_versions}")
exit(1)
# Add official Context to mapping query and try to find bindings
@@ -179,41 +182,43 @@ async def main(entityId, ontdir, entitiesfile, binding_name, entity_id, resource
tasks = []
for row in qres:
attribute = row.attribute.toPython()
+ binding = str(row.binding)
connectorAttribute = row.connectorAttribute.toPython()
map = str(row.map)
logicVar = row.logicVar.toPython()
connector = row.connector.toPython()
logicVarType = row.logicVarType
firmwareVersion = row.firmwareVersion.toPython()
- current_maps = attributes[attribute][firmwareVersion]['maps']
- if map not in current_maps:
+ current_maps = owl_bindings[binding][firmwareVersion]['maps']
+ if map not in current_maps.keys():
current_maps[map] = {}
current_maps[map]['logicVar'] = logicVar
+ current_maps[map]['updated'] = False
current_maps[map]['connector'] = connector
current_maps[map]['logicVarType'] = logicVarType
current_maps[map]['connectorAttribute'] = connectorAttribute
# Start a service for every Attribute
- for attribute in attributes.keys():
+ for binding in owl_bindings.keys():
firmwareVersion = None
- print(f'Start dataservice for attribute {attribute}')
# Determine exact attribute or look for legicographically maximum
- if requestedFirmwareVersion in attributes[attribute].keys():
+ if requestedFirmwareVersion in owl_bindings[binding].keys():
firmwareVersion = requestedFirmwareVersion
else:
- firmwareVersion = sorted(list(attributes[attribute].keys()))[0]
-
- attribute_dict = attributes[attribute][firmwareVersion]['maps']
- for map in attribute_dict:
- print(f"Requesting map {map} from {connector}")
- firmware_data = attributes[attribute][firmwareVersion]
- maps = firmware_data['maps'][map]
- connector = maps['connector']
+ firmwareVersion = sorted(list(owl_bindings[binding].keys()))[0]
+
+ attribute = owl_bindings[binding][firmwareVersion]['attribute']
+ print(f'Start dataservice for attribute {attribute}')
+ binding_dict = owl_bindings[binding][firmwareVersion]
+ for map in binding_dict['maps'].keys():
+ print(f"Requesting map {map} from {binding}")
+ maps = binding_dict['maps']
+ connector = maps[map]['connector']
if connector == prefixes['base'].TestConnector.toPython():
- task = asyncio.create_task(testConnector.subscribe(maps, firmwareVersion))
+ task = asyncio.create_task(testConnector.subscribe(maps[map], firmwareVersion))
tasks.append(task)
- elif opcuaConnector is not None and connector == prefixes['base'].OPCUAConnector.toPython():
- task = asyncio.create_task(opcuaConnector.subscribe(maps, firmwareVersion))
+ elif connector == prefixes['base'].OPCUAConnector.toPython():
+ task = asyncio.create_task(opcuaConnector.subscribe(maps[map], firmwareVersion))
tasks.append(task)
else:
print(f"Error: No connector found for {connector}")
@@ -222,7 +227,7 @@ async def main(entityId, ontdir, entitiesfile, binding_name, entity_id, resource
attribute_trust_level = 1.0
if requestedFirmwareVersion != firmwareVersion:
attribute_trust_level = 0.0
- task = asyncio.create_task(calculate_attribute(attribute, firmwareVersion,
+ task = asyncio.create_task(calculate_attribute(attribute, binding, firmwareVersion,
attribute_trust_level, 5, port, dryrun))
tasks.append(task)
try:
@@ -253,21 +258,21 @@ async def attribute_service(attribute, firmwareVersion, r):
exit(1)
-async def calculate_attribute(attribute, firmwareVersion, attribute_trust_level, sleep, port, dryrun):
+async def calculate_attribute(attribute, binding, firmwareVersion, attribute_trust_level, sleep, port, dryrun):
# Bind retrieved values
while True:
- bindings = {}
+ querybindings = {}
connector_attribute_trust_level = 1.0
- attribute_dict = attributes[attribute][firmwareVersion]
- for map in attribute_dict['maps']:
- logic_var = attribute_dict['maps'][map]['logicVar']
+ binding_dict = owl_bindings[binding][firmwareVersion]
+ for map in binding_dict['maps']:
+ logic_var = binding_dict['maps'][map]['logicVar']
value = None
try:
- value = attribute_dict['maps'][map]['value']
+ value = binding_dict['maps'][map]['value']
+ binding_dict['maps'][map]['updated'] = False
except:
pass
- attribute_dict['maps'][map]['updated'] = False
- bindings[Variable(logic_var)] = value
+ querybindings[Variable(logic_var)] = value
if value is None:
connector_attribute_trust_level = 0.0
@@ -275,19 +280,19 @@ async def calculate_attribute(attribute, firmwareVersion, attribute_trust_level,
overallTrust = min(attribute_trust_level,
connector_attribute_trust_level)
# Remove all (forbidden) pre-defined contexts
- if attribute_dict['logic'] is not None:
- query = 'SELECT ?type ?value ?object ?datasetId ?trustLevel ' + attribute_dict['logic']
+ if binding_dict['logic'] is not None:
+ query = 'SELECT ?type ?value ?object ?datasetId ?trustLevel ' + binding_dict['logic']
query = re.sub(r'^PREFIX .*\n', '', query)
- qres = g.query(query, initBindings=bindings, initNs=prefixes)
+ qres = g.query(query, initBindings=querybindings, initNs=prefixes)
if len(qres) == 0:
print("Warning: Could not derive any value binding from connector data.")
return
else: # if there is only one map, take this over directly
- if len(attribute_dict['maps']) == 1:
- map = next(iter(attribute_dict['maps'].values()))
+ if len(binding_dict['maps']) == 1:
+ map = next(iter(binding_dict['maps'].values()))
if 'value' in map:
- qres = [{'value': map['value'], 'type': URIRef(attribute_dict['attributeType'])}]
+ qres = [{'value': map['value'], 'type': map['logicVarType']}]
else:
qres = []
for row in qres:
@@ -301,11 +306,13 @@ async def calculate_attribute(attribute, firmwareVersion, attribute_trust_level,
results[datasetId] = {}
if row.get('type') is not None:
type = row.get('type')
+ results[datasetId]['type'] = row.get('type')
else:
- type = URIRef(attribute_dict['attributeType'])
- results[datasetId]['type'] = type
+ overallTrust = 0.0
if row.get('value') is not None:
results[datasetId]['value'] = row.get('value')
+ if type is None:
+ results[datasetId]['type'] = prefixes['ngsi-ld'].Property
else:
if type == prefixes['ngsi-ld'].Property:
overallTrust = 0.0
@@ -323,12 +330,13 @@ async def calculate_attribute(attribute, firmwareVersion, attribute_trust_level,
results[result]['trustLevel'] = min(overallTrust, results[result]
['trustLevel'])
if len(results) > 0:
- send(results, attribute, attribute_dict['entityId'], dryrun, port)
+ send(results, attribute, binding_dict['entityId'], dryrun, port)
update_found = False
while not update_found:
await asyncio.sleep(0)
- for map in attribute_dict['maps']:
- update_found = attribute_dict['maps'][map]['updated'] or update_found
+ update_found = True
+ for map in binding_dict['maps']:
+ update_found = binding_dict['maps'][map]['updated'] and update_found
def send(results, attribute, entityId, dryrun, port):
@@ -336,7 +344,7 @@ def send(results, attribute, entityId, dryrun, port):
for datasetId in results.keys():
result = results[datasetId]
value = result['value']
- type = result.get('type')
+ type = result['type']
datasetId = result
prefix = "Property"
if type == prefixes['ngsi-ld'].Relationship:
@@ -349,15 +357,11 @@ def send(results, attribute, entityId, dryrun, port):
"v": "{value.toPython()}", "t": "{prefix}", "i": "{entityId}"}}')
payloads = f'[{",".join(payload)}]'
if not dryrun:
- try:
- with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as client_socket:
- client_socket.connect(("127.0.0.1", port))
- client_socket.sendall(payloads.encode('ascii'))
- print(f"sent {payloads}")
- except Exception as e:
- print(f'Warning: Error while sending data: {e}')
- else:
- print(f"Dryrun: sent {payloads}")
+ client_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
+ client_socket.connect(("127.0.0.1", port))
+ client_socket.sendall(payloads.encode('ascii'))
+ client_socket.close()
+ print(f"sent {payloads}")
if __name__ == '__main__':
diff --git a/semantic-model/dataservice/tests/ontdirs/testConnector/bindings.ttl b/semantic-model/dataservice/tests/ontdirs/testConnector/bindings.ttl
new file mode 100644
index 00000000..b03269a9
--- /dev/null
+++ b/semantic-model/dataservice/tests/ontdirs/testConnector/bindings.ttl
@@ -0,0 +1,46 @@
+@prefix base: .
+@prefix binding: .
+@prefix entities: .
+
+entities:hasDifferentialPressure base:boundBy binding:binding_LRLLYKV8IG8PAIUP .
+
+entities:hasEnergyConsumption base:boundBy binding:binding_JKEXWUZG5YC9S3YG .
+
+entities:hasMotorTemperature base:boundBy binding:binding_RLZG83N97DJZMYTQ .
+
+binding:binding_JKEXWUZG5YC9S3YG a base:Binding ;
+ base:bindingVersion "0.1" ;
+ base:bindsEntity ;
+ base:bindsFirmware "firmware" ;
+ base:bindsMap binding:map_JKEXWUZG5YC9S3YG .
+
+binding:binding_LRLLYKV8IG8PAIUP a base:Binding ;
+ base:bindingVersion "0.1" ;
+ base:bindsEntity ;
+ base:bindsFirmware "firmware" ;
+ base:bindsMap binding:map_LRLLYKV8IG8PAIUP .
+
+binding:binding_RLZG83N97DJZMYTQ a base:Binding ;
+ base:bindingVersion "0.1" ;
+ base:bindsEntity ;
+ base:bindsFirmware "firmware" ;
+ base:bindsMap binding:map_RLZG83N97DJZMYTQ .
+
+binding:map_JKEXWUZG5YC9S3YG a base:BoundMap ;
+ base:bindsConnector base:OPCUAConnector ;
+ base:bindsConnectorParameter "nsu=http://yourorganisation.org/InstanceExample/;i=6017" ;
+ base:bindsLogicVar "var1" ;
+ base:bindsMapDatatype .
+
+binding:map_LRLLYKV8IG8PAIUP a base:BoundMap ;
+ base:bindsConnector base:OPCUAConnector ;
+ base:bindsConnectorParameter "nsu=http://yourorganisation.org/InstanceExample/;i=6117" ;
+ base:bindsLogicVar "var1" ;
+ base:bindsMapDatatype .
+
+binding:map_RLZG83N97DJZMYTQ a base:BoundMap ;
+ base:bindsConnector base:OPCUAConnector ;
+ base:bindsConnectorParameter "nsu=http://yourorganisation.org/InstanceExample/;i=6020" ;
+ base:bindsLogicVar "var1" ;
+ base:bindsMapDatatype .
+
diff --git a/semantic-model/dataservice/tests/server/binding_server.py b/semantic-model/dataservice/tests/server/binding_server.py
new file mode 100644
index 00000000..cb2fb185
--- /dev/null
+++ b/semantic-model/dataservice/tests/server/binding_server.py
@@ -0,0 +1,177 @@
+import asyncio
+import argparse
+from asyncua import ua, Server
+from rdflib import Graph, Namespace
+from rdflib.namespace import RDF
+import re
+import sys
+import random
+
+opcua_ns = Namespace('http://opcfoundation.org/UA/')
+
+
+async def setup_opcua_server(mapping_data):
+ server = Server()
+ await server.init()
+ server.set_endpoint("opc.tcp://localhost:4840/freeopcua/server/")
+
+ # Register base namespace
+ base_uri = "http://example.com/opcua"
+ base_idx = await server.register_namespace(base_uri)
+
+ # Register additional namespaces from mapping data
+ namespaces = {}
+ for namespace_uri in set(data['namespace'] for data in mapping_data.values()):
+ idx = await server.register_namespace(namespace_uri)
+ namespaces[namespace_uri] = idx
+ print(f"Registration: Namespace {namespace_uri} with id {idx}.")
+
+ # Create an OPC UA object node
+ objects = server.get_objects_node()
+ device = await objects.add_object(base_idx, "Device")
+
+ # Add variables based on the mapping data
+ for nodeid, data in mapping_data.items():
+ idx = namespaces.get(data['namespace'], base_idx)
+ if nodeid.startswith('i='):
+ numeric_id = int(nodeid[2:]) # Convert the "i=xxxx" part to an integer
+ else:
+ numeric_id = int(nodeid) # Handle cases where nodeid is already numeric
+ print(f"Provide nodeid {numeric_id} with datatype {data['datatype']} in namespace {data['namespace']} \
+with namespace idx {idx}")
+ node_id = ua.NodeId(numeric_id, idx)
+ node = await device.add_variable(node_id, f"Node_{numeric_id}", data['datatype']())
+ await node.set_writable()
+
+ # Set initial random value based on data type
+ if data['datatype'] == float:
+ value = random.uniform(0, 100)
+ elif data['datatype'] == bool:
+ value = random.choice([True, False])
+ elif data['datatype'] == int:
+ value = random.randint(0, 100)
+ else:
+ value = "RandomString"
+
+ await node.write_value(value)
+
+ # Start the server
+ async with server:
+ print("OPC UA Server is running...")
+ while True:
+ await asyncio.sleep(1)
+
+
+async def setup_opcua_server_backup(mapping_data):
+ server = Server()
+ await server.init()
+ server.set_endpoint("opc.tcp://localhost:4840/freeopcua/server/")
+
+ # Register base namespace
+ base_uri = "http://example.com/opcua"
+ base_idx = await server.register_namespace(base_uri)
+
+ # Register additional namespaces from mapping data
+ namespaces = {}
+ for namespace_uri in set(data['namespace'] for data in mapping_data.values()):
+ idx = await server.register_namespace(namespace_uri)
+ namespaces[namespace_uri] = idx
+ print(f"Registration: Namespace {namespace_uri} with id {idx}.")
+
+ # Create an OPC UA object node
+ objects = server.get_objects_node()
+ device = await objects.add_object(base_idx, "Device")
+
+ # Add variables based on the mapping data
+ for nodeid, data in mapping_data.items():
+ print(f"Provide nodeid {nodeid} with datatype {data['datatype']} in namespace {data['namespace']}")
+ idx = namespaces.get(data['namespace'], base_idx)
+ node = await device.add_variable(idx, f"Node_{nodeid}", data['datatype']())
+ await node.set_writable()
+
+ # Start the server
+ async with server:
+ print("OPC UA Server is running...")
+ while True:
+ await asyncio.sleep(1)
+
+
+def parse_rdf_to_mapping(rdf_file, base_ns, binding_ns=None, uaentity_ns=None):
+ g = Graph()
+ g.parse(rdf_file, format="turtle")
+
+ # Define the base namespace for the vocabulary
+ BASE = Namespace(base_ns)
+
+ # Check for binding and UA entity namespaces in RDF
+ found_binding_ns = g.namespace_manager.store.namespace("binding")
+ found_uaentity_ns = g.namespace_manager.store.namespace("uaentity")
+
+ if not binding_ns and not found_binding_ns:
+ print("Error: Binding namespace not found in RDF data, and no binding namespace provided.")
+ sys.exit(1)
+ if not uaentity_ns and not found_uaentity_ns:
+ print("Error: UA entity namespace not found in RDF data, and no UA entity namespace provided.")
+ sys.exit(1)
+
+ # Use the found namespaces if not provided
+ binding_ns = binding_ns or found_binding_ns
+ uaentity_ns = uaentity_ns or found_uaentity_ns
+
+ nsu_pattern = re.compile(r'nsu=(.*?);i=(\d+)')
+
+ mapping_data = {}
+ for s, p, o in g.triples((None, RDF.type, BASE.BoundMap)):
+ connector_attribute = g.value(s, BASE.bindsConnectorAttribute)
+ datatype_uri = g.value(s, BASE.bindsMapDatatype)
+
+ if connector_attribute and datatype_uri:
+ # Parse namespace and identifier from connector_attribute
+ match = nsu_pattern.match(str(connector_attribute))
+ if match:
+ namespace_uri = match.group(1)
+ node_id = f"i={match.group(2)}"
+ else:
+ namespace_uri = None
+ node_id = str(connector_attribute)
+
+ # Translate RDF Datatype to Python Type
+ if datatype_uri == opcua_ns.Double:
+ datatype = float
+ elif datatype_uri == opcua_ns.Boolean:
+ datatype = bool
+ elif datatype_uri == opcua_ns.String:
+ datatype = str
+ elif datatype_uri == opcua_ns.LocalizedText:
+ datatype = str
+ else:
+ print(f"Warning, could not determine python type for {datatype_uri}. Using default: str.")
+ datatype = str # Default to string if not recognized
+
+ mapping_data[node_id] = {
+ 'namespace': namespace_uri,
+ 'datatype': datatype
+ }
+
+ return mapping_data
+
+
+async def main(rdf_file, base_ns, binding_ns=None, uaentity_ns=None):
+ # Parse the RDF to extract the mapping
+ mapping_data = parse_rdf_to_mapping(rdf_file, base_ns, binding_ns, uaentity_ns)
+
+ # Setup OPC UA server based on extracted mapping
+ await setup_opcua_server(mapping_data)
+
+if __name__ == "__main__":
+ parser = argparse.ArgumentParser(description="Start an OPC UA server based on an RDF binding description.")
+ parser.add_argument("rdf_file", type=str, help="Path to the bindings.ttl RDF file")
+ parser.add_argument("--base-ns", type=str, default="https://industryfusion.github.io/contexts/ontology/v0/base/",
+ help="Base namespace for the vocabulary (default: \
+https://industryfusion.github.io/contexts/ontology/v0/base/)")
+ parser.add_argument("--binding-ns", type=str, help="Optional: Binding namespace")
+ parser.add_argument("--uaentity-ns", type=str, help="Optional: UA entity namespace")
+
+ args = parser.parse_args()
+
+ asyncio.run(main(args.rdf_file, args.base_ns, args.binding_ns, args.uaentity_ns))
diff --git a/semantic-model/dataservice/validation/bindings-validation-shacl.ttl b/semantic-model/dataservice/validation/bindings-validation-shacl.ttl
index c9b1d54a..8a760339 100644
--- a/semantic-model/dataservice/validation/bindings-validation-shacl.ttl
+++ b/semantic-model/dataservice/validation/bindings-validation-shacl.ttl
@@ -3,6 +3,7 @@
@prefix ns1: .
@prefix owl: .
@prefix sh: .
+@prefix ngsi-ld: .
ex:ns1_Binding a sh:NodeShape ;
sh:nodeKind sh:BlankNodeOrIRI ;
@@ -10,6 +11,7 @@ ex:ns1_Binding a sh:NodeShape ;
ex:ns1_bindsEntity,
ex:ns1_bindsFirmware,
ex:ns1_bindsLogic,
+ ex:ns1_bindsAttributeType,
ex:ns1_bindsMap ;
sh:targetClass ns1:Binding .
@@ -77,6 +79,13 @@ ex:ns1_bindsMapDatatype a sh:PropertyShape ;
sh:minCount 1 ;
sh:maxCount 1 .
+ex:ns1_bindsAttributeType a sh:PropertyShape ;
+ sh:nodeKind sh:IRI ;
+ sh:path ns1:bindsAttributeType ;
+ sh:in (ngsi-ld:Property ngsi-ld:Relationship);
+ sh:minCount 1 ;
+ sh:maxCount 1 .
+
ex:ns1_allbound a sh:NodeShape ;
sh:targetClass ns1:Binding ;
sh:property [
@@ -103,7 +112,7 @@ ex:BindingShape
$this ns1:bindsMap ?map .
?map a ns1:BoundMap ;
ns1:bindsLogicVar ?var .
- FILTER(!CONTAINS(?logic, CONCAT('?', ?var, 'x ')))
+ FILTER(!CONTAINS(?logic, CONCAT('?', ?var, ' ')))
}
""" ;
] .
diff --git a/semantic-model/opcua/.coveragerc b/semantic-model/opcua/.coveragerc
new file mode 100644
index 00000000..36d13d80
--- /dev/null
+++ b/semantic-model/opcua/.coveragerc
@@ -0,0 +1,2 @@
+[run]
+omit = extractType.py,nodeset2owl.py
\ No newline at end of file
diff --git a/semantic-model/opcua/Makefile b/semantic-model/opcua/Makefile
index 16e479f6..919ed9f3 100644
--- a/semantic-model/opcua/Makefile
+++ b/semantic-model/opcua/Makefile
@@ -19,7 +19,7 @@ LINTER := python3 -m flake8
PIP := pip
HELM_DIR := ../../helm/charts/shacl
NAMESPACE := iff
-TOLINT := nodeset2owl.py lib/nodesetparser.py lib/utils.py
+TOLINT := nodeset2owl.py extractType.py lib/nodesetparser.py lib/utils.py lib/shacl.py lib/entity.py lib/jsonld.py lib/bindings.py
PYTEST := python3 -m pytest
@@ -33,5 +33,6 @@ setup-dev: requirements-dev.txt
$(PIP) install -r requirements-dev.txt
test:
- ${PYTEST} tests
- (cd tests/nodeset2owl; bash ./test.bash)
\ No newline at end of file
+ ${PYTEST} tests --cov . --cov-fail-under=80
+ (cd tests/nodeset2owl; bash ./test.bash)
+ (cd tests/extractType; bash ./test.bash)
\ No newline at end of file
diff --git a/semantic-model/opcua/README.md b/semantic-model/opcua/README.md
index f0f31422..dec233b1 100644
--- a/semantic-model/opcua/README.md
+++ b/semantic-model/opcua/README.md
@@ -69,5 +69,49 @@ create pumpexample.ttl:
## extractType.py
-Coming soon
-
\ No newline at end of file
+Create SHACL, entities.ttl, json-ld and bindings.ttl from an OPCUA instance model.
+
+```console
+usage: extractType.py [-h] -t TYPE [-j JSONLD] [-e ENTITIES] [-s SHACL] [-k KNOWLEDGE] [-b BINDINGS] [-c CONTEXT] [-d] [-m] -n NAMESPACE [-i ID] [-xe ENTITY_NAMESPACE] [-xc CONTEXT_URL] [-xp ENTITY_PREFIX] instance
+
+parse nodeset instance and create ngsi-ld model
+
+positional arguments:
+ instance Path to the instance nodeset2 file.
+
+optional arguments:
+ -h, --help show this help message and exit
+ -t TYPE, --type TYPE Type of root object, e.g. http://opcfoundation.org/UA/Pumps/
+ -j JSONLD, --jsonld JSONLD
+ Filename of jsonld output file
+ -e ENTITIES, --entities ENTITIES
+ Filename of entities output file
+ -s SHACL, --shacl SHACL
+ Filename of SHACL output file
+ -k KNOWLEDGE, --knowledge KNOWLEDGE
+ Filename of SHACL output file
+ -b BINDINGS, --bindings BINDINGS
+ Filename of bindings output file
+ -c CONTEXT, --context CONTEXT
+ Filename of JSONLD context output file
+ -d, --debug Add additional debug info to structure (e.g. for better SHACL debug)
+ -m, --minimalshacl Remove all not monitored/updated shacl nodes
+ -n NAMESPACE, --namespace NAMESPACE
+ Namespace prefix for entities, SHACL and JSON-LD
+ -i ID, --id ID ID prefix of object. The ID for every object is generated by "urn::nodeId"
+ -xe ENTITY_NAMESPACE, --entity-namespace ENTITY_NAMESPACE
+ Overwrite Namespace for entities (which is otherwise derived from /entity)
+ -xc CONTEXT_URL, --context-url CONTEXT_URL
+ Context URL
+ -xp ENTITY_PREFIX, --entity-prefix ENTITY_PREFIX
+ prefix in context for entities
+```
+
+Extract ngsi-ld prototype:
+
+ python3 ./extractType.py -t http://opcfoundation.org/UA/Pumps/PumpType -n http://yourorganisation.org/InstanceExample/ pumpexample.ttl
+
+
+Check the SHACL compliance:
+
+ pyshacl -df json-ld entities.jsonld -s shacl.ttl -e knowledge.ttl
\ No newline at end of file
diff --git a/semantic-model/opcua/extractType.py b/semantic-model/opcua/extractType.py
new file mode 100644
index 00000000..b74e4f43
--- /dev/null
+++ b/semantic-model/opcua/extractType.py
@@ -0,0 +1,529 @@
+#
+# Copyright (c) 2024 Intel Corporation
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+import sys
+import urllib
+from rdflib import Graph, Namespace, URIRef
+from rdflib.namespace import OWL, RDF, SH
+import argparse
+import lib.utils as utils
+from lib.utils import RdfUtils
+from lib.bindings import Bindings
+from lib.jsonld import JsonLd
+from lib.entity import Entity
+from lib.shacl import Shacl
+
+
+query_namespaces = """
+PREFIX rdfs:
+PREFIX rdf:
+SELECT ?uri ?prefix ?ns WHERE {
+ ?ns rdf:type base:Namespace .
+ ?ns base:hasUri ?uri .
+ ?ns base:hasPrefix ?prefix .
+}
+"""
+
+query_subclasses = """
+PREFIX rdfs:
+PREFIX owl:
+
+CONSTRUCT {
+ ?subclass rdfs:subClassOf ?superclass .
+ ?subclass a owl:Class .
+ ?superclass a owl:Class .
+}
+WHERE {
+ ?subclass rdfs:subClassOf* .
+ ?subclass rdfs:subClassOf ?superclass .
+
+ # Ensure both subclasses and superclasses are marked as owl:Class
+ {
+ ?subclass a owl:Class .
+ } UNION {
+ ?superclass a owl:Class .
+ }
+}
+"""
+
+
+randnamelength = 16
+modelling_nodeid_optional = 80
+modelling_nodeid_mandatory = 78
+modelling_nodeid_optional_array = 11508
+entity_ontology_prefix = 'uaentity'
+basic_types = [
+ 'String',
+ 'Boolean',
+ 'Byte',
+ 'SByte',
+ 'Int16',
+ 'UInt16',
+ 'Int32',
+ 'UInt32',
+ 'Uin64',
+ 'Int64',
+ 'Float',
+ 'DateTime',
+ 'Guid',
+ 'ByteString',
+ 'Double'
+]
+workaround_instances = ['http://opcfoundation.org/UA/DI/FunctionalGroupType', 'http://opcfoundation.org/UA/FolderType']
+datasetid_urn = 'urn:iff:datasetId'
+
+
+def parse_args(args=sys.argv[1:]):
+ parser = argparse.ArgumentParser(description='\
+parse nodeset instance and create ngsi-ld model')
+
+ parser.add_argument('instance', help='Path to the instance nodeset2 file.')
+ parser.add_argument('-t', '--type',
+ help='Type of root object, e.g. http://opcfoundation.org/UA/Pumps/',
+ required=True)
+ parser.add_argument('-j', '--jsonld',
+ help='Filename of jsonld output file',
+ required=False,
+ default='instances.jsonld')
+ parser.add_argument('-e', '--entities',
+ help='Filename of entities output file',
+ required=False,
+ default='entities.ttl')
+ parser.add_argument('-s', '--shacl',
+ help='Filename of SHACL output file',
+ required=False,
+ default='shacl.ttl')
+ parser.add_argument('-b', '--bindings',
+ help='Filename of bindings output file',
+ required=False,
+ default='bindings.ttl')
+ parser.add_argument('-c', '--context',
+ help='Filename of JSONLD context output file',
+ required=False,
+ default='context.jsonld')
+ parser.add_argument('-d', '--debug',
+ help='Add additional debug info to structure (e.g. for better SHACL debug)',
+ required=False,
+ action='store_true')
+ parser.add_argument('-m', '--minimalshacl',
+ help='Remove all not monitored/updated shacl nodes',
+ required=False,
+ action='store_true')
+ parser.add_argument('-n', '--namespace', help='Namespace prefix for entities, SHACL and JSON-LD', required=True)
+ parser.add_argument('-i', '--id',
+ help='ID prefix of object. The ID for every object is generated by "urn::nodeId"',
+ required=False,
+ default="testid")
+ parser.add_argument('-xe', '--entity-namespace',
+ help='Overwrite Namespace for entities (which is otherwise derived from /entity)',
+ required=False)
+ parser.add_argument('-xc', '--context-url', help='Context URL',
+ default="https://industryfusion.github.io/contexts/staging/opcua/v0.1/context.jsonld",
+ required=False)
+ parser.add_argument('-xp', '--entity-prefix',
+ help='prefix in context for entities',
+ default="uaentity",
+ required=False)
+ parsed_args = parser.parse_args(args)
+ return parsed_args
+
+
+basens = None # Will be defined by the imported ontologies
+opcuans = None # dito
+ngsildns = Namespace('https://uri.etsi.org/ngsi-ld/')
+
+
+def check_object_consistency(instancetype, attribute_path, classtype):
+ needed_superclass = None
+ property = shaclg._get_property(instancetype, attribute_path)
+ if property is None:
+ e.add_instancetype(instancetype, attribute_path)
+ e.add_type(classtype)
+ return False, None
+ shclass = shaclg._get_shclass_from_property(property)
+ if shclass != classtype:
+ print(f"Warning: Potential inconsistency: {instancetype} => {attribute_path} => \
+{shclass} vs {instancetype} => {attribute_path} => {classtype}.")
+ common_superclass = utils.get_common_supertype(g, shclass, classtype)
+ print(f"Warning: Replacing both classes by common supertype {common_superclass}. This is typcially an \
+artefact of using FolderType and \
+FunctionalGroupTypes in many places but having same attribute name with different types. TODO: Check whether \
+{classtype} or {shclass} SHAPES can be removed.")
+ shaclg.update_shclass_in_property(property, common_superclass)
+ if common_superclass != classtype and common_superclass != shclass:
+ print(f"Warning: common_superclass is neither of {classtype} and {shclass}. This is not yet implemented.")
+ needed_superclass = common_superclass
+ return True, needed_superclass
+
+
+def check_variable_consistency(instancetype, attribute_path, classtype):
+ if shaclg.attribute_is_indomain(instancetype, attribute_path):
+ return True
+ e.add_instancetype(instancetype, attribute_path)
+ e.add_type(classtype)
+ return False
+
+
+def scan_type(node, instancetype):
+
+ generic_references = rdfutils.get_generic_references(g, node)
+ # Loop through all supertypes
+ supertypes = rdfutils.get_all_supertypes(g, instancetype, node)
+
+ # Loop through all components
+ shapename = shaclg.create_shacl_type(instancetype)
+ has_components = False
+ for (curtype, curnode) in supertypes:
+ components = g.triples((curnode, basens['hasComponent'], None))
+ for (_, _, o) in components:
+ has_components = scan_type_recursive(o, curnode, instancetype, shapename) or has_components
+ addins = g.triples((curnode, basens['hasAddIn'], None))
+ for (_, _, o) in addins:
+ has_components = scan_type_recursive(o, curnode, instancetype, shapename) or has_components
+ organizes = g.triples((curnode, basens['organizes'], None))
+ for (_, _, o) in organizes:
+ scan_type_nonrecursive(o, curnode, instancetype, shapename)
+ has_components = True
+ for generic_reference, o in generic_references:
+ if generic_reference not in ignored_references:
+ has_components = scan_type_nonrecursive(o, curnode, instancetype, shapename,
+ generic_reference) or has_components
+ return has_components
+
+
+def scan_type_recursive(o, node, instancetype, shapename):
+ has_components = False
+ shacl_rule = {}
+ browse_name = next(g.objects(o, basens['hasBrowseName']))
+ nodeclass, classtype = rdfutils.get_type(g, o)
+ if nodeclass == opcuans['MethodNodeClass']:
+ return False
+
+ # If defnition is self referential, stop recursion
+ if str(instancetype) == str(classtype):
+ return False
+
+ attributename = urllib.parse.quote(f'has{browse_name}')
+ rdfutils.get_modelling_rule(g, o, shacl_rule, instancetype)
+
+ decoded_attributename = urllib.parse.unquote(attributename)
+ if utils.contains_both_angle_brackets(decoded_attributename):
+ decoded_attributename = utils.normalize_angle_bracket_name(decoded_attributename)
+ attributename = urllib.parse.quote(decoded_attributename)
+ if attributename == 'has': # full template, ignore it
+ return False
+ shacl_rule['path'] = entity_namespace[attributename]
+
+ if rdfutils.isObjectNodeClass(nodeclass):
+ stop_scan, _ = check_object_consistency(instancetype, entity_namespace[attributename], classtype)
+ if stop_scan:
+ return True
+ shacl_rule['is_property'] = False
+ _, use_instance_declaration = rdfutils.get_modelling_rule(g, o, None, instancetype)
+ if use_instance_declaration:
+ # This information mixes two details
+ # 1. Use the instance declaration and not the object for instantiation
+ # 2. It could be zero or more instances (i.e. and array)
+ shacl_rule['array'] = True
+ _, typeiri = rdfutils.get_type(g, o)
+ try:
+ typenode = next(g.subjects(basens['definesType'], typeiri))
+ o = typenode
+ except:
+ pass
+ components_found = scan_type(o, classtype)
+ if components_found:
+ has_components = True
+ shacl_rule['contentclass'] = classtype
+ shaclg.create_shacl_property(shapename, shacl_rule['path'], shacl_rule['optional'], shacl_rule['array'],
+ False, True, shacl_rule['contentclass'], None, is_subcomponent=True)
+ elif rdfutils.isVariableNodeClass(nodeclass):
+ stop_scan = check_variable_consistency(instancetype, entity_namespace[attributename], classtype)
+ if stop_scan:
+ return True
+ has_components = True
+ try:
+ isAbstract = next(g.objects(classtype, basens['isAbstract']))
+ except:
+ isAbstract = False
+ if isAbstract:
+ return False
+ shacl_rule['is_property'] = True
+ shaclg.get_shacl_iri_and_contentclass(g, o, shacl_rule)
+ shaclg.create_shacl_property(shapename, shacl_rule['path'], shacl_rule['optional'], shacl_rule['array'],
+ True, shacl_rule['is_iri'], shacl_rule['contentclass'], shacl_rule['datatype'])
+ e.add_enum_class(g, shacl_rule['contentclass'])
+ return has_components
+
+
+def scan_type_nonrecursive(o, node, instancetype, shapename, generic_reference=None):
+ shacl_rule = {}
+ browse_name = next(g.objects(o, basens['hasBrowseName']))
+ nodeclass, classtype = rdfutils.get_type(g, o)
+ attributename = urllib.parse.quote(f'has{browse_name}')
+
+ full_attribute_name = entity_namespace[attributename]
+ if generic_reference is not None:
+ full_attribute_name = generic_reference
+ shacl_rule['path'] = full_attribute_name
+ if shaclg.attribute_is_indomain(instancetype, full_attribute_name):
+ return False
+ rdfutils.get_modelling_rule(g, node, shacl_rule, instancetype)
+ e.add_instancetype(instancetype, full_attribute_name)
+ e.add_type(classtype)
+
+ if rdfutils.isObjectNodeClass(nodeclass) or rdfutils.isObjectTypeNodeClass(nodeclass):
+ shacl_rule['is_property'] = False
+ shacl_rule['contentclass'] = classtype
+ shaclg.create_shacl_property(shapename, shacl_rule['path'], shacl_rule['optional'], False, False,
+ True, shacl_rule['contentclass'], None)
+ elif rdfutils.isVariableNodeClass(nodeclass):
+ print(f"Warning: Variable node {o} is target of non-owning reference {full_attribute_name}. \
+This will be ignored.")
+ return
+
+
+def scan_entity(node, instancetype, id, optional=False):
+ generic_references = rdfutils.get_generic_references(g, node)
+ node_id = jsonld.generate_node_id(g, rootentity, node, id)
+ instance = {}
+ instance['type'] = instancetype
+ instance['id'] = node_id
+ instance['@context'] = [
+ context_url
+ ]
+
+ # Loop through all components
+ has_components = False
+ components = g.triples((node, basens['hasComponent'], None))
+ for (_, _, o) in components:
+ has_components = scan_entitiy_recursive(node, id, instance, node_id, o) or has_components
+ addins = g.triples((node, basens['hasAddIn'], None))
+ for (_, _, o) in addins:
+ has_components = scan_entitiy_recursive(node, id, instance, node_id, o) or has_components
+ organizes = g.triples((node, basens['organizes'], None))
+ for (_, _, o) in organizes:
+ has_components = scan_entitiy_nonrecursive(node, id, instance, node_id, o) or has_components
+ for generic_reference, o in generic_references:
+ if generic_reference not in ignored_references:
+ has_components = scan_entitiy_nonrecursive(node, id, instance, node_id, o,
+ generic_reference) or has_components
+ if has_components or not optional:
+ jsonld.add_instance(instance)
+ return node_id
+ else:
+ return None
+
+
+def scan_entitiy_recursive(node, id, instance, node_id, o):
+ has_components = False
+ shacl_rule = {}
+ browse_name = next(g.objects(o, basens['hasBrowseName']))
+ nodeclass, classtype = rdfutils.get_type(g, o)
+ attributename = urllib.parse.quote(f'has{browse_name}')
+
+ decoded_attributename = utils.normalize_angle_bracket_name(urllib.parse.unquote(attributename))
+ optional, array = shaclg.get_modelling_rule(entity_namespace[decoded_attributename], URIRef(instance['type']))
+ shacl_rule['optional'] = optional
+ shacl_rule['array'] = array
+ datasetId = None
+ try:
+ is_placeholder = shaclg.is_placeholder(URIRef(instance['type']), entity_namespace[decoded_attributename])
+ except:
+ is_placeholder = False
+ if is_placeholder:
+ datasetId = f'{datasetid_urn}:{attributename}'
+ attributename = urllib.parse.quote(decoded_attributename)
+ shacl_rule['path'] = entity_namespace[attributename]
+
+ if rdfutils.isObjectNodeClass(nodeclass):
+ shacl_rule['is_property'] = False
+ relid = scan_entity(o, classtype, id, shacl_rule['optional'])
+ if relid is not None:
+ has_components = True
+ instance[f'{entity_ontology_prefix}:{attributename}'] = {
+ 'type': 'Relationship',
+ 'object': relid
+ }
+ if is_placeholder and datasetId is not None:
+ instance[f'{entity_ontology_prefix}:{attributename}']['datasetId'] = datasetId
+ if debug:
+ instance[f'{entity_ontology_prefix}:{attributename}']['debug'] = \
+ f'{entity_ontology_prefix}:{attributename}'
+ shacl_rule['contentclass'] = classtype
+ if not shacl_rule['optional']:
+ has_components = True
+ shacl_rule['contentclass'] = classtype
+ elif rdfutils.isVariableNodeClass(nodeclass):
+ shacl_rule['is_property'] = True
+ shaclg.get_shacl_iri_and_contentclass(g, o, shacl_rule)
+ try:
+ value = next(g.objects(o, basens['hasValue']))
+ if not shacl_rule['is_iri']:
+ value = value.toPython()
+ else:
+ value = e.get_contentclass(shacl_rule['contentclass'], value)
+
+ value = value.toPython()
+ except StopIteration:
+ if not shacl_rule['is_iri']:
+ value = utils.get_default_value(shacl_rule['datatype'])
+ else:
+ value = e.get_default_contentclass(shacl_rule['contentclass'])
+ has_components = True
+ if not shacl_rule['is_iri']:
+ instance[f'{entity_ontology_prefix}:{attributename}'] = {
+ 'type': 'Property',
+ 'value': value
+ }
+ else:
+ instance[f'{entity_ontology_prefix}:{attributename}'] = {
+ 'type': 'Property',
+ 'value': {
+ '@id': str(value)
+ }
+ }
+ if debug:
+ instance[f'{entity_ontology_prefix}:{attributename}']['debug'] = f'{entity_ontology_prefix}:{attributename}'
+ try:
+ is_updating = bool(next(g.objects(o, basens['isUpdating'])))
+ except:
+ is_updating = False
+ if is_updating or not minimal_shacl:
+ bindingsg.create_binding(g, URIRef(node_id), o, entity_namespace[attributename])
+ return has_components
+
+
+def scan_entitiy_nonrecursive(node, id, instance, node_id, o, generic_reference=None):
+ has_components = False
+ shacl_rule = {}
+ browse_name = next(g.objects(o, basens['hasBrowseName']))
+ nodeclass, classtype = rdfutils.get_type(g, o)
+ attributename = urllib.parse.quote(f'has{browse_name}')
+ shacl_rule['path'] = entity_namespace[attributename]
+ full_attribute_name = f'{entity_ontology_prefix}:{attributename}'
+ if generic_reference is not None:
+ full_attribute_name = g.qname(generic_reference)
+ if rdfutils.isObjectNodeClass(nodeclass):
+ shacl_rule['is_property'] = False
+ relid = jsonld.generate_node_id(g, rootentity, o, id)
+ if relid is not None:
+ has_components = True
+ instance[full_attribute_name] = {
+ 'type': 'Relationship',
+ 'object': relid
+ }
+ if debug:
+ instance[full_attribute_name]['debug'] = full_attribute_name
+ shacl_rule['contentclass'] = classtype
+ elif rdfutils.isVariableNodeClass(nodeclass):
+ print(f"Warning: Variable node {o} is target of non-owning reference {full_attribute_name}. \
+This will be ignored.")
+ return has_components
+
+
+if __name__ == '__main__':
+
+ args = parse_args()
+ instancename = args.instance
+ rootinstancetype = args.type
+ jsonldname = args.jsonld
+ entitiesname = args.entities
+ shaclname = args.shacl
+ bindingsname = args.bindings
+ contextname = args.context
+ debug = args.debug
+ namespace_prefix = args.namespace
+ entity_id = args.id
+ minimal_shacl = args.minimalshacl
+ context_url = args.context_url
+ entity_namespace = args.entity_namespace
+ entity_prefix = args.entity_prefix
+
+ entity_namespace = Namespace(f'{namespace_prefix}entity/') if entity_namespace is None else entity_namespace
+ shacl_namespace = Namespace(f'{namespace_prefix}shacl/')
+ binding_namespace = Namespace(f'{namespace_prefix}bindings/')
+ entity_ontology_prefix = entity_prefix
+ g = Graph(store='Oxigraph')
+ g.parse(instancename)
+ # get all owl imports
+ mainontology = next(g.subjects(RDF.type, OWL.Ontology))
+ imports = g.objects(mainontology, OWL.imports)
+ for imprt in imports:
+ h = Graph(store="Oxigraph")
+ print(f'Importing ontology {imprt}')
+ h.parse(imprt)
+ g += h
+ for k, v in list(h.namespaces()):
+ g.bind(k, v)
+
+ types = []
+ basens = next(Namespace(uri) for prefix, uri in list(g.namespaces()) if prefix == 'base')
+ opcuans = next(Namespace(uri) for prefix, uri in list(g.namespaces()) if prefix == 'opcua')
+ bindingsg = Bindings(namespace_prefix, basens)
+ bindingsg.bind(f'{entity_ontology_prefix}', entity_namespace)
+ bindingsg.bind('base', basens)
+ bindingsg.bind('binding', binding_namespace)
+ shaclg = Shacl(namespace_prefix, basens, opcuans)
+ shaclg.bind('shacl', shacl_namespace)
+ shaclg.bind('ngsi-ld', ngsildns)
+ shaclg.bind('sh', SH)
+ shaclg.bind('base', basens)
+ e = Entity(namespace_prefix, basens, opcuans)
+ e.bind('base', basens)
+ e.bind(f'{entity_ontology_prefix}', entity_namespace)
+ e.bind('ngsi-ld', ngsildns)
+ rdfutils = RdfUtils(basens, opcuans)
+ e.create_ontolgoy_header(entity_namespace)
+ for k, v in list(g.namespaces()):
+ e.bind(k, v)
+ shaclg.bind(k, v)
+
+ jsonld = JsonLd(basens, opcuans)
+ result = g.query(query_namespaces, initNs={'base': basens, 'opcua': opcuans})
+ for uri, prefix, _ in result:
+ e.bind(prefix, Namespace(uri))
+ ignored_references = rdfutils.get_ignored_references(g)
+
+ # First scan the templates to create the rules
+ try:
+ root = next(g.subjects(basens['definesType'], URIRef(rootinstancetype)))
+ except:
+ print(f"Error: root-instance with type {rootinstancetype} not found. Please review the type parameter.")
+ exit(1)
+ scan_type(root, rootinstancetype)
+ # Then scan the entity with the real values
+ rootentity = next(g.subjects(RDF.type, URIRef(rootinstancetype)))
+ scan_entity(rootentity, rootinstancetype, entity_id)
+ # Add types to entities
+ for type in types:
+ e.add_subclass(type)
+ jsonld.serialize(jsonldname)
+ # Add all subclassing to entities
+ if entitiesname is not None:
+ result = g.query(query_subclasses)
+ e.add_subclasses(result)
+ e.serialize(destination=entitiesname)
+ if shaclname is not None:
+ shaclg.serialize(destination=shaclname)
+ entities_ns = utils.extract_namespaces(e.get_graph())
+ shacl_ns = utils.extract_namespaces(shaclg.get_graph())
+ combined_namespaces = {**entities_ns, **shacl_ns}
+ final_namespaces = {}
+ for key, value in combined_namespaces.items():
+ final_namespaces[key] = value
+ jsonld.dump_context(contextname, final_namespaces)
+ if bindingsg.len() > 0:
+ bindingsg.serialize(destination=bindingsname)
diff --git a/semantic-model/opcua/lib/bindings.py b/semantic-model/opcua/lib/bindings.py
new file mode 100644
index 00000000..a797d307
--- /dev/null
+++ b/semantic-model/opcua/lib/bindings.py
@@ -0,0 +1,64 @@
+#
+# Copyright (c) 2024 Intel Corporation
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+
+import random
+import string
+from rdflib import Graph, Namespace, Literal
+from rdflib.namespace import RDF
+import lib.utils as utils
+
+randnamelength = 16
+
+
+class Bindings:
+ def __init__(self, namespace_prefix, basens):
+ self.bindingsg = Graph()
+ self.basens = basens
+ self.binding_namespace = Namespace(f'{namespace_prefix}bindings/')
+ self.bindingsg.bind('binding', self.binding_namespace)
+
+ def create_binding(self, g, parent_node_id, var_node, attribute_iri, version='0.1', firmware='firmware'):
+ randname = ''.join(random.choices(string.ascii_uppercase + string.digits, k=randnamelength))
+ bindingiri = self.binding_namespace['binding_' + randname]
+ mapiri = self.binding_namespace['map_' + randname]
+ dtype = next(g.objects(var_node, self.basens['hasDatatype']))
+ node_id = next(g.objects(var_node, self.basens['hasNodeId']))
+ idtype = next(g.objects(var_node, self.basens['hasIdentifierType']))
+ ns = next(g.objects(var_node, self.basens['hasNamespace']))
+ nsuri = next(g.objects(ns, self.basens['hasUri']))
+ self.bindingsg.add((bindingiri, RDF['type'], self.basens['Binding']))
+ self.bindingsg.add((bindingiri, self.basens['bindsEntity'], parent_node_id))
+ self.bindingsg.add((bindingiri, self.basens['bindingVersion'], Literal(version)))
+ self.bindingsg.add((bindingiri, self.basens['bindsFirmware'], Literal(firmware)))
+ self.bindingsg.add((bindingiri, self.basens['bindsMap'], mapiri))
+ self.bindingsg.add((bindingiri, self.basens['bindsAttributeType'], utils.NGSILD['Property']))
+ self.bindingsg.add((attribute_iri, self.basens['boundBy'], bindingiri))
+ self.bindingsg.add((mapiri, RDF['type'], self.basens['BoundMap']))
+ self.bindingsg.add((mapiri, self.basens['bindsConnector'], self.basens['OPCUAConnector']))
+ self.bindingsg.add((mapiri, self.basens['bindsMapDatatype'], dtype))
+ self.bindingsg.add((mapiri, self.basens['bindsLogicVar'], Literal('var1')))
+ self.bindingsg.add((mapiri,
+ self.basens['bindsConnectorAttribute'],
+ Literal(f'nsu={nsuri};{utils.idtype2String(idtype, self.basens)}={node_id}')))
+
+ def bind(self, prefix, namespace):
+ self.bindingsg.bind(prefix, namespace)
+
+ def len(self):
+ return len(self.bindingsg)
+
+ def serialize(self, destination):
+ self.bindingsg.serialize(destination)
diff --git a/semantic-model/opcua/lib/entity.py b/semantic-model/opcua/lib/entity.py
new file mode 100644
index 00000000..ea549400
--- /dev/null
+++ b/semantic-model/opcua/lib/entity.py
@@ -0,0 +1,193 @@
+#
+# Copyright (c) 2024 Intel Corporation
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+
+from rdflib import Graph, Namespace, Literal, URIRef
+from rdflib.namespace import OWL, RDF, RDFS
+
+ngsildns = Namespace('https://uri.etsi.org/ngsi-ld/')
+
+query_enumclass = """
+PREFIX owl:
+PREFIX rdfs:
+PREFIX rdf:
+
+CONSTRUCT { ?s ?p ?o .
+ ?c ?classpred ?classobj .
+ ?o2 base:hasEnumValue ?value .
+ ?o2 base:hasValueClass ?class .
+}
+WHERE
+ {
+ ?s ?p ?o .
+ ?s rdf:type ?c .
+ ?c ?classpred ?classobj .
+ ?s ?p2 ?o2 .
+ ?o2 a base:ValueNode .
+ ?o2 base:hasEnumValue ?value .
+ ?o2 base:hasValueClass ?class .
+}
+"""
+
+query_instance = """
+PREFIX owl:
+PREFIX rdfs:
+PREFIX rdf:
+SELECT ?instance WHERE {
+ ?instance a ?c .
+ ?instance base:hasValueNode ?valueNode .
+ ?valueNode base:hasEnumValue ?value .
+}
+"""
+
+query_default_instance = """
+PREFIX owl:
+PREFIX rdfs:
+PREFIX rdf:
+SELECT ?instance WHERE {
+ ?instance a ?c .
+ ?instance base:hasValueNode ?valueNode .
+ ?valueNode base:hasEnumValue ?value .
+} order by ?value limit 1
+"""
+
+
+class Entity:
+ def __init__(self, namespace_prefix, basens, opcuans):
+ self.e = Graph()
+ self.basens = basens
+ self.opcuans = opcuans
+ self.entity_namespace = Namespace(f'{namespace_prefix}entity/')
+ self.e.bind('uaentity', self.entity_namespace)
+ self.ngsildns = Namespace('https://uri.etsi.org/ngsi-ld/')
+ self.e.bind('ngsi-ld', self.ngsildns)
+ self.types = []
+
+ # def scan_type(self, g, node, instancetype):
+ # # Implementation of the scan_type logic
+ # pass
+
+ # def scan_entity(self, g, node, instancetype, id, optional=False):
+ # # Implementation of the scan_entity logic
+ # pass
+
+ # def generate_node_id(self, g, node, id, instancetype):
+ # # Implementation for generating node ID
+ # pass
+
+ def bind(self, prefix, namespace):
+ self.e.bind(prefix, namespace)
+
+ def add_type(self, type):
+ self.types.append(type)
+
+ def add_instancetype(self, instancetype, attributename):
+ if not isinstance(attributename, URIRef):
+ iri = self.entity_namespace[attributename]
+ else:
+ iri = attributename
+ self.e.add((iri, RDF.type, OWL.ObjectProperty))
+ self.e.add((iri, RDFS.domain, URIRef(instancetype)))
+ self.e.add((iri, RDF.type, OWL.NamedIndividual))
+
+ # def add_relationship(self, attributename):
+ # if isinstance(attributename, URIRef):
+ # self.e.add((attributename, RDFS.range, ngsildns['Relationship']))
+ # else:
+ # self.e.add((self.entity_namespace[attributename], RDFS.range, ngsildns['Relationship']))
+
+ # def add_subcomponent(self, attributename):
+ # self.e.add((self.entity_namespace[attributename], RDF.type, self.basens['SubComponentRelationship']))
+
+ # def add_placeholder(self, attributename):
+ # self.e.add((self.entity_namespace[attributename], self.basens['isPlaceHolder'], Literal(True)))
+
+ # def add_property(self, attributename):
+ # if isinstance(attributename, URIRef):
+ # self.e.add((attributename, RDFS.range, ngsildns['Property']))
+ # else:
+ # self.e.add((self.entity_namespace[attributename], RDFS.range, ngsildns['Property']))
+
+ # def is_typematch(self, full_attribute_name, type):
+ # try:
+ # if len(list(self.e.triples((full_attribute_name, RDFS.domain, type)))) > 0:
+ # return True
+ # except:
+ # return False
+
+ def add_subclass(self, type):
+ self.e.add((type, RDF.type, OWL.Class))
+ self.e.add((type, RDF.type, OWL.NamedIndividual))
+ self.e.add((type, RDFS.subClassOf, self.opcuans['BaseObjectType']))
+
+ def add_subclasses(self, classes):
+ self.e += classes
+
+ def serialize(self, destination):
+ self.e.serialize(destination)
+
+ def add(self, triple):
+ self.e.add(triple)
+
+ # def attritube_instance_exists(self, attributename):
+ # return len(list(self.e.objects(attributename, RDF.type))) > 0
+
+ def get_graph(self):
+ return self.e
+
+ # def add_opcdatatype(self, attribute_name, data_type):
+ # self.e.add((attribute_name, self.basens['hasOPCUADatatype'], data_type))
+
+ # def add_datatype(self, g, node, attribute_name):
+ # data_type = utils.get_datatype(g, node, self.basens)
+ # if data_type is not None:
+ # self.e.add((self.entity_namespace[attribute_name], self.basens['hasOPCUADatatype'], data_type))
+
+ def create_ontolgoy_header(self, entity_namespace, version=0.1, versionIRI=None):
+ self.e.add((URIRef(entity_namespace), RDF.type, OWL.Ontology))
+ if versionIRI is not None:
+ self.e.add((URIRef(entity_namespace), OWL.versionIRI, versionIRI))
+ self.e.add((URIRef(entity_namespace), OWL.versionInfo, Literal(0.1)))
+
+ def add_enum_class(self, graph, contentclass):
+ if contentclass is None or not isinstance(contentclass, URIRef):
+ return
+ bindings = {'c': contentclass}
+ print(f'Adding type {contentclass} to knowledge.')
+ result = graph.query(query_enumclass, initBindings=bindings,
+ initNs={'base': self.basens, 'opcua': self.opcuans})
+ self.e += result
+
+ def get_contentclass(self, contentclass, value):
+ bindings = {'c': contentclass, 'value': value}
+ result = self.e.query(query_instance, initBindings=bindings,
+ initNs={'base': self.basens, 'opcua': self.opcuans})
+ foundclass = None
+ if len(result) > 0:
+ foundclass = list(result)[0].instance
+ if foundclass is None:
+ print(f'Warning: no instance found for class {contentclass} with value {value}')
+ return foundclass
+
+ def get_default_contentclass(self, contentclass):
+ bindings = {'c': contentclass}
+ result = self.e.query(query_default_instance, initBindings=bindings,
+ initNs={'base': self.basens, 'opcua': self.opcuans})
+ foundclass = None
+ if len(result) > 0:
+ foundclass = list(result)[0].instance
+ if foundclass is None:
+ print(f'Warning: no default instance found for class {contentclass}')
+ return foundclass
diff --git a/semantic-model/opcua/lib/jsonld.py b/semantic-model/opcua/lib/jsonld.py
new file mode 100644
index 00000000..cd6c0ae9
--- /dev/null
+++ b/semantic-model/opcua/lib/jsonld.py
@@ -0,0 +1,94 @@
+#
+# Copyright (c) 2024 Intel Corporation
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+
+import json
+from rdflib.namespace import XSD
+import lib.utils as utils
+
+ngsild_context = "https://uri.etsi.org/ngsi-ld/v1/ngsi-ld-core-context.jsonld"
+
+
+class JsonLd:
+ def __init__(self, basens, opcuans):
+ self.instances = []
+ self.opcuans = opcuans
+ self.basens = basens
+
+ def add_instance(self, instance):
+ self.instances.append(instance)
+
+ def serialize(self, filename):
+ with open(filename, 'w') as f:
+ json.dump(self.instances, f, ensure_ascii=False, indent=4)
+
+ # def extract_namespaces(self, graph):
+ # return {
+ # str(prefix): {
+ # '@id': str(namespace),
+ # '@prefix': True
+ # } for prefix, namespace in graph.namespaces()
+ # }
+
+ # def append(self, instance):
+ # self.instances.append(instance)
+
+ def dump_context(self, filename, namespaces):
+ jsonld_context = {
+ "@context": [
+ namespaces,
+ ngsild_context
+ ]
+ }
+ with open(filename, "w") as f:
+ json.dump(jsonld_context, f, indent=2)
+
+ @staticmethod
+ def map_datatype_to_jsonld(data_type, opcuans):
+ boolean_types = [opcuans['Boolean']]
+ integer_types = [opcuans['Integer'],
+ opcuans['Int16'],
+ opcuans['Int32'],
+ opcuans['Int64'],
+ opcuans['SByte'],
+ opcuans['UInteger'],
+ opcuans['UInt16'],
+ opcuans['UInt32'],
+ opcuans['UInt64'],
+ opcuans['Byte']]
+ number_types = [opcuans['Decimal'],
+ opcuans['Double'],
+ opcuans['Duration'],
+ opcuans['Float']]
+ if data_type in boolean_types:
+ return XSD.boolean
+ if data_type in integer_types:
+ return XSD.integer
+ if data_type in number_types:
+ return XSD.double
+ return XSD.string
+
+ def generate_node_id(self, graph, rootentity, node, id):
+ try:
+ node_id = next(graph.objects(node, self.basens['hasNodeId']))
+ idtype = next(graph.objects(node, self.basens['hasIdentifierType']))
+ bn = next(graph.objects(rootentity, self.basens['hasBrowseName']))
+ except:
+ node_id = 'unknown'
+ idt = utils.idtype2String(idtype, self.basens)
+ if str(node) == str(rootentity):
+ return f'{id}:{bn}'
+ else:
+ return f'{id}:{bn}:sub:{idt}{node_id}'
diff --git a/semantic-model/opcua/lib/nodesetparser.py b/semantic-model/opcua/lib/nodesetparser.py
index 7737ed16..bcf71e11 100644
--- a/semantic-model/opcua/lib/nodesetparser.py
+++ b/semantic-model/opcua/lib/nodesetparser.py
@@ -154,7 +154,7 @@ def __init__(self, args, opcua_nodeset, opcua_inputs, version_iri, data_schema,
if args.namespace is None:
models = self.root.find('opcua:Models', self.xml_ns)
if models is None:
- print("Error: Namespace cannot be retrieved, plase set it explicitly.")
+ print("Error: Namespace cannot be retrieved, please set it explicitly.")
exit(1)
model = models.find('opcua:Model', self.xml_ns)
self.ontology_name = URIRef(model.get('ModelUri'))
@@ -446,6 +446,7 @@ def add_uadatatype(self, node):
self.g.add((typeIri, self.rdf_ns['base']['hasField'], itemname))
else: # Enumtype is considered as instance of class
self.g.add((itemname, RDF.type, typeIri))
+ self.g.add((itemname, RDF.type, OWL.NamedIndividual))
if value is not None:
bnode = BNode()
bbnode = self.rdf_ns['base']['_' + str(bnode)]
diff --git a/semantic-model/opcua/lib/shacl.py b/semantic-model/opcua/lib/shacl.py
new file mode 100644
index 00000000..e67025ab
--- /dev/null
+++ b/semantic-model/opcua/lib/shacl.py
@@ -0,0 +1,191 @@
+#
+# Copyright (c) 2024 Intel Corporation
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+
+import os
+from urllib.parse import urlparse
+from rdflib import Graph, Namespace, Literal, URIRef, BNode
+from rdflib.namespace import RDF, RDFS, SH
+import lib.utils as utils
+from lib.jsonld import JsonLd
+
+
+query_minmax = """
+SELECT ?mincount ?maxcount WHERE {
+ ?shape sh:targetClass ?targetclass .
+ OPTIONAL{
+ ?shape sh:property [
+ sh:path ?path;
+ sh:maxCount ?maxcount
+ ]
+ }
+ OPTIONAL{
+ ?shape sh:property [
+ sh:path ?path;
+ sh:minCount ?mincount
+ ]
+ }
+}
+"""
+
+
+class Shacl:
+ def __init__(self, namespace_prefix, basens, opcuans):
+ self.shaclg = Graph()
+ self.shacl_namespace = Namespace(f'{namespace_prefix}shacl/')
+ self.shaclg.bind('shacl', self.shacl_namespace)
+ self.shaclg.bind('sh', SH)
+ self.ngsildns = Namespace('https://uri.etsi.org/ngsi-ld/')
+ self.shaclg.bind('ngsi-ld', self.ngsildns)
+ self.basens = basens
+ self.opcuans = opcuans
+
+ def create_shacl_type(self, targetclass):
+ name = self.get_typename(targetclass) + 'Shape'
+ shapename = self.shacl_namespace[name]
+ self.shaclg.add((shapename, RDF.type, SH.NodeShape))
+ self.shaclg.add((shapename, SH.targetClass, URIRef(targetclass)))
+ return shapename
+
+ def create_shacl_property(self, shapename, path, optional, is_array, is_property, is_iri, contentclass, datatype,
+ is_subcomponent=False):
+ innerproperty = BNode()
+ property = BNode()
+ maxCount = 1
+ minCount = 1
+ if optional:
+ minCount = 0
+ self.shaclg.add((shapename, SH.property, property))
+ self.shaclg.add((property, SH.path, path))
+ self.shaclg.add((property, SH.nodeKind, SH.BlankNode))
+ self.shaclg.add((property, SH.minCount, Literal(minCount)))
+ if not is_array:
+ self.shaclg.add((property, SH.maxCount, Literal(maxCount)))
+ self.shaclg.add((property, SH.property, innerproperty))
+ if is_property:
+ self.shaclg.add((innerproperty, SH.path, self.ngsildns['hasValue']))
+ else:
+ self.shaclg.add((innerproperty, SH.path, self.ngsildns['hasObject']))
+ if is_array:
+ self.shaclg.add((property, self.basens['isPlaceHolder'], Literal(True)))
+ if is_subcomponent:
+ self.shaclg.add((property, RDF.type, self.basens['SubComponentRelationship']))
+ else:
+ self.shaclg.add((property, RDF.type, self.basens['PeerRelationship']))
+ if is_iri:
+ self.shaclg.add((innerproperty, SH.nodeKind, SH.IRI))
+ if contentclass is not None:
+ self.shaclg.add((innerproperty, SH['class'], contentclass))
+ elif is_property:
+ self.shaclg.add((innerproperty, SH.nodeKind, SH.Literal))
+ if datatype is not None:
+ self.shaclg.add((innerproperty, SH.datatype, datatype))
+
+ self.shaclg.add((innerproperty, SH.minCount, Literal(1)))
+ self.shaclg.add((innerproperty, SH.maxCount, Literal(1)))
+
+ def get_typename(self, url):
+ result = urlparse(url)
+ if result.fragment != '':
+ return result.fragment
+ else:
+ basename = os.path.basename(result.path)
+ return basename
+
+ def bind(self, prefix, namespace):
+ self.shaclg.bind(prefix, namespace)
+
+ def serialize(self, destination):
+ self.shaclg.serialize(destination)
+
+ def get_graph(self):
+ return self.shaclg
+
+ def get_shacl_iri_and_contentclass(self, g, node, shacl_rule):
+ try:
+ data_type = utils.get_datatype(g, node, self.basens)
+ if data_type is not None:
+ shacl_rule['datatype'] = JsonLd.map_datatype_to_jsonld(data_type, self.opcuans)
+ base_data_type = next(g.objects(data_type, RDFS.subClassOf))
+ if base_data_type != self.opcuans['Enumeration']:
+ shacl_rule['is_iri'] = False
+ shacl_rule['contentclass'] = None
+ else:
+ shacl_rule['is_iri'] = True
+ shacl_rule['contentclass'] = data_type
+ else:
+ shacl_rule['is_iri'] = False
+ shacl_rule['contentclass'] = None
+ except:
+ shacl_rule['is_iri'] = False
+ shacl_rule['contentclass'] = None
+
+ def get_modelling_rule(self, path, target_class):
+ bindings = {'targetclass': target_class, 'path': path}
+ optional = True
+ array = True
+ try:
+ results = list(self.shaclg.query(query_minmax, initBindings=bindings, initNs={'sh': SH}))
+ if len(results) > 0:
+ if int(results[0][0]) > 0:
+ optional = False
+ if int(results[0][1]) <= 1:
+ array = False
+ except:
+ pass
+ return optional, array
+
+ def attribute_is_indomain(self, targetclass, attributename):
+ property = self._get_property(targetclass, attributename)
+ return property is not None
+
+ def _get_property(self, targetclass, propertypath):
+ result = None
+ try:
+ # First find the right nodeshape
+ shape = next(self.shaclg.subjects(SH.targetClass, targetclass))
+ properties = self.shaclg.objects(shape, SH.property)
+ for property in properties:
+ path = next(self.shaclg.objects(property, SH.path))
+ if str(path) == str(propertypath):
+ result = property
+ break
+ except:
+ pass
+ return result
+
+ def is_placeholder(self, targetclass, attributename):
+ property = self._get_property(targetclass, attributename)
+ try:
+ return bool(next(self.shaclg.objects(property, self.basens['isPlaceHolder'])))
+ except:
+ return False
+
+ def _get_shclass_from_property(self, property):
+ result = None
+ try:
+ subproperty = next(self.shaclg.objects(property, SH.property))
+ result = next(self.shaclg.objects(subproperty, SH['class']))
+ except:
+ pass
+ return result
+
+ def update_shclass_in_property(self, property, shclass):
+ try:
+ subproperty = next(self.shaclg.objects(property, SH.property))
+ self.shaclg.remove((subproperty, SH['class'], None))
+ self.shaclg.add((subproperty, SH['class'], shclass))
+ except:
+ pass
diff --git a/semantic-model/opcua/lib/utils.py b/semantic-model/opcua/lib/utils.py
index b4211579..57a56314 100644
--- a/semantic-model/opcua/lib/utils.py
+++ b/semantic-model/opcua/lib/utils.py
@@ -14,6 +14,68 @@
# limitations under the License.
#
+from urllib.parse import urlparse
+from rdflib.namespace import RDFS, XSD
+from rdflib import URIRef, Namespace
+import re
+import os
+
+query_realtype = """
+PREFIX owl:
+
+SELECT ?nodeclass ?realtype WHERE {
+ {
+ ?node a ?nodeclass .
+ FILTER(?nodeclass != owl:NamedIndividual
+ && STRENDS(STR(?nodeclass), "NodeClass")
+ && STRSTARTS(STR(?nodeclass), STR(opcua:))
+ )
+ }
+ UNION
+ {
+ {
+ ?node a ?realtype .
+ FILTER ((!STRENDS(STR(?realtype), "NodeClass") || !STRSTARTS(STR(?realtype), STR(opcua:))) &&
+ ?realtype != owl:NamedIndividual
+ )
+ }
+ UNION
+ {
+ ?node base:definesType ?realtype .
+ ?node a opcua:ObjectTypeNodeClass .
+ }
+ }
+}
+"""
+
+query_generic_references = """
+PREFIX rdfs:
+
+select ?reference ?target where {
+ ?node ?reference ?target .
+ ?reference rdfs:subClassOf* opcua:References
+}
+"""
+
+query_ignored_references = """
+PREFIX rdfs:
+
+SELECT ?subclass WHERE {
+ VALUES ?reference {
+ opcua:GeneratesEvent
+ opcua:HasEventSource
+ }
+ ?subclass rdfs:subClassOf* ?reference .
+}
+"""
+
+modelling_nodeid_optional = 80
+modelling_nodeid_mandatory = 78
+modelling_nodeid_optional_array = 11508
+workaround_instances = ['http://opcfoundation.org/UA/DI/FunctionalGroupType', 'http://opcfoundation.org/UA/FolderType']
+NGSILD = Namespace('https://uri.etsi.org/ngsi-ld/')
+
+
def dump_graph(g):
for s, p, o in g:
print(s, p, o)
@@ -36,3 +98,222 @@ def convert_to_json_type(result, basic_json_type):
return int(result)
if basic_json_type == 'number':
return float(result)
+
+
+def idtype2String(idtype, basens):
+ if idtype == basens['numericID']:
+ idt = 'i'
+ elif idtype == basens['stringID']:
+ idt = 's'
+ elif idtype == basens['guidID']:
+ idt = 'g'
+ elif idtype == basens['opaqueID']:
+ idt = 'b'
+ else:
+ idt = 'x'
+ print('Warning no idtype found.')
+ return idt
+
+
+def extract_namespaces(graph):
+ return {
+ str(prefix): {
+ '@id': str(namespace),
+ '@prefix': True
+ } for prefix, namespace in graph.namespaces()}
+
+
+def get_datatype(graph, node, basens):
+ try:
+ return next(graph.objects(node, basens['hasDatatype']))
+ except:
+ return None
+
+
+def attributename_from_type(type):
+ basename = None
+ url = urlparse(type)
+ if url.path is not None:
+ basename = os.path.basename(url.path)
+ basename = basename.removesuffix('Type')
+ return basename
+
+
+def get_default_value(datatype):
+ if datatype == XSD.integer:
+ return 0
+ if datatype == XSD.double:
+ return 0.0
+ if datatype == XSD.string:
+ return ''
+ if datatype == XSD.boolean:
+ return False
+ print(f'Warning: unknown default value for datatype {datatype}')
+
+
+def normalize_angle_bracket_name(s):
+ # Remove content inside angle brackets and the brackets themselves
+ no_brackets = re.sub(r'<[^>]*>', '', s)
+ # Strip trailing numbers and non-alphabetic characters
+ normalized = re.sub(r'[^a-zA-Z]+$', '', no_brackets)
+ return normalized
+
+
+def contains_both_angle_brackets(s):
+ return '<' in s and '>' in s
+
+
+def get_typename(url):
+ result = urlparse(url)
+ if result.fragment != '':
+ return result.fragment
+ else:
+ basename = os.path.basename(result.path)
+ return basename
+
+
+def get_common_supertype(graph, class1, class2):
+ superclass = None
+
+ # Prepare the query by injecting the class1 and class2 URIs into the query string
+ query_common_superclass = """
+ PREFIX rdfs:
+
+ SELECT ?commonSuperclass (MIN(?depth1 + ?depth2) AS ?minDepth)
+ WHERE {{
+
+ # Find all superclasses of the first class (?class1)
+ {{
+ SELECT ?superclass1 (COUNT(?mid1) AS ?depth1)
+ WHERE {{
+ BIND(<{class1}> AS ?class1)
+ ?class1 rdfs:subClassOf* ?mid1 .
+ ?mid1 rdfs:subClassOf* ?superclass1 .
+ }}
+ GROUP BY ?superclass1
+ }}
+
+ # Find all superclasses of the second class (?class2)
+ {{
+ SELECT ?superclass2 (COUNT(?mid2) AS ?depth2)
+ WHERE {{
+ BIND(<{class2}> AS ?class2)
+ ?class2 rdfs:subClassOf* ?mid2 .
+ ?mid2 rdfs:subClassOf* ?superclass2 .
+ }}
+ GROUP BY ?superclass2
+ }}
+
+ # Find the common superclasses
+ FILTER(?superclass1 = ?superclass2)
+ BIND(?superclass1 AS ?commonSuperclass)
+ }}
+ GROUP BY ?commonSuperclass
+ ORDER BY ?minDepth
+ LIMIT 1
+ """.format(class1=class1, class2=class2) # Inject the URIs into the query
+
+ try:
+ result = graph.query(query_common_superclass)
+ superclass = list(result)[0]['commonSuperclass']
+ except Exception as e:
+ print(f"Error: {e}")
+ pass
+ return superclass
+
+
+class RdfUtils:
+ def __init__(self, basens, opcuans):
+ self.basens = basens
+ self.opcuans = opcuans
+
+ def isNodeclass(self, type):
+ nodeclasses = [self.opcuans['BaseNodeClass'],
+ self.opcuans['DataTypeNodeClass'],
+ self.opcuans['ObjectNodeClass'],
+ self.opcuans['ObjectTypeNodeClass'],
+ self.opcuans['ReferenceTypeNodeClass'],
+ self.opcuans['VariableNodeClass'],
+ self.opcuans['VariableNodeClass']]
+ result = bool([ele for ele in nodeclasses if (ele == type)])
+ return result
+
+ def isObjectNodeClass(self, type):
+ return type == self.opcuans['ObjectNodeClass']
+
+ def isObjectTypeNodeClass(self, type):
+ return type == self.opcuans['ObjectTypeNodeClass']
+
+ def isVariableNodeClass(self, type):
+ return type == self.opcuans['VariableNodeClass']
+
+ def get_type(self, g, node):
+ try:
+ bindings = {'node': node}
+ results = list(g.query(query_realtype, initBindings=bindings,
+ initNs={'opcua': self.opcuans, 'base': self.basens}))
+ nodeclass = None
+ type = None
+ for result in results:
+ if result[0] is not None:
+ nodeclass = result[0]
+ elif result[1] is not None:
+ type = result[1]
+ return (nodeclass, type)
+ except:
+ print(f"Warning: Could not find nodeclass of class node {node}. This should not happen")
+ return None, None
+
+ def get_all_supertypes(self, g, instancetype, node):
+ supertypes = []
+
+ curtype = URIRef(instancetype)
+ curnode = node
+ try:
+ cur_typenode = next(g.objects(URIRef(node), self.basens['definesType']))
+ except:
+ cur_typenode = None
+ if cur_typenode is None:
+ # node is not instancetype definition
+ cur_typenode = next(g.subjects(self.basens['definesType'], URIRef(curtype)))
+ supertypes.append((None, curnode))
+ curnode = cur_typenode
+
+ while curtype != self.opcuans['BaseObjectType']:
+ supertypes.append((curtype, curnode))
+ try:
+ curtype = next(g.objects(curtype, RDFS.subClassOf))
+ curnode = next(g.subjects(self.basens['definesType'], URIRef(curtype)))
+ except:
+ break
+ return supertypes
+
+ def get_modelling_rule(self, graph, node, shacl_rule, instancetype):
+ use_instance_declaration = False
+ is_optional = True
+ try:
+ modelling_node = next(graph.objects(node, self.basens['hasModellingRule']))
+ modelling_rule = next(graph.objects(modelling_node, self.basens['hasNodeId']))
+ if int(modelling_rule) == modelling_nodeid_optional or str(instancetype) in workaround_instances:
+ is_optional = True
+ elif int(modelling_rule) == modelling_nodeid_mandatory:
+ is_optional = False
+ elif int(modelling_rule) == modelling_nodeid_optional_array:
+ is_optional = True
+ use_instance_declaration = True
+ except:
+ pass
+ if shacl_rule is not None:
+ shacl_rule['optional'] = is_optional
+ shacl_rule['array'] = use_instance_declaration
+ return is_optional, use_instance_declaration
+
+ def get_generic_references(self, graph, node):
+ bindings = {'node': node}
+ result = graph.query(query_generic_references, initBindings=bindings, initNs={'opcua': self.opcuans})
+ return list(result)
+
+ def get_ignored_references(self, graph):
+ result = graph.query(query_ignored_references, initNs={'opcua': self.opcuans})
+ first_elements = [t[0] for t in set(result)]
+ return first_elements
diff --git a/semantic-model/opcua/requirements-dev.txt b/semantic-model/opcua/requirements-dev.txt
index c1709b20..4c71ed15 100644
--- a/semantic-model/opcua/requirements-dev.txt
+++ b/semantic-model/opcua/requirements-dev.txt
@@ -3,3 +3,4 @@ bandit==1.7.4
black==22.8.0
pytest==7.1.3
pytest-cov==4.0.0
+pyshacl==0.26.0
\ No newline at end of file
diff --git a/semantic-model/opcua/requirements.txt b/semantic-model/opcua/requirements.txt
index ef3d78c9..c9551cf5 100644
--- a/semantic-model/opcua/requirements.txt
+++ b/semantic-model/opcua/requirements.txt
@@ -1,3 +1,3 @@
-owlrl==6.0.2
-rdflib==6.2.0
+rdflib==6.3.2
xmlschema==3.3.2
+oxrdflib==0.3.7
\ No newline at end of file
diff --git a/semantic-model/opcua/tests/extractType/compare_graphs.py b/semantic-model/opcua/tests/extractType/compare_graphs.py
new file mode 100644
index 00000000..9f58f32c
--- /dev/null
+++ b/semantic-model/opcua/tests/extractType/compare_graphs.py
@@ -0,0 +1,76 @@
+import argparse
+from rdflib import Graph
+from rdflib.compare import to_isomorphic, graph_diff
+
+def load_graph(filename, format=None):
+ """
+ Load an RDF graph from a file.
+
+ Parameters:
+ - filename: The path to the file containing the RDF graph.
+ - format: Optional format specifier for the RDF file (e.g., 'ttl', 'xml', 'n3').
+
+ Returns:
+ - An rdflib.Graph object loaded with the contents of the file.
+ """
+ g = Graph()
+ g.parse(filename, format=format)
+ return g
+
+def graphs_are_isomorphic(graph1, graph2):
+ """
+ Check if two RDF graphs are isomorphic (contain the same triples).
+
+ Parameters:
+ - graph1: The first rdflib.Graph to compare.
+ - graph2: The second rdflib.Graph to compare.
+
+ Returns:
+ - True if the graphs are isomorphic, False otherwise.
+ """
+ return to_isomorphic(graph1) == to_isomorphic(graph2)
+
+def show_diff(graph1, graph2):
+ """
+ Show the difference between two RDF graphs.
+
+ Parameters:
+ - graph1: The first rdflib.Graph to compare.
+ - graph2: The second rdflib.Graph to compare.
+ """
+ iso1 = to_isomorphic(graph1)
+ iso2 = to_isomorphic(graph2)
+
+ in_both, in_graph1_not_graph2, in_graph2_not_graph1 = graph_diff(iso1, iso2)
+
+ print("\nTriples in graph1 but not in graph2:")
+ for triple in in_graph1_not_graph2:
+ print(triple)
+
+ print("\nTriples in graph2 but not in graph1:")
+ for triple in in_graph2_not_graph1:
+ print(triple)
+
+def main():
+ # Set up argument parsing
+ parser = argparse.ArgumentParser(description="Compare two RDF graphs for isomorphism.")
+ parser.add_argument('graph1', help='Path to the first RDF graph file.')
+ parser.add_argument('graph2', help='Path to the second RDF graph file.')
+ parser.add_argument('--format', '-f', default=None, help='Format of the RDF files (e.g., ttl, xml, n3). If not provided, it will be guessed based on file extension.')
+ args = parser.parse_args()
+
+ # Load the graphs
+ graph1 = load_graph(args.graph1, format=args.format)
+ graph2 = load_graph(args.graph2, format=args.format)
+
+ # Compare the graphs
+ if graphs_are_isomorphic(graph1, graph2):
+ print("Graphs are isomorphic (identical).")
+ exit(0)
+ else:
+ print("Graphs are not isomorphic (different).")
+ show_diff(graph1, graph2)
+ exit(1)
+
+if __name__ == "__main__":
+ main()
diff --git a/semantic-model/opcua/tests/extractType/query.py b/semantic-model/opcua/tests/extractType/query.py
new file mode 100644
index 00000000..5547b50b
--- /dev/null
+++ b/semantic-model/opcua/tests/extractType/query.py
@@ -0,0 +1,43 @@
+import rdflib
+import argparse
+
+def load_ttl_and_query(input_file, input_format, sparql_query_file):
+ # Initialize an RDF graph
+ graph = rdflib.Graph()
+ contextgraph = rdflib.Graph()
+
+ # Load the Turtle file into the graph
+ graph.parse(input_file, format=input_format)
+ contextgraph.parse("context.jsonld", format="json-ld")
+ # Extract namespaces from the graph
+ init_ns = {prefix: rdflib.Namespace(ns) for prefix, ns in contextgraph.namespaces()}
+
+ # Load the SPARQL query from the file
+ with open(sparql_query_file, "r") as f:
+ sparql_query = f.read()
+
+ # Execute the SPARQL query against the graph with initial namespaces
+ results = graph.query(sparql_query, initNs=init_ns)
+
+ # Write the results to stdout
+ for row in results:
+ print(row)
+
+ if not results:
+ print("No results found.")
+
+def main():
+ # Set up argument parsing
+ parser = argparse.ArgumentParser(description="Apply a SPARQL query to a Turtle file and output the results.")
+ parser.add_argument("input_file", help="The path to the input file")
+ parser.add_argument("-f", "--input_format", help="Format of input file, e.g. ttl or json-ld", default="ttl")
+ parser.add_argument("sparql_query_file", help="The path to the SPARQL query file")
+
+ # Parse the arguments
+ args = parser.parse_args()
+
+ # Load the TTL and SPARQL query, and execute
+ load_ttl_and_query(args.input_file, args.input_format, args.sparql_query_file)
+
+if __name__ == "__main__":
+ main()
diff --git a/semantic-model/opcua/tests/extractType/serve_context.py b/semantic-model/opcua/tests/extractType/serve_context.py
new file mode 100644
index 00000000..8873bce3
--- /dev/null
+++ b/semantic-model/opcua/tests/extractType/serve_context.py
@@ -0,0 +1,72 @@
+import http.server
+import socketserver
+import argparse
+import os
+import signal
+import sys
+import socket
+import threading
+
+class SingleFileRequestHandler(http.server.SimpleHTTPRequestHandler):
+ def __init__(self, *args, **kwargs):
+ self.file_to_serve = kwargs.pop('file_to_serve')
+ super().__init__(*args, **kwargs)
+
+ def do_GET(self):
+ if self.path == '/' or self.path == f'/{os.path.basename(self.file_to_serve)}':
+ self.path = f'/{self.file_to_serve}'
+ else:
+ self.send_error(404, "File not found")
+ return
+
+ return super().do_GET()
+
+class GracefulTCPServer(socketserver.TCPServer):
+ allow_reuse_address = True
+
+ def __init__(self, *args, **kwargs):
+ super().__init__(*args, **kwargs)
+ self._shutdown_event = threading.Event()
+
+ def serve_forever(self):
+ while not self._shutdown_event.is_set():
+ self.handle_request()
+
+ def shutdown(self):
+ self._shutdown_event.set()
+ super().shutdown()
+
+def run_server(port, file_to_serve):
+ handler = lambda *args, **kwargs: SingleFileRequestHandler(*args, file_to_serve=file_to_serve, **kwargs)
+
+ with GracefulTCPServer(("", port), handler) as httpd:
+ # Set the socket option to reuse the address
+ httpd.socket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
+
+ def signal_handler(sig, frame):
+ print("Received shutdown signal. Shutting down the server...")
+ #httpd.shutdown()
+ sys.exit(0)
+
+ # Register signal handlers for SIGINT (Ctrl+C) and SIGTERM
+ signal.signal(signal.SIGINT, signal_handler)
+ signal.signal(signal.SIGTERM, signal_handler)
+
+ print(f"Serving {file_to_serve} on port {port}")
+ try:
+ httpd.serve_forever()
+ except KeyboardInterrupt:
+ httpd.shutdown()
+
+if __name__ == "__main__":
+ parser = argparse.ArgumentParser(description="Minimal web server to serve a single file.")
+ parser.add_argument('file', help="The file to be served")
+ parser.add_argument('-p', '--port', type=int, default=8000, help="Port number to serve the file on (default: 8000)")
+
+ args = parser.parse_args()
+
+ if not os.path.isfile(args.file):
+ print(f"Error: {args.file} is not a valid file.")
+ exit(1)
+
+ run_server(args.port, args.file)
diff --git a/semantic-model/opcua/tests/extractType/test.bash b/semantic-model/opcua/tests/extractType/test.bash
new file mode 100644
index 00000000..154304bc
--- /dev/null
+++ b/semantic-model/opcua/tests/extractType/test.bash
@@ -0,0 +1,183 @@
+NODESET_VERSION=UA-1.05.03-2023-12-15
+CORE_NODESET=https://raw.githubusercontent.com/OPCFoundation/UA-Nodeset/${NODESET_VERSION}/Schema/Opc.Ua.NodeSet2.xml
+DI_NODESET=https://raw.githubusercontent.com/OPCFoundation/UA-Nodeset/${NODESET_VERSION}/DI/Opc.Ua.Di.NodeSet2.xml
+MACHINERY_NODESET=https://raw.githubusercontent.com/OPCFoundation/UA-Nodeset/${NODESET_VERSION}/Machinery/Opc.Ua.Machinery.NodeSet2.xml
+PUMPS_NODESET=https://raw.githubusercontent.com/OPCFoundation/UA-Nodeset/${NODESET_VERSION}/Pumps/Opc.Ua.Pumps.NodeSet2.xml
+BASE_ONTOLOGY=https://industryfusion.github.io/contexts/staging/ontology/v0.1/base.ttl
+CORE_ONTOLOGY=core.ttl
+DEVICES_ONTOLOGY=devices.ttl
+MACHINERY_ONTOLOGY=machinery.ttl
+PUMPS_ONTOLOGY=pumps.ttl
+RESULT=result.ttl
+NODESET2OWL_RESULT=nodeset2owl_result.ttl
+CORE_RESULT=core.ttl
+CLEANED=cleaned.ttl
+NODESET2OWL=../../nodeset2owl.py
+TESTURI=http://my.test/
+DEBUG=${DEBUG:-false}
+if [ "$DEBUG" = "true" ]; then
+ DEBUG_CMDLINE="-m debugpy --listen 5678"
+fi
+TESTNODESETS=(
+ test_object_wrong.NodeSet2,${TESTURI}AlphaType
+ test_object_overwrite_type.NodeSet2,${TESTURI}AlphaType
+ test_variable_enum.NodeSet2,${TESTURI}AlphaType
+ test_object_subtypes.NodeSet2,${TESTURI}AlphaType
+ test_object_hierarchies_no_DataValue,${TESTURI}AlphaType
+ test_ignore_references.NodeSet2,${TESTURI}AlphaType
+ test_references_to_typedefinitions.NodeSet2,${TESTURI}AlphaType
+ test_minimal_object.NodeSet2,http://example.org/MinimalNodeset/ObjectType
+ test_object_types.NodeSet2,${TESTURI}AlphaType
+ test_pumps_instanceexample,http://opcfoundation.org/UA/Pumps/PumpType,http://yourorganisation.org/InstanceExample/,pumps
+ )
+#TESTNODESETS=(test_object_types.NodeSet2,http://my.demo/AlphaType )
+CLEANGRAPH=cleangraph.py
+TYPEURI=http://example.org/MinimalNodeset
+TESTURN=urn:test
+SHACL=shacl.ttl
+ENTITIES_FILE=entities.ttl
+INSTANCES=instances.jsonld
+SPARQLQUERY=query.py
+SERVE_CONTEXT=serve_context.py
+SERVE_CONTEXT_PORT=8099
+CONTEXT_FILE=context.jsonld
+LOCAL_CONTEXT=http://localhost:${SERVE_CONTEXT_PORT}/${CONTEXT_FILE}
+PYSHACL_RESULT=pyshacl.ttl
+EXTRACTTYPE="../../extractType.py"
+COMPARE_GRAPHS="./compare_graphs.py"
+
+
+function mydiff() {
+ format="$4"
+ echo "$1"
+ result="$2"
+ expected="$3"
+ echo "expected <=> result"
+ python3 ${COMPARE_GRAPHS} -f ${format} ${expected} ${result} || exit 1
+ echo Done
+}
+
+function ask() {
+ echo $1
+ query=$3
+ ENTITIES=$2
+ FORMAT=${4:-ttl}
+
+ result=$(python3 "${SPARQLQUERY}" -f ${FORMAT} "${ENTITIES}" "$query")
+
+ if [ "$result" != "True" ]; then
+ echo "Wrong result of query: ${result}."
+ exit 1
+ else
+ echo "OK"
+ fi
+}
+
+function startstop_context_server() {
+ echo $1
+ start=$2
+ if [ "$start" = "true" ]; then
+ (python3 ${SERVE_CONTEXT} -p ${SERVE_CONTEXT_PORT} ${CONTEXT_FILE} &)
+ else
+ pkill -f ${SERVE_CONTEXT}
+ sleep 1
+ fi
+ sleep 1
+}
+
+function checkqueries() {
+ echo "$1"
+
+ # Correctly capture the list of query files into an array
+ queries=()
+ while IFS= read -r -d '' file; do
+ queries+=("$file")
+ done < <(find . -maxdepth 1 -name "$2.query[0-9]*" -print0)
+
+ # Check if the array is empty
+ if [ ${#queries[@]} -eq 0 ]; then
+ echo "Skipping advanced sparql tests: No queries found matching pattern $2.query[0-9]*"
+ return 1
+ fi
+ for query in "${queries[@]}"; do
+ echo "Executing query for entities $ENTITIES_FILE and query $query"
+ result=$(python3 "${SPARQLQUERY}" "${ENTITIES_FILE}" "$query")
+
+ if [ "$result" != "True" ]; then
+ echo "Wrong result of query: ${result}."
+ exit 1
+ fi
+ done
+
+ echo "Done"
+}
+
+if [ ! "$DEBUG" = "true" ]; then
+ echo Prepare core, device, machinery, pumps nodesets for testcases
+ echo -------------------------
+ echo create core
+ python3 ${NODESET2OWL} ${CORE_NODESET} -i ${BASE_ONTOLOGY} -v http://example.com/v0.1/UA/ -p opcua -o ${CORE_RESULT} || exit 1
+ echo create devices.ttl
+ python3 ${NODESET2OWL} ${DI_NODESET} -i ${BASE_ONTOLOGY} ${CORE_ONTOLOGY} -v http://example.com/v0.1/DI/ -p devices -o devices.ttl
+ echo create machinery.ttl
+ python3 ${NODESET2OWL} ${MACHINERY_NODESET} -i ${BASE_ONTOLOGY} ${CORE_ONTOLOGY} ${DEVICES_ONTOLOGY} -v http://example.com/v0.1/Machinery/ -p machinery -o machinery.ttl
+ echo create pumps.ttl
+ python3 ${NODESET2OWL} ${PUMPS_NODESET} -i ${BASE_ONTOLOGY} ${CORE_ONTOLOGY} ${DEVICES_ONTOLOGY} ${MACHINERY_ONTOLOGY} -v http://example.com/v0.1/Pumps/ -p pumps -o pumps.ttl
+else
+ echo Skipping preparation of core, device, machinery, pumps nodesets due to DEBUG mode
+fi
+
+echo Starting Feature Tests
+echo --------------------------------
+echo --------------------------------
+startstop_context_server "Stopping context server" false
+for tuple in "${TESTNODESETS[@]}"; do IFS=","
+ set -- $tuple;
+ nodeset=$1
+ instancetype=$2
+ instancenamespace=$3
+ imports=$4
+ if [ -n "$instancenamespace" ]; then
+ echo "Insancenamespace defined: '$instancenamespace'"
+ INSTANCENAMESPACE=("-n" "$instancenamespace")
+ else
+ INSTANCENAMESPACE=()
+ fi
+ if [ "$imports" = "pumps" ]; then
+ IMPORTS=("${BASE_ONTOLOGY}" "${CORE_ONTOLOGY}" "${DEVICES_ONTOLOGY}" "${MACHINERY_ONTOLOGY}" "${PUMPS_ONTOLOGY}")
+ else
+ IMPORTS=("${BASE_ONTOLOGY}" "${CORE_ONTOLOGY}")
+ fi
+ echo "==> test $nodeset with instancetype $instancetype"
+ echo --------------------------------------------------
+ if [ "$DEBUG" = "true" ]; then
+ echo DEBUG: python3 ${NODESET2OWL} ${nodeset}.xml -i ${IMPORTS[@]} ${INSTANCENAMESPACE[@]} -v http://example.com/v0.1/UA/ -p test -o ${NODESET2OWL_RESULT}
+ echo DEBUG: python3 ${EXTRACTTYPE} -t ${instancetype} -n ${TESTURI} ${NODESET2OWL_RESULT} -i ${TESTURN} -xc ${LOCAL_CONTEXT}
+ fi
+ echo Create owl nodesets
+ echo -------------------
+ python3 ${NODESET2OWL} ${nodeset}.xml -i ${IMPORTS[@]} ${INSTANCENAMESPACE[@]} -v http://example.com/v0.1/UA/ -p test -o ${NODESET2OWL_RESULT} || exit 1
+ echo Extract types and instances
+ echo ---------------------------
+ python3 ${EXTRACTTYPE} -t ${instancetype} -n ${TESTURI} ${NODESET2OWL_RESULT} -i ${TESTURN} -xc ${LOCAL_CONTEXT} || exit 1
+ startstop_context_server "Starting context server" true
+ #ask "Compare SHACL" ${SHACL} ${nodeset}.shacl
+ mydiff "Compare SHACL" "${nodeset}.shacl" "${SHACL}" "ttl"
+ mydiff "Compare instances" "${nodeset}.instances" "${INSTANCES}" "json-ld"
+ #ask "Compare INSTANCE" ${INSTANCES} ${nodeset}.instances json-ld
+ checkqueries "Check basic entities structure" ${nodeset}
+ echo SHACL test
+ echo ----------
+ if [ -f ${nodeset}.pyshacl ]; then
+ echo "Testing custom shacl result"
+ pyshacl -s ${SHACL} -df json-ld -e ${ENTITIES_FILE} ${INSTANCES} -f turtle -o ${PYSHACL_RESULT}
+ echo "expected <=> result"
+ mydiff "Compare CUSTOM SHACL RESULTS" ${nodeset}.pyshacl ${PYSHACL_RESULT} "ttl"
+ echo OK
+ else
+ echo executing pyshacl -s ${SHACL} -df json-ld -e ${ENTITIES_FILE} ${INSTANCES}
+ pyshacl -s ${SHACL} -df json-ld -e ${ENTITIES_FILE} ${INSTANCES} || exit 1
+ fi
+ startstop_context_server "Stopping context server" false
+ echo "Test finished successfully"
+done
diff --git a/semantic-model/opcua/tests/extractType/test_ignore_references.NodeSet2.instances b/semantic-model/opcua/tests/extractType/test_ignore_references.NodeSet2.instances
new file mode 100644
index 00000000..563ca1e7
--- /dev/null
+++ b/semantic-model/opcua/tests/extractType/test_ignore_references.NodeSet2.instances
@@ -0,0 +1,32 @@
+[
+ {
+ "type": "http://my.test/BType",
+ "id": "urn:test:AlphaInstance:sub:i2012",
+ "@context": [
+ "http://localhost:8099/context.jsonld"
+ ],
+ "uaentity:hasMyVariable": {
+ "type": "Property",
+ "value": false
+ }
+ },
+ {
+ "type": "http://my.test/AlphaType",
+ "id": "urn:test:AlphaInstance",
+ "@context": [
+ "http://localhost:8099/context.jsonld"
+ ],
+ "uaentity:hasC": {
+ "type": "Property",
+ "value": 0.0
+ },
+ "uaentity:hasB": {
+ "type": "Relationship",
+ "object": "urn:test:AlphaInstance:sub:i2012"
+ },
+ "test:X": {
+ "type": "Relationship",
+ "object": "urn:test:AlphaInstance:sub:i2012"
+ }
+ }
+]
\ No newline at end of file
diff --git a/semantic-model/opcua/tests/extractType/test_ignore_references.NodeSet2.shacl b/semantic-model/opcua/tests/extractType/test_ignore_references.NodeSet2.shacl
new file mode 100644
index 00000000..71ebabd7
--- /dev/null
+++ b/semantic-model/opcua/tests/extractType/test_ignore_references.NodeSet2.shacl
@@ -0,0 +1,51 @@
+@prefix base: .
+@prefix ngsi-ld: .
+@prefix sh: .
+@prefix shacl: .
+@prefix test: .
+@prefix xsd: .
+
+shacl:AlphaTypeShape a sh:NodeShape ;
+ sh:property [ sh:maxCount 1 ;
+ sh:minCount 0 ;
+ sh:nodeKind sh:BlankNode ;
+ sh:path ;
+ sh:property [ sh:datatype xsd:double ;
+ sh:maxCount 1 ;
+ sh:minCount 1 ;
+ sh:nodeKind sh:Literal ;
+ sh:path ngsi-ld:hasValue ] ],
+ [ a base:PeerRelationship ;
+ sh:maxCount 1 ;
+ sh:minCount 0 ;
+ sh:nodeKind sh:BlankNode ;
+ sh:path test:X ;
+ sh:property [ sh:class test:BType ;
+ sh:maxCount 1 ;
+ sh:minCount 1 ;
+ sh:nodeKind sh:IRI ;
+ sh:path ngsi-ld:hasObject ] ],
+ [ a base:SubComponentRelationship ;
+ sh:maxCount 1 ;
+ sh:minCount 0 ;
+ sh:nodeKind sh:BlankNode ;
+ sh:path ;
+ sh:property [ sh:class test:BType ;
+ sh:maxCount 1 ;
+ sh:minCount 1 ;
+ sh:nodeKind sh:IRI ;
+ sh:path ngsi-ld:hasObject ] ] ;
+ sh:targetClass test:AlphaType .
+
+shacl:BTypeShape a sh:NodeShape ;
+ sh:property [ sh:maxCount 1 ;
+ sh:minCount 0 ;
+ sh:nodeKind sh:BlankNode ;
+ sh:path ;
+ sh:property [ sh:datatype xsd:boolean ;
+ sh:maxCount 1 ;
+ sh:minCount 1 ;
+ sh:nodeKind sh:Literal ;
+ sh:path ngsi-ld:hasValue ] ] ;
+ sh:targetClass test:BType .
+
diff --git a/semantic-model/opcua/tests/extractType/test_ignore_references.NodeSet2.xml b/semantic-model/opcua/tests/extractType/test_ignore_references.NodeSet2.xml
new file mode 100644
index 00000000..43104564
--- /dev/null
+++ b/semantic-model/opcua/tests/extractType/test_ignore_references.NodeSet2.xml
@@ -0,0 +1,152 @@
+
+
+
+
+
+
+
+
+ http://my.test/
+
+
+ i=1
+ i=2
+ i=3
+ i=4
+ i=5
+ i=6
+ i=7
+ i=8
+ i=9
+ i=10
+ i=11
+ i=13
+ i=12
+ i=15
+ i=14
+ i=16
+ i=17
+ i=18
+ i=20
+ i=21
+ i=19
+ i=22
+ i=26
+ i=27
+ i=28
+ i=47
+ i=46
+ i=35
+ i=36
+ i=48
+ i=45
+ i=40
+ i=37
+ i=38
+ i=39
+ i=53
+ i=52
+ i=51
+ i=54
+ i=9004
+ i=9005
+ i=17597
+ i=9006
+ i=15112
+ i=17604
+ i=17603
+ i=41
+
+
+
+ X Reference
+
+ i=32
+
+
+
+ AlphaType
+ A custom object type Alpha
+
+ i=58
+ ns=1;i=1003
+ ns=1;i=1003
+ ns=1;i=1003
+
+
+
+ B Type
+ A custom object type B
+
+ i=58
+
+
+
+ Object B
+
+ ns=1;i=1002
+ ns=1;i=1004
+
+
+
+ Variable of B-object
+
+ i=63
+
+
+
+
+
+
+ Variable of AlphaType
+
+ i=63
+ ns=1;i=1001
+
+
+
+ Variable of Variable C
+
+ i=63
+ ns=1;i=2001
+
+
+
+ Object B
+
+ ns=1;i=1002
+
+
+
+ Alpha Instance
+
+ ns=1;i=2011
+ ns=1;i=2012
+ ns=1;i=1001
+ ns=1;i=2012
+ ns=1;i=2012
+
+
+
+ C Variable of AlphaType
+
+ i=63
+ ns=1;i=2010
+
+
+
+ Object B
+
+ ns=1;i=1002
+ ns=1;i=2013
+
+
+
+ Variable of B-object
+
+ i=63
+
+
+
diff --git a/semantic-model/opcua/tests/extractType/test_minimal_object.NodeSet2.instances b/semantic-model/opcua/tests/extractType/test_minimal_object.NodeSet2.instances
new file mode 100644
index 00000000..01fe66a9
--- /dev/null
+++ b/semantic-model/opcua/tests/extractType/test_minimal_object.NodeSet2.instances
@@ -0,0 +1,13 @@
+[
+ {
+ "type": "http://example.org/MinimalNodeset/ObjectType",
+ "id": "urn:test:Object",
+ "@context": [
+ "http://localhost:8099/context.jsonld"
+ ],
+ "uaentity:hasVariable": {
+ "type": "Property",
+ "value": 123.456
+ }
+ }
+]
\ No newline at end of file
diff --git a/semantic-model/opcua/tests/extractType/test_minimal_object.NodeSet2.query1 b/semantic-model/opcua/tests/extractType/test_minimal_object.NodeSet2.query1
new file mode 100644
index 00000000..63a20507
--- /dev/null
+++ b/semantic-model/opcua/tests/extractType/test_minimal_object.NodeSet2.query1
@@ -0,0 +1,4 @@
+ask where {
+ test:ObjectType a owl:Class .
+ test:ObjectType rdfs:subClassOf opcua:BaseObjectType .
+}
\ No newline at end of file
diff --git a/semantic-model/opcua/tests/extractType/test_minimal_object.NodeSet2.shacl b/semantic-model/opcua/tests/extractType/test_minimal_object.NodeSet2.shacl
new file mode 100644
index 00000000..44194978
--- /dev/null
+++ b/semantic-model/opcua/tests/extractType/test_minimal_object.NodeSet2.shacl
@@ -0,0 +1,18 @@
+@prefix ngsi-ld: .
+@prefix sh: .
+@prefix shacl: .
+@prefix test: .
+@prefix xsd: .
+
+shacl:ObjectTypeShape a sh:NodeShape ;
+ sh:property [ sh:maxCount 1 ;
+ sh:minCount 0 ;
+ sh:nodeKind sh:BlankNode ;
+ sh:path ;
+ sh:property [ sh:datatype xsd:double ;
+ sh:maxCount 1 ;
+ sh:minCount 1 ;
+ sh:nodeKind sh:Literal ;
+ sh:path ngsi-ld:hasValue ] ] ;
+ sh:targetClass test:ObjectType .
+
diff --git a/semantic-model/opcua/tests/extractType/test_minimal_object.NodeSet2.xml b/semantic-model/opcua/tests/extractType/test_minimal_object.NodeSet2.xml
new file mode 100644
index 00000000..327c0f6c
--- /dev/null
+++ b/semantic-model/opcua/tests/extractType/test_minimal_object.NodeSet2.xml
@@ -0,0 +1,58 @@
+
+
+
+ http://example.org/MinimalNodeset
+
+
+
+
+
+
+
+ i=1
+ i=11
+ i=13
+ i=12
+ i=21
+ i=47
+ i=46
+ i=35
+ i=45
+ i=40
+ i=37
+ i=17604
+ i=256
+ i=291
+ i=887
+
+
+
+ ObjectType
+ A custom Object Type
+
+ ns=1;i=2001
+ i=58
+
+
+
+
+
+ Object
+
+ ns=1;i=2001
+ ns=1;i=1001
+
+
+
+
+
+ Variable
+ A variable within the object
+
+ 123.456
+
+
+ i=63
+
+
+
diff --git a/semantic-model/opcua/tests/extractType/test_object_hierarchies_no_DataValue.instances b/semantic-model/opcua/tests/extractType/test_object_hierarchies_no_DataValue.instances
new file mode 100644
index 00000000..10d09ecf
--- /dev/null
+++ b/semantic-model/opcua/tests/extractType/test_object_hierarchies_no_DataValue.instances
@@ -0,0 +1,24 @@
+[
+ {
+ "type": "http://my.test/BType",
+ "id": "urn:test:AlphaInstance:sub:i2012",
+ "@context": [
+ "http://localhost:8099/context.jsonld"
+ ]
+ },
+ {
+ "type": "http://my.test/AlphaType",
+ "id": "urn:test:AlphaInstance",
+ "@context": [
+ "http://localhost:8099/context.jsonld"
+ ],
+ "uaentity:hasC": {
+ "type": "Property",
+ "value": 0.0
+ },
+ "uaentity:hasB": {
+ "type": "Relationship",
+ "object": "urn:test:AlphaInstance:sub:i2012"
+ }
+ }
+]
\ No newline at end of file
diff --git a/semantic-model/opcua/tests/extractType/test_object_hierarchies_no_DataValue.shacl b/semantic-model/opcua/tests/extractType/test_object_hierarchies_no_DataValue.shacl
new file mode 100644
index 00000000..147d5732
--- /dev/null
+++ b/semantic-model/opcua/tests/extractType/test_object_hierarchies_no_DataValue.shacl
@@ -0,0 +1,45 @@
+@prefix base: .
+@prefix ngsi-ld: .
+@prefix sh: .
+@prefix shacl: .
+@prefix test: .
+@prefix xsd: .
+
+shacl:AlphaTypeShape a sh:NodeShape ;
+ sh:property [ a base:SubComponentRelationship ;
+ sh:maxCount 1 ;
+ sh:minCount 1 ;
+ sh:nodeKind sh:BlankNode ;
+ sh:path ;
+ sh:property [ sh:class test:BType ;
+ sh:maxCount 1 ;
+ sh:minCount 1 ;
+ sh:nodeKind sh:IRI ;
+ sh:path ngsi-ld:hasObject ] ] ;
+ sh:targetClass test:AlphaType .
+
+shacl:BTypeShape a sh:NodeShape ;
+ sh:property [ a base:SubComponentRelationship ;
+ sh:maxCount 1 ;
+ sh:minCount 0 ;
+ sh:nodeKind sh:BlankNode ;
+ sh:path ;
+ sh:property [ sh:class test:DType ;
+ sh:maxCount 1 ;
+ sh:minCount 1 ;
+ sh:nodeKind sh:IRI ;
+ sh:path ngsi-ld:hasObject ] ] ;
+ sh:targetClass test:BType .
+
+shacl:DTypeShape a sh:NodeShape ;
+ sh:property [ sh:maxCount 1 ;
+ sh:minCount 0 ;
+ sh:nodeKind sh:BlankNode ;
+ sh:path ;
+ sh:property [ sh:datatype xsd:boolean ;
+ sh:maxCount 1 ;
+ sh:minCount 1 ;
+ sh:nodeKind sh:Literal ;
+ sh:path ngsi-ld:hasValue ] ] ;
+ sh:targetClass test:DType .
+
diff --git a/semantic-model/opcua/tests/extractType/test_object_hierarchies_no_DataValue.xml b/semantic-model/opcua/tests/extractType/test_object_hierarchies_no_DataValue.xml
new file mode 100644
index 00000000..4c8a7347
--- /dev/null
+++ b/semantic-model/opcua/tests/extractType/test_object_hierarchies_no_DataValue.xml
@@ -0,0 +1,164 @@
+
+
+
+
+
+
+
+
+ http://my.test/
+
+
+ i=1
+ i=2
+ i=3
+ i=4
+ i=5
+ i=6
+ i=7
+ i=8
+ i=9
+ i=10
+ i=11
+ i=13
+ i=12
+ i=15
+ i=14
+ i=16
+ i=17
+ i=18
+ i=20
+ i=21
+ i=19
+ i=22
+ i=26
+ i=27
+ i=28
+ i=47
+ i=46
+ i=35
+ i=36
+ i=48
+ i=45
+ i=40
+ i=37
+ i=38
+ i=39
+ i=53
+ i=52
+ i=51
+ i=54
+ i=9004
+ i=9005
+ i=17597
+ i=9006
+ i=15112
+ i=17604
+ i=17603
+
+
+
+ AlphaType
+ A custom object type Alpha
+
+ i=58
+ ns=1;i=1003
+
+
+
+ B Type
+ A custom object type B
+
+ i=58
+
+
+
+ Object B
+
+ ns=1;i=1002
+ ns=1;i=1011
+ i=78
+
+
+
+ D Type
+ A custom object type D
+
+ i=58
+ ns=1;i=1004
+ i=80
+
+
+
+ Object D
+
+ ns=1;i=1010
+
+
+
+ Variable of B-object
+
+ i=63
+
+
+
+
+
+
+ Variable of AlphaType
+
+ i=63
+ ns=1;i=2020
+
+
+
+ Variable of Variable C
+
+ i=63
+ ns=1;i=2001
+
+
+
+ Object B
+
+ ns=1;i=1002
+ ns=1;i=2020
+
+
+
+ Object D
+
+ ns=1;i=1010
+
+
+
+ Alpha Instance
+
+ ns=1;i=2011
+ ns=1;i=2012
+ ns=1;i=1001
+
+
+
+ C Variable of AlphaType
+
+ i=63
+ ns=1;i=2010
+
+
+
+ Object B
+
+ ns=1;i=1002
+ i=78
+
+
+
+ Variable of B-object
+
+ i=63
+
+
+
diff --git a/semantic-model/opcua/tests/extractType/test_object_overwrite_type.NodeSet2.instances b/semantic-model/opcua/tests/extractType/test_object_overwrite_type.NodeSet2.instances
new file mode 100644
index 00000000..c09e0a08
--- /dev/null
+++ b/semantic-model/opcua/tests/extractType/test_object_overwrite_type.NodeSet2.instances
@@ -0,0 +1,32 @@
+[
+ {
+ "type": "http://my.test/BType",
+ "id": "urn:test:AlphaInstance:sub:i2012",
+ "@context": [
+ "http://localhost:8099/context.jsonld"
+ ],
+ "uaentity:hasMyVariable": {
+ "type": "Property",
+ "value": false
+ }
+ },
+ {
+ "type": "http://my.test/AlphaType",
+ "id": "urn:test:AlphaInstance",
+ "@context": [
+ "http://localhost:8099/context.jsonld"
+ ],
+ "uaentity:hasC": {
+ "type": "Property",
+ "value": 0.0
+ },
+ "uaentity:hasB": {
+ "type": "Relationship",
+ "object": "urn:test:AlphaInstance:sub:i2012"
+ },
+ "test:Y": {
+ "type": "Relationship",
+ "object": "urn:test:AlphaInstance:sub:i2012"
+ }
+ }
+]
\ No newline at end of file
diff --git a/semantic-model/opcua/tests/extractType/test_object_overwrite_type.NodeSet2.shacl b/semantic-model/opcua/tests/extractType/test_object_overwrite_type.NodeSet2.shacl
new file mode 100644
index 00000000..70e7fb90
--- /dev/null
+++ b/semantic-model/opcua/tests/extractType/test_object_overwrite_type.NodeSet2.shacl
@@ -0,0 +1,51 @@
+@prefix base: .
+@prefix ngsi-ld: .
+@prefix sh: .
+@prefix shacl: .
+@prefix test: .
+@prefix xsd: .
+
+shacl:AlphaTypeShape a sh:NodeShape ;
+ sh:property [ sh:maxCount 1 ;
+ sh:minCount 0 ;
+ sh:nodeKind sh:BlankNode ;
+ sh:path ;
+ sh:property [ sh:datatype xsd:double ;
+ sh:maxCount 1 ;
+ sh:minCount 1 ;
+ sh:nodeKind sh:Literal ;
+ sh:path ngsi-ld:hasValue ] ],
+ [ a base:SubComponentRelationship ;
+ sh:maxCount 1 ;
+ sh:minCount 0 ;
+ sh:nodeKind sh:BlankNode ;
+ sh:path ;
+ sh:property [ sh:class test:BType ;
+ sh:maxCount 1 ;
+ sh:minCount 1 ;
+ sh:nodeKind sh:IRI ;
+ sh:path ngsi-ld:hasObject ] ],
+ [ a base:PeerRelationship ;
+ sh:maxCount 1 ;
+ sh:minCount 0 ;
+ sh:nodeKind sh:BlankNode ;
+ sh:path test:X ;
+ sh:property [ sh:class test:BType ;
+ sh:maxCount 1 ;
+ sh:minCount 1 ;
+ sh:nodeKind sh:IRI ;
+ sh:path ngsi-ld:hasObject ] ] ;
+ sh:targetClass test:AlphaType .
+
+shacl:BTypeShape a sh:NodeShape ;
+ sh:property [ sh:maxCount 1 ;
+ sh:minCount 0 ;
+ sh:nodeKind sh:BlankNode ;
+ sh:path ;
+ sh:property [ sh:datatype xsd:boolean ;
+ sh:maxCount 1 ;
+ sh:minCount 1 ;
+ sh:nodeKind sh:Literal ;
+ sh:path ngsi-ld:hasValue ] ] ;
+ sh:targetClass test:BType .
+
diff --git a/semantic-model/opcua/tests/extractType/test_object_overwrite_type.NodeSet2.xml b/semantic-model/opcua/tests/extractType/test_object_overwrite_type.NodeSet2.xml
new file mode 100644
index 00000000..b33520c8
--- /dev/null
+++ b/semantic-model/opcua/tests/extractType/test_object_overwrite_type.NodeSet2.xml
@@ -0,0 +1,164 @@
+
+
+
+
+
+
+
+
+ http://my.test/
+
+
+ i=1
+ i=2
+ i=3
+ i=4
+ i=5
+ i=6
+ i=7
+ i=8
+ i=9
+ i=10
+ i=11
+ i=13
+ i=12
+ i=15
+ i=14
+ i=16
+ i=17
+ i=18
+ i=20
+ i=21
+ i=19
+ i=22
+ i=26
+ i=27
+ i=28
+ i=47
+ i=46
+ i=35
+ i=36
+ i=48
+ i=45
+ i=40
+ i=37
+ i=38
+ i=39
+ i=53
+ i=52
+ i=51
+ i=54
+ i=9004
+ i=9005
+ i=17597
+ i=9006
+ i=15112
+ i=17604
+ i=17603
+
+
+
+ X Reference
+
+ i=32
+
+
+
+ Y Reference
+
+ i=32
+
+
+
+ AlphaType
+ A custom object type Alpha
+
+ i=58
+ ns=1;i=2001
+ ns=1;i=1003
+ ns=1;i=1003
+
+
+
+ B Type
+ A custom object type B
+
+ i=58
+ ns=1;i=1005
+
+
+
+ Object B
+
+ ns=1;i=1002
+ ns=1;i=1004
+
+
+
+ Variable of B-object
+
+ i=63
+
+
+
+ Variable of B-object
+
+ i=63
+
+
+
+
+
+
+ Variable of AlphaType
+
+ i=63
+ ns=1;i=1001
+
+
+
+ Variable of Variable C
+
+ i=63
+ ns=1;i=2001
+
+
+
+ Object B
+
+ ns=1;i=1002
+
+
+
+ Alpha Instance
+
+ ns=1;i=2011
+ ns=1;i=2012
+ ns=1;i=1001
+ ns=1;i=2012
+
+
+
+ C Variable of AlphaType
+
+ i=63
+ ns=1;i=2010
+ ns=1;i=2011
+
+
+
+ Object B
+
+ ns=1;i=1002
+ ns=1;i=2013
+
+
+
+ Variable of B-object
+
+ i=63
+
+
+
diff --git a/semantic-model/opcua/tests/extractType/test_object_subtypes.NodeSet2.instances b/semantic-model/opcua/tests/extractType/test_object_subtypes.NodeSet2.instances
new file mode 100644
index 00000000..e6eda93f
--- /dev/null
+++ b/semantic-model/opcua/tests/extractType/test_object_subtypes.NodeSet2.instances
@@ -0,0 +1,32 @@
+[
+ {
+ "type": "http://my.test/BSubType",
+ "id": "urn:test:AlphaInstance:sub:i2012",
+ "@context": [
+ "http://localhost:8099/context.jsonld"
+ ],
+ "uaentity:hasMyVariable": {
+ "type": "Property",
+ "value": false
+ }
+ },
+ {
+ "type": "http://my.test/AlphaType",
+ "id": "urn:test:AlphaInstance",
+ "@context": [
+ "http://localhost:8099/context.jsonld"
+ ],
+ "uaentity:hasC": {
+ "type": "Property",
+ "value": 0.0
+ },
+ "uaentity:hasB": {
+ "type": "Relationship",
+ "object": "urn:test:AlphaInstance:sub:i2012"
+ },
+ "test:Y": {
+ "type": "Relationship",
+ "object": "urn:test:AlphaInstance:sub:i2012"
+ }
+ }
+]
\ No newline at end of file
diff --git a/semantic-model/opcua/tests/extractType/test_object_subtypes.NodeSet2.shacl b/semantic-model/opcua/tests/extractType/test_object_subtypes.NodeSet2.shacl
new file mode 100644
index 00000000..167e03c5
--- /dev/null
+++ b/semantic-model/opcua/tests/extractType/test_object_subtypes.NodeSet2.shacl
@@ -0,0 +1,51 @@
+@prefix base: .
+@prefix ngsi-ld: .
+@prefix sh: .
+@prefix shacl: .
+@prefix test: .
+@prefix xsd: .
+
+shacl:AlphaTypeShape a sh:NodeShape ;
+ sh:property [ a base:SubComponentRelationship ;
+ sh:maxCount 1 ;
+ sh:minCount 0 ;
+ sh:nodeKind sh:BlankNode ;
+ sh:path ;
+ sh:property [ sh:class test:BType ;
+ sh:maxCount 1 ;
+ sh:minCount 1 ;
+ sh:nodeKind sh:IRI ;
+ sh:path ngsi-ld:hasObject ] ],
+ [ sh:maxCount 1 ;
+ sh:minCount 0 ;
+ sh:nodeKind sh:BlankNode ;
+ sh:path ;
+ sh:property [ sh:datatype xsd:double ;
+ sh:maxCount 1 ;
+ sh:minCount 1 ;
+ sh:nodeKind sh:Literal ;
+ sh:path ngsi-ld:hasValue ] ],
+ [ a base:PeerRelationship ;
+ sh:maxCount 1 ;
+ sh:minCount 0 ;
+ sh:nodeKind sh:BlankNode ;
+ sh:path test:X ;
+ sh:property [ sh:class test:BType ;
+ sh:maxCount 1 ;
+ sh:minCount 1 ;
+ sh:nodeKind sh:IRI ;
+ sh:path ngsi-ld:hasObject ] ] ;
+ sh:targetClass test:AlphaType .
+
+shacl:BTypeShape a sh:NodeShape ;
+ sh:property [ sh:maxCount 1 ;
+ sh:minCount 0 ;
+ sh:nodeKind sh:BlankNode ;
+ sh:path ;
+ sh:property [ sh:datatype xsd:boolean ;
+ sh:maxCount 1 ;
+ sh:minCount 1 ;
+ sh:nodeKind sh:Literal ;
+ sh:path ngsi-ld:hasValue ] ] ;
+ sh:targetClass test:BType .
+
diff --git a/semantic-model/opcua/tests/extractType/test_object_subtypes.NodeSet2.xml b/semantic-model/opcua/tests/extractType/test_object_subtypes.NodeSet2.xml
new file mode 100644
index 00000000..517b83f1
--- /dev/null
+++ b/semantic-model/opcua/tests/extractType/test_object_subtypes.NodeSet2.xml
@@ -0,0 +1,157 @@
+
+
+
+
+
+
+
+
+ http://my.test/
+
+
+ i=1
+ i=2
+ i=3
+ i=4
+ i=5
+ i=6
+ i=7
+ i=8
+ i=9
+ i=10
+ i=11
+ i=13
+ i=12
+ i=15
+ i=14
+ i=16
+ i=17
+ i=18
+ i=20
+ i=21
+ i=19
+ i=22
+ i=26
+ i=27
+ i=28
+ i=47
+ i=46
+ i=35
+ i=36
+ i=48
+ i=45
+ i=40
+ i=37
+ i=38
+ i=39
+ i=53
+ i=52
+ i=51
+ i=54
+ i=9004
+ i=9005
+ i=17597
+ i=9006
+ i=15112
+ i=17604
+ i=17603
+
+
+
+ X Reference
+
+ i=32
+
+
+
+ Y Reference
+
+ i=32
+
+
+
+ AlphaType
+ A custom object type Alpha
+
+ i=58
+ ns=1;i=2001
+ ns=1;i=1003
+ ns=1;i=1003
+
+
+
+ B Type
+ A custom object type B
+
+ i=58
+
+
+
+ Object B
+
+ ns=1;i=1002
+ ns=1;i=1004
+
+
+
+ Variable of B-object
+
+ i=63
+
+
+
+ B Sub Type
+ A custom subobject of type B
+
+ ns=1;i=1002
+
+
+
+
+
+ Variable of AlphaType
+
+ i=63
+ ns=1;i=1001
+
+
+
+ Variable of Variable C
+
+ i=63
+ ns=1;i=2001
+
+
+
+ Alpha Instance
+
+ ns=1;i=2011
+ ns=1;i=2012
+ ns=1;i=1001
+ ns=1;i=2012
+
+
+
+ C Variable of AlphaType
+
+ i=63
+ ns=1;i=2010
+ ns=1;i=2011
+
+
+
+ Object B subtype
+
+ ns=1;i=1005
+ ns=1;i=2013
+
+
+
+ Variable of B-object
+
+ i=63
+
+
+
diff --git a/semantic-model/opcua/tests/extractType/test_object_types.NodeSet2.instances b/semantic-model/opcua/tests/extractType/test_object_types.NodeSet2.instances
new file mode 100644
index 00000000..c09e0a08
--- /dev/null
+++ b/semantic-model/opcua/tests/extractType/test_object_types.NodeSet2.instances
@@ -0,0 +1,32 @@
+[
+ {
+ "type": "http://my.test/BType",
+ "id": "urn:test:AlphaInstance:sub:i2012",
+ "@context": [
+ "http://localhost:8099/context.jsonld"
+ ],
+ "uaentity:hasMyVariable": {
+ "type": "Property",
+ "value": false
+ }
+ },
+ {
+ "type": "http://my.test/AlphaType",
+ "id": "urn:test:AlphaInstance",
+ "@context": [
+ "http://localhost:8099/context.jsonld"
+ ],
+ "uaentity:hasC": {
+ "type": "Property",
+ "value": 0.0
+ },
+ "uaentity:hasB": {
+ "type": "Relationship",
+ "object": "urn:test:AlphaInstance:sub:i2012"
+ },
+ "test:Y": {
+ "type": "Relationship",
+ "object": "urn:test:AlphaInstance:sub:i2012"
+ }
+ }
+]
\ No newline at end of file
diff --git a/semantic-model/opcua/tests/extractType/test_object_types.NodeSet2.query2 b/semantic-model/opcua/tests/extractType/test_object_types.NodeSet2.query2
new file mode 100644
index 00000000..d4e2b702
--- /dev/null
+++ b/semantic-model/opcua/tests/extractType/test_object_types.NodeSet2.query2
@@ -0,0 +1,4 @@
+ask where {
+ test:Y a owl:ObjectProperty ;
+ rdfs:domain test:AlphaType .
+}
\ No newline at end of file
diff --git a/semantic-model/opcua/tests/extractType/test_object_types.NodeSet2.query3 b/semantic-model/opcua/tests/extractType/test_object_types.NodeSet2.query3
new file mode 100644
index 00000000..d16815a7
--- /dev/null
+++ b/semantic-model/opcua/tests/extractType/test_object_types.NodeSet2.query3
@@ -0,0 +1,4 @@
+ask where {
+ test:AlphaType a owl:Class ;
+ rdfs:subClassOf opcua:BaseObjectType .
+}
\ No newline at end of file
diff --git a/semantic-model/opcua/tests/extractType/test_object_types.NodeSet2.shacl b/semantic-model/opcua/tests/extractType/test_object_types.NodeSet2.shacl
new file mode 100644
index 00000000..000cf769
--- /dev/null
+++ b/semantic-model/opcua/tests/extractType/test_object_types.NodeSet2.shacl
@@ -0,0 +1,51 @@
+@prefix base: .
+@prefix ngsi-ld: .
+@prefix sh: .
+@prefix shacl: .
+@prefix test: .
+@prefix xsd: .
+
+shacl:AlphaTypeShape a sh:NodeShape ;
+ sh:property [ a base:PeerRelationship ;
+ sh:maxCount 1 ;
+ sh:minCount 0 ;
+ sh:nodeKind sh:BlankNode ;
+ sh:path test:X ;
+ sh:property [ sh:class test:BType ;
+ sh:maxCount 1 ;
+ sh:minCount 1 ;
+ sh:nodeKind sh:IRI ;
+ sh:path ngsi-ld:hasObject ] ],
+ [ sh:maxCount 1 ;
+ sh:minCount 0 ;
+ sh:nodeKind sh:BlankNode ;
+ sh:path ;
+ sh:property [ sh:datatype xsd:double ;
+ sh:maxCount 1 ;
+ sh:minCount 1 ;
+ sh:nodeKind sh:Literal ;
+ sh:path ngsi-ld:hasValue ] ],
+ [ a base:SubComponentRelationship ;
+ sh:maxCount 1 ;
+ sh:minCount 0 ;
+ sh:nodeKind sh:BlankNode ;
+ sh:path ;
+ sh:property [ sh:class test:BType ;
+ sh:maxCount 1 ;
+ sh:minCount 1 ;
+ sh:nodeKind sh:IRI ;
+ sh:path ngsi-ld:hasObject ] ] ;
+ sh:targetClass test:AlphaType .
+
+shacl:BTypeShape a sh:NodeShape ;
+ sh:property [ sh:maxCount 1 ;
+ sh:minCount 0 ;
+ sh:nodeKind sh:BlankNode ;
+ sh:path ;
+ sh:property [ sh:datatype xsd:boolean ;
+ sh:maxCount 1 ;
+ sh:minCount 1 ;
+ sh:nodeKind sh:Literal ;
+ sh:path ngsi-ld:hasValue ] ] ;
+ sh:targetClass test:BType .
+
diff --git a/semantic-model/opcua/tests/extractType/test_object_types.NodeSet2.xml b/semantic-model/opcua/tests/extractType/test_object_types.NodeSet2.xml
new file mode 100644
index 00000000..60075c21
--- /dev/null
+++ b/semantic-model/opcua/tests/extractType/test_object_types.NodeSet2.xml
@@ -0,0 +1,157 @@
+
+
+
+
+
+
+
+
+ http://my.test/
+
+
+ i=1
+ i=2
+ i=3
+ i=4
+ i=5
+ i=6
+ i=7
+ i=8
+ i=9
+ i=10
+ i=11
+ i=13
+ i=12
+ i=15
+ i=14
+ i=16
+ i=17
+ i=18
+ i=20
+ i=21
+ i=19
+ i=22
+ i=26
+ i=27
+ i=28
+ i=47
+ i=46
+ i=35
+ i=36
+ i=48
+ i=45
+ i=40
+ i=37
+ i=38
+ i=39
+ i=53
+ i=52
+ i=51
+ i=54
+ i=9004
+ i=9005
+ i=17597
+ i=9006
+ i=15112
+ i=17604
+ i=17603
+
+
+
+ X Reference
+
+ i=32
+
+
+
+ Y Reference
+
+ i=32
+
+
+
+ AlphaType
+ A custom object type Alpha
+
+ i=58
+ ns=1;i=2001
+ ns=1;i=1003
+ ns=1;i=1003
+
+
+
+ B Type
+ A custom object type B
+
+ i=58
+
+
+
+ Object B
+
+ ns=1;i=1002
+ ns=1;i=1004
+
+
+
+ Variable of B-object
+
+ i=63
+
+
+
+
+
+
+ Variable of AlphaType
+
+ i=63
+ ns=1;i=1001
+
+
+
+ Variable of Variable C
+
+ i=63
+ ns=1;i=2001
+
+
+
+ Object B
+
+ ns=1;i=1002
+
+
+
+ Alpha Instance
+
+ ns=1;i=2011
+ ns=1;i=2012
+ ns=1;i=1001
+ ns=1;i=2012
+
+
+
+ C Variable of AlphaType
+
+ i=63
+ ns=1;i=2010
+ ns=1;i=2011
+
+
+
+ Object B
+
+ ns=1;i=1002
+ ns=1;i=2013
+
+
+
+ Variable of B-object
+
+ i=63
+
+
+
diff --git a/semantic-model/opcua/tests/extractType/test_object_wrong.NodeSet2.instances b/semantic-model/opcua/tests/extractType/test_object_wrong.NodeSet2.instances
new file mode 100644
index 00000000..5510dd42
--- /dev/null
+++ b/semantic-model/opcua/tests/extractType/test_object_wrong.NodeSet2.instances
@@ -0,0 +1,32 @@
+[
+ {
+ "type": "http://my.test/BWrongType",
+ "id": "urn:test:AlphaInstance:sub:i2012",
+ "@context": [
+ "http://localhost:8099/context.jsonld"
+ ],
+ "uaentity:hasMyVariable": {
+ "type": "Property",
+ "value": false
+ }
+ },
+ {
+ "type": "http://my.test/AlphaType",
+ "id": "urn:test:AlphaInstance",
+ "@context": [
+ "http://localhost:8099/context.jsonld"
+ ],
+ "uaentity:hasC": {
+ "type": "Property",
+ "value": 0.0
+ },
+ "uaentity:hasB": {
+ "type": "Relationship",
+ "object": "urn:test:AlphaInstance:sub:i2012"
+ },
+ "test:Y": {
+ "type": "Relationship",
+ "object": "urn:test:AlphaInstance:sub:i2012"
+ }
+ }
+]
\ No newline at end of file
diff --git a/semantic-model/opcua/tests/extractType/test_object_wrong.NodeSet2.pyshacl b/semantic-model/opcua/tests/extractType/test_object_wrong.NodeSet2.pyshacl
new file mode 100644
index 00000000..e79ba011
--- /dev/null
+++ b/semantic-model/opcua/tests/extractType/test_object_wrong.NodeSet2.pyshacl
@@ -0,0 +1,21 @@
+@prefix ngsi-ld: .
+@prefix sh: .
+@prefix test: .
+@prefix xsd: .
+
+[] a sh:ValidationReport ;
+ sh:conforms false ;
+ sh:result [ a sh:ValidationResult ;
+ sh:focusNode [ a ngsi-ld:Relationship ;
+ ngsi-ld:hasObject ] ;
+ sh:resultMessage "Value does not have class test:BType" ;
+ sh:resultPath ngsi-ld:hasObject ;
+ sh:resultSeverity sh:Violation ;
+ sh:sourceConstraintComponent sh:ClassConstraintComponent ;
+ sh:sourceShape [ sh:class test:BType ;
+ sh:maxCount 1 ;
+ sh:minCount 1 ;
+ sh:nodeKind sh:IRI ;
+ sh:path ngsi-ld:hasObject ] ;
+ sh:value ] .
+
diff --git a/semantic-model/opcua/tests/extractType/test_object_wrong.NodeSet2.shacl b/semantic-model/opcua/tests/extractType/test_object_wrong.NodeSet2.shacl
new file mode 100644
index 00000000..000cf769
--- /dev/null
+++ b/semantic-model/opcua/tests/extractType/test_object_wrong.NodeSet2.shacl
@@ -0,0 +1,51 @@
+@prefix base: .
+@prefix ngsi-ld: .
+@prefix sh: .
+@prefix shacl: .
+@prefix test: .
+@prefix xsd: .
+
+shacl:AlphaTypeShape a sh:NodeShape ;
+ sh:property [ a base:PeerRelationship ;
+ sh:maxCount 1 ;
+ sh:minCount 0 ;
+ sh:nodeKind sh:BlankNode ;
+ sh:path test:X ;
+ sh:property [ sh:class test:BType ;
+ sh:maxCount 1 ;
+ sh:minCount 1 ;
+ sh:nodeKind sh:IRI ;
+ sh:path ngsi-ld:hasObject ] ],
+ [ sh:maxCount 1 ;
+ sh:minCount 0 ;
+ sh:nodeKind sh:BlankNode ;
+ sh:path ;
+ sh:property [ sh:datatype xsd:double ;
+ sh:maxCount 1 ;
+ sh:minCount 1 ;
+ sh:nodeKind sh:Literal ;
+ sh:path ngsi-ld:hasValue ] ],
+ [ a base:SubComponentRelationship ;
+ sh:maxCount 1 ;
+ sh:minCount 0 ;
+ sh:nodeKind sh:BlankNode ;
+ sh:path ;
+ sh:property [ sh:class test:BType ;
+ sh:maxCount 1 ;
+ sh:minCount 1 ;
+ sh:nodeKind sh:IRI ;
+ sh:path ngsi-ld:hasObject ] ] ;
+ sh:targetClass test:AlphaType .
+
+shacl:BTypeShape a sh:NodeShape ;
+ sh:property [ sh:maxCount 1 ;
+ sh:minCount 0 ;
+ sh:nodeKind sh:BlankNode ;
+ sh:path ;
+ sh:property [ sh:datatype xsd:boolean ;
+ sh:maxCount 1 ;
+ sh:minCount 1 ;
+ sh:nodeKind sh:Literal ;
+ sh:path ngsi-ld:hasValue ] ] ;
+ sh:targetClass test:BType .
+
diff --git a/semantic-model/opcua/tests/extractType/test_object_wrong.NodeSet2.xml b/semantic-model/opcua/tests/extractType/test_object_wrong.NodeSet2.xml
new file mode 100644
index 00000000..d2e154b3
--- /dev/null
+++ b/semantic-model/opcua/tests/extractType/test_object_wrong.NodeSet2.xml
@@ -0,0 +1,157 @@
+
+
+
+
+
+
+
+
+ http://my.test/
+
+
+ i=1
+ i=2
+ i=3
+ i=4
+ i=5
+ i=6
+ i=7
+ i=8
+ i=9
+ i=10
+ i=11
+ i=13
+ i=12
+ i=15
+ i=14
+ i=16
+ i=17
+ i=18
+ i=20
+ i=21
+ i=19
+ i=22
+ i=26
+ i=27
+ i=28
+ i=47
+ i=46
+ i=35
+ i=36
+ i=48
+ i=45
+ i=40
+ i=37
+ i=38
+ i=39
+ i=53
+ i=52
+ i=51
+ i=54
+ i=9004
+ i=9005
+ i=17597
+ i=9006
+ i=15112
+ i=17604
+ i=17603
+
+
+
+ X Reference
+
+ i=32
+
+
+
+ Y Reference
+
+ i=32
+
+
+
+ AlphaType
+ A custom object type Alpha
+
+ i=58
+ ns=1;i=2001
+ ns=1;i=1003
+ ns=1;i=1003
+
+
+
+ B Type
+ A custom object type B
+
+ i=58
+
+
+
+ Object B
+
+ ns=1;i=1002
+ ns=1;i=1004
+
+
+
+ Variable of B-object
+
+ i=63
+
+
+
+ B Wrong Type
+ A custom subobject of type B
+
+ i=58
+
+
+
+
+
+ Variable of AlphaType
+
+ i=63
+ ns=1;i=1001
+
+
+
+ Variable of Variable C
+
+ i=63
+ ns=1;i=2001
+
+
+
+ Alpha Instance
+
+ ns=1;i=2011
+ ns=1;i=2012
+ ns=1;i=1001
+ ns=1;i=2012
+
+
+
+ C Variable of AlphaType
+
+ i=63
+ ns=1;i=2010
+ ns=1;i=2011
+
+
+
+ Object B subtype
+
+ ns=1;i=1005
+ ns=1;i=2013
+
+
+
+ Variable of B-object
+
+ i=63
+
+
+
diff --git a/semantic-model/opcua/tests/extractType/test_pumps_instanceexample.instances b/semantic-model/opcua/tests/extractType/test_pumps_instanceexample.instances
new file mode 100644
index 00000000..9f4ca07e
--- /dev/null
+++ b/semantic-model/opcua/tests/extractType/test_pumps_instanceexample.instances
@@ -0,0 +1,408 @@
+[
+ {
+ "type": "http://opcfoundation.org/UA/Pumps/DriveMeasurementsType",
+ "id": "urn:test:ExamplePump:sub:i5006",
+ "@context": [
+ "http://localhost:8099/context.jsonld"
+ ],
+ "uaentity:hasFrequency": {
+ "type": "Property",
+ "value": 0.0
+ },
+ "uaentity:hasMotorVoltage": {
+ "type": "Property",
+ "value": 0.0
+ },
+ "uaentity:hasDriverPowerInput": {
+ "type": "Property",
+ "value": 0.0
+ },
+ "uaentity:hasMotorCurrent": {
+ "type": "Property",
+ "value": 0.0
+ },
+ "uaentity:hasEnergyConsumption": {
+ "type": "Property",
+ "value": 0.0
+ },
+ "uaentity:hasMotorTemperature": {
+ "type": "Property",
+ "value": 0.0
+ }
+ },
+ {
+ "type": "http://opcfoundation.org/UA/Pumps/DrivePortType",
+ "id": "urn:test:ExamplePump:sub:i5004",
+ "@context": [
+ "http://localhost:8099/context.jsonld"
+ ],
+ "uaentity:hasMeasurements": {
+ "type": "Relationship",
+ "object": "urn:test:ExamplePump:sub:i5006"
+ }
+ },
+ {
+ "type": "http://opcfoundation.org/UA/Pumps/PortsGroupType",
+ "id": "urn:test:ExamplePump:sub:i5003",
+ "@context": [
+ "http://localhost:8099/context.jsonld"
+ ],
+ "uaentity:has%3CDrive%3E": {
+ "type": "Relationship",
+ "object": "urn:test:ExamplePump:sub:i5004"
+ }
+ },
+ {
+ "type": "http://opcfoundation.org/UA/Pumps/DesignType",
+ "id": "urn:test:ExamplePump:sub:i5021",
+ "@context": [
+ "http://localhost:8099/context.jsonld"
+ ],
+ "uaentity:hasMaximumAllowableContinuousSpeed": {
+ "type": "Property",
+ "value": 56.67
+ },
+ "uaentity:hasOfferedControlModes": {
+ "type": "Property",
+ "value": ""
+ },
+ "uaentity:hasPumpClass": {
+ "type": "Property",
+ "value": {
+ "@id": "http://opcfoundation.org/UA/Pumps/LiquidPump"
+ }
+ },
+ "uaentity:hasMaximumPumpPowerInput": {
+ "type": "Property",
+ "value": 85.0
+ },
+ "uaentity:hasMaximumAllowableHead": {
+ "type": "Property",
+ "value": 6.3
+ },
+ "uaentity:hasClockwiseRotation": {
+ "type": "Property",
+ "value": true
+ },
+ "uaentity:hasMinimumAllowableContinuousSpeed": {
+ "type": "Property",
+ "value": 23.33
+ },
+ "uaentity:hasMinimumAllowableAmbientTemperature": {
+ "type": "Property",
+ "value": 278.15
+ },
+ "uaentity:hasControllable": {
+ "type": "Property",
+ "value": false
+ },
+ "uaentity:hasOfferedFieldbuses": {
+ "type": "Property",
+ "value": ""
+ },
+ "uaentity:hasMaximumAllowableRelativeHumidity": {
+ "type": "Property",
+ "value": 80.0
+ },
+ "uaentity:hasMaximumAllowableAmbientTemperature": {
+ "type": "Property",
+ "value": 343.15
+ }
+ },
+ {
+ "type": "http://opcfoundation.org/UA/Pumps/SystemRequirementsType",
+ "id": "urn:test:ExamplePump:sub:i5022",
+ "@context": [
+ "http://localhost:8099/context.jsonld"
+ ],
+ "uaentity:hasFieldbus": {
+ "type": "Property",
+ "value": {
+ "@id": "http://opcfoundation.org/UA/Pumps/BACnet_MSTP"
+ }
+ }
+ },
+ {
+ "type": "http://opcfoundation.org/UA/Pumps/ConfigurationGroupType",
+ "id": "urn:test:ExamplePump:sub:i5020",
+ "@context": [
+ "http://localhost:8099/context.jsonld"
+ ],
+ "uaentity:hasDesign": {
+ "type": "Relationship",
+ "object": "urn:test:ExamplePump:sub:i5021"
+ },
+ "uaentity:hasSystemRequirements": {
+ "type": "Relationship",
+ "object": "urn:test:ExamplePump:sub:i5022"
+ }
+ },
+ {
+ "type": "http://opcfoundation.org/UA/Pumps/SupervisionProcessFluidType",
+ "id": "urn:test:ExamplePump:sub:i5010",
+ "@context": [
+ "http://localhost:8099/context.jsonld"
+ ],
+ "uaentity:hasTemperature": {
+ "type": "Property",
+ "value": false
+ },
+ "uaentity:hasBlockage": {
+ "type": "Property",
+ "value": false
+ }
+ },
+ {
+ "type": "http://opcfoundation.org/UA/Pumps/SupervisionPumpOperationType",
+ "id": "urn:test:ExamplePump:sub:i5011",
+ "@context": [
+ "http://localhost:8099/context.jsonld"
+ ],
+ "uaentity:hasMaximumOperationTime": {
+ "type": "Property",
+ "value": false
+ },
+ "uaentity:hasMaximumNumberStarts": {
+ "type": "Property",
+ "value": false
+ }
+ },
+ {
+ "type": "http://opcfoundation.org/UA/Pumps/SupervisionMechanicsType",
+ "id": "urn:test:ExamplePump:sub:i5009",
+ "@context": [
+ "http://localhost:8099/context.jsonld"
+ ],
+ "uaentity:hasRotorBlocked": {
+ "type": "Property",
+ "value": false
+ },
+ "uaentity:hasUnbalance": {
+ "type": "Property",
+ "value": false
+ },
+ "uaentity:hasExcessVibration": {
+ "type": "Property",
+ "value": false
+ }
+ },
+ {
+ "type": "http://opcfoundation.org/UA/Pumps/SupervisionType",
+ "id": "urn:test:ExamplePump:sub:i5008",
+ "@context": [
+ "http://localhost:8099/context.jsonld"
+ ],
+ "uaentity:hasSupervisionProcessFluid": {
+ "type": "Relationship",
+ "object": "urn:test:ExamplePump:sub:i5010"
+ },
+ "uaentity:hasSupervisionPumpOperation": {
+ "type": "Relationship",
+ "object": "urn:test:ExamplePump:sub:i5011"
+ },
+ "uaentity:hasSupervisionMechanics": {
+ "type": "Relationship",
+ "object": "urn:test:ExamplePump:sub:i5009"
+ }
+ },
+ {
+ "type": "http://opcfoundation.org/UA/Pumps/DocumentationType",
+ "id": "urn:test:ExamplePump:sub:i5005",
+ "@context": [
+ "http://localhost:8099/context.jsonld"
+ ],
+ "uaentity:hasTechnicalDataLink": {
+ "type": "Property",
+ "value": "www.LinkToTechnicalData.com/example"
+ }
+ },
+ {
+ "type": "http://opcfoundation.org/UA/Pumps/PreventiveMaintenanceType",
+ "id": "urn:test:ExamplePump:sub:i5015",
+ "@context": [
+ "http://localhost:8099/context.jsonld"
+ ],
+ "uaentity:hasNextInspectionDate": {
+ "type": "Property",
+ "value": "2022-05-01T10:00:00Z"
+ },
+ "uaentity:hasNextServicingDate": {
+ "type": "Property",
+ "value": "2022-05-01T10:00:00Z"
+ },
+ "uaentity:hasInstallationDate": {
+ "type": "Property",
+ "value": "2021-05-01T10:00:00Z"
+ },
+ "uaentity:hasLastInspectionDate": {
+ "type": "Property",
+ "value": "2021-05-01T10:00:00Z"
+ },
+ "uaentity:hasLastServicingDate": {
+ "type": "Property",
+ "value": "2021-05-01T10:00:00Z"
+ }
+ },
+ {
+ "type": "http://opcfoundation.org/UA/Pumps/GeneralMaintenanceType",
+ "id": "urn:test:ExamplePump:sub:i5014",
+ "@context": [
+ "http://localhost:8099/context.jsonld"
+ ],
+ "uaentity:hasActiveMaintenanceTime": {
+ "type": "Property",
+ "value": 0.0
+ },
+ "uaentity:hasOperatingTime": {
+ "type": "Property",
+ "value": 0.0
+ }
+ },
+ {
+ "type": "http://opcfoundation.org/UA/Pumps/BreakdownMaintenanceType",
+ "id": "urn:test:ExamplePump:sub:i5013",
+ "@context": [
+ "http://localhost:8099/context.jsonld"
+ ],
+ "uaentity:hasFailure": {
+ "type": "Property",
+ "value": false
+ },
+ "uaentity:hasNumberOfFailures": {
+ "type": "Property",
+ "value": 0
+ }
+ },
+ {
+ "type": "http://opcfoundation.org/UA/Pumps/MaintenanceGroupType",
+ "id": "urn:test:ExamplePump:sub:i5012",
+ "@context": [
+ "http://localhost:8099/context.jsonld"
+ ],
+ "uaentity:hasPreventiveMaintenance": {
+ "type": "Relationship",
+ "object": "urn:test:ExamplePump:sub:i5015"
+ },
+ "uaentity:hasGeneralMaintenance": {
+ "type": "Relationship",
+ "object": "urn:test:ExamplePump:sub:i5014"
+ },
+ "uaentity:hasBreakdownMaintenance": {
+ "type": "Relationship",
+ "object": "urn:test:ExamplePump:sub:i5013"
+ }
+ },
+ {
+ "type": "http://opcfoundation.org/UA/Pumps/PumpActuationType",
+ "id": "urn:test:ExamplePump:sub:i5017",
+ "@context": [
+ "http://localhost:8099/context.jsonld"
+ ],
+ "uaentity:hasActualControlMode": {
+ "type": "Property",
+ "value": {
+ "@id": "http://opcfoundation.org/UA/Pumps/ConstantPressureControl"
+ }
+ },
+ "uaentity:hasOnOff": {
+ "type": "Property",
+ "value": false
+ }
+ },
+ {
+ "type": "http://opcfoundation.org/UA/Pumps/VibrationMeasurementType",
+ "id": "urn:test:ExamplePump:sub:i5019",
+ "@context": [
+ "http://localhost:8099/context.jsonld"
+ ],
+ "uaentity:hasOverallVibrationAcceleration0_P": {
+ "type": "Property",
+ "value": 0.0
+ },
+ "uaentity:hasOverallVibrationAccelerationP_P": {
+ "type": "Property",
+ "value": 0.0
+ },
+ "uaentity:hasOverallVibrationAccelerationPerG0_P": {
+ "type": "Property",
+ "value": 0.0
+ }
+ },
+ {
+ "type": "http://opcfoundation.org/UA/Pumps/MeasurementsType",
+ "id": "urn:test:ExamplePump:sub:i5018",
+ "@context": [
+ "http://localhost:8099/context.jsonld"
+ ],
+ "uaentity:hasSensor1": {
+ "type": "Relationship",
+ "object": "urn:test:ExamplePump:sub:i5019"
+ },
+ "uaentity:hasSpeed": {
+ "type": "Property",
+ "value": 0.0
+ },
+ "uaentity:hasFluidTemperature": {
+ "type": "Property",
+ "value": 0.0
+ },
+ "uaentity:hasDifferentialPressure": {
+ "type": "Property",
+ "value": 0.0
+ },
+ "uaentity:hasPumpPowerInput": {
+ "type": "Property",
+ "value": 0.0
+ },
+ "uaentity:hasNumberOfStarts": {
+ "type": "Property",
+ "value": 0
+ }
+ },
+ {
+ "type": "http://opcfoundation.org/UA/Pumps/OperationalGroupType",
+ "id": "urn:test:ExamplePump:sub:i5016",
+ "@context": [
+ "http://localhost:8099/context.jsonld"
+ ],
+ "uaentity:hasPumpActuation": {
+ "type": "Relationship",
+ "object": "urn:test:ExamplePump:sub:i5017"
+ },
+ "uaentity:hasMeasurements": {
+ "type": "Relationship",
+ "object": "urn:test:ExamplePump:sub:i5018"
+ }
+ },
+ {
+ "type": "http://opcfoundation.org/UA/Pumps/PumpType",
+ "id": "urn:test:ExamplePump",
+ "@context": [
+ "http://localhost:8099/context.jsonld"
+ ],
+ "uaentity:hasPorts": {
+ "type": "Relationship",
+ "object": "urn:test:ExamplePump:sub:i5003"
+ },
+ "uaentity:hasConfiguration": {
+ "type": "Relationship",
+ "object": "urn:test:ExamplePump:sub:i5020"
+ },
+ "uaentity:hasEvents": {
+ "type": "Relationship",
+ "object": "urn:test:ExamplePump:sub:i5008"
+ },
+ "uaentity:hasDocumentation": {
+ "type": "Relationship",
+ "object": "urn:test:ExamplePump:sub:i5005"
+ },
+ "uaentity:hasMaintenance": {
+ "type": "Relationship",
+ "object": "urn:test:ExamplePump:sub:i5012"
+ },
+ "uaentity:hasOperational": {
+ "type": "Relationship",
+ "object": "urn:test:ExamplePump:sub:i5016"
+ }
+ }
+]
\ No newline at end of file
diff --git a/semantic-model/opcua/tests/extractType/test_pumps_instanceexample.shacl b/semantic-model/opcua/tests/extractType/test_pumps_instanceexample.shacl
new file mode 100644
index 00000000..656c3c55
--- /dev/null
+++ b/semantic-model/opcua/tests/extractType/test_pumps_instanceexample.shacl
@@ -0,0 +1,4826 @@
+@prefix base: .
+@prefix devices: .
+@prefix ngsi-ld: .
+@prefix opcua: .
+@prefix pumps: .
+@prefix sh: .
+@prefix shacl: .
+@prefix xsd: .
+
+shacl:ActuationTypeShape a sh:NodeShape ;
+ sh:property [ sh:maxCount 1 ;
+ sh:minCount 0 ;
+ sh:nodeKind sh:BlankNode ;
+ sh:path ;
+ sh:property [ sh:datatype xsd:double ;
+ sh:maxCount 1 ;
+ sh:minCount 1 ;
+ sh:nodeKind sh:Literal ;
+ sh:path ngsi-ld:hasValue ] ],
+ [ sh:maxCount 1 ;
+ sh:minCount 0 ;
+ sh:nodeKind sh:BlankNode ;
+ sh:path ;
+ sh:property [ sh:datatype xsd:boolean ;
+ sh:maxCount 1 ;
+ sh:minCount 1 ;
+ sh:nodeKind sh:Literal ;
+ sh:path ngsi-ld:hasValue ] ],
+ [ sh:maxCount 1 ;
+ sh:minCount 0 ;
+ sh:nodeKind sh:BlankNode ;
+ sh:path ;
+ sh:property [ sh:datatype xsd:boolean ;
+ sh:maxCount 1 ;
+ sh:minCount 1 ;
+ sh:nodeKind sh:Literal ;
+ sh:path ngsi-ld:hasValue ] ],
+ [ sh:maxCount 1 ;
+ sh:minCount 0 ;
+ sh:nodeKind sh:BlankNode ;
+ sh:path ;
+ sh:property [ sh:datatype xsd:double ;
+ sh:maxCount 1 ;
+ sh:minCount 1 ;
+ sh:nodeKind sh:Literal ;
+ sh:path ngsi-ld:hasValue ] ],
+ [ sh:maxCount 1 ;
+ sh:minCount 0 ;
+ sh:nodeKind sh:BlankNode ;
+ sh:path ;
+ sh:property [ sh:datatype xsd:double ;
+ sh:maxCount 1 ;
+ sh:minCount 1 ;
+ sh:nodeKind sh:Literal ;
+ sh:path ngsi-ld:hasValue ] ],
+ [ sh:maxCount 1 ;
+ sh:minCount 0 ;
+ sh:nodeKind sh:BlankNode ;
+ sh:path ;
+ sh:property [ sh:datatype xsd:double ;
+ sh:maxCount 1 ;
+ sh:minCount 1 ;
+ sh:nodeKind sh:Literal ;
+ sh:path ngsi-ld:hasValue ] ],
+ [ sh:maxCount 1 ;
+ sh:minCount 0 ;
+ sh:nodeKind sh:BlankNode ;
+ sh:path ;
+ sh:property [ sh:datatype xsd:boolean ;
+ sh:maxCount 1 ;
+ sh:minCount 1 ;
+ sh:nodeKind sh:Literal ;
+ sh:path ngsi-ld:hasValue ] ] ;
+ sh:targetClass pumps:ActuationType .
+
+shacl:BaseObjectTypeShape a sh:NodeShape ;
+ sh:targetClass opcua:BaseObjectType .
+
+shacl:BreakdownMaintenanceTypeShape a sh:NodeShape ;
+ sh:property [ sh:maxCount 1 ;
+ sh:minCount 0 ;
+ sh:nodeKind sh:BlankNode ;
+ sh:path ;
+ sh:property [ sh:datatype xsd:boolean ;
+ sh:maxCount 1 ;
+ sh:minCount 1 ;
+ sh:nodeKind sh:Literal ;
+ sh:path ngsi-ld:hasValue ] ],
+ [ sh:maxCount 1 ;
+ sh:minCount 0 ;
+ sh:nodeKind sh:BlankNode ;
+ sh:path ;
+ sh:property [ sh:datatype xsd:integer ;
+ sh:maxCount 1 ;
+ sh:minCount 1 ;
+ sh:nodeKind sh:Literal ;
+ sh:path ngsi-ld:hasValue ] ],
+ [ sh:maxCount 1 ;
+ sh:minCount 0 ;
+ sh:nodeKind sh:BlankNode ;
+ sh:path ;
+ sh:property [ sh:datatype xsd:double ;
+ sh:maxCount 1 ;
+ sh:minCount 1 ;
+ sh:nodeKind sh:Literal ;
+ sh:path ngsi-ld:hasValue ] ],
+ [ sh:maxCount 1 ;
+ sh:minCount 0 ;
+ sh:nodeKind sh:BlankNode ;
+ sh:path ;
+ sh:property [ sh:datatype xsd:double ;
+ sh:maxCount 1 ;
+ sh:minCount 1 ;
+ sh:nodeKind sh:Literal ;
+ sh:path ngsi-ld:hasValue ] ],
+ [ sh:maxCount 1 ;
+ sh:minCount 0 ;
+ sh:nodeKind sh:BlankNode ;
+ sh:path ;
+ sh:property [ sh:datatype xsd:string ;
+ sh:maxCount 1 ;
+ sh:minCount 1 ;
+ sh:nodeKind sh:Literal ;
+ sh:path ngsi-ld:hasValue ] ] ;
+ sh:targetClass pumps:BreakdownMaintenanceType .
+
+shacl:ConditionBasedMaintenanceTypeShape a sh:NodeShape ;
+ sh:property [ sh:maxCount 1 ;
+ sh:minCount 0 ;
+ sh:nodeKind sh:BlankNode ;
+ sh:path ;
+ sh:property [ sh:datatype xsd:double ;
+ sh:maxCount 1 ;
+ sh:minCount 1 ;
+ sh:nodeKind sh:Literal ;
+ sh:path ngsi-ld:hasValue ] ],
+ [ sh:maxCount 1 ;
+ sh:minCount 0 ;
+ sh:nodeKind sh:BlankNode ;
+ sh:path ;
+ sh:property [ sh:datatype xsd:double ;
+ sh:maxCount 1 ;
+ sh:minCount 1 ;
+ sh:nodeKind sh:Literal ;
+ sh:path ngsi-ld:hasValue ] ],
+ [ sh:maxCount 1 ;
+ sh:minCount 0 ;
+ sh:nodeKind sh:BlankNode ;
+ sh:path ;
+ sh:property [ sh:datatype xsd:double ;
+ sh:maxCount 1 ;
+ sh:minCount 1 ;
+ sh:nodeKind sh:Literal ;
+ sh:path ngsi-ld:hasValue ] ],
+ [ sh:maxCount 1 ;
+ sh:minCount 0 ;
+ sh:nodeKind sh:BlankNode ;
+ sh:path ;
+ sh:property [ sh:datatype xsd:double ;
+ sh:maxCount 1 ;
+ sh:minCount 1 ;
+ sh:nodeKind sh:Literal ;
+ sh:path ngsi-ld:hasValue ] ],
+ [ sh:maxCount 1 ;
+ sh:minCount 0 ;
+ sh:nodeKind sh:BlankNode ;
+ sh:path ;
+ sh:property [ sh:datatype xsd:double ;
+ sh:maxCount 1 ;
+ sh:minCount 1 ;
+ sh:nodeKind sh:Literal ;
+ sh:path ngsi-ld:hasValue ] ],
+ [ sh:maxCount 1 ;
+ sh:minCount 0 ;
+ sh:nodeKind sh:BlankNode ;
+ sh:path ;
+ sh:property [ sh:datatype xsd:double ;
+ sh:maxCount 1 ;
+ sh:minCount 1 ;
+ sh:nodeKind sh:Literal ;
+ sh:path ngsi-ld:hasValue ] ],
+ [ sh:maxCount 1 ;
+ sh:minCount 0 ;
+ sh:nodeKind sh:BlankNode ;
+ sh:path ;
+ sh:property [ sh:datatype xsd:double ;
+ sh:maxCount 1 ;
+ sh:minCount 1 ;
+ sh:nodeKind sh:Literal ;
+ sh:path ngsi-ld:hasValue ] ],
+ [ sh:maxCount 1 ;
+ sh:minCount 0 ;
+ sh:nodeKind sh:BlankNode ;
+ sh:path ;
+ sh:property [ sh:datatype xsd:double ;
+ sh:maxCount 1 ;
+ sh:minCount 1 ;
+ sh:nodeKind sh:Literal ;
+ sh:path ngsi-ld:hasValue ] ],
+ [ sh:maxCount 1 ;
+ sh:minCount 0 ;
+ sh:nodeKind sh:BlankNode ;
+ sh:path ;
+ sh:property [ sh:datatype xsd:double ;
+ sh:maxCount 1 ;
+ sh:minCount 1 ;
+ sh:nodeKind sh:Literal ;
+ sh:path ngsi-ld:hasValue ] ],
+ [ sh:maxCount 1 ;
+ sh:minCount 0 ;
+ sh:nodeKind sh:BlankNode ;
+ sh:path ;
+ sh:property [ sh:datatype xsd:double ;
+ sh:maxCount 1 ;
+ sh:minCount 1 ;
+ sh:nodeKind sh:Literal ;
+ sh:path ngsi-ld:hasValue ] ],
+ [ sh:maxCount 1 ;
+ sh:minCount 0 ;
+ sh:nodeKind sh:BlankNode ;
+ sh:path ;
+ sh:property [ sh:datatype xsd:double ;
+ sh:maxCount 1 ;
+ sh:minCount 1 ;
+ sh:nodeKind sh:Literal ;
+ sh:path ngsi-ld:hasValue ] ] ;
+ sh:targetClass pumps:ConditionBasedMaintenanceType .
+
+shacl:ConfigurationGroupTypeShape a sh:NodeShape ;
+ sh:property [ a base:SubComponentRelationship ;
+ sh:maxCount 1 ;
+ sh:minCount 0 ;
+ sh:nodeKind sh:BlankNode ;
+ sh:path ;
+ sh:property [ sh:class pumps:DesignType ;
+ sh:maxCount 1 ;
+ sh:minCount 1 ;
+ sh:nodeKind sh:IRI ;
+ sh:path ngsi-ld:hasObject ] ],
+ [ a base:SubComponentRelationship ;
+ sh:maxCount 1 ;
+ sh:minCount 0 ;
+ sh:nodeKind sh:BlankNode ;
+ sh:path ;
+ sh:property [ sh:class pumps:ImplementationType ;
+ sh:maxCount 1 ;
+ sh:minCount 1 ;
+ sh:nodeKind sh:IRI ;
+ sh:path ngsi-ld:hasObject ] ],
+ [ a base:SubComponentRelationship ;
+ sh:maxCount 1 ;
+ sh:minCount 0 ;
+ sh:nodeKind sh:BlankNode ;
+ sh:path ;
+ sh:property [ sh:class pumps:SystemRequirementsType ;
+ sh:maxCount 1 ;
+ sh:minCount 1 ;
+ sh:nodeKind sh:IRI ;
+ sh:path ngsi-ld:hasObject ] ] ;
+ sh:targetClass pumps:ConfigurationGroupType .
+
+shacl:ControlTypeShape a sh:NodeShape ;
+ sh:property [ sh:maxCount 1 ;
+ sh:minCount 0 ;
+ sh:nodeKind sh:BlankNode ;
+ sh:path ;
+ sh:property [ sh:datatype xsd:double ;
+ sh:maxCount 1 ;
+ sh:minCount 1 ;
+ sh:nodeKind sh:Literal ;
+ sh:path ngsi-ld:hasValue ] ],
+ [ sh:maxCount 1 ;
+ sh:minCount 0 ;
+ sh:nodeKind sh:BlankNode ;
+ sh:path ;
+ sh:property [ sh:datatype xsd:double ;
+ sh:maxCount 1 ;
+ sh:minCount 1 ;
+ sh:nodeKind sh:Literal ;
+ sh:path ngsi-ld:hasValue ] ],
+ [ sh:maxCount 1 ;
+ sh:minCount 0 ;
+ sh:nodeKind sh:BlankNode ;
+ sh:path ;
+ sh:property [ sh:datatype xsd:double ;
+ sh:maxCount 1 ;
+ sh:minCount 1 ;
+ sh:nodeKind sh:Literal ;
+ sh:path ngsi-ld:hasValue ] ],
+ [ sh:maxCount 1 ;
+ sh:minCount 0 ;
+ sh:nodeKind sh:BlankNode ;
+ sh:path ;
+ sh:property [ sh:datatype xsd:double ;
+ sh:maxCount 1 ;
+ sh:minCount 1 ;
+ sh:nodeKind sh:Literal ;
+ sh:path ngsi-ld:hasValue ] ],
+ [ sh:maxCount 1 ;
+ sh:minCount 0 ;
+ sh:nodeKind sh:BlankNode ;
+ sh:path ;
+ sh:property [ sh:datatype xsd:double ;
+ sh:maxCount 1 ;
+ sh:minCount 1 ;
+ sh:nodeKind sh:Literal ;
+ sh:path ngsi-ld:hasValue ] ],
+ [ sh:maxCount 1 ;
+ sh:minCount 0 ;
+ sh:nodeKind sh:BlankNode ;
+ sh:path ;
+ sh:property [ sh:datatype xsd:string ;
+ sh:maxCount 1 ;
+ sh:minCount 1 ;
+ sh:nodeKind sh:Literal ;
+ sh:path ngsi-ld:hasValue ] ],
+ [ sh:maxCount 1 ;
+ sh:minCount 0 ;
+ sh:nodeKind sh:BlankNode ;
+ sh:path ;
+ sh:property [ sh:datatype xsd:double ;
+ sh:maxCount 1 ;
+ sh:minCount 1 ;
+ sh:nodeKind sh:Literal ;
+ sh:path ngsi-ld:hasValue ] ],
+ [ sh:maxCount 1 ;
+ sh:minCount 0 ;
+ sh:nodeKind sh:BlankNode ;
+ sh:path ;
+ sh:property [ sh:datatype xsd:double ;
+ sh:maxCount 1 ;
+ sh:minCount 1 ;
+ sh:nodeKind sh:Literal ;
+ sh:path ngsi-ld:hasValue ] ],
+ [ sh:maxCount 1 ;
+ sh:minCount 0 ;
+ sh:nodeKind sh:BlankNode ;
+ sh:path ;
+ sh:property [ sh:datatype xsd:double ;
+ sh:maxCount 1 ;
+ sh:minCount 1 ;
+ sh:nodeKind sh:Literal ;
+ sh:path ngsi-ld:hasValue ] ],
+ [ sh:maxCount 1 ;
+ sh:minCount 0 ;
+ sh:nodeKind sh:BlankNode ;
+ sh:path ;
+ sh:property [ sh:datatype xsd:double ;
+ sh:maxCount 1 ;
+ sh:minCount 1 ;
+ sh:nodeKind sh:Literal ;
+ sh:path ngsi-ld:hasValue ] ],
+ [ sh:maxCount 1 ;
+ sh:minCount 0 ;
+ sh:nodeKind sh:BlankNode ;
+ sh:path ;
+ sh:property [ sh:datatype xsd:double ;
+ sh:maxCount 1 ;
+ sh:minCount 1 ;
+ sh:nodeKind sh:Literal ;
+ sh:path ngsi-ld:hasValue ] ],
+ [ sh:maxCount 1 ;
+ sh:minCount 0 ;
+ sh:nodeKind sh:BlankNode ;
+ sh:path ;
+ sh:property [ sh:datatype xsd:double ;
+ sh:maxCount 1 ;
+ sh:minCount 1 ;
+ sh:nodeKind sh:Literal ;
+ sh:path ngsi-ld:hasValue ] ],
+ [ sh:maxCount 1 ;
+ sh:minCount 0 ;
+ sh:nodeKind sh:BlankNode ;
+ sh:path ;
+ sh:property [ sh:datatype xsd:double ;
+ sh:maxCount 1 ;
+ sh:minCount 1 ;
+ sh:nodeKind sh:Literal ;
+ sh:path ngsi-ld:hasValue ] ] ;
+ sh:targetClass pumps:ControlType .
+
+shacl:DesignTypeShape a sh:NodeShape ;
+ sh:property [ sh:maxCount 1 ;
+ sh:minCount 0 ;
+ sh:nodeKind sh:BlankNode ;
+ sh:path ;
+ sh:property [ sh:datatype xsd:double ;
+ sh:maxCount 1 ;
+ sh:minCount 1 ;
+ sh:nodeKind sh:Literal ;
+ sh:path ngsi-ld:hasValue ] ],
+ [ sh:maxCount 1 ;
+ sh:minCount 0 ;
+ sh:nodeKind sh:BlankNode ;
+ sh:path ;
+ sh:property [ sh:datatype xsd:double ;
+ sh:maxCount 1 ;
+ sh:minCount 1 ;
+ sh:nodeKind sh:Literal ;
+ sh:path ngsi-ld:hasValue ] ],
+ [ sh:maxCount 1 ;
+ sh:minCount 0 ;
+ sh:nodeKind sh:BlankNode ;
+ sh:path ;
+ sh:property [ sh:datatype xsd:double ;
+ sh:maxCount 1 ;
+ sh:minCount 1 ;
+ sh:nodeKind sh:Literal ;
+ sh:path ngsi-ld:hasValue ] ],
+ [ sh:maxCount 1 ;
+ sh:minCount 0 ;
+ sh:nodeKind sh:BlankNode ;
+ sh:path ;
+ sh:property [ sh:datatype xsd:double ;
+ sh:maxCount 1 ;
+ sh:minCount 1 ;
+ sh:nodeKind sh:Literal ;
+ sh:path ngsi-ld:hasValue ] ],
+ [ sh:maxCount 1 ;
+ sh:minCount 0 ;
+ sh:nodeKind sh:BlankNode ;
+ sh:path ;
+ sh:property [ sh:datatype xsd:double ;
+ sh:maxCount 1 ;
+ sh:minCount 1 ;
+ sh:nodeKind sh:Literal ;
+ sh:path ngsi-ld:hasValue ] ],
+ [ sh:maxCount 1 ;
+ sh:minCount 0 ;
+ sh:nodeKind sh:BlankNode ;
+ sh:path ;
+ sh:property [ sh:datatype xsd:double ;
+ sh:maxCount 1 ;
+ sh:minCount 1 ;
+ sh:nodeKind sh:Literal ;
+ sh:path ngsi-ld:hasValue ] ],
+ [ sh:maxCount 1 ;
+ sh:minCount 0 ;
+ sh:nodeKind sh:BlankNode ;
+ sh:path ;
+ sh:property [ sh:datatype xsd:string ;
+ sh:maxCount 1 ;
+ sh:minCount 1 ;
+ sh:nodeKind sh:Literal ;
+ sh:path ngsi-ld:hasValue ] ],
+ [ sh:maxCount 1 ;
+ sh:minCount 0 ;
+ sh:nodeKind sh:BlankNode ;
+ sh:path ;
+ sh:property [ sh:datatype xsd:double ;
+ sh:maxCount 1 ;
+ sh:minCount 1 ;
+ sh:nodeKind sh:Literal ;
+ sh:path ngsi-ld:hasValue ] ],
+ [ sh:maxCount 1 ;
+ sh:minCount 0 ;
+ sh:nodeKind sh:BlankNode ;
+ sh:path ;
+ sh:property [ sh:datatype xsd:double ;
+ sh:maxCount 1 ;
+ sh:minCount 1 ;
+ sh:nodeKind sh:Literal ;
+ sh:path ngsi-ld:hasValue ] ],
+ [ sh:maxCount 1 ;
+ sh:minCount 0 ;
+ sh:nodeKind sh:BlankNode ;
+ sh:path ;
+ sh:property [ sh:datatype xsd:double ;
+ sh:maxCount 1 ;
+ sh:minCount 1 ;
+ sh:nodeKind sh:Literal ;
+ sh:path ngsi-ld:hasValue ] ],
+ [ sh:maxCount 1 ;
+ sh:minCount 0 ;
+ sh:nodeKind sh:BlankNode ;
+ sh:path ;
+ sh:property [ sh:datatype xsd:double ;
+ sh:maxCount 1 ;
+ sh:minCount 1 ;
+ sh:nodeKind sh:Literal ;
+ sh:path ngsi-ld:hasValue ] ],
+ [ sh:maxCount 1 ;
+ sh:minCount 0 ;
+ sh:nodeKind sh:BlankNode ;
+ sh:path ;
+ sh:property [ sh:datatype xsd:double ;
+ sh:maxCount 1 ;
+ sh:minCount 1 ;
+ sh:nodeKind sh:Literal ;
+ sh:path ngsi-ld:hasValue ] ],
+ [ sh:maxCount 1 ;
+ sh:minCount 0 ;
+ sh:nodeKind sh:BlankNode ;
+ sh:path ;
+ sh:property [ sh:datatype xsd:double ;
+ sh:maxCount 1 ;
+ sh:minCount 1 ;
+ sh:nodeKind sh:Literal ;
+ sh:path ngsi-ld:hasValue ] ],
+ [ sh:maxCount 1 ;
+ sh:minCount 0 ;
+ sh:nodeKind sh:BlankNode ;
+ sh:path ;
+ sh:property [ sh:datatype xsd:boolean ;
+ sh:maxCount 1 ;
+ sh:minCount 1 ;
+ sh:nodeKind sh:Literal ;
+ sh:path ngsi-ld:hasValue ] ],
+ [ sh:maxCount 1 ;
+ sh:minCount 0 ;
+ sh:nodeKind sh:BlankNode ;
+ sh:path ;
+ sh:property [ sh:datatype xsd:double ;
+ sh:maxCount 1 ;
+ sh:minCount 1 ;
+ sh:nodeKind sh:Literal ;
+ sh:path ngsi-ld:hasValue ] ],
+ [ sh:maxCount 1 ;
+ sh:minCount 0 ;
+ sh:nodeKind sh:BlankNode ;
+ sh:path ;
+ sh:property [ sh:datatype xsd:string ;
+ sh:maxCount 1 ;
+ sh:minCount 1 ;
+ sh:nodeKind sh:Literal ;
+ sh:path ngsi-ld:hasValue ] ],
+ [ sh:maxCount 1 ;
+ sh:minCount 0 ;
+ sh:nodeKind sh:BlankNode ;
+ sh:path ;
+ sh:property [ sh:datatype xsd:double ;
+ sh:maxCount 1 ;
+ sh:minCount 1 ;
+ sh:nodeKind sh:Literal ;
+ sh:path ngsi-ld:hasValue ] ],
+ [ sh:maxCount 1 ;
+ sh:minCount 0 ;
+ sh:nodeKind sh:BlankNode ;
+ sh:path ;
+ sh:property [ sh:datatype xsd:double ;
+ sh:maxCount 1 ;
+ sh:minCount 1 ;
+ sh:nodeKind sh:Literal ;
+ sh:path ngsi-ld:hasValue ] ],
+ [ sh:maxCount 1 ;
+ sh:minCount 0 ;
+ sh:nodeKind sh:BlankNode ;
+ sh:path ;
+ sh:property [ sh:class pumps:PumpClassEnum ;
+ sh:maxCount 1 ;
+ sh:minCount 1 ;
+ sh:nodeKind sh:IRI ;
+ sh:path ngsi-ld:hasValue ] ],
+ [ sh:maxCount 1 ;
+ sh:minCount 0 ;
+ sh:nodeKind sh:BlankNode ;
+ sh:path ;
+ sh:property [ sh:datatype xsd:string ;
+ sh:maxCount 1 ;
+ sh:minCount 1 ;
+ sh:nodeKind sh:Literal ;
+ sh:path ngsi-ld:hasValue ] ],
+ [ sh:maxCount 1 ;
+ sh:minCount 0 ;
+ sh:nodeKind sh:BlankNode ;
+ sh:path ;
+ sh:property [ sh:datatype xsd:boolean ;
+ sh:maxCount 1 ;
+ sh:minCount 1 ;
+ sh:nodeKind sh:Literal ;
+ sh:path ngsi-ld:hasValue ] ],
+ [ sh:maxCount 1 ;
+ sh:minCount 0 ;
+ sh:nodeKind sh:BlankNode ;
+ sh:path ;
+ sh:property [ sh:datatype xsd:double ;
+ sh:maxCount 1 ;
+ sh:minCount 1 ;
+ sh:nodeKind sh:Literal ;
+ sh:path ngsi-ld:hasValue ] ],
+ [ sh:maxCount 1 ;
+ sh:minCount 0 ;
+ sh:nodeKind sh:BlankNode ;
+ sh:path ;
+ sh:property [ sh:datatype xsd:double ;
+ sh:maxCount 1 ;
+ sh:minCount 1 ;
+ sh:nodeKind sh:Literal ;
+ sh:path ngsi-ld:hasValue ] ],
+ [ sh:maxCount 1 ;
+ sh:minCount 0 ;
+ sh:nodeKind sh:BlankNode ;
+ sh:path ;
+ sh:property [ sh:datatype xsd:double ;
+ sh:maxCount 1 ;
+ sh:minCount 1 ;
+ sh:nodeKind sh:Literal ;
+ sh:path ngsi-ld:hasValue ] ],
+ [ sh:maxCount 1 ;
+ sh:minCount 0 ;
+ sh:nodeKind sh:BlankNode ;
+ sh:path ;
+ sh:property [ sh:datatype xsd:double ;
+ sh:maxCount 1 ;
+ sh:minCount 1 ;
+ sh:nodeKind sh:Literal ;
+ sh:path ngsi-ld:hasValue ] ],
+ [ sh:maxCount 1 ;
+ sh:minCount 0 ;
+ sh:nodeKind sh:BlankNode ;
+ sh:path ;
+ sh:property [ sh:datatype xsd:double ;
+ sh:maxCount 1 ;
+ sh:minCount 1 ;
+ sh:nodeKind sh:Literal ;
+ sh:path ngsi-ld:hasValue ] ],
+ [ sh:maxCount 1 ;
+ sh:minCount 0 ;
+ sh:nodeKind sh:BlankNode ;
+ sh:path ;
+ sh:property [ sh:datatype xsd:double ;
+ sh:maxCount 1 ;
+ sh:minCount 1 ;
+ sh:nodeKind sh:Literal ;
+ sh:path ngsi-ld:hasValue ] ],
+ [ sh:maxCount 1 ;
+ sh:minCount 0 ;
+ sh:nodeKind sh:BlankNode ;
+ sh:path ;
+ sh:property [ sh:datatype xsd:double ;
+ sh:maxCount 1 ;
+ sh:minCount 1 ;
+ sh:nodeKind sh:Literal ;
+ sh:path ngsi-ld:hasValue ] ],
+ [ sh:maxCount 1 ;
+ sh:minCount 0 ;
+ sh:nodeKind sh:BlankNode ;
+ sh:path ;
+ sh:property [ sh:datatype xsd:double ;
+ sh:maxCount 1 ;
+ sh:minCount 1 ;
+ sh:nodeKind sh:Literal ;
+ sh:path ngsi-ld:hasValue ] ],
+ [ sh:maxCount 1 ;
+ sh:minCount 0 ;
+ sh:nodeKind sh:BlankNode ;
+ sh:path ;
+ sh:property [ sh:datatype xsd:double ;
+ sh:maxCount 1 ;
+ sh:minCount 1 ;
+ sh:nodeKind sh:Literal ;
+ sh:path ngsi-ld:hasValue ] ],
+ [ sh:maxCount 1 ;
+ sh:minCount 0 ;
+ sh:nodeKind sh:BlankNode ;
+ sh:path ;
+ sh:property [ sh:datatype xsd:double ;
+ sh:maxCount 1 ;
+ sh:minCount 1 ;
+ sh:nodeKind sh:Literal ;
+ sh:path ngsi-ld:hasValue ] ],
+ [ sh:maxCount 1 ;
+ sh:minCount 0 ;
+ sh:nodeKind sh:BlankNode ;
+ sh:path ;
+ sh:property [ sh:datatype xsd:double ;
+ sh:maxCount 1 ;
+ sh:minCount 1 ;
+ sh:nodeKind sh:Literal ;
+ sh:path ngsi-ld:hasValue ] ],
+ [ sh:maxCount 1 ;
+ sh:minCount 0 ;
+ sh:nodeKind sh:BlankNode ;
+ sh:path ;
+ sh:property [ sh:datatype xsd:double ;
+ sh:maxCount 1 ;
+ sh:minCount 1 ;
+ sh:nodeKind sh:Literal ;
+ sh:path ngsi-ld:hasValue ] ],
+ [ sh:maxCount 1 ;
+ sh:minCount 0 ;
+ sh:nodeKind sh:BlankNode ;
+ sh:path ;
+ sh:property [ sh:datatype xsd:double ;
+ sh:maxCount 1 ;
+ sh:minCount 1 ;
+ sh:nodeKind sh:Literal ;
+ sh:path ngsi-ld:hasValue ] ],
+ [ sh:maxCount 1 ;
+ sh:minCount 0 ;
+ sh:nodeKind sh:BlankNode ;
+ sh:path ;
+ sh:property [ sh:datatype xsd:double ;
+ sh:maxCount 1 ;
+ sh:minCount 1 ;
+ sh:nodeKind sh:Literal ;
+ sh:path ngsi-ld:hasValue ] ],
+ [ sh:maxCount 1 ;
+ sh:minCount 0 ;
+ sh:nodeKind sh:BlankNode ;
+ sh:path ;
+ sh:property [ sh:datatype xsd:double ;
+ sh:maxCount 1 ;
+ sh:minCount 1 ;
+ sh:nodeKind sh:Literal ;
+ sh:path ngsi-ld:hasValue ] ],
+ [ sh:maxCount 1 ;
+ sh:minCount 0 ;
+ sh:nodeKind sh:BlankNode ;
+ sh:path ;
+ sh:property [ sh:datatype xsd:double ;
+ sh:maxCount 1 ;
+ sh:minCount 1 ;
+ sh:nodeKind sh:Literal ;
+ sh:path ngsi-ld:hasValue ] ],
+ [ sh:maxCount 1 ;
+ sh:minCount 0 ;
+ sh:nodeKind sh:BlankNode ;
+ sh:path ;
+ sh:property [ sh:datatype xsd:double ;
+ sh:maxCount 1 ;
+ sh:minCount 1 ;
+ sh:nodeKind sh:Literal ;
+ sh:path ngsi-ld:hasValue ] ],
+ [ sh:maxCount 1 ;
+ sh:minCount 0 ;
+ sh:nodeKind sh:BlankNode ;
+ sh:path ;
+ sh:property [ sh:datatype xsd:double ;
+ sh:maxCount 1 ;
+ sh:minCount 1 ;
+ sh:nodeKind sh:Literal ;
+ sh:path ngsi-ld:hasValue ] ],
+ [ sh:maxCount 1 ;
+ sh:minCount 0 ;
+ sh:nodeKind sh:BlankNode ;
+ sh:path ;
+ sh:property [ sh:datatype xsd:boolean ;
+ sh:maxCount 1 ;
+ sh:minCount 1 ;
+ sh:nodeKind sh:Literal ;
+ sh:path ngsi-ld:hasValue ] ],
+ [ sh:maxCount 1 ;
+ sh:minCount 0 ;
+ sh:nodeKind sh:BlankNode ;
+ sh:path ;
+ sh:property [ sh:datatype xsd:double ;
+ sh:maxCount 1 ;
+ sh:minCount 1 ;
+ sh:nodeKind sh:Literal ;
+ sh:path ngsi-ld:hasValue ] ],
+ [ sh:maxCount 1 ;
+ sh:minCount 0 ;
+ sh:nodeKind sh:BlankNode ;
+ sh:path ;
+ sh:property [ sh:datatype xsd:double ;
+ sh:maxCount 1 ;
+ sh:minCount 1 ;
+ sh:nodeKind sh:Literal ;
+ sh:path ngsi-ld:hasValue ] ],
+ [ sh:maxCount 1 ;
+ sh:minCount 0 ;
+ sh:nodeKind sh:BlankNode ;
+ sh:path ;
+ sh:property [ sh:datatype xsd:double ;
+ sh:maxCount 1 ;
+ sh:minCount 1 ;
+ sh:nodeKind sh:Literal ;
+ sh:path ngsi-ld:hasValue ] ],
+ [ sh:maxCount 1 ;
+ sh:minCount 0 ;
+ sh:nodeKind sh:BlankNode ;
+ sh:path ;
+ sh:property [ sh:datatype xsd:double ;
+ sh:maxCount 1 ;
+ sh:minCount 1 ;
+ sh:nodeKind sh:Literal ;
+ sh:path ngsi-ld:hasValue ] ],
+ [ sh:maxCount 1 ;
+ sh:minCount 0 ;
+ sh:nodeKind sh:BlankNode ;
+ sh:path ;
+ sh:property [ sh:datatype xsd:double ;
+ sh:maxCount 1 ;
+ sh:minCount 1 ;
+ sh:nodeKind sh:Literal ;
+ sh:path ngsi-ld:hasValue ] ],
+ [ sh:maxCount 1 ;
+ sh:minCount 0 ;
+ sh:nodeKind sh:BlankNode ;
+ sh:path ;
+ sh:property [ sh:datatype xsd:double ;
+ sh:maxCount 1 ;
+ sh:minCount 1 ;
+ sh:nodeKind sh:Literal ;
+ sh:path ngsi-ld:hasValue ] ],
+ [ sh:maxCount 1 ;
+ sh:minCount 0 ;
+ sh:nodeKind sh:BlankNode ;
+ sh:path ;
+ sh:property [ sh:datatype xsd:double ;
+ sh:maxCount 1 ;
+ sh:minCount 1 ;
+ sh:nodeKind sh:Literal ;
+ sh:path ngsi-ld:hasValue ] ],
+ [ sh:maxCount 1 ;
+ sh:minCount 0 ;
+ sh:nodeKind sh:BlankNode ;
+ sh:path ;
+ sh:property [ sh:datatype xsd:double ;
+ sh:maxCount 1 ;
+ sh:minCount 1 ;
+ sh:nodeKind sh:Literal ;
+ sh:path ngsi-ld:hasValue ] ],
+ [ sh:maxCount 1 ;
+ sh:minCount 0 ;
+ sh:nodeKind sh:BlankNode ;
+ sh:path ;
+ sh:property [ sh:datatype xsd:double ;
+ sh:maxCount 1 ;
+ sh:minCount 1 ;
+ sh:nodeKind sh:Literal ;
+ sh:path ngsi-ld:hasValue ] ],
+ [ sh:maxCount 1 ;
+ sh:minCount 0 ;
+ sh:nodeKind sh:BlankNode ;
+ sh:path ;
+ sh:property [ sh:datatype xsd:double ;
+ sh:maxCount 1 ;
+ sh:minCount 1 ;
+ sh:nodeKind sh:Literal ;
+ sh:path ngsi-ld:hasValue ] ],
+ [ sh:maxCount 1 ;
+ sh:minCount 0 ;
+ sh:nodeKind sh:BlankNode ;
+ sh:path ;
+ sh:property [ sh:datatype xsd:string ;
+ sh:maxCount 1 ;
+ sh:minCount 1 ;
+ sh:nodeKind sh:Literal ;
+ sh:path ngsi-ld:hasValue ] ],
+ [ sh:maxCount 1 ;
+ sh:minCount 0 ;
+ sh:nodeKind sh:BlankNode ;
+ sh:path ;
+ sh:property [ sh:datatype xsd:double ;
+ sh:maxCount 1 ;
+ sh:minCount 1 ;
+ sh:nodeKind sh:Literal ;
+ sh:path ngsi-ld:hasValue ] ],
+ [ sh:maxCount 1 ;
+ sh:minCount 0 ;
+ sh:nodeKind sh:BlankNode ;
+ sh:path ;
+ sh:property [ sh:datatype xsd:double ;
+ sh:maxCount 1 ;
+ sh:minCount 1 ;
+ sh:nodeKind sh:Literal ;
+ sh:path ngsi-ld:hasValue ] ],
+ [ sh:maxCount 1 ;
+ sh:minCount 0 ;
+ sh:nodeKind sh:BlankNode ;
+ sh:path ;
+ sh:property [ sh:datatype xsd:double ;
+ sh:maxCount 1 ;
+ sh:minCount 1 ;
+ sh:nodeKind sh:Literal ;
+ sh:path ngsi-ld:hasValue ] ],
+ [ sh:maxCount 1 ;
+ sh:minCount 0 ;
+ sh:nodeKind sh:BlankNode ;
+ sh:path ;
+ sh:property [ sh:datatype xsd:double ;
+ sh:maxCount 1 ;
+ sh:minCount 1 ;
+ sh:nodeKind sh:Literal ;
+ sh:path ngsi-ld:hasValue ] ],
+ [ sh:maxCount 1 ;
+ sh:minCount 0 ;
+ sh:nodeKind sh:BlankNode ;
+ sh:path ;
+ sh:property [ sh:datatype xsd:string ;
+ sh:maxCount 1 ;
+ sh:minCount 1 ;
+ sh:nodeKind sh:Literal ;
+ sh:path ngsi-ld:hasValue ] ],
+ [ sh:maxCount 1 ;
+ sh:minCount 0 ;
+ sh:nodeKind sh:BlankNode ;
+ sh:path ;
+ sh:property [ sh:datatype xsd:double ;
+ sh:maxCount 1 ;
+ sh:minCount 1 ;
+ sh:nodeKind sh:Literal ;
+ sh:path ngsi-ld:hasValue ] ],
+ [ sh:maxCount 1 ;
+ sh:minCount 0 ;
+ sh:nodeKind sh:BlankNode ;
+ sh:path ;
+ sh:property [ sh:datatype xsd:double ;
+ sh:maxCount 1 ;
+ sh:minCount 1 ;
+ sh:nodeKind sh:Literal ;
+ sh:path ngsi-ld:hasValue ] ],
+ [ sh:maxCount 1 ;
+ sh:minCount 0 ;
+ sh:nodeKind sh:BlankNode ;
+ sh:path ;
+ sh:property [ sh:datatype xsd:double ;
+ sh:maxCount 1 ;
+ sh:minCount 1 ;
+ sh:nodeKind sh:Literal ;
+ sh:path ngsi-ld:hasValue ] ],
+ [ sh:maxCount 1 ;
+ sh:minCount 0 ;
+ sh:nodeKind sh:BlankNode ;
+ sh:path ;
+ sh:property [ sh:datatype xsd:double ;
+ sh:maxCount 1 ;
+ sh:minCount 1 ;
+ sh:nodeKind sh:Literal ;
+ sh:path ngsi-ld:hasValue ] ],
+ [ sh:maxCount 1 ;
+ sh:minCount 0 ;
+ sh:nodeKind sh:BlankNode ;
+ sh:path ;
+ sh:property [ sh:datatype xsd:double ;
+ sh:maxCount 1 ;
+ sh:minCount 1 ;
+ sh:nodeKind sh:Literal ;
+ sh:path ngsi-ld:hasValue ] ],
+ [ sh:maxCount 1 ;
+ sh:minCount 0 ;
+ sh:nodeKind sh:BlankNode ;
+ sh:path ;
+ sh:property [ sh:datatype xsd:double ;
+ sh:maxCount 1 ;
+ sh:minCount 1 ;
+ sh:nodeKind sh:Literal ;
+ sh:path ngsi-ld:hasValue ] ],
+ [ sh:maxCount 1 ;
+ sh:minCount 0 ;
+ sh:nodeKind sh:BlankNode ;
+ sh:path ;
+ sh:property [ sh:datatype xsd:double ;
+ sh:maxCount 1 ;
+ sh:minCount 1 ;
+ sh:nodeKind sh:Literal ;
+ sh:path ngsi-ld:hasValue ] ],
+ [ sh:maxCount 1 ;
+ sh:minCount 0 ;
+ sh:nodeKind sh:BlankNode ;
+ sh:path ;
+ sh:property [ sh:datatype xsd:double ;
+ sh:maxCount 1 ;
+ sh:minCount 1 ;
+ sh:nodeKind sh:Literal ;
+ sh:path ngsi-ld:hasValue ] ],
+ [ sh:maxCount 1 ;
+ sh:minCount 0 ;
+ sh:nodeKind sh:BlankNode ;
+ sh:path ;
+ sh:property [ sh:datatype xsd:double ;
+ sh:maxCount 1 ;
+ sh:minCount 1 ;
+ sh:nodeKind sh:Literal ;
+ sh:path ngsi-ld:hasValue ] ],
+ [ sh:maxCount 1 ;
+ sh:minCount 0 ;
+ sh:nodeKind sh:BlankNode ;
+ sh:path ;
+ sh:property [ sh:datatype xsd:boolean ;
+ sh:maxCount 1 ;
+ sh:minCount 1 ;
+ sh:nodeKind sh:Literal ;
+ sh:path ngsi-ld:hasValue ] ],
+ [ sh:maxCount 1 ;
+ sh:minCount 0 ;
+ sh:nodeKind sh:BlankNode ;
+ sh:path ;
+ sh:property [ sh:datatype xsd:string ;
+ sh:maxCount 1 ;
+ sh:minCount 1 ;
+ sh:nodeKind sh:Literal ;
+ sh:path ngsi-ld:hasValue ] ],
+ [ sh:maxCount 1 ;
+ sh:minCount 0 ;
+ sh:nodeKind sh:BlankNode ;
+ sh:path ;
+ sh:property [ sh:datatype xsd:double ;
+ sh:maxCount 1 ;
+ sh:minCount 1 ;
+ sh:nodeKind sh:Literal ;
+ sh:path ngsi-ld:hasValue ] ],
+ [ sh:maxCount 1 ;
+ sh:minCount 0 ;
+ sh:nodeKind sh:BlankNode ;
+ sh:path ;
+ sh:property [ sh:datatype xsd:double ;
+ sh:maxCount 1 ;
+ sh:minCount 1 ;
+ sh:nodeKind sh:Literal ;
+ sh:path ngsi-ld:hasValue ] ],
+ [ sh:maxCount 1 ;
+ sh:minCount 0 ;
+ sh:nodeKind sh:BlankNode ;
+ sh:path ;
+ sh:property [ sh:datatype xsd:double ;
+ sh:maxCount 1 ;
+ sh:minCount 1 ;
+ sh:nodeKind sh:Literal ;
+ sh:path ngsi-ld:hasValue ] ],
+ [ sh:maxCount 1 ;
+ sh:minCount 0 ;
+ sh:nodeKind sh:BlankNode ;
+ sh:path ;
+ sh:property [ sh:datatype xsd:double ;
+ sh:maxCount 1 ;
+ sh:minCount 1 ;
+ sh:nodeKind sh:Literal ;
+ sh:path ngsi-ld:hasValue ] ],
+ [ sh:maxCount 1 ;
+ sh:minCount 0 ;
+ sh:nodeKind sh:BlankNode ;
+ sh:path ;
+ sh:property [ sh:datatype xsd:string ;
+ sh:maxCount 1 ;
+ sh:minCount 1 ;
+ sh:nodeKind sh:Literal ;
+ sh:path ngsi-ld:hasValue ] ],
+ [ sh:maxCount 1 ;
+ sh:minCount 0 ;
+ sh:nodeKind sh:BlankNode ;
+ sh:path ;
+ sh:property [ sh:datatype xsd:double ;
+ sh:maxCount 1 ;
+ sh:minCount 1 ;
+ sh:nodeKind sh:Literal ;
+ sh:path ngsi-ld:hasValue ] ] ;
+ sh:targetClass pumps:DesignType .
+
+shacl:DiscreteInputObjectTypeShape a sh:NodeShape ;
+ sh:property [ sh:maxCount 1 ;
+ sh:minCount 0 ;
+ sh:nodeKind sh:BlankNode ;
+ sh:path ;
+ sh:property [ sh:datatype xsd:boolean ;
+ sh:maxCount 1 ;
+ sh:minCount 1 ;
+ sh:nodeKind sh:Literal ;
+ sh:path ngsi-ld:hasValue ] ],
+ [ sh:maxCount 1 ;
+ sh:minCount 0 ;
+ sh:nodeKind sh:BlankNode ;
+ sh:path ;
+ sh:property [ sh:datatype xsd:integer ;
+ sh:maxCount 1 ;
+ sh:minCount 1 ;
+ sh:nodeKind sh:Literal ;
+ sh:path ngsi-ld:hasValue ] ],
+ [ sh:maxCount 1 ;
+ sh:minCount 1 ;
+ sh:nodeKind sh:BlankNode ;
+ sh:path ;
+ sh:property [ sh:datatype xsd:boolean ;
+ sh:maxCount 1 ;
+ sh:minCount 1 ;
+ sh:nodeKind sh:Literal ;
+ sh:path ngsi-ld:hasValue ] ] ;
+ sh:targetClass pumps:DiscreteInputObjectType .
+
+shacl:DiscreteOutputObjectTypeShape a sh:NodeShape ;
+ sh:property [ sh:maxCount 1 ;
+ sh:minCount 0 ;
+ sh:nodeKind sh:BlankNode ;
+ sh:path ;
+ sh:property [ sh:datatype xsd:boolean ;
+ sh:maxCount 1 ;
+ sh:minCount 1 ;
+ sh:nodeKind sh:Literal ;
+ sh:path ngsi-ld:hasValue ] ],
+ [ sh:maxCount 1 ;
+ sh:minCount 0 ;
+ sh:nodeKind sh:BlankNode ;
+ sh:path ;
+ sh:property [ sh:datatype xsd:integer ;
+ sh:maxCount 1 ;
+ sh:minCount 1 ;
+ sh:nodeKind sh:Literal ;
+ sh:path ngsi-ld:hasValue ] ],
+ [ sh:maxCount 1 ;
+ sh:minCount 0 ;
+ sh:nodeKind sh:BlankNode ;
+ sh:path ;
+ sh:property [ sh:datatype xsd:boolean ;
+ sh:maxCount 1 ;
+ sh:minCount 1 ;
+ sh:nodeKind sh:Literal ;
+ sh:path ngsi-ld:hasValue ] ],
+ [ sh:maxCount 1 ;
+ sh:minCount 0 ;
+ sh:nodeKind sh:BlankNode ;
+ sh:path ;
+ sh:property [ sh:datatype xsd:boolean ;
+ sh:maxCount 1 ;
+ sh:minCount 1 ;
+ sh:nodeKind sh:Literal ;
+ sh:path ngsi-ld:hasValue ] ],
+ [ sh:maxCount 1 ;
+ sh:minCount 1 ;
+ sh:nodeKind sh:BlankNode ;
+ sh:path ;
+ sh:property [ sh:datatype xsd:boolean ;
+ sh:maxCount 1 ;
+ sh:minCount 1 ;
+ sh:nodeKind sh:Literal ;
+ sh:path ngsi-ld:hasValue ] ] ;
+ sh:targetClass pumps:DiscreteOutputObjectType .
+
+shacl:DocumentationTypeShape a sh:NodeShape ;
+ sh:property [ sh:maxCount 1 ;
+ sh:minCount 0 ;
+ sh:nodeKind sh:BlankNode ;
+ sh:path ;
+ sh:property [ sh:datatype xsd:string ;
+ sh:maxCount 1 ;
+ sh:minCount 1 ;
+ sh:nodeKind sh:Literal ;
+ sh:path ngsi-ld:hasValue ] ],
+ [ sh:maxCount 1 ;
+ sh:minCount 0 ;
+ sh:nodeKind sh:BlankNode ;
+ sh:path ;
+ sh:property [ sh:datatype xsd:string ;
+ sh:maxCount 1 ;
+ sh:minCount 1 ;
+ sh:nodeKind sh:Literal ;
+ sh:path ngsi-ld:hasValue ] ],
+ [ sh:maxCount 1 ;
+ sh:minCount 0 ;
+ sh:nodeKind sh:BlankNode ;
+ sh:path ;
+ sh:property [ sh:datatype xsd:string ;
+ sh:maxCount 1 ;
+ sh:minCount 1 ;
+ sh:nodeKind sh:Literal ;
+ sh:path ngsi-ld:hasValue ] ],
+ [ sh:maxCount 1 ;
+ sh:minCount 0 ;
+ sh:nodeKind sh:BlankNode ;
+ sh:path ;
+ sh:property [ sh:datatype xsd:string ;
+ sh:maxCount 1 ;
+ sh:minCount 1 ;
+ sh:nodeKind sh:Literal ;
+ sh:path ngsi-ld:hasValue ] ],
+ [ sh:maxCount 1 ;
+ sh:minCount 0 ;
+ sh:nodeKind sh:BlankNode ;
+ sh:path ;
+ sh:property [ sh:datatype xsd:string ;
+ sh:maxCount 1 ;
+ sh:minCount 1 ;
+ sh:nodeKind sh:Literal ;
+ sh:path ngsi-ld:hasValue ] ],
+ [ sh:maxCount 1 ;
+ sh:minCount 0 ;
+ sh:nodeKind sh:BlankNode ;
+ sh:path ;
+ sh:property [ sh:datatype xsd:string ;
+ sh:maxCount 1 ;
+ sh:minCount 1 ;
+ sh:nodeKind sh:Literal ;
+ sh:path ngsi-ld:hasValue ] ],
+ [ sh:maxCount 1 ;
+ sh:minCount 0 ;
+ sh:nodeKind sh:BlankNode ;
+ sh:path ;
+ sh:property [ sh:datatype xsd:string ;
+ sh:maxCount 1 ;
+ sh:minCount 1 ;
+ sh:nodeKind sh:Literal ;
+ sh:path ngsi-ld:hasValue ] ],
+ [ sh:maxCount 1 ;
+ sh:minCount 0 ;
+ sh:nodeKind sh:BlankNode ;
+ sh:path ;
+ sh:property [ sh:datatype xsd:string ;
+ sh:maxCount 1 ;
+ sh:minCount 1 ;
+ sh:nodeKind sh:Literal ;
+ sh:path ngsi-ld:hasValue ] ],
+ [ sh:maxCount 1 ;
+ sh:minCount 0 ;
+ sh:nodeKind sh:BlankNode ;
+ sh:path ;
+ sh:property [ sh:datatype xsd:string ;
+ sh:maxCount 1 ;
+ sh:minCount 1 ;
+ sh:nodeKind sh:Literal ;
+ sh:path ngsi-ld:hasValue ] ],
+ [ sh:maxCount 1 ;
+ sh:minCount 0 ;
+ sh:nodeKind sh:BlankNode ;
+ sh:path ;
+ sh:property [ sh:datatype xsd:string ;
+ sh:maxCount 1 ;
+ sh:minCount 1 ;
+ sh:nodeKind sh:Literal ;
+ sh:path ngsi-ld:hasValue ] ],
+ [ sh:maxCount 1 ;
+ sh:minCount 0 ;
+ sh:nodeKind sh:BlankNode ;
+ sh:path ;
+ sh:property [ sh:datatype xsd:string ;
+ sh:maxCount 1 ;
+ sh:minCount 1 ;
+ sh:nodeKind sh:Literal ;
+ sh:path ngsi-ld:hasValue ] ],
+ [ sh:maxCount 1 ;
+ sh:minCount 0 ;
+ sh:nodeKind sh:BlankNode ;
+ sh:path ;
+ sh:property [ sh:datatype xsd:string ;
+ sh:maxCount 1 ;
+ sh:minCount 1 ;
+ sh:nodeKind sh:Literal ;
+ sh:path ngsi-ld:hasValue ] ],
+ [ sh:maxCount 1 ;
+ sh:minCount 0 ;
+ sh:nodeKind sh:BlankNode ;
+ sh:path ;
+ sh:property [ sh:datatype xsd:string ;
+ sh:maxCount 1 ;
+ sh:minCount 1 ;
+ sh:nodeKind sh:Literal ;
+ sh:path ngsi-ld:hasValue ] ],
+ [ sh:maxCount 1 ;
+ sh:minCount 0 ;
+ sh:nodeKind sh:BlankNode ;
+ sh:path ;
+ sh:property [ sh:datatype xsd:string ;
+ sh:maxCount 1 ;
+ sh:minCount 1 ;
+ sh:nodeKind sh:Literal ;
+ sh:path ngsi-ld:hasValue ] ],
+ [ sh:maxCount 1 ;
+ sh:minCount 0 ;
+ sh:nodeKind sh:BlankNode ;
+ sh:path ;
+ sh:property [ sh:datatype xsd:string ;
+ sh:maxCount 1 ;
+ sh:minCount 1 ;
+ sh:nodeKind sh:Literal ;
+ sh:path ngsi-ld:hasValue ] ],
+ [ sh:maxCount 1 ;
+ sh:minCount 0 ;
+ sh:nodeKind sh:BlankNode ;
+ sh:path ;
+ sh:property [ sh:datatype xsd:string ;
+ sh:maxCount 1 ;
+ sh:minCount 1 ;
+ sh:nodeKind sh:Literal ;
+ sh:path ngsi-ld:hasValue ] ],
+ [ sh:maxCount 1 ;
+ sh:minCount 0 ;
+ sh:nodeKind sh:BlankNode ;
+ sh:path ;
+ sh:property [ sh:datatype xsd:string ;
+ sh:maxCount 1 ;
+ sh:minCount 1 ;
+ sh:nodeKind sh:Literal ;
+ sh:path ngsi-ld:hasValue ] ],
+ [ sh:maxCount 1 ;
+ sh:minCount 0 ;
+ sh:nodeKind sh:BlankNode ;
+ sh:path ;
+ sh:property [ sh:datatype xsd:string ;
+ sh:maxCount 1 ;
+ sh:minCount 1 ;
+ sh:nodeKind sh:Literal ;
+ sh:path ngsi-ld:hasValue ] ],
+ [ sh:maxCount 1 ;
+ sh:minCount 0 ;
+ sh:nodeKind sh:BlankNode ;
+ sh:path ;
+ sh:property [ sh:datatype xsd:string ;
+ sh:maxCount 1 ;
+ sh:minCount 1 ;
+ sh:nodeKind sh:Literal ;
+ sh:path ngsi-ld:hasValue ] ],
+ [ sh:maxCount 1 ;
+ sh:minCount 0 ;
+ sh:nodeKind sh:BlankNode ;
+ sh:path ;
+ sh:property [ sh:datatype xsd:string ;
+ sh:maxCount 1 ;
+ sh:minCount 1 ;
+ sh:nodeKind sh:Literal ;
+ sh:path ngsi-ld:hasValue ] ],
+ [ sh:maxCount 1 ;
+ sh:minCount 0 ;
+ sh:nodeKind sh:BlankNode ;
+ sh:path ;
+ sh:property [ sh:datatype xsd:string ;
+ sh:maxCount 1 ;
+ sh:minCount 1 ;
+ sh:nodeKind sh:Literal ;
+ sh:path ngsi-ld:hasValue ] ],
+ [ sh:maxCount 1 ;
+ sh:minCount 0 ;
+ sh:nodeKind sh:BlankNode ;
+ sh:path ;
+ sh:property [ sh:datatype xsd:string ;
+ sh:maxCount 1 ;
+ sh:minCount 1 ;
+ sh:nodeKind sh:Literal ;
+ sh:path ngsi-ld:hasValue ] ] ;
+ sh:targetClass pumps:DocumentationType .
+
+shacl:FileTypeShape a sh:NodeShape ;
+ sh:targetClass opcua:FileType .
+
+shacl:FunctionalGroupTypeShape a sh:NodeShape ;
+ sh:targetClass devices:FunctionalGroupType .
+
+shacl:GeneralMaintenanceTypeShape a sh:NodeShape ;
+ sh:property [ sh:maxCount 1 ;
+ sh:minCount 0 ;
+ sh:nodeKind sh:BlankNode ;
+ sh:path ;
+ sh:property [ sh:datatype xsd:double ;
+ sh:maxCount 1 ;
+ sh:minCount 1 ;
+ sh:nodeKind sh:Literal ;
+ sh:path ngsi-ld:hasValue ] ],
+ [ sh:maxCount 1 ;
+ sh:minCount 0 ;
+ sh:nodeKind sh:BlankNode ;
+ sh:path ;
+ sh:property [ sh:datatype xsd:double ;
+ sh:maxCount 1 ;
+ sh:minCount 1 ;
+ sh:nodeKind sh:Literal ;
+ sh:path ngsi-ld:hasValue ] ],
+ [ sh:maxCount 1 ;
+ sh:minCount 0 ;
+ sh:nodeKind sh:BlankNode ;
+ sh:path ;
+ sh:property [ sh:datatype xsd:double ;
+ sh:maxCount 1 ;
+ sh:minCount 1 ;
+ sh:nodeKind sh:Literal ;
+ sh:path ngsi-ld:hasValue ] ],
+ [ sh:maxCount 1 ;
+ sh:minCount 0 ;
+ sh:nodeKind sh:BlankNode ;
+ sh:path ;
+ sh:property [ sh:datatype xsd:double ;
+ sh:maxCount 1 ;
+ sh:minCount 1 ;
+ sh:nodeKind sh:Literal ;
+ sh:path ngsi-ld:hasValue ] ],
+ [ sh:maxCount 1 ;
+ sh:minCount 0 ;
+ sh:nodeKind sh:BlankNode ;
+ sh:path ;
+ sh:property [ sh:datatype xsd:double ;
+ sh:maxCount 1 ;
+ sh:minCount 1 ;
+ sh:nodeKind sh:Literal ;
+ sh:path ngsi-ld:hasValue ] ],
+ [ sh:maxCount 1 ;
+ sh:minCount 0 ;
+ sh:nodeKind sh:BlankNode ;
+ sh:path ;
+ sh:property [ sh:datatype xsd:double ;
+ sh:maxCount 1 ;
+ sh:minCount 1 ;
+ sh:nodeKind sh:Literal ;
+ sh:path ngsi-ld:hasValue ] ],
+ [ sh:maxCount 1 ;
+ sh:minCount 0 ;
+ sh:nodeKind sh:BlankNode ;
+ sh:path ;
+ sh:property [ sh:datatype xsd:double ;
+ sh:maxCount 1 ;
+ sh:minCount 1 ;
+ sh:nodeKind sh:Literal ;
+ sh:path ngsi-ld:hasValue ] ],
+ [ sh:maxCount 1 ;
+ sh:minCount 0 ;
+ sh:nodeKind sh:BlankNode ;
+ sh:path ;
+ sh:property [ sh:datatype xsd:double ;
+ sh:maxCount 1 ;
+ sh:minCount 1 ;
+ sh:nodeKind sh:Literal ;
+ sh:path ngsi-ld:hasValue ] ],
+ [ sh:maxCount 1 ;
+ sh:minCount 0 ;
+ sh:nodeKind sh:BlankNode ;
+ sh:path ;
+ sh:property [ sh:datatype xsd:boolean ;
+ sh:maxCount 1 ;
+ sh:minCount 1 ;
+ sh:nodeKind sh:Literal ;
+ sh:path ngsi-ld:hasValue ] ],
+ [ sh:maxCount 1 ;
+ sh:minCount 0 ;
+ sh:nodeKind sh:BlankNode ;
+ sh:path ;
+ sh:property [ sh:class pumps:StateOfTheItemEnum ;
+ sh:maxCount 1 ;
+ sh:minCount 1 ;
+ sh:nodeKind sh:IRI ;
+ sh:path ngsi-ld:hasValue ] ],
+ [ sh:maxCount 1 ;
+ sh:minCount 0 ;
+ sh:nodeKind sh:BlankNode ;
+ sh:path ;
+ sh:property [ sh:datatype xsd:double ;
+ sh:maxCount 1 ;
+ sh:minCount 1 ;
+ sh:nodeKind sh:Literal ;
+ sh:path ngsi-ld:hasValue ] ],
+ [ sh:maxCount 1 ;
+ sh:minCount 0 ;
+ sh:nodeKind sh:BlankNode ;
+ sh:path ;
+ sh:property [ sh:class pumps:MaintenanceLevelEnum ;
+ sh:maxCount 1 ;
+ sh:minCount 1 ;
+ sh:nodeKind sh:IRI ;
+ sh:path ngsi-ld:hasValue ] ],
+ [ sh:maxCount 1 ;
+ sh:minCount 0 ;
+ sh:nodeKind sh:BlankNode ;
+ sh:path ;
+ sh:property [ sh:datatype xsd:double ;
+ sh:maxCount 1 ;
+ sh:minCount 1 ;
+ sh:nodeKind sh:Literal ;
+ sh:path ngsi-ld:hasValue ] ],
+ [ sh:maxCount 1 ;
+ sh:minCount 0 ;
+ sh:nodeKind sh:BlankNode ;
+ sh:path ;
+ sh:property [ sh:datatype xsd:double ;
+ sh:maxCount 1 ;
+ sh:minCount 1 ;
+ sh:nodeKind sh:Literal ;
+ sh:path ngsi-ld:hasValue ] ],
+ [ sh:maxCount 1 ;
+ sh:minCount 0 ;
+ sh:nodeKind sh:BlankNode ;
+ sh:path ;
+ sh:property [ sh:datatype xsd:double ;
+ sh:maxCount 1 ;
+ sh:minCount 1 ;
+ sh:nodeKind sh:Literal ;
+ sh:path ngsi-ld:hasValue ] ],
+ [ sh:maxCount 1 ;
+ sh:minCount 0 ;
+ sh:nodeKind sh:BlankNode ;
+ sh:path ;
+ sh:property [ sh:datatype xsd:double ;
+ sh:maxCount 1 ;
+ sh:minCount 1 ;
+ sh:nodeKind sh:Literal ;
+ sh:path ngsi-ld:hasValue ] ],
+ [ sh:maxCount 1 ;
+ sh:minCount 0 ;
+ sh:nodeKind sh:BlankNode ;
+ sh:path ;
+ sh:property [ sh:datatype xsd:double ;
+ sh:maxCount 1 ;
+ sh:minCount 1 ;
+ sh:nodeKind sh:Literal ;
+ sh:path ngsi-ld:hasValue ] ],
+ [ sh:maxCount 1 ;
+ sh:minCount 0 ;
+ sh:nodeKind sh:BlankNode ;
+ sh:path ;
+ sh:property [ sh:datatype xsd:double ;
+ sh:maxCount 1 ;
+ sh:minCount 1 ;
+ sh:nodeKind sh:Literal ;
+ sh:path ngsi-ld:hasValue ] ],
+ [ sh:maxCount 1 ;
+ sh:minCount 0 ;
+ sh:nodeKind sh:BlankNode ;
+ sh:path ;
+ sh:property [ sh:datatype xsd:double ;
+ sh:maxCount 1 ;
+ sh:minCount 1 ;
+ sh:nodeKind sh:Literal ;
+ sh:path ngsi-ld:hasValue ] ],
+ [ sh:maxCount 1 ;
+ sh:minCount 0 ;
+ sh:nodeKind sh:BlankNode ;
+ sh:path