Skip to content

Commit

Permalink
Add SAMM - Semantic Aspect Meta Model exporter
Browse files Browse the repository at this point in the history
  • Loading branch information
Kostadin-Ivanov committed Aug 16, 2024
1 parent 13873cb commit ad4856c
Show file tree
Hide file tree
Showing 11 changed files with 2,143 additions and 0 deletions.
1 change: 1 addition & 0 deletions src/vss_tools/vspec/vspec.py
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,7 @@ def cli(ctx: click.Context, log_level: str, log_file: Path):
"jsonschema": "vss_tools.vspec.vssexporters.vss2jsonschema:cli",
"protobuf": "vss_tools.vspec.vssexporters.vss2protobuf:cli",
"yaml": "vss_tools.vspec.vssexporters.vss2yaml:cli",
"samm": "vss_tools.vspec.vssexporters.vss2samm.vss2samm:cli",
},
)
@click.pass_context
Expand Down
34 changes: 34 additions & 0 deletions src/vss_tools/vspec/vssexporters/vss2samm/config/config.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
# Copyright (c) 2024 Contributors to COVESA
#
# This program and the accompanying materials are made available under the
# terms of the Mozilla Public License 2.0 which is available at
# https://www.mozilla.org/en-US/MPL/2.0/
#
# SPDX-License-Identifier: MPL-2.0

# General CONFIG variables
SAMM_TYPE = 'samm'
SAMM_VERSION = '2.1.0'
# Custom string, which we use to escape " and ' characters in VSS node description/comments
# Used in fileHelper.write_graph_to_file to properly escape characters in filedata, before to write it to a file.
CUSTOM_ESCAPE_CHAR = '#V2E-ESC-CHAR#'

# CONFIG Variable defined at runtime as per user input and in available init function
OUTPUT_NAMESPACE = None
VSPEC_VERSION = None
SPLIT_DEPTH = None


def init(output_namespace: str, vspec_version: str, split_depth: int):

# Set user defined or OUTPUT_NAMESPACE
global OUTPUT_NAMESPACE
OUTPUT_NAMESPACE = output_namespace

# Set user defined or OUTPUT_NAMESPACE
global VSPEC_VERSION
VSPEC_VERSION = vspec_version

# Make sure that split_depth is in correct type and value, else set it to DEFAULT: 1
global SPLIT_DEPTH
SPLIT_DEPTH = split_depth if (type(split_depth) is int and split_depth > 0) else 1
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
# Copyright (c) 2024 Contributors to COVESA
#
# This program and the accompanying materials are made available under the
# terms of the Mozilla Public License 2.0 which is available at
# https://www.mozilla.org/en-US/MPL/2.0/
#
# SPDX-License-Identifier: MPL-2.0

from rdflib import XSD
from .namespaces import get_unit_uri

DataTypes = {
'uint8' : XSD.unsignedByte, # noqa: E203
'int8' : XSD.byte, # noqa: E203
'uint16' : XSD.unsignedShort, # noqa: E203
'int16' : XSD.short, # noqa: E203
'uint32' : XSD.unsignedInt, # noqa: E203
'int32' : XSD.int, # noqa: E203
'uint64' : XSD.unsignedLong, # noqa: E203
'int64' : XSD.long, # noqa: E203
'boolean' : XSD.boolean, # noqa: E203
'float' : XSD.float, # noqa: E203
'double' : XSD.double, # noqa: E203
'string' : XSD.string, # noqa: E203
'dateTime' : XSD.dateTime, # noqa: E203
'dateTimeStamp' : XSD.dateTimeStamp, # noqa: E203
'iso8601' : XSD.dateTimeStamp, # noqa: E203
'anyURI' : XSD.anyURI # noqa: E203
}

