Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
45 changes: 45 additions & 0 deletions data/deploymentview.dv.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
<DeploymentView version="1.2" UiFile="deploymentview.ui.xml" creatorHash="383508e" modifierHash="383508e">
<Node id="{d90e674a-8471-4e59-8bc6-c79e82c1067b}" name="SAM V71 RTEMS N7S_1" type="ocarina_processors_arm::samv71.rtems" node_label="Node_2" namespace="ocarina_processors_arm">
<Partition id="{6d924f84-5366-47d0-8a89-56a2614f6813}" name="ASW">
<Function id="{81aa583e-d1d0-47bb-ae8b-de3323dac654}" name="Frontend" path="Frontend"/>
<Function id="{4893b901-a505-42da-bad7-e3fa914fda5c}" name="Backend" path="Backend"/>
</Partition>
<Device id="{3d4974d8-cd66-4f5b-a8c7-813e8284d726}" name="uart0" requirement_ids="r10" requires_bus_access="ocarina_buses::serial.ccsds" port="uart0" asn1file="/home/taste/tool-inst/include/TASTE-SAMV71-RTEMS-Drivers/configurations/samv71-rtems-serial-driver.asn" asn1type="Serial-SamV71-Rtems-Conf-T" asn1module="SAMV71-RTEMS-SERIAL-DRIVER" namespace="ocarina_drivers" extends="ocarina_drivers::serial_ccsds" impl_extends="ocarina_drivers::serial_ccsds.samv71_rtems" bus_namespace="ocarina_buses">
</Device>
<Device id="{429de864-ffda-4d9a-846b-6851debc08d0}" name="uart1" requires_bus_access="ocarina_buses::serial.ccsds" port="uart1" asn1file="/home/taste/tool-inst/include/TASTE-SAMV71-RTEMS-Drivers/configurations/samv71-rtems-serial-driver.asn" asn1type="Serial-SamV71-Rtems-Conf-T" asn1module="SAMV71-RTEMS-SERIAL-DRIVER" namespace="ocarina_drivers" extends="ocarina_drivers::serial_ccsds" impl_extends="ocarina_drivers::serial_ccsds.samv71_rtems" bus_namespace="ocarina_buses">
</Device>
<Device id="{3b9b9f0e-4376-441c-9fb4-20a82d9adfaf}" name="uart2" requires_bus_access="ocarina_buses::serial.ccsds" port="uart2" asn1file="/home/taste/tool-inst/include/TASTE-SAMV71-RTEMS-Drivers/configurations/samv71-rtems-serial-driver.asn" asn1type="Serial-SamV71-Rtems-Conf-T" asn1module="SAMV71-RTEMS-SERIAL-DRIVER" namespace="ocarina_drivers" extends="ocarina_drivers::serial_ccsds" impl_extends="ocarina_drivers::serial_ccsds.samv71_rtems" bus_namespace="ocarina_buses">
</Device>
<Device id="{46e38140-b204-4360-bdb2-766800b08c13}" name="uart3" requires_bus_access="ocarina_buses::serial.ccsds" port="uart3" asn1file="/home/taste/tool-inst/include/TASTE-SAMV71-RTEMS-Drivers/configurations/samv71-rtems-serial-driver.asn" asn1type="Serial-SamV71-Rtems-Conf-T" asn1module="SAMV71-RTEMS-SERIAL-DRIVER" namespace="ocarina_drivers" extends="ocarina_drivers::serial_ccsds" impl_extends="ocarina_drivers::serial_ccsds.samv71_rtems" bus_namespace="ocarina_buses">
</Device>
<Device id="{179bb7d9-a647-401f-b8fc-8398825c82aa}" name="uart4" requires_bus_access="ocarina_buses::serial.ccsds" port="uart4" asn1file="/home/taste/tool-inst/include/TASTE-SAMV71-RTEMS-Drivers/configurations/samv71-rtems-serial-driver.asn" asn1type="Serial-SamV71-Rtems-Conf-T" asn1module="SAMV71-RTEMS-SERIAL-DRIVER" namespace="ocarina_drivers" extends="ocarina_drivers::serial_ccsds" impl_extends="ocarina_drivers::serial_ccsds.samv71_rtems" bus_namespace="ocarina_buses">
</Device>
</Node>
<Node id="{d2ed099d-9b64-446a-828c-639641de9cf7}" name="x86 Linux C++_1" type="ocarina_processors_x86::x86.generic_linux" requirement_ids="r21,r20" node_label="Node_1" namespace="ocarina_processors_x86">
<Partition id="{3306c70d-08de-4beb-a6b2-e8b3d4861243}" name="Ground">
<Function id="{0042ca70-4823-4460-88fe-d873823b9e73}" name="EGSE" path="EGSE"/>
</Partition>
<Device id="{a294e422-1f35-4716-88f2-c89c5621e71c}" name="tcp1" requires_bus_access="ocarina_buses::ip.generic" port="tcp1" asn1file="/home/taste/tool-inst/include/TASTE-Linux-Drivers/configurations/linux-socket-ip-driver.asn" asn1type="Socket-IP-Conf-T" asn1module="LINUX-SOCKET-IP-DRIVER" namespace="ocarina_drivers" extends="ocarina_drivers::ip_socket" impl_extends="ocarina_drivers::ip_socket.linux" bus_namespace="ocarina_buses">
</Device>
<Device id="{143518ef-a9fa-4c50-a310-7dfd56a92e64}" name="tcp2" requires_bus_access="ocarina_buses::ip.generic" port="tcp2" asn1file="/home/taste/tool-inst/include/TASTE-Linux-Drivers/configurations/linux-socket-ip-driver.asn" asn1type="Socket-IP-Conf-T" asn1module="LINUX-SOCKET-IP-DRIVER" namespace="ocarina_drivers" extends="ocarina_drivers::ip_socket" impl_extends="ocarina_drivers::ip_socket.linux" bus_namespace="ocarina_buses">
</Device>
<Device id="{4d3b0112-4110-4f88-a0c4-5cbb5c301911}" name="tcp3" requires_bus_access="ocarina_buses::ip.generic" port="tcp3" asn1file="/home/taste/tool-inst/include/TASTE-Linux-Drivers/configurations/linux-socket-ip-driver.asn" asn1type="Socket-IP-Conf-T" asn1module="LINUX-SOCKET-IP-DRIVER" namespace="ocarina_drivers" extends="ocarina_drivers::ip_socket" impl_extends="ocarina_drivers::ip_socket.linux" bus_namespace="ocarina_buses">
</Device>
<Device id="{486034e5-9983-4c09-add7-d33615bad4ab}" name="tcp4" requires_bus_access="ocarina_buses::ip.generic" port="tcp4" asn1file="/home/taste/tool-inst/include/TASTE-Linux-Drivers/configurations/linux-socket-ip-driver.asn" asn1type="Socket-IP-Conf-T" asn1module="LINUX-SOCKET-IP-DRIVER" namespace="ocarina_drivers" extends="ocarina_drivers::ip_socket" impl_extends="ocarina_drivers::ip_socket.linux" bus_namespace="ocarina_buses">
</Device>
<Device id="{461825c1-d258-4142-87f3-aa1cf7e4ec58}" name="udp1" requires_bus_access="ocarina_buses::ip.generic" port="udp1" asn1file="/home/taste/tool-inst/include/TASTE-Linux-Drivers/configurations/linux-socket-ip-driver.asn" asn1type="Socket-IP-Conf-T" asn1module="LINUX-SOCKET-IP-DRIVER" namespace="ocarina_drivers" extends="ocarina_drivers::udp" impl_extends="ocarina_drivers::udp.linux" bus_namespace="ocarina_buses">
</Device>
<Device id="{e4adae5b-da7a-4116-ab07-cca5836ae956}" name="udp2" requires_bus_access="ocarina_buses::ip.generic" port="udp2" asn1file="/home/taste/tool-inst/include/TASTE-Linux-Drivers/configurations/linux-socket-ip-driver.asn" asn1type="Socket-IP-Conf-T" asn1module="LINUX-SOCKET-IP-DRIVER" namespace="ocarina_drivers" extends="ocarina_drivers::udp" impl_extends="ocarina_drivers::udp.linux" bus_namespace="ocarina_buses">
</Device>
<Device id="{2babb575-5420-4079-a675-10024db8d065}" name="udp3" requires_bus_access="ocarina_buses::ip.generic" port="udp3" asn1file="/home/taste/tool-inst/include/TASTE-Linux-Drivers/configurations/linux-socket-ip-driver.asn" asn1type="Socket-IP-Conf-T" asn1module="LINUX-SOCKET-IP-DRIVER" namespace="ocarina_drivers" extends="ocarina_drivers::udp" impl_extends="ocarina_drivers::udp.linux" bus_namespace="ocarina_buses">
</Device>
<Device id="{95e09c1c-a585-48ba-989e-05876c88e4b6}" name="udp4" requires_bus_access="ocarina_buses::ip.generic" port="udp4" asn1file="/home/taste/tool-inst/include/TASTE-Linux-Drivers/configurations/linux-socket-ip-driver.asn" asn1type="Socket-IP-Conf-T" asn1module="LINUX-SOCKET-IP-DRIVER" namespace="ocarina_drivers" extends="ocarina_drivers::udp" impl_extends="ocarina_drivers::udp.linux" bus_namespace="ocarina_buses">
</Device>
<Device id="{e18013d2-4879-47f6-8fd9-a6eefb7d1c38}" name="uart0" requires_bus_access="ocarina_buses::serial.ccsds" port="uart0" asn1file="/home/taste/tool-inst/include/TASTE-Linux-Drivers/configurations/linux-serial-ccsds-driver.asn" asn1type="Serial-CCSDS-Linux-Conf-T" asn1module="LINUX-SERIAL-CCSDS-DRIVER" namespace="ocarina_drivers" extends="ocarina_drivers::serial_ccsds" impl_extends="ocarina_drivers::serial_ccsds.linux" bus_namespace="ocarina_buses">
</Device>
</Node>
<Connection id="{22fe1747-61e5-4c8c-be5b-64a520be8244}" name="Connection_1" from_node="x86 Linux C++_1" from_port="uart0" to_bus="ocarina_buses::serial.ccsds" to_node="SAM V71 RTEMS N7S_1" to_port="uart0">
<Message id="{8db75af6-2fc4-458c-9e30-7fd7e44479eb}" name="Message_1" from_function="EGSE" from_interface="tc" to_function="Frontend" to_interface="tc"/>
<Message id="{7cd12471-ac99-4dfc-8c7d-a62280efc46b}" name="Message_2" from_function="Frontend" from_interface="tm" to_function="EGSE" to_interface="tm"/>
</Connection>
</DeploymentView>
131 changes: 131 additions & 0 deletions templateprocessor/dv.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,131 @@
"""
TASTE Deployment View (DV) data model classes.

This module provides Python classes that reflect the schema/structure of
TASTE Deployment View XML files, allowing for parsing, manipulation, and
generation of DV data.
"""

