Skip to content

Commit

Permalink
Switch to agreed scxml and bt format (#16)
Browse files Browse the repository at this point in the history
Signed-off-by: Marco Lampacrescia <[email protected]>
  • Loading branch information
MarcoLm993 authored Jul 31, 2024
1 parent 9bf282c commit 52b2c06
Show file tree
Hide file tree
Showing 28 changed files with 175 additions and 65 deletions.
15 changes: 9 additions & 6 deletions jani_generator/src/jani_generator/scxml_helpers/scxml_tags.py
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@
parse_ecmascript_to_jani_expression
from as2fm_common.ecmascript_interpretation import \
interpret_ecma_script_expr
from scxml_converter.scxml_entries import (ScxmlAssign, ScxmlBase,
from scxml_converter.scxml_entries import (ScxmlAssign, ScxmlBase, ScxmlData,
ScxmlDataModel, ScxmlExecutionBody,
ScxmlIf, ScxmlRoot, ScxmlSend,
ScxmlState, ScxmlTransition)
Expand Down Expand Up @@ -284,14 +284,17 @@ def get_children(self) -> List[ScxmlBase]:
return []

def write_model(self):
for name, expr in self.element.get_data_entries():
assert expr is not None, f"No init value for {name}."
for scxml_data in self.element.get_data_entries():
assert isinstance(scxml_data, ScxmlData), "Unexpected element in the DataModel."
assert scxml_data.check_validity(), "Found invalid data entry."
# TODO: ScxmlData from scxml_helpers provide many more options.
# It should be ported to scxml_entries.ScxmlDataModel
init_value = parse_ecmascript_to_jani_expression(expr)
expr_type = type(interpret_ecma_script_expr(expr))
init_value = parse_ecmascript_to_jani_expression(scxml_data.get_expr())
expr_type = type(interpret_ecma_script_expr(scxml_data.get_expr()))
assert expr_type == scxml_data.get_type(), \
f"Expected type {scxml_data.get_type()}, got {expr_type}."
self.automaton.add_variable(
JaniVariable(name, expr_type, init_value))
JaniVariable(scxml_data.get_name(), scxml_data.get_type(), init_value))


class ScxmlTag(BaseTag):
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
xmlns="http://www.w3.org/2005/07/scxml">

<datamodel>
<data id="battery_percent" expr="100" />
<data id="battery_percent" expr="100" type="int16" />
</datamodel>

<state id="use_battery">
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
xmlns="http://www.w3.org/2005/07/scxml">

<datamodel>
<data id="battery_alarm" expr="false" />
<data id="battery_alarm" expr="false" type="bool" />
</datamodel>

<state id="check_battery">
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,8 @@
xmlns="http://www.w3.org/2005/07/scxml">

<datamodel>
<data id="a_seq" expr="0" type="int" />
<data id="b_seq" expr="0" type="int" />
<data id="a_seq" expr="0" type="int16" />
<data id="b_seq" expr="0" type="int16" />
</datamodel>

<state id="receive_a">
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,8 @@
xmlns="http://www.w3.org/2005/07/scxml">

<datamodel>
<data id="msg_a_seq" expr="0" type="int" />
<data id="msg_b_seq" expr="0" type="int" />
<data id="msg_a_seq" expr="0" type="int16" />
<data id="msg_b_seq" expr="0" type="int16" />
</datamodel>

<state id="send_a_1">
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
xmlns="http://www.w3.org/2005/07/scxml">

<datamodel>
<data id="counter" expr="0" type="int" />
<data id="counter" expr="0" type="int16" />
</datamodel>

<ros_topic_publisher topic="/receiver_counter" type="std_msgs/Int32" />
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
xmlns="http://www.w3.org/2005/07/scxml">

<datamodel>
<data id="counter" expr="0" type="int" />
<data id="counter" expr="0" type="int16" />
</datamodel>

<ros_topic_publisher topic="/sender_a_counter" type="std_msgs/Int32" />
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
xmlns="http://www.w3.org/2005/07/scxml">

<datamodel>
<data id="counter" expr="0" type="int" />
<data id="counter" expr="0" type="int16" />
</datamodel>

<ros_topic_publisher topic="/sender_b_counter" type="std_msgs/Int32" />
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
xmlns="http://www.w3.org/2005/07/scxml">

<datamodel>
<data id="battery_percent" expr="100" />
<data id="battery_percent" expr="100" type="int16" />
</datamodel>

<!-- <ros_topic_subscriber topic="charge" type="std_msgs/Empty" /> -->
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
xmlns="http://www.w3.org/2005/07/scxml">

<datamodel>
<data id="battery_alarm" expr="false" />
<data id="battery_alarm" expr="false" type="bool" />
</datamodel>

<ros_topic_subscriber topic="level" type="std_msgs/Int32" />
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
xmlns="http://www.w3.org/2005/07/scxml">

<datamodel>
<data id="battery_percent" expr="100" />
<data id="battery_percent" expr="100" type="int16" />
</datamodel>

<ros_topic_subscriber topic="charge" type="std_msgs/Empty" />
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
xmlns="http://www.w3.org/2005/07/scxml">

<datamodel>
<data id="battery_alarm" expr="false" />
<data id="battery_alarm" expr="false" type="bool" />
</datamodel>

<ros_topic_subscriber topic="level" type="std_msgs/Int32" />
Expand Down
4 changes: 2 additions & 2 deletions jani_generator/test/_test_data/ros_example_w_bt/bt.xml
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
<root BTCPP_format="4">
<BehaviorTree>
<Sequence>
<TopicCondition name="alarm" />
<TopicAction name="charge" />
<Condition ID="TopicCondition" name="alarm" />
<Action ID="TopicAction" name="charge" />
</Sequence>
</BehaviorTree>
</root>
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
xmlns="http://www.w3.org/2005/07/scxml">

<datamodel>
<data id="last_msg" expr="false" />
<data id="last_msg" expr="false" type="bool" />
</datamodel>

<ros_topic_subscriber topic="alarm" type="std_msgs/Bool" />
Expand Down
5 changes: 2 additions & 3 deletions scxml_converter/src/scxml_converter/bt_converter.py
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,6 @@
from enum import Enum, auto
from typing import List

import networkx as nx
from btlib.bt_to_fsm.bt_to_fsm import Bt2FSM
from btlib.bts import xml_to_networkx
from btlib.common import NODE_CAT
Expand Down Expand Up @@ -87,8 +86,8 @@ def bt_converter(
assert 'category' in bt_graph.nodes[node], 'Node must have a category.'
if bt_graph.nodes[node]['category'] == NODE_CAT.LEAF:
leaf_node_ids.append(node)
assert 'NAME' in bt_graph.nodes[node], 'Leaf node must have a type.'
node_type = bt_graph.nodes[node]['NAME']
assert 'ID' in bt_graph.nodes[node], 'Leaf node must have a type.'
node_type = bt_graph.nodes[node]['ID']
node_id = node
assert node_type in bt_plugins_scxml, \
f'Leaf node must have a plugin. {node_type} not found.'
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
from .scxml_base import ScxmlBase # noqa: F401
from .utils import HelperRosDeclarations # noqa: F401
from .scxml_data import ScxmlData # noqa: F401
from .scxml_data_model import ScxmlDataModel # noqa: F401
from .scxml_param import ScxmlParam # noqa: F401
from .scxml_executable_entries import ScxmlAssign, ScxmlIf, ScxmlSend # noqa: F401
Expand Down
120 changes: 120 additions & 0 deletions scxml_converter/src/scxml_converter/scxml_entries/scxml_data.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,120 @@
# Copyright (c) 2024 - for information on the respective copyright owner
# see the NOTICE file

# 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.

"""
Container for a single variable definition in SCXML. In XML, it has the tag `data`.
"""

from typing import Any
from scxml_converter.scxml_entries import ScxmlBase

from xml.etree import ElementTree as ET


# TODO: add lower and upper bounds depending on the n. of bits used.
# TODO: add support to uint
SCXML_DATA_MAPPING = {
"bool": bool,
"float32": float,
"float64": float,
"int8": int,
"int16": int,
"int32": int,
"int64": int
}


class ScxmlData(ScxmlBase):
"""This class represents the variables defined in the model."""

@staticmethod
def get_tag_name() -> str:
return "data"

@staticmethod
def from_xml_tree(xml_tree: ET.Element) -> "ScxmlData":
"""Create a ScxmlData object from an XML tree."""
assert xml_tree.tag == ScxmlData.get_tag_name(), \
f"Error: SCXML data: XML tag name is not {ScxmlData.get_tag_name()}."
data_id = xml_tree.attrib.get("id")
assert data_id is not None, "Error: SCXML data: 'id' not found."
data_expr = xml_tree.attrib.get("expr")
assert data_expr is not None, "Error: SCXML data: 'expr' not found."
data_type = xml_tree.attrib.get("type")
assert data_type is not None, "Error: SCXML data: 'type' not found."
lower_bound = xml_tree.attrib.get("lower_bound_incl", None)
upper_bound = xml_tree.attrib.get("upper_bound_incl", None)
return ScxmlData(data_id, data_expr, data_type, lower_bound, upper_bound)

def __init__(
self, id: str, expr: str, data_type: str,
lower_bound: Any = None, upper_bound: Any = None):
self._id = id
self._expr = expr
self._data_type = data_type
self._lower_bound = lower_bound
self._upper_bound = upper_bound

def get_name(self) -> str:
return self._id

def get_type(self) -> type:
return SCXML_DATA_MAPPING[self._data_type]

def get_expr(self) -> str:
return self._expr

def check_validity(self) -> bool:
validity = True
# ID
if not (isinstance(self._id, str) and len(self._id) > 0):
print(f"Error: SCXML data: 'id' {self._id} is not valid.")
validity = False
# Expression
if not (isinstance(self._expr, str) and len(self._expr) > 0):
print(f"Error: SCXML data: 'expr' {self._expr} is not valid.")
validity = False
# Data type
if not (isinstance(self._data_type, str) and self._data_type in SCXML_DATA_MAPPING):
print(f"Error: SCXML data: 'type' {self._data_type} is not valid.")
validity = False
type_of_data = SCXML_DATA_MAPPING[self._data_type]
# Lower bound
if self._lower_bound is not None:
if not isinstance(self._lower_bound, type_of_data):
print(f"Error: SCXML data: 'lower_bound_incl' type {self._lower_bound} is invalid.")
validity = False
# Upper bound
if self._upper_bound is not None:
if not isinstance(self._upper_bound, type_of_data):
print(f"Error: SCXML data: 'upper_bound_incl' type {self._upper_bound} is invalid.")
validity = False
# Check if lower bound is smaller than upper bound
if validity and self._upper_bound is not None and self._lower_bound is not None:
if self._lower_bound >= self._upper_bound:
print(f"Error: SCXML data: 'lower_bound_incl' {self._lower_bound} is not smaller "
f"than 'upper_bound_incl' {self._upper_bound}.")
validity = False
return validity

def as_xml(self) -> ET.Element:
assert self.check_validity(), "SCXML: found invalid data object."
xml_data = ET.Element(ScxmlData.get_tag_name(),
{"id": self._id, "expr": self._expr, "type": self._data_type})
if self._lower_bound is not None:
xml_data.set("lower_bound_incl", str(self._lower_bound_incl))
if self._upper_bound is not None:
xml_data.set("upper_bound_incl", str(self._upper_bound))
return xml_data
Original file line number Diff line number Diff line change
Expand Up @@ -17,15 +17,12 @@
Container for the variables defined in the SCXML model. In XML, it has the tag `datamodel`.
"""

from scxml_converter.scxml_entries import ScxmlBase
from scxml_converter.scxml_entries import ScxmlBase, ScxmlData

from typing import List, Optional, Tuple
from typing import List, Optional

from xml.etree import ElementTree as ET

# Tuple with the variable name and, if set, the expression for the init value
ScxmlData = Tuple[str, Optional[str]]


class ScxmlDataModel(ScxmlBase):
"""This class represents the variables defined in the model."""
Expand All @@ -47,10 +44,7 @@ def from_xml_tree(xml_tree: ET.Element) -> "ScxmlDataModel":
assert data_entries_xml is not None, "Error: SCXML datamodel: No data entries found."
data_entries = []
for data_entry_xml in data_entries_xml:
name = data_entry_xml.attrib.get("id")
assert name is not None, "Error: SCXML datamodel: 'id' not found for data entry."
expr = data_entry_xml.attrib.get("expr", None)
data_entries.append((name, expr))
data_entries.append(ScxmlData.from_xml_tree(data_entry_xml))
return ScxmlDataModel(data_entries)

def check_validity(self) -> bool:
Expand All @@ -59,16 +53,11 @@ def check_validity(self) -> bool:
valid_data_entries = isinstance(self._data_entries, list)
if valid_data_entries:
for data_entry in self._data_entries:
valid_data_entry = isinstance(data_entry, tuple) and len(data_entry) == 2
valid_data_entry = isinstance(data_entry, ScxmlData) and \
data_entry.check_validity()
if not valid_data_entry:
valid_data_entries = False
break
name, expr = data_entry
valid_name = isinstance(name, str) and len(name) > 0
valid_expr = expr is None or isinstance(expr, str)
if not valid_name or not valid_expr:
valid_data_entries = False
break
if not valid_data_entries:
print("Error: SCXML datamodel: data entries are not valid.")
return valid_data_entries
Expand All @@ -79,9 +68,5 @@ def as_xml(self) -> Optional[ET.Element]:
return None
xml_datamodel = ET.Element(ScxmlDataModel.get_tag_name())
for data_entry in self._data_entries:
name, expr = data_entry
xml_data = ET.Element("data", {"id": name})
if expr is not None:
xml_data.set("expr", expr)
xml_datamodel.append(xml_data)
xml_datamodel.append(data_entry.as_xml())
return xml_datamodel
Original file line number Diff line number Diff line change
Expand Up @@ -47,11 +47,11 @@ def __init__(self,

def get_tag_name() -> str:
return "if"

def get_conditional_executions(self) -> List[ConditionalExecutionBody]:
"""Get the conditional executions."""
return self._conditional_executions

def get_else_execution(self) -> Optional[ScxmlExecutionBody]:
"""Get the else execution."""
return self._else_execution
Expand Down Expand Up @@ -215,11 +215,11 @@ def __init__(self, location: str, expr: str):

def get_tag_name() -> str:
return "assign"

def get_location(self) -> str:
"""Get the location to assign."""
return self._location

def get_expr(self) -> str:
"""Get the expression to assign."""
return self._expr
Expand Down
Loading

0 comments on commit 52b2c06

Please sign in to comment.