DataUnits = {
'cm3' : get_unit_uri('cubicCentimetre'), # noqa: E203
'cm^3' : get_unit_uri('cubicCentimetre'), # noqa: E203
'kw' : get_unit_uri('kilowatt'), # noqa: E203
'kW' : get_unit_uri('kilowatt'), # noqa: E203
'kWh' : get_unit_uri('kilowattHour'), # noqa: E203
'l' : get_unit_uri('litre'), # noqa: E203
'l/100km' : get_unit_uri('litrePerHour'), # noqa: E203
'mm' : get_unit_uri('millimetre'), # noqa: E203
'kg' : get_unit_uri('kilogram'), # noqa: E203
'inch' : get_unit_uri('inch'), # noqa: E203
'A' : get_unit_uri('ampere'), # noqa: E203
'Ah' : get_unit_uri('ampereHour'), # noqa: E203
'Nm' : get_unit_uri('newtonMetre'), # noqa: E203
'N.m' : get_unit_uri('newtonMetre'), # noqa: E203
'V' : get_unit_uri('volt'), # noqa: E203
'celsius' : get_unit_uri('degreeCelsius'), # noqa: E203
'cm/s' : get_unit_uri('centimetrePerSecond'), # noqa: E203
'degree' : get_unit_uri('degreeUnitOfAngle'), # noqa: E203
'degrees' : get_unit_uri('degreeUnitOfAngle'), # noqa: E203
'degrees/s' : get_unit_uri('degreePerSecond'), # noqa: E203
'g/s' : get_unit_uri('gramPerSecond'), # noqa: E203
'kilometer' : get_unit_uri('kilometre'), # noqa: E203
'km' : get_unit_uri('kilometre'), # noqa: E203
'km/h' : get_unit_uri('kilometrePerHour'), # noqa: E203
'kpa' : get_unit_uri('kilopascal'), # noqa: E203
'kPa' : get_unit_uri('kilopascal'), # noqa: E203
'l/h' : get_unit_uri('litrePerHour'), # noqa: E203
'm' : get_unit_uri('metre'), # noqa: E203
'm/s' : get_unit_uri('metrePerSecond'), # noqa: E203
'm/s2' : get_unit_uri('metrePerSecondSquared'), # noqa: E203
'm/s^2' : get_unit_uri('metrePerSecondSquared'), # noqa: E203
'mbar' : get_unit_uri('millibar'), # noqa: E203
'min' : get_unit_uri('minuteUnitOfTime'), # noqa: E203
'ml' : get_unit_uri('millilitre'), # noqa: E203
'pa' : get_unit_uri('pascal'), # noqa: E203
'Pa' : get_unit_uri('pascal'), # noqa: E203
'percent' : get_unit_uri('percent'), # noqa: E203
'percentage' : get_unit_uri('percent'), # noqa: E203
'ratio' : get_unit_uri('rate'), # noqa: E203
'rpm' : get_unit_uri('revolutionsPerMinute'), # noqa: E203
'g/km' : get_unit_uri('kilogramPerKilometre'), # noqa: E203
's' : get_unit_uri('secondUnitOfTime'), # noqa: E203
'h' : get_unit_uri('secondUnitOfTime'), # noqa: E203
'W' : get_unit_uri('watt'), # noqa: E203
'cpm' : get_unit_uri('cycle'), # noqa: E203
'bpm' : get_unit_uri('cycle'), # noqa: E203
'iso8601' : get_unit_uri('secondUnitOfTime'), # noqa: E203
'blank' : get_unit_uri('blank') # noqa: E203
}
59 changes: 59 additions & 0 deletions src/vss_tools/vspec/vssexporters/vss2samm/helpers/fileHelper.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
# Copyright (c) 2024 Contributors to COVESA
#
# This program and the accompanying materials are made available under the
# terms of the Mozilla Public License 2.0 which is available at
# https://www.mozilla.org/en-US/MPL/2.0/
#
# SPDX-License-Identifier: MPL-2.0

import os
from rdflib import Graph
from vss_tools import log
from .. config import config as cnfg


# Write RDF Graph data to specified file
def write_graph_to_file(path_to_file: str, file_name: str, graph: Graph):

log.debug("Writing graph to \n -- file: '%s' \n -- location: '%s'", file_name, path_to_file)

log.debug("Current working directory: '%s'", os.getcwd())

filedata = graph.serialize(format='ttl')