from dataclasses import dataclass, field
from typing import List


@dataclass
class DeploymentFunction:
"""Function deployed to a partition."""

id: str
name: str
path: str


@dataclass
class Partition:
"""
Partition within a node.

A partition represents an execution context that can host one or more
functions (software components).
"""

id: str
name: str
functions: List[DeploymentFunction] = field(default_factory=list)


@dataclass
class Device:
"""
Hardware device attached to a node.

Devices represent connection hardpoints (e.g., UART, TCP/UDP ports) that
provide bus access for communication.
"""

id: str
name: str
requires_bus_access: str
port: str
asn1file: str
asn1type: str
asn1module: str
namespace: str
extends: str
impl_extends: str
bus_namespace: str
requirement_ids: List[str] = field(default_factory=list)


@dataclass
class Node:
"""
Deployment node (processor/platform).

A node represents a physical or virtual hardware platform that can host
partitions and devices. Examples include embedded processors, Linux systems,
or other execution platforms.
"""

id: str
name: str
type: str
node_label: str
namespace: str
partitions: List[Partition] = field(default_factory=list)
devices: List[Device] = field(default_factory=list)
requirement_ids: List[str] = field(default_factory=list)


@dataclass
class Message:
"""
Message routed through a connection.

Represents data flow from one function's interface to another function's
interface over a physical connection.
"""

