2 Commits

Author SHA1 Message Date
Martino Ferrari b3e0054c23 Merge branch 'neodoc' into exp 2026-04-21 18:01:11 +02:00
Martino Ferrari 483c4799e0 Implementing new features 2026-04-21 17:46:53 +02:00
10 changed files with 6979 additions and 15 deletions
+1
View File
@@ -1 +1,2 @@
from autodoc.autodoc import *
from autodoc.configdb import *
+4 -1
View File
@@ -11,6 +11,7 @@ import sys
from argparse import ArgumentParser
import autodoc
from configdb import ConfigDB
__version__ = "0.0.0"
@@ -78,7 +79,9 @@ def main():
filename = args.output
else:
filename = os.path.splitext(os.path.basename(args.filename))[0]
autodoc.document(filename, args, config)
# autodoc.document(filename, args, config)
cfgdb = ConfigDB()
if __name__ == "__main__":
+92
View File
@@ -0,0 +1,92 @@
"""
Autodoc functions
"""
import os
from datetime import datetime
import common as doc
from doc_state import *
from doc_state_machine import doc_state_machine
def __doc_app_state__(state, tab=" │ "):
res = ""
if "+Threads" in state:
for i, tname in enumerate(state["+Threads"]):
if tname[0] == "+":
ch = "├─●" if i < len(state["+Threads"]) - 1 else "╰─●"
res += f"{tab} {ch} {tname[1:]}\n"
return res
def __doc_application__(label, app):
logging.info(f"Documenting real time application {label}")
mdapp = f'\n## Real-time application: "{label[1:]}"\n\n'
mdapp += f"Below are descrbed the states and threads of the real-time application {label[1:]}.\n"
mdapp += "\n"
mdapp += "```\n"
# mdapp += f'╭{"─"*(len(label)-1)}╮\n'
# mdapp += f"│{label[1:]}│\n"
# mdapp += f'╰┬{"─"*(len(label)-2)}╯\n'
states = app["+States"]
for i, (state, obj) in enumerate(states.items()):
if state[0] == "+":
mdapp += f" - {state[1:]}\n"
for thread in obj["+Threads"]:
if thread[0] == "+":
mdapp += f" - {thread[1:]}\n"
ch = "├─▷" if i < len(states) - 1 else "╰─▷"
tab = " │ " if i < len(states) - 1 else " "
if label[0] == "+":
mdapp += f" {ch} {label[1:]}\n"
mdapp += __doc_app_state__(obj, tab)
mdapp += "```\n"
for label, obj in states.items():
if label[0] == "+":
mdapp += f'\n### State: "{label[1:]}"\n'
mdapp += doc_state(label, obj, app)
return mdapp
def document(fname, args, config):
"""Document MARTe object."""
doc.set_path(os.path.join(args.destination, fname))
doc.set_config(args)
if not os.path.isdir(doc.path()):
os.mkdir(doc.path())
now = datetime.now() # current date and time
today = now.strftime("%d/%m/%Y")
mddoc = "---\n"
mddoc += "toc: true\n"
mddoc += f"title: {fname} Documentation\n"
mddoc += "author: MARTe AutoDOC\n"
mddoc += "titlepage: true\n"
mddoc += f"date: {today}\n"
mddoc += "geometry: margin=2cm\n"
mddoc += "---\n\n"
mddoc += f"# {fname}\n"
for label, obj in config.items():
if "Class" in obj:
if obj["Class"] == "RealTimeApplication":
mddoc += __doc_application__(label, obj)
if obj["Class"] == "StateMachine":
mddoc += f'\n## State-Machine: "{label[1:]}"\n'
mddoc += doc_state_machine(label[1:], obj)
fpath = os.path.join(doc.path(), f"{fname}.md")
outpath = os.path.join(doc.path(), fname)
logging.info(f"Saving markdown: {fpath}")
with open(fpath, "w") as file:
file.write(mddoc)
if doc.build():
logging.info(f"Generating html: {outpath}.html")
os.system(
f"pandoc --css pandoc/styling.css --resource-path={doc.path()} --embed-resource --to html5 -s {fpath} -o {outpath}.html"
)
logging.info(f"Generating pdf: {outpath}.pdf")
os.system(
f"pandoc -H pandoc/disable_float.tex --resource-path={doc.path()} -s {fpath} -o {outpath}.pdf"
)
+4 -1
View File
@@ -1,4 +1,5 @@
import logging
import sys
__PATH__ = "."
__CONFIG__ = None
@@ -29,12 +30,14 @@ def type_bytes_count(mtype):
return 1
def signal_byte_size(sig):
def signal_byte_size(parent_id, sig_id, sig):
noe = 1 if "NumberOfElements" not in sig else int(sig["NumberOfElements"])
nod = 1 if "NumberOfDimensions" not in sig else int(sig["NumberOfDimensions"])
nod = nod if nod > 0 else 1
noe = noe if noe > 0 else 1
nos = 1 if "Samples" not in sig else int(sig["Samples"])
if "Type" not in sig:
logging.warning(f"no type defined for signal {parent_id}.{sig_id}")
if "Type" in sig:
bytes = type_bytes_count(sig["Type"])
else:
+61
View File
@@ -0,0 +1,61 @@
from re import search
from MARTe.StandardParser import logging
class ConfigDB:
def __new__(cls):
if not hasattr(cls, "instance"):
cls.instance = super(ConfigDB, cls).__new__(cls)
return cls.instance
def __init__(self):
ConfigDB.__INST__ = self
self.__db__ = {}
def get(self, path: str):
ipath = path.split(".")
obj = self.__db__
for p in ipath:
if p in obj:
obj = obj[p]
else:
logging.error(f"no key {p} found for {path}")
return None
return obj
def insert(self, key: dict, obj):
pass
class MARTeOBJ:
def __init__(self, fname, id, cfg):
self.__name__ = id
self.__mcls__ = cfg["Class"]
self.__struct__ = {}
for key, itm in cfg.items():
if key != "Class":
if isinstance(itm, dict) and "Class" in itm:
self.__struct__[key] = MARTeOBJ(fname, key, itm)
else:
self.__struct__[key] = itm
def pretty_print(self, spacing=""):
str = f"{spacing}{self.__name__}[{self.__mcls__}]:\n"
for key, itm in self.__struct__.items():
if isinstance(itm, MARTeOBJ):
str += itm.pretty_print(spacing + " ")
else:
str += f"{spacing} {key}: {itm}\n"
return str
def __repr__(self) -> str:
str = f"{self.__name__}[{self.__mcls__}]:\n"
for key, itm in self.__struct__.items():
str += f" {key}: {itm}\n"
return str
def parse_config(fname: str, config: dict):
for key, obj in config.items():
ConfigDB().insert(key, obj)
+13 -13
View File
@@ -111,7 +111,7 @@ def __doc_iogam__(id, obj, edges, thread):
while input_size <= output_size and in_i < len(inputs):
in_label = input_names[in_i]
input = inputs[in_label]
input_size += doc.signal_byte_size(input)
input_size += doc.signal_byte_size(id, in_label, input)
in_i += 1
alias = doc.signal_alias(in_label, input)
mtype = doc.signal_type(input)
@@ -134,7 +134,7 @@ def __doc_iogam__(id, obj, edges, thread):
while output_size < input_size and out_i < len(outputs):
out_label = output_names[out_i]
output = outputs[out_label]
output_size += doc.signal_byte_size(output)
output_size += doc.signal_byte_size(id, out_label, output)
out_i += 1
alias = doc.signal_alias(out_label, output)
mtype = doc.signal_type(output)
@@ -154,14 +154,14 @@ def __doc_iogam__(id, obj, edges, thread):
)
)
orows.append(orow)
fill = "#eeeeee"
fcolor = "#000000"
fill = "#eee"
fcolor = "#000"
total = max(len(orows), len(irows))
if bold or total > 1:
fill = "#eeeeee"
fill = "#eee"
bold = True
else:
fill = "#eeeeee"
fill = "#eee"
inode = f"{id}_{counter} [shape=plaintext,"
inode += f'tooltip=" IOGAM::{id} ", label=<'
@@ -321,7 +321,7 @@ def __doc_mathgam__(id, obj, edges, thread):
node = __gam__(id, label, obj["Class"])
node += (
"<tr><td colspan='2' align='left' balign='left' bgcolor='#ffeeff'><font face='mono' point-size='9'>"
"<tr><td colspan='2' align='left' balign='left' bgcolor='#fef'><font face='mono' point-size='9'>"
+ __sanitize__(obj["Expression"])
+ "</font></td></tr><hr/> "
)
@@ -413,12 +413,12 @@ def __process_gamds__(name, cls, signals, edges):
"</td></tr>"
)
if node:
label = f"""<table border="0" cellpadding="4" cellborder="1" cellspacing="0" bgcolor="#eeeeee">
label = f"""<table border="0" cellpadding="4" cellborder="1" cellspacing="0" bgcolor="#eee">
<tr><td><b>{name}</b><br/>{cls}</td></tr>
{node}
</table>
"""
node = f'{name} [shape=plaintext, label=<{label}>, color="#00aaff"];\n'
node = f'{name} [shape=plaintext, label=<{label}>, color="#0af"];\n'
return node, edges
@@ -470,7 +470,7 @@ def __process_ds__(name, obj, edges):
sig_type = sigs[sig_name]
node += f"{sig_name} ({doc.type_to_string(sig_type)})"
node += "</td></tr>\n"
node += '</table>>, color="#00aaff"];\n'
node += '</table>>, color="#0af"];\n'
if not multi_thread and node:
node = f"subgraph cluster_{thread}" + "{\n" + node + "};\n"
return node, edges
@@ -561,7 +561,7 @@ def __simple_gam__(label, obj):
id = f"fn{doc.format_name(label)}"
style = "style=rounded"
if cls == "IOGAM":
style = 'style="rounded,filled", fillcolor="#eeeeee"'
style = 'style="rounded,filled", fillcolor="#eee"'
elif cls == "MessageGAM":
style = 'style="rounded,filled", fillcolor=lightyellow'
if "InputSignals" in obj:
@@ -593,10 +593,10 @@ def __simple_ds__(label, obj, edges):
multi_thread = True
fillcolor = "lightblue"
bordercolor = "#00aaff"
bordercolor = "#0af"
style = "penwidth=2"
if cls == "GAMDataSource":
fillcolor = "#eeeeee"
fillcolor = "#eee"
node = f"{id}[label=<<B>{label}</B><BR/>{cls}>, "
node += f'{style}, fillcolor="{fillcolor}", color="{bordercolor}"]\n'
if thread and not multi_thread:
File diff suppressed because it is too large Load Diff
+186
View File
@@ -0,0 +1,186 @@
{
"$App": {
"Class": "RealTimeApplication",
"+Data": {
"Class": "ReferenceContainer",
"+RTTimer": {
"Class": "LinuxTimer",
"SleepNature": "Busy",
"CPUMask": 2,
"Locked": 1,
"Signals": {
"Counter": {
"Type": "uint32"
},
"Time": {
"Type": "uint32"
}
}
},
"+Timing": {
"Class": "TimingDataSource",
"Locked": 1
},
"+DDB0": {
"Class": "GAMDataSource",
"Locked": 1,
"Signals": {
"Time": {
"Type": "uint32"
},
"Times": {
"Type": "uint32",
"NumberOfDimensions": 1,
"NumberOfElements": 3
},
"Amplitudes": {
"Type": "float32",
"NumberOfDimensions": 1,
"NumberOfElements": 3
}
}
},
"+EpicsIn": {
"Class": "EPICSCA::EPICSCAInput",
"Locked": 1,
"Signals": {
"Amplitude": {
"Type": "float32",
"PVName": "Amplitude"
},
"Frequency": {
"Type": "float32",
"PVName": "Frequency"
}
}
},
"DefaultDataSource": "DDB0",
"+Logger": {
"Class": "OnChangeLoggerDataSource",
"Locked": 1,
"Signals": {
"Times": {
"Type": "uint32",
"NumberOfDimensions": 1,
"NumberOfElements": 3
},
"Amplitudes": {
"Type": "float32",
"NumberOfDimensions": 1,
"NumberOfElements": 3
}
}
}
},
"+Functions": {
"+TimerGAM": {
"Class": "IOGAM",
"InputSignals": {
"Time": {
"Frequency": 100,
"DataSource": "RTTimer",
"Type": "uint32",
"Alias": "Time"
}
},
"OutputSignals": {
"Time": {
"DataSource": "DDB0",
"Type": "uint32",
"Alias": "Time"
}
}
},
"+Chai": {
"Class": "ChaiGAM",
"Code": "global g = 4;\nfun() {\n\tg = g + 1;\n\ty[0] = -A;\n \ty[1] = A;\n\ty[2] = -A;\n\tt[0] = g;\n\tif (freq <= 0) {\n\t\tt[1] = 0;\n\t\tt[2] = 0;\n\t} else {\n\t\tt[1] = int(5e5 / freq);\n\t\tt[2] = int(1e6 / freq);\n\t}\n}",
"InputSignals": {
"A": {
"DataSource": "EpicsIn",
"Type": "float32",
"Alias": "Amplitude"
},
"freq": {
"DataSource": "EpicsIn",
"Type": "float32",
"Alias": "Frequency"
}
},
"OutputSignals": {
"t": {
"NumberOfElements": 3,
"NumberOfDimensions": 1,
"DataSource": "DDB0",
"Type": "uint32",
"Alias": "Times"
},
"y": {
"NumberOfElements": 3,
"NumberOfDimensions": 1,
"DataSource": "DDB0",
"Type": "float32",
"Alias": "Amplitudes"
}
}
},
"Class": "ReferenceContainer",
"+LogGAM": {
"Class": "IOGAM",
"InputSignals": {
"t": {
"NumberOfElements": 3,
"NumberOfDimensions": 1,
"DataSource": "DDB0",
"Type": "uint32",
"Alias": "Times"
},
"y": {
"NumberOfElements": 3,
"NumberOfDimensions": 1,
"DataSource": "DDB0",
"Type": "float32",
"Alias": "Amplitudes"
}
},
"OutputSignals": {
"times": {
"NumberOfElements": 3,
"NumberOfDimensions": 1,
"DataSource": "Logger",
"Type": "uint32",
"Alias": "Times"
},
"amplitudes": {
"NumberOfElements": 3,
"NumberOfDimensions": 1,
"DataSource": "Logger",
"Type": "float32",
"Alias": "Amplitudes"
}
}
}
},
"+States": {
"Class": "ReferenceContainer",
"+Exec": {
"Class": "RealTimeState",
"+Threads": {
"+Comm": {
"Class": "RealTimeThread",
"CPUs": 1,
"Functions": [
"TimerGAM",
"Chai",
"LogGAM"
]
},
"Class": "ReferenceContainer"
}
}
},
"+Scheduler": {
"Class": "GAMScheduler",
"TimingDataSource": "Timing"
}
}
}
+27
View File
@@ -0,0 +1,27 @@
packages:
core:
description: "MARTe2 Core Package"
namespace: ""
url: "https://vcis-gitlab.f4e.europa.eu/aneto/MARTe2"
datasources:
GAMDataSource:
brief: "DataSource implementation for the exchange of signals between GAM components."
prameters:
HeapName:
type: "string"
comment: "GetStandardHeap() if not defined"
default: ""
AllowNoProducer:
type: "bool"
mandatory: false
default: 0
ResetUnusedVariableAtStateChange:
type: "bool"
mandatory: false
default: 1
Signals:
mode: "any"
components:
description: "MARTe2 Components"
url: "https://vcis-gitlab.f4e.europa.eu/aneto/MARTe2-components"
namespace: ""
+177
View File
@@ -0,0 +1,177 @@
import gi
gi.require_version("Gtk", "3.0")
gi.require_version("Gdk", "3.0")
from gi.repository import Gdk, Gtk
class ControlPanel(Gtk.Box):
def __init__(self):
super().__init__(orientation=Gtk.Orientation.VERTICAL, spacing=5)
# Place the control panel in the top right
self.set_halign(Gtk.Align.END)
self.set_valign(Gtk.Align.START)
# Add the .control-panel CSS class to this widget
context = self.get_style_context()
context.add_class("control-panel")
def add_group(self, group):
self.pack_start(group, False, False, 0)
class ControlPanelGroup(Gtk.Expander):
def __init__(self, title: str):
Gtk.Expander.__init__(self, label=title)
self._inner = Gtk.Box(orientation=Gtk.Orientation.VERTICAL, spacing=5)
# Set the size request to 200 pixels wide
self.set_size_request(200, -1)
# Add a bit of margin after the header
self._inner.set_margin_top(5)
self.add(self._inner)
def add_row(self, widget):
self._inner.pack_start(widget, False, False, 0)
class MyControlPanel(ControlPanel):
def __init__(self):
super().__init__()
self._first_panel = ControlPanelGroup("Some Buttons")
self._first_panel.add_row(Gtk.Button(label="Button 1"))
self._first_panel.add_row(Gtk.Button(label="Button 2"))
self.add_group(self._first_panel)
self._second_panel = ControlPanelGroup("Extra Settings")
self._second_panel.add_row(Gtk.Button(label="Button 3"))
self._second_panel.add_row(Gtk.Button(label="Button 4"))
self._second_panel.add_row(Gtk.CheckButton.new_with_label("First checkbox"))
self._second_panel.add_row(Gtk.CheckButton.new_with_label("Second checkbox"))
combo = Gtk.ComboBoxText()
combo.append("first", "First Choice")
combo.append("second", "Second Choice")
combo.append("third", "Third Choice")
combo.append("forth", "This one is quite long")
combo.set_active_id("first")
self._second_panel.add_row(combo)
self.add_group(self._second_panel)
class MapEditor(Gtk.DrawingArea):
def __init__(self):
super().__init__()
self.set_events(
Gdk.EventMask.BUTTON_PRESS_MASK
| Gdk.EventMask.BUTTON_RELEASE_MASK
| Gdk.EventMask.POINTER_MOTION_MASK
)
self._camera_x = 0 # The X coordinate of the camera
self._camera_y = 0 # The Y coordinate of the camera
self._mouse_x = 0 # The X coordinate of the pointer
self._mouse_y = 0 # The Y coordinate of the pointer
self._button = None # The currently held mouse button
# Connect to the draw and mouse events
self.connect("draw", self.on_draw)
self.connect("button-press-event", self.on_button_press_event)
self.connect("button-release-event", self.on_button_release_event)
self.connect("motion-notify-event", self.on_motion_notify_event)
def on_button_press_event(self, widget, event):
self._mouse_x = event.x
self._mouse_y = event.y
self._button = event.button
def on_button_release_event(self, widget, event):
self._mouse_x = 0
self._mouse_y = 0
self._button = None
def on_motion_notify_event(self, widget, event):
if self._button == Gdk.BUTTON_SECONDARY:
self._camera_x += event.x - self._mouse_x
self._camera_y += event.y - self._mouse_y
self._mouse_x = event.x
self._mouse_y = event.y
self.queue_draw()
def on_draw(self, widget, context):
# Get the width and height of the widget
width = self.get_allocated_width()
height = self.get_allocated_height()
# Render a rectangle for the background of the editor
context.set_source_rgb(0.11, 0.11, 0.11)
context.rectangle(0, 0, width, height)
context.fill()
# Render our grids
context.set_line_width(1)
for color, step in [(0.2, 10), (0.3, 100)]:
context.set_source_rgb(color, color, color)
y = int(self._camera_y % step) + 0.5
while y < height:
context.move_to(0, y)
context.line_to(width, y)
y += step
x = int(self._camera_x % step) + 0.5
while x < width:
context.move_to(x, 0)
context.line_to(x, height)
x += step
context.stroke()
class MainWindow(Gtk.Window):
def __init__(self):
super().__init__()
self.set_title("Collapsible Controls Overlay Demo")
# Set the default window size to 800x800
self.set_default_size(800, 800)
# When our window is destroyed, exit the main event loop
self.connect("destroy", Gtk.main_quit)
# Create our map editor and control panel instances
self._editor = MapEditor()
self._controls = MyControlPanel()
# Create the overlay widget
self._overlay = Gtk.Overlay()
# Add our editor and control panel as overlays
self._overlay.add_overlay(self._editor)
self._overlay.add_overlay(self._controls)
# Add the overlay as the immediate child of the window
self.add(self._overlay)
def install_css():
screen = Gdk.Screen.get_default()
provider = Gtk.CssProvider()
provider.load_from_data(
b"""
.control-panel {
background-color: rgba(255,255,255,0.8);
padding: 4px;
border-bottom-left-radius: 4px;
}
"""
)
Gtk.StyleContext.add_provider_for_screen(screen, provider, 600)
install_css()
window = MainWindow()
window.show_all()
Gtk.main()