# Clean up entries like: samm:operations "()" OR samm:operations "( )"
filedata = filedata.replace(' \"()\" ', ' () ')
filedata = filedata.replace(' \"( )\" ', ' ( ) ')
filedata = filedata.replace(' \"( ', ' ( ')
filedata = filedata.replace(' )\" ', ' ) ')

# Cleanup other escape characters, that were introduced automatically by some of used libraries/tools
filedata = filedata.replace('\\', '')

# Cleanup some CUSTOM ESCAPED, by this script characters.
# Usually double and single quotes in node.description or node.comment field
filedata = filedata.replace(cnfg.CUSTOM_ESCAPE_CHAR, '\\')

# Cleanup xsd:anyURI with xsd:double
filedata = filedata.replace('xsd:anyURI', 'xsd:double')

# Make sure that path_to_file ends with a back slash: \
if not str(path_to_file).endswith('\\'):
path_to_file = f"{path_to_file}\\"

# Create and open ttl file for writing
output_file = f"{path_to_file}{file_name}.ttl"
os.makedirs(os.path.dirname(output_file), exist_ok=True)
file_writer = open(output_file, "w")

# Write filedata to file
file_writer.write(filedata)

# Add new line and close the file
file_writer.write("\n")
file_writer.close()

log.debug("Output file: '%s'\n", output_file)

# Return file location
return output_file
47 changes: 47 additions & 0 deletions src/vss_tools/vspec/vssexporters/vss2samm/helpers/namespaces.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
# Copyright (c) 2024 Contributors to COVESA
#
# This program and the accompanying materials are made available under the
# terms of the Mozilla Public License 2.0 which is available at
# https://www.mozilla.org/en-US/MPL/2.0/
#
# SPDX-License-Identifier: MPL-2.0

from rdflib import URIRef
from vss_tools import log
from .. config import config as cnfg


def get_vspec_uri(node_name: str):
return URIRef(f'{samm_output_namespace}{node_name}')


def get_node_name_from_vspec_uri(node_uri: URIRef):
return node_uri.replace(samm_output_namespace, '')


def get_unit_uri(unit_name: str):
return URIRef(f"{samm_base_namespace}:unit:{cnfg.SAMM_VERSION}#{unit_name}")


log.debug(
"VSS to SAMM CONFIG:\n\t -- SAMM_TYPE : %s\n\t -- SAMM_VERSION: %s",
cnfg.SAMM_TYPE,
cnfg.SAMM_VERSION
)

# NOTE: base_name is more for the ESMF core libraries
# TODO: read https://eclipse-esmf.github.io/samm-specification/snapshot/namespaces.html
# and make sure these are more abstract and can be configured for particular project we use
samm_prefix = "urn:samm"
samm_base_namespace = f"{samm_prefix}:org.eclipse.esmf.samm"
Namespaces = {
'samm': f"{samm_base_namespace}:meta-model:{cnfg.SAMM_VERSION}#",
'samm-c': f"{samm_base_namespace}:characteristic:{cnfg.SAMM_VERSION}#",
'samm-e': f"{samm_base_namespace}:entity:{cnfg.SAMM_VERSION}#",
'unit': f"{samm_base_namespace}:unit:{cnfg.SAMM_VERSION}#",
}

# Below formatted namespace should look like: urn:samm:com.covesa.vss.spec:5.0.0#
# and is used for the ":" bindings of the converted to TTLs, VSS Aspect models
# that will refer to the user specified output_namespace
samm_output_namespace = f"{samm_prefix}:{cnfg.OUTPUT_NAMESPACE}:{cnfg.VSPEC_VERSION}#"
108 changes: 108 additions & 0 deletions src/vss_tools/vspec/vssexporters/vss2samm/helpers/sammConcepts.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,108 @@
# Copyright (c) 2024 Contributors to COVESA
#
# This program and the accompanying materials are made available under the
# terms of the Mozilla Public License 2.0 which is available at
# https://www.mozilla.org/en-US/MPL/2.0/
#
# SPDX-License-Identifier: MPL-2.0


from enum import Enum
from rdflib import URIRef