id: str
name: str
from_function: str
from_interface: str
to_function: str
to_interface: str


@dataclass
class Connection:
"""
Physical connection between nodes.

Represents a communication link between devices on different nodes,
potentially through a bus. Contains the messages that are routed
through this connection.
"""

id: str
name: str
from_node: str
from_port: str
to_bus: str
to_node: str
to_port: str
messages: List[Message] = field(default_factory=list)


@dataclass
class DeploymentView:
"""
Root element representing a TASTE Deployment View.

This is the main data structure that contains all nodes, connections,
and other elements that define how a TASTE system is deployed to
physical/virtual hardware.
"""

version: str = ""
ui_file: str = ""
creator_hash: str = ""
modifier_hash: str = ""
nodes: List[Node] = field(default_factory=list)
connections: List[Connection] = field(default_factory=list)
201 changes: 201 additions & 0 deletions templateprocessor/dvreader.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,201 @@
"""
TASTE Deployment View XML Reader.

This module provides functionality to parse TASTE Deployment View XML files
and construct DeploymentView data model instances.
"""

import xml.etree.ElementTree as ET
from pathlib import Path
from typing import Union

from templateprocessor.dv import (
DeploymentView,
Node,
Partition,
DeploymentFunction,
Device,
Connection,
Message,
)


class DVReader:
"""
Reader for TASTE Deployment View XML files.

Parses XML files conforming to the TASTE Deployment View schema and
constructs corresponding DeploymentView objects.

Example:
reader = DVReader()
deployment_view = reader.read("deploymentview.dv.xml")
"""

