statable 3.1.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- codegen/__init__.py +38 -0
- codegen/c_code_generator.py +1752 -0
- codegen/code_merger.py +371 -0
- codegen/code_templates.py +509 -0
- codegen/config.py +167 -0
- codegen/enum_generator.py +377 -0
- codegen/event_queue_generator.py +303 -0
- codegen/interrupt_generator.py +345 -0
- codegen/naming_convention.py +171 -0
- codegen/osal_generator.py +282 -0
- codegen/role_function_generator.py +1244 -0
- codegen/sample_data.py +269 -0
- codegen/state_actions_generator.py +395 -0
- codegen/struct_generator.py +721 -0
- codegen/timer_generator.py +299 -0
- codegen/transition_generator.py +907 -0
- codegen/type_mapper.py +69 -0
- codegen/validate/__init__.py +38 -0
- codegen/validate/change_actions.py +90 -0
- codegen/validate/change_applier.py +416 -0
- codegen/validate/clipboard_manager.py +47 -0
- codegen/validate/data/__init__.py +30 -0
- codegen/validate/data/action_definitions.py +200 -0
- codegen/validate/data/keywords.py +46 -0
- codegen/validate/data/prompt_templates.py +176 -0
- codegen/validate/data/validation_rules.py +205 -0
- codegen/validate/items/__init__.py +30 -0
- codegen/validate/items/base_validator.py +25 -0
- codegen/validate/items/cell_validator.py +275 -0
- codegen/validate/items/custom_type_validator.py +75 -0
- codegen/validate/items/event_validator.py +78 -0
- codegen/validate/items/flag_validator.py +74 -0
- codegen/validate/items/interrupt_validator.py +75 -0
- codegen/validate/items/queue_validator.py +73 -0
- codegen/validate/items/role_function_validator.py +175 -0
- codegen/validate/items/state_validator.py +88 -0
- codegen/validate/items/timer_validator.py +120 -0
- codegen/validate/items/transition_validator.py +113 -0
- codegen/validate/items/variable_validator.py +132 -0
- codegen/validate/logger.py +52 -0
- codegen/validate/models.py +177 -0
- codegen/validate/prompt_generator.py +724 -0
- codegen/validate/response_parser.py +154 -0
- codegen/validate/response_validator.py +275 -0
- codegen/validate/validation_dialog.py +322 -0
- codegen/validate/validator.py +94 -0
- codegen/variable_generator.py +419 -0
- statable/__init__.py +71 -0
- statable/__main__.py +49 -0
- statable/cli.py +251 -0
- statable/global_defs.py +256 -0
- statable/mermaid_gen.py +144 -0
- statable/model.py +426 -0
- statable/parser.py +64 -0
- statable/sample_data.py +435 -0
- statable/shared/__init__.py +24 -0
- statable/shared/condition_library.py +63 -0
- statable/shared/literal_library.py +66 -0
- statable/shared/role_function_library.py +150 -0
- statable/state_machine.py +121 -0
- statable/xml_io.py +1164 -0
- statable-3.1.0.dist-info/METADATA +151 -0
- statable-3.1.0.dist-info/RECORD +117 -0
- statable-3.1.0.dist-info/WHEEL +5 -0
- statable-3.1.0.dist-info/entry_points.txt +2 -0
- statable-3.1.0.dist-info/top_level.txt +3 -0
- statable_gui/__init__.py +23 -0
- statable_gui/action_edit_dialog.py +214 -0
- statable_gui/code_generation_dialog.py +486 -0
- statable_gui/code_generation_settings_dialog.py +522 -0
- statable_gui/common_widgets.py +537 -0
- statable_gui/condition_builder_dialog.py +500 -0
- statable_gui/condition_edit_dialog.py +123 -0
- statable_gui/condition_template_dialog.py +58 -0
- statable_gui/config.py +21 -0
- statable_gui/dialogs.py +439 -0
- statable_gui/event_definition_dialog.py +605 -0
- statable_gui/event_delivery_settings_dialog.py +191 -0
- statable_gui/event_queue_dialog.py +245 -0
- statable_gui/global_defs.py +167 -0
- statable_gui/global_defs_dialog.py +796 -0
- statable_gui/i18n/__init__.py +118 -0
- statable_gui/interrupt_handler_edit_dialog.py +810 -0
- statable_gui/layer_settings_dialog.py +180 -0
- statable_gui/libcntrl/__init__.py +23 -0
- statable_gui/libcntrl/condition_library.py +13 -0
- statable_gui/libcntrl/literal_library.py +13 -0
- statable_gui/libcntrl/literal_management_dialog.py +185 -0
- statable_gui/libcntrl/role_function_edit_dialog.py +145 -0
- statable_gui/libcntrl/role_function_library.py +13 -0
- statable_gui/literal_definition_dialog.py +62 -0
- statable_gui/logger.py +83 -0
- statable_gui/main_window.py +1380 -0
- statable_gui/matrix_table.py +413 -0
- statable_gui/preference_keys.py +18 -0
- statable_gui/preferences.py +74 -0
- statable_gui/role_function_dialog.py +244 -0
- statable_gui/sample_data.py +11 -0
- statable_gui/state_actions_dialog.py +398 -0
- statable_gui/symbol_picker.py +170 -0
- statable_gui/traceball.py +37 -0
- statable_gui/transition_editor_direct/__init__.py +15 -0
- statable_gui/transition_editor_direct/actions_tab.py +856 -0
- statable_gui/transition_editor_direct/code_widget.py +344 -0
- statable_gui/transition_editor_direct/coverage_analyzer.py +163 -0
- statable_gui/transition_editor_direct/dialog.py +335 -0
- statable_gui/transition_editor_direct/draft.py +270 -0
- statable_gui/transition_editor_direct/edit_dialogs.py +160 -0
- statable_gui/transition_editor_direct/overview_tab.py +173 -0
- statable_gui/transition_editor_direct/palette_widget.py +233 -0
- statable_gui/transition_editor_direct/relations_edit_dialog.py +156 -0
- statable_gui/transition_editor_direct/relations_tab.py +230 -0
- statable_gui/transition_editor_direct/system_global_dialog.py +52 -0
- statable_gui/transition_editor_direct/transition_actions_dialog.py +109 -0
- statable_gui/transition_editor_direct/transitions_tab.py +620 -0
- statable_gui/validation_dialog.py +285 -0
- statable_gui/widgets.py +1315 -0
|
@@ -0,0 +1,191 @@
|
|
|
1
|
+
"""Event delivery settings dialog"""
|
|
2
|
+
|
|
3
|
+
from PySide6.QtCore import Qt
|
|
4
|
+
from PySide6.QtWidgets import (
|
|
5
|
+
QDialog, QVBoxLayout, QTableWidget, QTableWidgetItem,
|
|
6
|
+
QLabel, QHeaderView, QComboBox, QDialogButtonBox,
|
|
7
|
+
QCheckBox, QAbstractItemView
|
|
8
|
+
)
|
|
9
|
+
from PySide6.QtGui import QFont, QMouseEvent
|
|
10
|
+
|
|
11
|
+
from statable.state_machine import StateMachine
|
|
12
|
+
from statable.model import EventDeliveryType
|
|
13
|
+
from statable.global_defs import GlobalDefinitions
|
|
14
|
+
from .logger import StaTableLogger
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
class DoubleClickTable(QTableWidget):
|
|
18
|
+
"""Table that reliably captures double-click events"""
|
|
19
|
+
def __init__(self, *args, **kwargs):
|
|
20
|
+
super().__init__(*args, **kwargs)
|
|
21
|
+
self.setEditTriggers(QAbstractItemView.NoEditTriggers)
|
|
22
|
+
self.setSelectionBehavior(QAbstractItemView.SelectRows)
|
|
23
|
+
self.setSelectionMode(QAbstractItemView.SingleSelection)
|
|
24
|
+
|
|
25
|
+
def mouseDoubleClickEvent(self, event: QMouseEvent):
|
|
26
|
+
pos = event.position().toPoint()
|
|
27
|
+
item = self.itemAt(pos)
|
|
28
|
+
if item:
|
|
29
|
+
self.cellDoubleClicked.emit(item.row(), item.column())
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
class EventDeliverySettingsDialog(QDialog):
|
|
33
|
+
"""Dialog to configure event delivery type (DIRECT/QUEUE) and display automatic DOUBLE conversion based on ISR usage"""
|
|
34
|
+
def __init__(self, sm: StateMachine, global_defs: GlobalDefinitions, auto_convert: bool = True, parent=None):
|
|
35
|
+
super().__init__(parent)
|
|
36
|
+
self.sm = sm
|
|
37
|
+
self.global_defs = global_defs
|
|
38
|
+
self.auto_convert = auto_convert
|
|
39
|
+
|
|
40
|
+
self.setWindowTitle(self.tr("Event delivery settings"))
|
|
41
|
+
self.setMinimumSize(900, 500)
|
|
42
|
+
|
|
43
|
+
layout = QVBoxLayout(self)
|
|
44
|
+
|
|
45
|
+
#Global settings checkbox
|
|
46
|
+
self.auto_convert_check = QCheckBox(self.tr("Automatically convert DIRECT events used in ISR to DOUBLE"))
|
|
47
|
+
self.auto_convert_check.setChecked(self.auto_convert)
|
|
48
|
+
layout.addWidget(self.auto_convert_check)
|
|
49
|
+
|
|
50
|
+
# Event list table
|
|
51
|
+
self.table = DoubleClickTable(0, 6)
|
|
52
|
+
self.table.setHorizontalHeaderLabels(["Title", "Event name", "Source", "Delivery type", "ISR usage", "Converted"])
|
|
53
|
+
self.table.horizontalHeader().setSectionResizeMode(QHeaderView.Stretch)
|
|
54
|
+
self.table.setFont(QFont("Consolas", 10))
|
|
55
|
+
layout.addWidget(self.table, stretch=1)
|
|
56
|
+
|
|
57
|
+
# Help
|
|
58
|
+
help_label = QLabel(
|
|
59
|
+
self.tr("If a DIRECT event is notified from an ISR, it is automatically converted to DOUBLE.\n"
|
|
60
|
+
"Select QUEUE if you need event counts or data.")
|
|
61
|
+
)
|
|
62
|
+
help_label.setStyleSheet("color: gray;")
|
|
63
|
+
layout.addWidget(help_label)
|
|
64
|
+
|
|
65
|
+
# OK/Cancel
|
|
66
|
+
buttons = QDialogButtonBox(QDialogButtonBox.Ok | QDialogButtonBox.Cancel)
|
|
67
|
+
buttons.accepted.connect(self._on_accept)
|
|
68
|
+
buttons.rejected.connect(self.reject)
|
|
69
|
+
layout.addWidget(buttons)
|
|
70
|
+
|
|
71
|
+
self._build_table()
|
|
72
|
+
self.auto_convert_check.stateChanged.connect(self._update_converted_column)
|
|
73
|
+
|
|
74
|
+
StaTableLogger.debug("EventDeliverySettingsDialog initialized")
|
|
75
|
+
|
|
76
|
+
def _build_table(self):
|
|
77
|
+
"""Build the table"""
|
|
78
|
+
self.table.setRowCount(0)
|
|
79
|
+
for event in self.sm.events.values():
|
|
80
|
+
row = self.table.rowCount()
|
|
81
|
+
self.table.insertRow(row)
|
|
82
|
+
|
|
83
|
+
# Title (read-only)
|
|
84
|
+
title_item = QTableWidgetItem(event.title)
|
|
85
|
+
title_item.setFlags(title_item.flags() & ~Qt.ItemIsEditable)
|
|
86
|
+
self.table.setItem(row, 0, title_item)
|
|
87
|
+
|
|
88
|
+
# Event name (read-only)
|
|
89
|
+
name_item = QTableWidgetItem(event.name if event.name else "(completion)")
|
|
90
|
+
name_item.setFlags(name_item.flags() & ~Qt.ItemIsEditable)
|
|
91
|
+
self.table.setItem(row, 1, name_item)
|
|
92
|
+
|
|
93
|
+
# Source (read-only)
|
|
94
|
+
source_item = QTableWidgetItem(event.source_layer.value)
|
|
95
|
+
source_item.setFlags(source_item.flags() & ~Qt.ItemIsEditable)
|
|
96
|
+
self.table.setItem(row, 2, source_item)
|
|
97
|
+
|
|
98
|
+
# Delivery type combo
|
|
99
|
+
combo = QComboBox()
|
|
100
|
+
combo.addItem(self.tr("DIRECT"), EventDeliveryType.DIRECT)
|
|
101
|
+
combo.addItem(self.tr("QUEUE"), EventDeliveryType.QUEUE)
|
|
102
|
+
combo.addItem(self.tr("DOUBLE"), EventDeliveryType.DOUBLE)
|
|
103
|
+
idx = combo.findData(event.delivery_type)
|
|
104
|
+
if idx >= 0:
|
|
105
|
+
combo.setCurrentIndex(idx)
|
|
106
|
+
combo.currentIndexChanged.connect(lambda _index, r=row: self._on_delivery_changed(r))
|
|
107
|
+
self.table.setCellWidget(row, 3, combo)
|
|
108
|
+
|
|
109
|
+
#ISR usage column (read-only)
|
|
110
|
+
isr_used = self._check_isr_usage(event.name)
|
|
111
|
+
isr_item = QTableWidgetItem("Yes" if isr_used else "")
|
|
112
|
+
isr_item.setFlags(isr_item.flags() & ~Qt.ItemIsEditable)
|
|
113
|
+
self.table.setItem(row, 4, isr_item)
|
|
114
|
+
|
|
115
|
+
# Conversion column (read-only)
|
|
116
|
+
converted_item = QTableWidgetItem()
|
|
117
|
+
converted_item.setFlags(converted_item.flags() & ~Qt.ItemIsEditable)
|
|
118
|
+
self.table.setItem(row, 5, converted_item)
|
|
119
|
+
|
|
120
|
+
StaTableLogger.debug(f" Event: '{event.name}' -> ISR used: {isr_used}")
|
|
121
|
+
|
|
122
|
+
self._update_converted_column()
|
|
123
|
+
|
|
124
|
+
def _check_isr_usage(self, event_name: str) -> bool:
|
|
125
|
+
"""Determine if event used from interrupt handler"""
|
|
126
|
+
if not event_name:
|
|
127
|
+
return False
|
|
128
|
+
|
|
129
|
+
for intr in self.global_defs.interrupts:
|
|
130
|
+
#Check event_names list
|
|
131
|
+
if event_name in intr.event_names:
|
|
132
|
+
StaTableLogger.debug(f" '{event_name}' found in interrupt '{intr.name}' event_names")
|
|
133
|
+
return True
|
|
134
|
+
|
|
135
|
+
#Check the code in actions
|
|
136
|
+
for act in intr.actions:
|
|
137
|
+
combined = act.condition + "\n" + act.action
|
|
138
|
+
if event_name in combined:
|
|
139
|
+
StaTableLogger.debug(f" '{event_name}' found in interrupt '{intr.name}' action code")
|
|
140
|
+
return True
|
|
141
|
+
|
|
142
|
+
return False
|
|
143
|
+
|
|
144
|
+
def _find_row_by_event_name(self, event_name: str) -> int:
|
|
145
|
+
"""Find the row number from the event name"""
|
|
146
|
+
for row in range(self.table.rowCount()):
|
|
147
|
+
item = self.table.item(row, 1)
|
|
148
|
+
if item and item.text() == event_name:
|
|
149
|
+
return row
|
|
150
|
+
return -1
|
|
151
|
+
|
|
152
|
+
def _on_delivery_changed(self, row: int):
|
|
153
|
+
"""Update the converted column when the delivery type combo changes"""
|
|
154
|
+
self._update_converted_column()
|
|
155
|
+
|
|
156
|
+
def _update_converted_column(self):
|
|
157
|
+
"""Recompute conversion column from settings"""
|
|
158
|
+
auto = self.auto_convert_check.isChecked()
|
|
159
|
+
for row in range(self.table.rowCount()):
|
|
160
|
+
name_item = self.table.item(row, 1)
|
|
161
|
+
event_name = name_item.text() if name_item else ""
|
|
162
|
+
if event_name == "(completion)":
|
|
163
|
+
event_name = ""
|
|
164
|
+
|
|
165
|
+
combo = self.table.cellWidget(row, 3)
|
|
166
|
+
if not combo:
|
|
167
|
+
continue
|
|
168
|
+
current_type = combo.currentData()
|
|
169
|
+
isr_used = self.table.item(row, 4).text() == "Yes"
|
|
170
|
+
converted = current_type
|
|
171
|
+
if auto and isr_used and current_type == EventDeliveryType.DIRECT:
|
|
172
|
+
converted = EventDeliveryType.DOUBLE
|
|
173
|
+
converted_item = self.table.item(row, 5)
|
|
174
|
+
if converted_item:
|
|
175
|
+
converted_item.setText(converted.value if converted else "")
|
|
176
|
+
|
|
177
|
+
def _on_accept(self):
|
|
178
|
+
"""OK button: update each event's delivery type and close"""
|
|
179
|
+
for row in range(self.table.rowCount()):
|
|
180
|
+
name_item = self.table.item(row, 1)
|
|
181
|
+
event_name = name_item.text() if name_item else ""
|
|
182
|
+
if event_name == "(completion)":
|
|
183
|
+
event_name = ""
|
|
184
|
+
combo = self.table.cellWidget(row, 3)
|
|
185
|
+
if combo and event_name in self.sm.events:
|
|
186
|
+
self.sm.events[event_name].delivery_type = combo.currentData()
|
|
187
|
+
self.accept()
|
|
188
|
+
|
|
189
|
+
def get_auto_convert(self) -> bool:
|
|
190
|
+
"""Return the checkbox state"""
|
|
191
|
+
return self.auto_convert_check.isChecked()
|
|
@@ -0,0 +1,245 @@
|
|
|
1
|
+
"""Event queue definition dialog"""
|
|
2
|
+
|
|
3
|
+
from typing import Optional, List
|
|
4
|
+
|
|
5
|
+
from PySide6.QtCore import Qt
|
|
6
|
+
from PySide6.QtGui import QFont, QMouseEvent
|
|
7
|
+
from PySide6.QtWidgets import (
|
|
8
|
+
QDialog, QVBoxLayout, QHBoxLayout, QTableWidget, QTableWidgetItem,
|
|
9
|
+
QPushButton, QLabel, QHeaderView, QLineEdit, QSpinBox,
|
|
10
|
+
QCheckBox, QFormLayout, QDialogButtonBox, QMessageBox, QAbstractItemView,
|
|
11
|
+
QWidget, QListWidget, QListWidgetItem
|
|
12
|
+
)
|
|
13
|
+
|
|
14
|
+
from statable.global_defs import GlobalDefinitions, EventQueueDef
|
|
15
|
+
from .common_widgets import TitleEditWidget, TypeComboBox
|
|
16
|
+
from .logger import StaTableLogger
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
class DoubleClickTable(QTableWidget):
|
|
20
|
+
def __init__(self, *args, **kwargs):
|
|
21
|
+
super().__init__(*args, **kwargs)
|
|
22
|
+
self.setEditTriggers(QAbstractItemView.NoEditTriggers)
|
|
23
|
+
self.setSelectionBehavior(QAbstractItemView.SelectRows)
|
|
24
|
+
self.setSelectionMode(QAbstractItemView.SingleSelection)
|
|
25
|
+
|
|
26
|
+
def mouseDoubleClickEvent(self, event: QMouseEvent):
|
|
27
|
+
pos = event.position().toPoint()
|
|
28
|
+
item = self.itemAt(pos)
|
|
29
|
+
if item:
|
|
30
|
+
self.cellDoubleClicked.emit(item.row(), item.column())
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
class EventQueueEditDialog(QDialog):
|
|
34
|
+
def __init__(self, parent=None, queue_def: Optional[EventQueueDef] = None,
|
|
35
|
+
event_names: Optional[List[str]] = None, global_defs=None):
|
|
36
|
+
super().__init__(parent)
|
|
37
|
+
self.global_defs = global_defs if global_defs else GlobalDefinitions()
|
|
38
|
+
self.event_names = event_names or []
|
|
39
|
+
self.setWindowTitle(self.tr("EventQueueEdit"))
|
|
40
|
+
self.setMinimumWidth(500)
|
|
41
|
+
|
|
42
|
+
layout = QVBoxLayout(self)
|
|
43
|
+
form = QFormLayout()
|
|
44
|
+
layout.addLayout(form)
|
|
45
|
+
|
|
46
|
+
# Title input field (required / provisional title auto-set)
|
|
47
|
+
self.title_widget = TitleEditWidget(self, title=queue_def.title if queue_def else "")
|
|
48
|
+
form.addRow("", self.title_widget)
|
|
49
|
+
|
|
50
|
+
# Queue name
|
|
51
|
+
self.name_edit = QLineEdit(queue_def.name if queue_def else "")
|
|
52
|
+
form.addRow("Queue name", self.name_edit)
|
|
53
|
+
|
|
54
|
+
# Size
|
|
55
|
+
self.size_spin = QSpinBox()
|
|
56
|
+
self.size_spin.setRange(2, 256)
|
|
57
|
+
self.size_spin.setValue(queue_def.size if queue_def else 8)
|
|
58
|
+
form.addRow("Size", self.size_spin)
|
|
59
|
+
|
|
60
|
+
# Element type
|
|
61
|
+
self.type_combo = TypeComboBox(self, global_defs=self.global_defs)
|
|
62
|
+
if queue_def:
|
|
63
|
+
self.type_combo.set_current_text(queue_def.element_type)
|
|
64
|
+
form.addRow("Element type", self.type_combo)
|
|
65
|
+
|
|
66
|
+
# Related event selection list
|
|
67
|
+
self.event_list = QListWidget()
|
|
68
|
+
for event_name in self.event_names:
|
|
69
|
+
item = QListWidgetItem(event_name)
|
|
70
|
+
item.setFlags(item.flags() | Qt.ItemIsUserCheckable)
|
|
71
|
+
item.setCheckState(Qt.Checked if queue_def and event_name in queue_def.event_ids else Qt.Unchecked)
|
|
72
|
+
self.event_list.addItem(item)
|
|
73
|
+
form.addRow("Related events", self.event_list)
|
|
74
|
+
|
|
75
|
+
# With priority
|
|
76
|
+
self.priority_check = QCheckBox()
|
|
77
|
+
self.priority_check.setChecked(queue_def.priority_enabled if queue_def else False)
|
|
78
|
+
form.addRow("With priority", self.priority_check)
|
|
79
|
+
|
|
80
|
+
# Interrupt protection
|
|
81
|
+
self.safe_check = QCheckBox()
|
|
82
|
+
self.safe_check.setChecked(queue_def.interrupt_safe if queue_def else True)
|
|
83
|
+
form.addRow("Interrupt protection", self.safe_check)
|
|
84
|
+
|
|
85
|
+
# RTOS usage
|
|
86
|
+
self.rtos_check = QCheckBox()
|
|
87
|
+
self.rtos_check.setChecked(queue_def.rtos_enabled if queue_def else False)
|
|
88
|
+
form.addRow("RTOS usage", self.rtos_check)
|
|
89
|
+
|
|
90
|
+
# description
|
|
91
|
+
self.desc_edit = QLineEdit(queue_def.description if queue_def else "")
|
|
92
|
+
form.addRow("Description", self.desc_edit)
|
|
93
|
+
|
|
94
|
+
buttons = QDialogButtonBox(QDialogButtonBox.Ok | QDialogButtonBox.Cancel)
|
|
95
|
+
buttons.accepted.connect(self._on_accept)
|
|
96
|
+
buttons.rejected.connect(self.reject)
|
|
97
|
+
layout.addWidget(buttons)
|
|
98
|
+
|
|
99
|
+
StaTableLogger.debug("EventQueueEditDialog initialized")
|
|
100
|
+
|
|
101
|
+
def _on_accept(self):
|
|
102
|
+
"""OK button: auto-set provisional title if title is empty"""
|
|
103
|
+
auto_title = f"Queue: {self.name_edit.text().strip() or '(unnamed)'}"
|
|
104
|
+
self.title_widget.ensure_title(auto_title)
|
|
105
|
+
self.accept()
|
|
106
|
+
|
|
107
|
+
def get_queue_def(self) -> EventQueueDef:
|
|
108
|
+
event_ids = []
|
|
109
|
+
for i in range(self.event_list.count()):
|
|
110
|
+
item = self.event_list.item(i)
|
|
111
|
+
if item.checkState() == Qt.Checked:
|
|
112
|
+
event_ids.append(item.text())
|
|
113
|
+
return EventQueueDef(
|
|
114
|
+
name=self.name_edit.text().strip(),
|
|
115
|
+
size=self.size_spin.value(),
|
|
116
|
+
element_type=self.type_combo.current_text(),
|
|
117
|
+
event_ids=event_ids,
|
|
118
|
+
priority_enabled=self.priority_check.isChecked(),
|
|
119
|
+
interrupt_safe=self.safe_check.isChecked(),
|
|
120
|
+
rtos_enabled=self.rtos_check.isChecked(),
|
|
121
|
+
description=self.desc_edit.text().strip(),
|
|
122
|
+
title=self.title_widget.get_title(),
|
|
123
|
+
)
|
|
124
|
+
|
|
125
|
+
|
|
126
|
+
class EventQueueDefsDialog(QDialog):
|
|
127
|
+
"""Event queue definition list dialog"""
|
|
128
|
+
def __init__(self, global_defs: GlobalDefinitions, event_names: Optional[List[str]] = None, parent=None):
|
|
129
|
+
super().__init__(parent)
|
|
130
|
+
self.global_defs = global_defs
|
|
131
|
+
self.event_names = event_names or []
|
|
132
|
+
self.setWindowTitle(self.tr("Event queue definition"))
|
|
133
|
+
self.setMinimumSize(1100, 600)
|
|
134
|
+
|
|
135
|
+
layout = QVBoxLayout(self)
|
|
136
|
+
|
|
137
|
+
# Search
|
|
138
|
+
search_layout = QHBoxLayout()
|
|
139
|
+
search_layout.addWidget(QLabel(self.tr("Search (prefix match):")))
|
|
140
|
+
self.search_edit = QLineEdit()
|
|
141
|
+
self.search_edit.setPlaceholderText(self.tr("Title, queue name, description"))
|
|
142
|
+
self.search_edit.textChanged.connect(self.refresh_table)
|
|
143
|
+
search_layout.addWidget(self.search_edit)
|
|
144
|
+
layout.addLayout(search_layout)
|
|
145
|
+
|
|
146
|
+
# List table (title column added / direct edit)
|
|
147
|
+
self.table = DoubleClickTable(0, 9)
|
|
148
|
+
self.table.setHorizontalHeaderLabels(["Title", "Queue name", "Size", "Element type", "Related events", "Priority", "Interrupt protection", "RTOS", "Description"])
|
|
149
|
+
self.table.horizontalHeader().setSectionResizeMode(QHeaderView.Stretch)
|
|
150
|
+
self.table.cellDoubleClicked.connect(self.on_double_clicked)
|
|
151
|
+
self.table.itemChanged.connect(self.on_item_changed)
|
|
152
|
+
layout.addWidget(self.table)
|
|
153
|
+
|
|
154
|
+
# Buttons
|
|
155
|
+
btn_layout = QHBoxLayout()
|
|
156
|
+
add_btn = QPushButton(self.tr("Add"))
|
|
157
|
+
add_btn.clicked.connect(self.add_queue)
|
|
158
|
+
del_btn = QPushButton(self.tr("Delete"))
|
|
159
|
+
del_btn.clicked.connect(self.delete_queue)
|
|
160
|
+
btn_layout.addWidget(add_btn)
|
|
161
|
+
btn_layout.addWidget(del_btn)
|
|
162
|
+
btn_layout.addStretch()
|
|
163
|
+
layout.addLayout(btn_layout)
|
|
164
|
+
|
|
165
|
+
# Close
|
|
166
|
+
close_btn = QPushButton(self.tr("Close"))
|
|
167
|
+
close_btn.clicked.connect(self.accept)
|
|
168
|
+
layout.addWidget(close_btn, alignment=Qt.AlignRight)
|
|
169
|
+
|
|
170
|
+
self.refresh_table()
|
|
171
|
+
StaTableLogger.debug("EventQueueDefsDialog initialized")
|
|
172
|
+
|
|
173
|
+
def refresh_table(self):
|
|
174
|
+
query = self.search_edit.text().strip().lower() if hasattr(self, 'search_edit') else ""
|
|
175
|
+
self.table.setRowCount(0)
|
|
176
|
+
for q in self.global_defs.event_queues:
|
|
177
|
+
if not self._matches(q, query):
|
|
178
|
+
continue
|
|
179
|
+
row = self.table.rowCount()
|
|
180
|
+
self.table.insertRow(row)
|
|
181
|
+
|
|
182
|
+
title_item = QTableWidgetItem(q.title)
|
|
183
|
+
title_item.setToolTip(self.tr("Title of this queue. Can be edited directly."))
|
|
184
|
+
self.table.setItem(row, 0, title_item)
|
|
185
|
+
|
|
186
|
+
self.table.setItem(row, 1, QTableWidgetItem(q.name))
|
|
187
|
+
self.table.setItem(row, 2, QTableWidgetItem(str(q.size)))
|
|
188
|
+
self.table.setItem(row, 3, QTableWidgetItem(q.element_type))
|
|
189
|
+
self.table.setItem(row, 4, QTableWidgetItem(", ".join(q.event_ids)))
|
|
190
|
+
self.table.setItem(row, 5, QTableWidgetItem("Yes" if q.priority_enabled else "None"))
|
|
191
|
+
self.table.setItem(row, 6, QTableWidgetItem("Yes" if q.interrupt_safe else "None"))
|
|
192
|
+
self.table.setItem(row, 7, QTableWidgetItem("Yes" if q.rtos_enabled else "None"))
|
|
193
|
+
self.table.setItem(row, 8, QTableWidgetItem(q.description))
|
|
194
|
+
|
|
195
|
+
def _matches(self, q: EventQueueDef, query: str) -> bool:
|
|
196
|
+
"""Search filter (prefix match)"""
|
|
197
|
+
if not query:
|
|
198
|
+
return True
|
|
199
|
+
return (
|
|
200
|
+
q.title.lower().startswith(query) or
|
|
201
|
+
q.name.lower().startswith(query) or
|
|
202
|
+
q.description.lower().startswith(query)
|
|
203
|
+
)
|
|
204
|
+
|
|
205
|
+
def on_item_changed(self, item):
|
|
206
|
+
"""Handler when the title column is edited directly"""
|
|
207
|
+
if item.column() == 0:
|
|
208
|
+
row = item.row()
|
|
209
|
+
name_item = self.table.item(row, 1)
|
|
210
|
+
if name_item and row < len(self.global_defs.event_queues):
|
|
211
|
+
for q in self.global_defs.event_queues:
|
|
212
|
+
if q.name == name_item.text():
|
|
213
|
+
q.title = item.text().strip() or f"Queue: {q.name}"
|
|
214
|
+
break
|
|
215
|
+
|
|
216
|
+
def on_double_clicked(self, row, col):
|
|
217
|
+
if col == 0:
|
|
218
|
+
return
|
|
219
|
+
if row < 0 or row >= len(self.global_defs.event_queues):
|
|
220
|
+
return
|
|
221
|
+
target = self.global_defs.event_queues[row]
|
|
222
|
+
dlg = EventQueueEditDialog(self, queue_def=target, event_names=self.event_names, global_defs=self.global_defs)
|
|
223
|
+
if dlg.exec() == QDialog.Accepted:
|
|
224
|
+
new_q = dlg.get_queue_def()
|
|
225
|
+
if not new_q.name:
|
|
226
|
+
QMessageBox.warning(self, self.tr("Warning"), self.tr("Please enter a queue name."))
|
|
227
|
+
return
|
|
228
|
+
self.global_defs.event_queues[row] = new_q
|
|
229
|
+
self.refresh_table()
|
|
230
|
+
|
|
231
|
+
def add_queue(self):
|
|
232
|
+
dlg = EventQueueEditDialog(self, event_names=self.event_names, global_defs=self.global_defs)
|
|
233
|
+
if dlg.exec() == QDialog.Accepted:
|
|
234
|
+
q = dlg.get_queue_def()
|
|
235
|
+
if not q.name:
|
|
236
|
+
QMessageBox.warning(self, self.tr("Warning"), self.tr("Please enter a queue name."))
|
|
237
|
+
return
|
|
238
|
+
self.global_defs.event_queues.append(q)
|
|
239
|
+
self.refresh_table()
|
|
240
|
+
|
|
241
|
+
def delete_queue(self):
|
|
242
|
+
row = self.table.currentRow()
|
|
243
|
+
if 0 <= row < len(self.global_defs.event_queues):
|
|
244
|
+
self.global_defs.event_queues.pop(row)
|
|
245
|
+
self.refresh_table()
|
|
@@ -0,0 +1,167 @@
|
|
|
1
|
+
"""Data model for global variables, event flags, interrupt handlers, device resources, and timer settings"""
|
|
2
|
+
|
|
3
|
+
from dataclasses import dataclass, field
|
|
4
|
+
from typing import List, Optional
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
@dataclass
|
|
8
|
+
class SystemVariable:
|
|
9
|
+
"""Global variables shared across the system"""
|
|
10
|
+
name: str
|
|
11
|
+
type: str
|
|
12
|
+
unit: str = ""
|
|
13
|
+
default_value: str = ""
|
|
14
|
+
group: str = ""
|
|
15
|
+
description: str = ""
|
|
16
|
+
title: str = ""
|
|
17
|
+
|
|
18
|
+
def __post_init__(self):
|
|
19
|
+
if not self.title:
|
|
20
|
+
self.title = f"Variable: {self.name}"
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
@dataclass
|
|
24
|
+
class EventFlag:
|
|
25
|
+
"""Event flags managed as bit fields"""
|
|
26
|
+
name: str
|
|
27
|
+
min_value: int
|
|
28
|
+
max_value: int
|
|
29
|
+
group: str = ""
|
|
30
|
+
description: str = ""
|
|
31
|
+
title: str = ""
|
|
32
|
+
|
|
33
|
+
def __post_init__(self):
|
|
34
|
+
if not self.title:
|
|
35
|
+
self.title = f"Flag: {self.name}"
|
|
36
|
+
|
|
37
|
+
@property
|
|
38
|
+
def bit_width(self) -> int:
|
|
39
|
+
if self.max_value < self.min_value:
|
|
40
|
+
return 0
|
|
41
|
+
return (self.max_value - self.min_value).bit_length()
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
@dataclass
|
|
45
|
+
class InterruptAction:
|
|
46
|
+
"""Conditional actions inside an interrupt handler"""
|
|
47
|
+
condition: str = ""
|
|
48
|
+
action: str = ""
|
|
49
|
+
|
|
50
|
+
|
|
51
|
+
@dataclass
|
|
52
|
+
class InterruptHandlerDef:
|
|
53
|
+
"""Interrupt handler definition"""
|
|
54
|
+
name: str
|
|
55
|
+
description: str = ""
|
|
56
|
+
event_names: List[str] = field(default_factory=list)
|
|
57
|
+
is_timer: bool = False
|
|
58
|
+
actions: List[InterruptAction] = field(default_factory=list)
|
|
59
|
+
title: str = ""
|
|
60
|
+
|
|
61
|
+
def __post_init__(self):
|
|
62
|
+
if not self.title:
|
|
63
|
+
self.title = f"Interrupt: {self.name}"
|
|
64
|
+
|
|
65
|
+
|
|
66
|
+
@dataclass
|
|
67
|
+
class DevicePlaceholderDef:
|
|
68
|
+
"""Device resource placeholder definition"""
|
|
69
|
+
name: str
|
|
70
|
+
description: str = ""
|
|
71
|
+
title: str = ""
|
|
72
|
+
|
|
73
|
+
def __post_init__(self):
|
|
74
|
+
if not self.title:
|
|
75
|
+
self.title = f"Device: {self.name}"
|
|
76
|
+
|
|
77
|
+
|
|
78
|
+
@dataclass
|
|
79
|
+
class TimerDerivedDef:
|
|
80
|
+
"""Derived timer variable definition"""
|
|
81
|
+
period_name: str
|
|
82
|
+
multiplier: int
|
|
83
|
+
variable_name: str
|
|
84
|
+
data_type: str = "uint8_t"
|
|
85
|
+
title: str = ""
|
|
86
|
+
|
|
87
|
+
def __post_init__(self):
|
|
88
|
+
if not self.title:
|
|
89
|
+
self.title = f"Timer: {self.variable_name}"
|
|
90
|
+
|
|
91
|
+
|
|
92
|
+
@dataclass
|
|
93
|
+
class TimerBaseDef:
|
|
94
|
+
"""Timer tick variable definition"""
|
|
95
|
+
variable_name: str = "g_system_tick"
|
|
96
|
+
unit: str = "1ms"
|
|
97
|
+
data_type: str = "volatile uint32_t"
|
|
98
|
+
derived: List[TimerDerivedDef] = field(default_factory=list)
|
|
99
|
+
title: str = ""
|
|
100
|
+
interrupt_name: str = "" # Interrupt name driving this timer
|
|
101
|
+
|
|
102
|
+
def __post_init__(self):
|
|
103
|
+
if not self.title:
|
|
104
|
+
self.title = f"Timer base: {self.variable_name}"
|
|
105
|
+
|
|
106
|
+
|
|
107
|
+
@dataclass
|
|
108
|
+
class EventQueueDef:
|
|
109
|
+
"""Event queue definition"""
|
|
110
|
+
name: str
|
|
111
|
+
size: int
|
|
112
|
+
element_type: str
|
|
113
|
+
event_ids: List[str] = field(default_factory=list)
|
|
114
|
+
priority_enabled: bool = False
|
|
115
|
+
interrupt_safe: bool = True
|
|
116
|
+
rtos_enabled: bool = False
|
|
117
|
+
description: str = ""
|
|
118
|
+
title: str = ""
|
|
119
|
+
|
|
120
|
+
def __post_init__(self):
|
|
121
|
+
if not self.title:
|
|
122
|
+
self.title = f"Queue: {self.name}"
|
|
123
|
+
|
|
124
|
+
|
|
125
|
+
class GlobalDefinitions:
|
|
126
|
+
"""Management class for global variables, event flags, interrupt handlers, device resources, timer settings, and event queues"""
|
|
127
|
+
|
|
128
|
+
def __init__(self):
|
|
129
|
+
self.variables: List[SystemVariable] = []
|
|
130
|
+
self.flags: List[EventFlag] = []
|
|
131
|
+
self.interrupts: List[InterruptHandlerDef] = []
|
|
132
|
+
self.placeholders: List[DevicePlaceholderDef] = []
|
|
133
|
+
self.timer_base: TimerBaseDef = TimerBaseDef()
|
|
134
|
+
self.extra_timers: List[TimerBaseDef] = [] # Add timer base
|
|
135
|
+
self.event_queues: List[EventQueueDef] = []
|
|
136
|
+
|
|
137
|
+
def add_timer_variables(self):
|
|
138
|
+
"""Register all timer base variables and derived timer variables as global variables"""
|
|
139
|
+
self.variables = [v for v in self.variables if v.group != "Timer"]
|
|
140
|
+
|
|
141
|
+
all_timers = [self.timer_base] + self.extra_timers
|
|
142
|
+
for timer in all_timers:
|
|
143
|
+
self.variables.append(SystemVariable(
|
|
144
|
+
name=timer.variable_name,
|
|
145
|
+
type=timer.data_type,
|
|
146
|
+
unit=timer.unit,
|
|
147
|
+
default_value="0",
|
|
148
|
+
group="Timer",
|
|
149
|
+
description="Timer base variable",
|
|
150
|
+
title=timer.title,
|
|
151
|
+
))
|
|
152
|
+
for d in timer.derived:
|
|
153
|
+
self.variables.append(SystemVariable(
|
|
154
|
+
name=d.variable_name,
|
|
155
|
+
type=d.data_type,
|
|
156
|
+
unit=d.period_name,
|
|
157
|
+
default_value="0",
|
|
158
|
+
group="Timer",
|
|
159
|
+
description=f"Derived timer variable ({d.period_name})",
|
|
160
|
+
title=d.title,
|
|
161
|
+
))
|
|
162
|
+
|
|
163
|
+
def variable_groups(self) -> List[str]:
|
|
164
|
+
return sorted({v.group for v in self.variables if v.group})
|
|
165
|
+
|
|
166
|
+
def flag_groups(self) -> List[str]:
|
|
167
|
+
return sorted({f.group for f in self.flags if f.group})
|