from .. config import config as cnfg
from . namespaces import samm_base_namespace, samm_output_namespace


class VSSConcepts (Enum):
EMPTY = "" # noqa: E221
BELONGS_TO = "belongsToVehicleComponent" # noqa: E221
HOLDS_VALUE = "holdsState" # noqa: E221
HAS_SIGNAL = "hasDynamicVehicleProperty" # noqa: E221
HAS_ATTRIBUTE = "hasStaticVehicleProperty" # noqa: E221
PART_OF_VEHICLE = "partOfVehicle" # noqa: E221
PART_OF_VEH_COMP = "partOf" # noqa: E221
HAS_COMP_INST = "hasInstance" # noqa: E221
VEHICLE = "Vehicle" # noqa: E221
VEHICLE_SIGNAL = "ObservableVehicleProperty" # noqa: E221
VEHICLE_ACT = "ActuatableVehicleProperty" # noqa: E221
VEHICLE_COMP = "VehicleComponent" # noqa: E221
VEHICLE_PROP = "DynamicVehicleProperty" # noqa: E221
VEHICLE_STAT = "StaticVehicleProperty" # noqa: E221

def __init__(self, vss_name):
self.ns = samm_output_namespace
self.vsso_name = vss_name

@property
def uri(self):
return URIRef(self.uri_string)

@property
def uri_string(self):
return f'{self.ns}{self.value}'


class SammConcepts (Enum):
ASPECT = "Aspect" # noqa: E221
PROPERTY = "Property" # noqa: E221
ENTITY = "Entity" # noqa: E221
CHARACTERISTIC = "Characteristic" # noqa: E221
NAME = "name" # noqa: E221
CHARACTERISTIC_RELATION = "characteristic" # noqa: E221
PREFERRED_NAME = "preferredName" # noqa: E221
PAYLOAD_NAME = "payloadName" # noqa: E221
DESCRIPTION = "description" # noqa: E221
PROPERTIES = "properties" # noqa: E221
OPERATIONS = "operations" # noqa: E221
EVENTS = "events" # noqa: E221
DATA_TYPE = "dataType" # noqa: E221
OPTIONAL = "optional" # noqa: E221
EXAMPLE_VALUE = "exampleValue" # noqa: E221

def __init__(self, vss_name):
self.ns = f"{samm_base_namespace}:meta-model:{cnfg.SAMM_VERSION}#"
self.vsso_name = vss_name

@property
def uri(self):
return URIRef(self.uri_string)

@property
def uri_string(self):
return f'{self.ns}{self.value}'


class SammCConcepts (Enum):
ASPECT = "Aspect" # noqa: E221
PROPERTY = "Property" # noqa: E221
ENTITY = "Entity" # noqa: E221
SINGLE_ENTITY = "SingleEntity" # noqa: E221
STATE = "State" # noqa: E221
TRAIT = "Trait" # noqa: E221
TIMESTAMP = "Timestamp" # noqa: E221
RANGE_CONSTRAINT = "RangeConstraint" # noqa: E221
MEASUREMENT = "Measurement" # noqa: E221
QUANTIFIABLE = "Quantifiable" # noqa: E221
LIST = "List" # noqa: E221
ENUM = "Enumeration" # noqa: E221
BOOLEAN = "Boolean" # noqa: E221
VALUES = "values" # noqa: E221
UNIT = "unit" # noqa: E221
CONSTRAINT = "constraint" # noqa: E221
BASE_CHARACTERISTICS = "baseCharacteristic" # noqa: E221
MAX_VALUE = "maxValue" # noqa: E221
MIN_VALUE = "minValue" # noqa: E221
DEFAULT_VALUE = "defaultValue" # noqa: E221

def __init__(self, vss_name):
self.ns = f"{samm_base_namespace}:characteristic:{cnfg.SAMM_VERSION}#"
self.vsso_name = vss_name

@property
def uri(self):
return URIRef(self.uri_string)

@property
def uri_string(self):
return f'{self.ns}{self.value}'
Loading

0 comments on commit ad4856c

Please sign in to comment.