Skip to content

Commit

Permalink
Add SAMM - Semantic Aspect Meta Model exporter
Browse files Browse the repository at this point in the history
Signed-off-by: Kostadin Ivanov (BD/TBC-BG) <kostadin.ivanov@bosch.com>
  • Loading branch information
Kostadin-Ivanov committed Sep 3, 2024
1 parent 811f4f4 commit 428119c
Show file tree
Hide file tree
Showing 11 changed files with 2,239 additions and 0 deletions.
1 change: 1 addition & 0 deletions src/vss_tools/vspec/cli.py
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,7 @@ def cli(ctx: click.Context, log_level: str, log_file: Path):
"protobuf": "vss_tools.vspec.vssexporters.vss2protobuf:cli",
"yaml": "vss_tools.vspec.vssexporters.vss2yaml:cli",
"tree": "vss_tools.vspec.vssexporters.vss2tree: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,
"int8": XSD.byte,
"uint16": XSD.unsignedShort,
"int16": XSD.short,
"uint32": XSD.unsignedInt,
"int32": XSD.int,
"uint64": XSD.unsignedLong,
"int64": XSD.long,
"boolean": XSD.boolean,
"float": XSD.float,
"double": XSD.double,
"string": XSD.string,
"dateTime": XSD.dateTime,
"dateTimeStamp": XSD.dateTimeStamp,
"iso8601": XSD.dateTimeStamp,
"anyURI": XSD.anyURI
}

DataUnits = {
"cm3": get_unit_uri("cubicCentimetre"),
"cm^3": get_unit_uri("cubicCentimetre"),
"kw": get_unit_uri("kilowatt"),
"kW": get_unit_uri("kilowatt"),
"kWh": get_unit_uri("kilowattHour"),
"l": get_unit_uri("litre"),
"l/100km" : get_unit_uri("litrePerHour"),
"mm": get_unit_uri("millimetre"),
"kg": get_unit_uri("kilogram"),
"inch": get_unit_uri("inch"),
"A": get_unit_uri("ampere"),
"Ah": get_unit_uri("ampereHour"),
"Nm": get_unit_uri("newtonMetre"),
"N.m": get_unit_uri("newtonMetre"),
"V": get_unit_uri("volt"),
"celsius": get_unit_uri("degreeCelsius"),
"cm/s": get_unit_uri("centimetrePerSecond"),
"degree" : get_unit_uri("degreeUnitOfAngle"),
"degrees": get_unit_uri("degreeUnitOfAngle"),
"degrees/s": get_unit_uri("degreePerSecond"),
"g/s": get_unit_uri("gramPerSecond"),
"kilometer": get_unit_uri("kilometre"),
"km": get_unit_uri("kilometre"),
"km/h": get_unit_uri("kilometrePerHour"),
"kpa": get_unit_uri("kilopascal"),
"kPa": get_unit_uri("kilopascal"),
"l/h": get_unit_uri("litrePerHour"),
"m": get_unit_uri("metre"),
"m/s": get_unit_uri("metrePerSecond"),
"m/s2": get_unit_uri("metrePerSecondSquared"),
"m/s^2": get_unit_uri("metrePerSecondSquared"),
"mbar": get_unit_uri("millibar"),
"min": get_unit_uri("minuteUnitOfTime"),
"ml": get_unit_uri("millilitre"),
"pa": get_unit_uri("pascal"),
"Pa": get_unit_uri("pascal"),
"percent": get_unit_uri("percent"),
"percentage": get_unit_uri("percent"),
"ratio": get_unit_uri("rate"),
"rpm": get_unit_uri("revolutionsPerMinute"),
"g/km": get_unit_uri("kilogramPerKilometre"),
"s": get_unit_uri("secondUnitOfTime"),
"h": get_unit_uri("secondUnitOfTime"),
"W": get_unit_uri("watt"),
"cpm": get_unit_uri("cycle"),
"bpm": get_unit_uri("cycle"),
"iso8601": get_unit_uri("secondUnitOfTime"),
"blank": get_unit_uri("blank")
}
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 pathlib import Path
from rdflib import Graph
from vss_tools import log
from ..config import config as cfg


# Write RDF Graph data to specified file
def write_graph_to_file(path_to_file: Path, file_name: str, graph: Graph):
log.debug(
"Writing RDF Graph to \n -- file: '%s' \n -- location: '%s'\n -- current working directory: '%s'\n",
file_name,
path_to_file,
Path.cwd(),
) # type: ignore

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(cfg.CUSTOM_ESCAPE_CHAR, "\\")

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

# Create and open ttl file for writing
output_file: Path = Path(f"{path_to_file}/{file_name}.ttl")

# Make sure that output_file is created
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()

# Return file location
return output_file
43 changes: 43 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,43 @@
# 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 cfg


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:{cfg.SAMM_VERSION}#{unit_name}")


log.debug("VSS to SAMM CONFIG:\n -- SAMM_TYPE : %s\n -- SAMM_VERSION: %s\n", cfg.SAMM_TYPE, cfg.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:{cfg.SAMM_VERSION}#",
"samm-c": f"{samm_base_namespace}:characteristic:{cfg.SAMM_VERSION}#",
"samm-e": f"{samm_base_namespace}:entity:{cfg.SAMM_VERSION}#",
"unit": f"{samm_base_namespace}:unit:{cfg.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}:{cfg.OUTPUT_NAMESPACE}:{cfg.VSPEC_VERSION}#"
109 changes: 109 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,109 @@
# 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 cfg
from . import stringHelper as str_helper
from .namespaces import samm_base_namespace, samm_output_namespace

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

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"
CHARACTERISTIC = "Characteristic"
CHARACTERISTIC_RELATION = "characteristic"
DATA_TYPE = "dataType"
DESCRIPTION = "description"
ENTITY = "Entity"
EVENTS = "events"
EXAMPLE_VALUE = "exampleValue"
NAME = "name"
OPERATIONS = "operations"
OPTIONAL = "optional"
PAYLOAD_NAME = "payloadName"
PREFERRED_NAME = "preferredName"
PROPERTIES = "properties"
PROPERTY = "Property"

def __init__(self, vss_name):
self.ns = f"{samm_base_namespace}:meta-model:{cfg.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}"

@property
def samm_name(self):
# Make sure that enum value is lc_first
return f"samm:{str_helper.str_to_lc_first_camel_case(self.value)}"


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

def __init__(self, vss_name):
self.ns = f"{samm_base_namespace}:characteristic:{cfg.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 428119c

Please sign in to comment.