def read(self, file_path: Union[str, Path]) -> DeploymentView:
"""
Read and parse a TASTE Deployment View XML file.

Args:
file_path: Path to the DV XML file

Returns:
DeploymentView object populated with parsed data

Raises:
FileNotFoundError: If the file does not exist
xml.etree.ElementTree.ParseError: If XML is malformed
"""
file_path = Path(file_path)
if not file_path.exists():
raise FileNotFoundError(f"Deployment View file not found: {file_path}")

tree = ET.parse(file_path)
root = tree.getroot()

return self._parse_deployment_view(root)

def read_string(self, xml_content: str) -> DeploymentView:
"""
Read and parse TASTE Deployment View XML from a string.

Args:
xml_content: XML content as string

Returns:
DeploymentView object populated with parsed data

Raises:
xml.etree.ElementTree.ParseError: If XML is malformed
"""
root = ET.fromstring(xml_content)
return self._parse_deployment_view(root)

def _parse_deployment_view(self, root: ET.Element) -> DeploymentView:
"""Parse the root DeploymentView element."""
dv = DeploymentView(
version=root.get("version", ""),
ui_file=root.get("UiFile", ""),
creator_hash=root.get("creatorHash", ""),
modifier_hash=root.get("modifierHash", ""),
)

# Parse all Node elements
for node_elem in root.findall("Node"):
node = self._parse_node(node_elem)
dv.nodes.append(node)

# Parse all Connection elements
for conn_elem in root.findall("Connection"):
connection = self._parse_connection(conn_elem)
dv.connections.append(connection)

return dv

def _parse_node(self, elem: ET.Element) -> Node:
"""Parse a Node element."""
# Parse requirement_ids if present
requirement_ids = []
req_ids_str = elem.get("requirement_ids", "")
if req_ids_str:
requirement_ids = [
rid.strip() for rid in req_ids_str.split(",") if rid.strip()
]

node = Node(
id=elem.get("id", ""),
name=elem.get("name", ""),
type=elem.get("type", ""),
node_label=elem.get("node_label", ""),
namespace=elem.get("namespace", ""),
requirement_ids=requirement_ids,
)

# Parse partitions
for partition_elem in elem.findall("Partition"):
partition = self._parse_partition(partition_elem)
node.partitions.append(partition)

# Parse devices
for device_elem in elem.findall("Device"):
device = self._parse_device(device_elem)
node.devices.append(device)

return node

def _parse_partition(self, elem: ET.Element) -> Partition:
"""Parse a Partition element."""
partition = Partition(
id=elem.get("id", ""),
name=elem.get("name", ""),
)

# Parse functions
for func_elem in elem.findall("Function"):
function = self._parse_deployment_function(func_elem)
partition.functions.append(function)

return partition

def _parse_deployment_function(self, elem: ET.Element) -> DeploymentFunction:
"""Parse a Function element within a Partition."""
return DeploymentFunction(
id=elem.get("id", ""),
name=elem.get("name", ""),
path=elem.get("path", ""),
)

def _parse_device(self, elem: ET.Element) -> Device:
"""Parse a Device element."""
# Parse requirement_ids if present
requirement_ids = []
req_ids_str = elem.get("requirement_ids", "")
if req_ids_str:
requirement_ids = [
rid.strip() for rid in req_ids_str.split(",") if rid.strip()
]

return Device(
id=elem.get("id", ""),
name=elem.get("name", ""),
requires_bus_access=elem.get("requires_bus_access", ""),
port=elem.get("port", ""),
asn1file=elem.get("asn1file", ""),
asn1type=elem.get("asn1type", ""),
asn1module=elem.get("asn1module", ""),
namespace=elem.get("namespace", ""),
extends=elem.get("extends", ""),
impl_extends=elem.get("impl_extends", ""),
bus_namespace=elem.get("bus_namespace", ""),
requirement_ids=requirement_ids,
)

def _parse_connection(self, elem: ET.Element) -> Connection:
"""Parse a Connection element."""
connection = Connection(
id=elem.get("id", ""),
name=elem.get("name", ""),
from_node=elem.get("from_node", ""),
from_port=elem.get("from_port", ""),
to_bus=elem.get("to_bus", ""),
to_node=elem.get("to_node", ""),
to_port=elem.get("to_port", ""),
)

# Parse messages
for msg_elem in elem.findall("Message"):
message = self._parse_message(msg_elem)
connection.messages.append(message)

return connection

def _parse_message(self, elem: ET.Element) -> Message:
"""Parse a Message element."""
return Message(
id=elem.get("id", ""),
name=elem.get("name", ""),
from_function=elem.get("from_function", ""),
from_interface=elem.get("from_interface", ""),
to_function=elem.get("to_function", ""),
to_interface=elem.get("to_interface", ""),
)
1 change: 1 addition & 0 deletions tests/Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ PYTHON ?= python3

TESTS = \
test_ivreader.py \
test_dvreader.py \
test_soreader.py

.PHONY: \
Expand Down
Loading