pySimBlocks 0.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.
- examples/advanced/hardware/block_diagram.py +162 -0
- examples/advanced/hardware/camera.py +93 -0
- examples/advanced/hardware/gui_emio.py +231 -0
- examples/advanced/hardware/lmi.py +69 -0
- examples/advanced/hardware/main.py +57 -0
- examples/advanced/hardware/parameters.py +54 -0
- examples/advanced/qp_problem/params.py +10 -0
- examples/advanced/qp_problem/run.py +14 -0
- examples/advanced/sofa/gui/finger/Finger.py +108 -0
- examples/advanced/sofa/gui/finger/FingerController.py +53 -0
- examples/advanced/sofa/gui/finger/parameters_auto.py +22 -0
- examples/advanced/sofa/gui/sofa_plant/run.py +14 -0
- examples/advanced/sofa/python/Finger.py +107 -0
- examples/advanced/sofa/python/FingerController.py +48 -0
- examples/advanced/sofa/python/closedloop.py +64 -0
- examples/advanced/sofa/python/openloop.py +58 -0
- examples/basics/algebraic_function/g.py +7 -0
- examples/basics/algebraic_function/run.py +14 -0
- examples/basics/dc_motor/gui/run.py +14 -0
- examples/basics/dc_motor/multiple_order.py +91 -0
- examples/basics/dc_motor/openloop.py +62 -0
- examples/basics/dc_motor/pi.py +99 -0
- examples/basics/dc_motor/pid.py +118 -0
- examples/basics/dc_motor/pid_block.py +82 -0
- examples/basics/dc_motor/pid_block_vs_manual.py +143 -0
- examples/basics/external/parameters.py +4 -0
- examples/basics/external/run.py +14 -0
- examples/basics/file_sources/temp.py +16 -0
- examples/basics/observer.py +76 -0
- examples/basics/state_feedback/gui/parameters.py +9 -0
- examples/basics/state_feedback/gui/run.py +14 -0
- examples/basics/state_feedback/state_feedback.py +102 -0
- examples/basics/state_space/linear/gui/run.py +14 -0
- examples/basics/state_space/linear/ss_block.py +89 -0
- examples/basics/state_space/linear/ss_manual.py +105 -0
- examples/basics/state_space/non_linear/nlss.py +47 -0
- examples/basics/state_space/non_linear/run.py +14 -0
- examples/basics/state_space/polytope/params.py +39 -0
- examples/basics/state_space/polytope/weights.py +12 -0
- examples/comparison/simulink/convert.py +29 -0
- examples/comparison/simulink/openloop.py +127 -0
- examples/comparison/simulink/pi.py +148 -0
- examples/comparison/simulink/pid.py +154 -0
- examples/comparison/simulink/pid_backward.py +155 -0
- examples/quick_start/filter.py +35 -0
- examples/quick_start/gui/run.py +14 -0
- examples/tutorials/tutorial_1_python/main.py +99 -0
- examples/tutorials/tutorial_3_sofa/finger/Finger.py +120 -0
- examples/tutorials/tutorial_3_sofa/finger/FingerController.py +37 -0
- pySimBlocks/__init__.py +35 -0
- pySimBlocks/blocks/__init__.py +68 -0
- pySimBlocks/blocks/controllers/__init__.py +27 -0
- pySimBlocks/blocks/controllers/pid.py +250 -0
- pySimBlocks/blocks/controllers/state_feedback.py +141 -0
- pySimBlocks/blocks/interfaces/__init__.py +27 -0
- pySimBlocks/blocks/interfaces/external_input.py +107 -0
- pySimBlocks/blocks/interfaces/external_output.py +113 -0
- pySimBlocks/blocks/observers/__init__.py +23 -0
- pySimBlocks/blocks/observers/luenberger.py +175 -0
- pySimBlocks/blocks/operators/__init__.py +47 -0
- pySimBlocks/blocks/operators/algebraic_function.py +258 -0
- pySimBlocks/blocks/operators/dead_zone.py +186 -0
- pySimBlocks/blocks/operators/delay.py +254 -0
- pySimBlocks/blocks/operators/demux.py +123 -0
- pySimBlocks/blocks/operators/discrete_derivator.py +237 -0
- pySimBlocks/blocks/operators/discrete_integrator.py +265 -0
- pySimBlocks/blocks/operators/gain.py +294 -0
- pySimBlocks/blocks/operators/mux.py +131 -0
- pySimBlocks/blocks/operators/product.py +194 -0
- pySimBlocks/blocks/operators/rate_limiter.py +214 -0
- pySimBlocks/blocks/operators/saturation.py +178 -0
- pySimBlocks/blocks/operators/sum.py +188 -0
- pySimBlocks/blocks/operators/zero_order_hold.py +141 -0
- pySimBlocks/blocks/optimizers/__init__.py +23 -0
- pySimBlocks/blocks/optimizers/quadratic_program.py +326 -0
- pySimBlocks/blocks/sources/__init__.py +37 -0
- pySimBlocks/blocks/sources/chirp.py +168 -0
- pySimBlocks/blocks/sources/constant.py +82 -0
- pySimBlocks/blocks/sources/file_source.py +294 -0
- pySimBlocks/blocks/sources/function_source.py +197 -0
- pySimBlocks/blocks/sources/ramp.py +99 -0
- pySimBlocks/blocks/sources/sinusoidal.py +113 -0
- pySimBlocks/blocks/sources/step.py +138 -0
- pySimBlocks/blocks/sources/white_noise.py +107 -0
- pySimBlocks/blocks/systems/__init__.py +27 -0
- pySimBlocks/blocks/systems/linear_state_space.py +180 -0
- pySimBlocks/blocks/systems/non_linear_state_space.py +309 -0
- pySimBlocks/blocks/systems/polytopic_state_space.py +193 -0
- pySimBlocks/blocks/systems/sofa/__init__.py +30 -0
- pySimBlocks/blocks/systems/sofa/sofa_controller.py +472 -0
- pySimBlocks/blocks/systems/sofa/sofa_exchange_i_o.py +120 -0
- pySimBlocks/blocks/systems/sofa/sofa_plant.py +306 -0
- pySimBlocks/cli.py +127 -0
- pySimBlocks/core/__init__.py +32 -0
- pySimBlocks/core/block.py +167 -0
- pySimBlocks/core/block_source.py +94 -0
- pySimBlocks/core/config.py +90 -0
- pySimBlocks/core/fixed_time_manager.py +46 -0
- pySimBlocks/core/model.py +250 -0
- pySimBlocks/core/scheduler.py +30 -0
- pySimBlocks/core/simulator.py +329 -0
- pySimBlocks/core/task.py +73 -0
- pySimBlocks/docs/blocks/controllers/pid.md +78 -0
- pySimBlocks/docs/blocks/controllers/state_feedback.md +60 -0
- pySimBlocks/docs/blocks/interfaces/external_input.md +39 -0
- pySimBlocks/docs/blocks/interfaces/external_output.md +39 -0
- pySimBlocks/docs/blocks/observers/luenberger.md +70 -0
- pySimBlocks/docs/blocks/operators/algebraic_function.md +76 -0
- pySimBlocks/docs/blocks/operators/dead_zone.md +67 -0
- pySimBlocks/docs/blocks/operators/delay.md +76 -0
- pySimBlocks/docs/blocks/operators/demux.md +64 -0
- pySimBlocks/docs/blocks/operators/discrete_derivator.md +61 -0
- pySimBlocks/docs/blocks/operators/discrete_integrator.md +87 -0
- pySimBlocks/docs/blocks/operators/gain.md +97 -0
- pySimBlocks/docs/blocks/operators/mux.md +64 -0
- pySimBlocks/docs/blocks/operators/product.md +51 -0
- pySimBlocks/docs/blocks/operators/rate_limiter.md +70 -0
- pySimBlocks/docs/blocks/operators/saturation.md +62 -0
- pySimBlocks/docs/blocks/operators/sum.md +49 -0
- pySimBlocks/docs/blocks/operators/zero_order_hold.md +61 -0
- pySimBlocks/docs/blocks/optimizers/quadratic_program.md +98 -0
- pySimBlocks/docs/blocks/sources/chirp.md +102 -0
- pySimBlocks/docs/blocks/sources/constant.md +53 -0
- pySimBlocks/docs/blocks/sources/file_source.md +51 -0
- pySimBlocks/docs/blocks/sources/function_source.md +52 -0
- pySimBlocks/docs/blocks/sources/ramp.md +58 -0
- pySimBlocks/docs/blocks/sources/sinusoidal.md +60 -0
- pySimBlocks/docs/blocks/sources/step.md +63 -0
- pySimBlocks/docs/blocks/sources/white_noise.md +58 -0
- pySimBlocks/docs/blocks/systems/linear_state_space.md +66 -0
- pySimBlocks/docs/blocks/systems/non_linear_state_space.md +90 -0
- pySimBlocks/docs/blocks/systems/polytopic_state_space.md +72 -0
- pySimBlocks/docs/blocks/systems/sofa/sofa_exchange_i_o.md +63 -0
- pySimBlocks/docs/blocks/systems/sofa/sofa_plant.md +62 -0
- pySimBlocks/gui/__init__.py +20 -0
- pySimBlocks/gui/addons/__init__.py +19 -0
- pySimBlocks/gui/addons/sofa/__init__.py +19 -0
- pySimBlocks/gui/addons/sofa/sofa_dialog.py +190 -0
- pySimBlocks/gui/addons/sofa/sofa_service.py +209 -0
- pySimBlocks/gui/blocks/block_dialog_session.py +47 -0
- pySimBlocks/gui/blocks/block_meta.py +408 -0
- pySimBlocks/gui/blocks/controllers/pid.py +116 -0
- pySimBlocks/gui/blocks/controllers/state_feedback.py +80 -0
- pySimBlocks/gui/blocks/interfaces/external_input.py +64 -0
- pySimBlocks/gui/blocks/interfaces/external_output.py +63 -0
- pySimBlocks/gui/blocks/observers/luenberger.py +108 -0
- pySimBlocks/gui/blocks/operators/algebraic_function.py +232 -0
- pySimBlocks/gui/blocks/operators/dead_zone.py +79 -0
- pySimBlocks/gui/blocks/operators/delay.py +79 -0
- pySimBlocks/gui/blocks/operators/demux.py +98 -0
- pySimBlocks/gui/blocks/operators/discrete_derivator.py +67 -0
- pySimBlocks/gui/blocks/operators/discrete_integrator.py +80 -0
- pySimBlocks/gui/blocks/operators/gain.py +79 -0
- pySimBlocks/gui/blocks/operators/mux.py +100 -0
- pySimBlocks/gui/blocks/operators/product.py +109 -0
- pySimBlocks/gui/blocks/operators/rate_limiter.py +79 -0
- pySimBlocks/gui/blocks/operators/saturation.py +72 -0
- pySimBlocks/gui/blocks/operators/sum.py +91 -0
- pySimBlocks/gui/blocks/operators/zero_order_hold.py +64 -0
- pySimBlocks/gui/blocks/optimizers/quadratic_program.py +128 -0
- pySimBlocks/gui/blocks/parameter_meta.py +34 -0
- pySimBlocks/gui/blocks/port_meta.py +31 -0
- pySimBlocks/gui/blocks/sources/chirp.py +146 -0
- pySimBlocks/gui/blocks/sources/constant.py +63 -0
- pySimBlocks/gui/blocks/sources/file_source.py +196 -0
- pySimBlocks/gui/blocks/sources/function_source.py +202 -0
- pySimBlocks/gui/blocks/sources/ramp.py +73 -0
- pySimBlocks/gui/blocks/sources/sinusoidal.py +81 -0
- pySimBlocks/gui/blocks/sources/step.py +83 -0
- pySimBlocks/gui/blocks/sources/white_noise.py +70 -0
- pySimBlocks/gui/blocks/systems/linear_state_space.py +102 -0
- pySimBlocks/gui/blocks/systems/non_linear_state_space.py +233 -0
- pySimBlocks/gui/blocks/systems/polytopic_state_space.py +109 -0
- pySimBlocks/gui/blocks/systems/sofa/sofa_exchange_i_o.py +221 -0
- pySimBlocks/gui/blocks/systems/sofa/sofa_plant.py +220 -0
- pySimBlocks/gui/dialogs/__init__.py +19 -0
- pySimBlocks/gui/dialogs/block_dialog.py +125 -0
- pySimBlocks/gui/dialogs/display_yaml_dialog.py +89 -0
- pySimBlocks/gui/dialogs/help_dialog.py +39 -0
- pySimBlocks/gui/dialogs/plot_dialog.py +230 -0
- pySimBlocks/gui/dialogs/settings/plots.py +187 -0
- pySimBlocks/gui/dialogs/settings/project.py +146 -0
- pySimBlocks/gui/dialogs/settings/simulation.py +137 -0
- pySimBlocks/gui/dialogs/settings_dialog.py +88 -0
- pySimBlocks/gui/dialogs/unsaved_dialog.py +92 -0
- pySimBlocks/gui/editor.py +49 -0
- pySimBlocks/gui/graphics/__init__.py +29 -0
- pySimBlocks/gui/graphics/block_item.py +289 -0
- pySimBlocks/gui/graphics/connection_item.py +266 -0
- pySimBlocks/gui/graphics/port_item.py +153 -0
- pySimBlocks/gui/graphics/theme.py +119 -0
- pySimBlocks/gui/main_window.py +162 -0
- pySimBlocks/gui/models/__init__.py +31 -0
- pySimBlocks/gui/models/block_instance.py +114 -0
- pySimBlocks/gui/models/connection_instance.py +43 -0
- pySimBlocks/gui/models/port_instance.py +52 -0
- pySimBlocks/gui/models/project_simulation_params.py +50 -0
- pySimBlocks/gui/models/project_state.py +114 -0
- pySimBlocks/gui/project_controller.py +302 -0
- pySimBlocks/gui/services/__init__.py +20 -0
- pySimBlocks/gui/services/project_loader.py +381 -0
- pySimBlocks/gui/services/project_saver.py +68 -0
- pySimBlocks/gui/services/simulation_runner.py +66 -0
- pySimBlocks/gui/services/yaml_tools.py +261 -0
- pySimBlocks/gui/widgets/__init__.py +19 -0
- pySimBlocks/gui/widgets/block_list.py +131 -0
- pySimBlocks/gui/widgets/diagram_view.py +312 -0
- pySimBlocks/gui/widgets/toolbar_view.py +161 -0
- pySimBlocks/project/__init__.py +35 -0
- pySimBlocks/project/build_model.py +92 -0
- pySimBlocks/project/generate_run_script.py +109 -0
- pySimBlocks/project/generate_sofa_controller.py +215 -0
- pySimBlocks/project/load_project_config.py +197 -0
- pySimBlocks/project/load_simulation_config.py +166 -0
- pySimBlocks/project/load_simulator.py +45 -0
- pySimBlocks/project/plot_from_config.py +174 -0
- pySimBlocks/project/pySimBlocks_blocks_index.yaml +106 -0
- pySimBlocks/real_time/__init__.py +23 -0
- pySimBlocks/real_time/real_time_runner.py +148 -0
- pySimBlocks/tools/__init__.py +19 -0
- pySimBlocks/tools/blocks_registry.py +141 -0
- pySimBlocks/tools/generate_blocks_index.py +124 -0
- pysimblocks-0.1.0.dist-info/METADATA +180 -0
- pysimblocks-0.1.0.dist-info/RECORD +262 -0
- pysimblocks-0.1.0.dist-info/WHEEL +5 -0
- pysimblocks-0.1.0.dist-info/entry_points.txt +2 -0
- pysimblocks-0.1.0.dist-info/licenses/LICENSE.md +165 -0
- pysimblocks-0.1.0.dist-info/top_level.txt +3 -0
- tests/blocks/controllers/test_pid.py +143 -0
- tests/blocks/controllers/test_state_feedback.py +193 -0
- tests/blocks/observers/test_luenberger.py +225 -0
- tests/blocks/operators/test_algebraic_function.py +230 -0
- tests/blocks/operators/test_dead_zone.py +106 -0
- tests/blocks/operators/test_delay.py +167 -0
- tests/blocks/operators/test_demux.py +111 -0
- tests/blocks/operators/test_discrete_derivator.py +160 -0
- tests/blocks/operators/test_discrete_integrator.py +149 -0
- tests/blocks/operators/test_gain.py +182 -0
- tests/blocks/operators/test_mux.py +101 -0
- tests/blocks/operators/test_product.py +171 -0
- tests/blocks/operators/test_rate_limiter.py +166 -0
- tests/blocks/operators/test_saturation.py +157 -0
- tests/blocks/operators/test_sum.py +183 -0
- tests/blocks/operators/test_zero_order_hold.py +106 -0
- tests/blocks/sources/test_chirp.py +157 -0
- tests/blocks/sources/test_constant.py +55 -0
- tests/blocks/sources/test_file_source.py +185 -0
- tests/blocks/sources/test_function_source.py +135 -0
- tests/blocks/sources/test_ramp.py +110 -0
- tests/blocks/sources/test_sinusoidal.py +113 -0
- tests/blocks/sources/test_step.py +76 -0
- tests/blocks/sources/test_white_noise.py +108 -0
- tests/blocks/systems/test_linear_state_space.py +135 -0
- tests/blocks/systems/test_polytopic_state_space.py +133 -0
- tests/core/test_core_errors_and_determinism.py +82 -0
- tests/core/test_core_multirate_tasks.py +88 -0
- tests/core/test_core_two_phase_and_propagation.py +132 -0
- tests/gui/main_window_open_test.py +162 -0
- tests/gui/test_block_meta_param_cache.py +16 -0
- tests/gui/test_file_source_meta.py +17 -0
- tests/gui/test_function_source_meta.py +28 -0
- tests/regression/test_simulink_dc_motor.py +217 -0
|
@@ -0,0 +1,408 @@
|
|
|
1
|
+
# ******************************************************************************
|
|
2
|
+
# pySimBlocks
|
|
3
|
+
# Copyright (c) 2026 Université de Lille & INRIA
|
|
4
|
+
# ******************************************************************************
|
|
5
|
+
# This program is free software: you can redistribute it and/or modify it
|
|
6
|
+
# under the terms of the GNU Lesser General Public License as published by
|
|
7
|
+
# the Free Software Foundation, either version 3 of the License, or (at your
|
|
8
|
+
# option) any later version.
|
|
9
|
+
#
|
|
10
|
+
# This program is distributed in the hope that it will be useful, but WITHOUT
|
|
11
|
+
# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
|
12
|
+
# FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License
|
|
13
|
+
# for more details.
|
|
14
|
+
#
|
|
15
|
+
# You should have received a copy of the GNU Lesser General Public License
|
|
16
|
+
# along with this program. If not, see <https://www.gnu.org/licenses/>.
|
|
17
|
+
# ******************************************************************************
|
|
18
|
+
# Authors: see Authors.txt
|
|
19
|
+
# ******************************************************************************
|
|
20
|
+
|
|
21
|
+
import ast
|
|
22
|
+
import os
|
|
23
|
+
from abc import ABC
|
|
24
|
+
from pathlib import Path
|
|
25
|
+
from typing import Any, Dict, Literal, Sequence
|
|
26
|
+
|
|
27
|
+
from PySide6.QtCore import Qt
|
|
28
|
+
from PySide6.QtWidgets import (
|
|
29
|
+
QComboBox,
|
|
30
|
+
QFileDialog,
|
|
31
|
+
QFormLayout,
|
|
32
|
+
QFrame,
|
|
33
|
+
QHBoxLayout,
|
|
34
|
+
QLabel,
|
|
35
|
+
QLineEdit,
|
|
36
|
+
QPushButton,
|
|
37
|
+
QSizePolicy,
|
|
38
|
+
QTextBrowser,
|
|
39
|
+
QVBoxLayout,
|
|
40
|
+
QWidget,
|
|
41
|
+
)
|
|
42
|
+
|
|
43
|
+
from pySimBlocks.gui.blocks.block_dialog_session import BlockDialogSession
|
|
44
|
+
from pySimBlocks.gui.blocks.parameter_meta import ParameterMeta
|
|
45
|
+
from pySimBlocks.gui.blocks.port_meta import PortMeta
|
|
46
|
+
from pySimBlocks.gui.models import BlockInstance, PortInstance
|
|
47
|
+
|
|
48
|
+
|
|
49
|
+
class BlockMeta(ABC):
|
|
50
|
+
|
|
51
|
+
"""
|
|
52
|
+
Template for child class
|
|
53
|
+
|
|
54
|
+
from pySimBlocks.gui.blocks.block_meta import BlockMeta
|
|
55
|
+
from pySimBlocks.gui.blocks.parameter_meta import ParameterMeta
|
|
56
|
+
from pySimBlocks.gui.blocks.port_meta import PortMeta
|
|
57
|
+
|
|
58
|
+
class MyBlockMeta(BlockMeta):
|
|
59
|
+
|
|
60
|
+
def __init__(self):
|
|
61
|
+
self.name = ""
|
|
62
|
+
self.category = ""
|
|
63
|
+
self.type = ""
|
|
64
|
+
self.summary = ""
|
|
65
|
+
self.description = (
|
|
66
|
+
""
|
|
67
|
+
)
|
|
68
|
+
|
|
69
|
+
self.parameters = [
|
|
70
|
+
ParameterMeta(
|
|
71
|
+
name="",
|
|
72
|
+
type=""
|
|
73
|
+
),
|
|
74
|
+
]
|
|
75
|
+
|
|
76
|
+
self.inputs = [
|
|
77
|
+
PortMeta(
|
|
78
|
+
name="",
|
|
79
|
+
display_as=""
|
|
80
|
+
shape=...
|
|
81
|
+
),
|
|
82
|
+
]
|
|
83
|
+
|
|
84
|
+
self.outputs = [
|
|
85
|
+
PortMeta(
|
|
86
|
+
name="",
|
|
87
|
+
display_as=""
|
|
88
|
+
shape=...
|
|
89
|
+
),
|
|
90
|
+
]
|
|
91
|
+
"""
|
|
92
|
+
|
|
93
|
+
|
|
94
|
+
# ----------- Mandatory class attributes (must be overridden) -----------
|
|
95
|
+
name: str
|
|
96
|
+
category: str
|
|
97
|
+
type: str
|
|
98
|
+
summary: str
|
|
99
|
+
description: str
|
|
100
|
+
|
|
101
|
+
# ----------- Optional declarations -----------
|
|
102
|
+
doc_path: Path | None = None
|
|
103
|
+
parameters: Sequence[ParameterMeta] = ()
|
|
104
|
+
inputs: Sequence[PortMeta] = ()
|
|
105
|
+
outputs: Sequence[PortMeta] = ()
|
|
106
|
+
|
|
107
|
+
# --------------------------------------------------------------------------
|
|
108
|
+
# Dialog session management
|
|
109
|
+
# --------------------------------------------------------------------------
|
|
110
|
+
def create_dialog_session(
|
|
111
|
+
self,
|
|
112
|
+
instance: BlockInstance,
|
|
113
|
+
project_dir: Path | None = None,
|
|
114
|
+
) -> BlockDialogSession:
|
|
115
|
+
return BlockDialogSession(self, instance, project_dir)
|
|
116
|
+
|
|
117
|
+
# --------------------------------------------------------------------------
|
|
118
|
+
# Parameter resolution
|
|
119
|
+
# --------------------------------------------------------------------------
|
|
120
|
+
def is_parameter_active(self,
|
|
121
|
+
param_name: str,
|
|
122
|
+
instance_params: Dict[str, Any]) -> bool:
|
|
123
|
+
"""
|
|
124
|
+
Default: all parameters are always active.
|
|
125
|
+
Children override if needed.
|
|
126
|
+
"""
|
|
127
|
+
return True
|
|
128
|
+
|
|
129
|
+
# ------------------------------------------------------------
|
|
130
|
+
def gather_params(self, session: BlockDialogSession) -> dict[str, Any]:
|
|
131
|
+
# Keep full local state, including inactive params, so values are cached
|
|
132
|
+
# across visibility toggles and dialog reopen.
|
|
133
|
+
return session.local_params.copy()
|
|
134
|
+
|
|
135
|
+
# --------------------------------------------------------------------------
|
|
136
|
+
# Port resolution
|
|
137
|
+
# --------------------------------------------------------------------------
|
|
138
|
+
def resolve_port_group(self,
|
|
139
|
+
port_meta: PortMeta,
|
|
140
|
+
direction: Literal['input', 'output'],
|
|
141
|
+
instance: "BlockInstance"
|
|
142
|
+
) -> list["PortInstance"]:
|
|
143
|
+
"""
|
|
144
|
+
Default behavior: fixed port.
|
|
145
|
+
Children override for dynamic ports.
|
|
146
|
+
"""
|
|
147
|
+
return [PortInstance(port_meta.name, port_meta.display_as, direction, instance)]
|
|
148
|
+
|
|
149
|
+
# ------------------------------------------------------------
|
|
150
|
+
def build_ports(self, instance: "BlockInstance") -> list["PortInstance"]:
|
|
151
|
+
ports = []
|
|
152
|
+
|
|
153
|
+
for pmeta in self.inputs:
|
|
154
|
+
ports.extend(self.resolve_port_group(pmeta, "input", instance))
|
|
155
|
+
|
|
156
|
+
for pmeta in self.outputs:
|
|
157
|
+
ports.extend(self.resolve_port_group(pmeta, "output", instance))
|
|
158
|
+
|
|
159
|
+
return ports
|
|
160
|
+
|
|
161
|
+
|
|
162
|
+
# --------------------------------------------------------------------------
|
|
163
|
+
# QT dialog display
|
|
164
|
+
# --------------------------------------------------------------------------
|
|
165
|
+
def build_description(self, form: QFormLayout):
|
|
166
|
+
""" Default description display. Children can override if needed. """
|
|
167
|
+
title = QLabel(f"<b>{self.name}</b>")
|
|
168
|
+
title.setAlignment(Qt.AlignmentFlag.AlignLeft)
|
|
169
|
+
form.addRow(title)
|
|
170
|
+
|
|
171
|
+
frame = QFrame()
|
|
172
|
+
frame.setFrameShape(QFrame.StyledPanel)
|
|
173
|
+
frame.setFrameShadow(QFrame.Raised)
|
|
174
|
+
frame.setLineWidth(1)
|
|
175
|
+
|
|
176
|
+
frame_layout = QVBoxLayout(frame)
|
|
177
|
+
frame_layout.setContentsMargins(8, 6, 8, 6)
|
|
178
|
+
|
|
179
|
+
desc = QTextBrowser()
|
|
180
|
+
desc.setMarkdown(self.description)
|
|
181
|
+
desc.setReadOnly(True)
|
|
182
|
+
desc.setFrameShape(QFrame.NoFrame)
|
|
183
|
+
desc.setSizePolicy(QSizePolicy.Preferred, QSizePolicy.Minimum)
|
|
184
|
+
desc.document().setTextWidth(400)
|
|
185
|
+
desc.setFixedHeight(int(desc.document().size().height()) + 6)
|
|
186
|
+
|
|
187
|
+
frame_layout.addWidget(desc)
|
|
188
|
+
form.addRow(frame)
|
|
189
|
+
|
|
190
|
+
|
|
191
|
+
# ------------------------------------------------------
|
|
192
|
+
def build_pre_param(self,
|
|
193
|
+
session: BlockDialogSession,
|
|
194
|
+
form: QFormLayout,
|
|
195
|
+
readonly: bool = False):
|
|
196
|
+
""" Default: no pre-parameter widgets. Children override if needed. """
|
|
197
|
+
pass
|
|
198
|
+
|
|
199
|
+
# ------------------------------------------------------------
|
|
200
|
+
def build_param(self,
|
|
201
|
+
session: BlockDialogSession,
|
|
202
|
+
form: QFormLayout,
|
|
203
|
+
readonly: bool = False):
|
|
204
|
+
""" Default: no parameter widgets. Children override if needed. """
|
|
205
|
+
|
|
206
|
+
|
|
207
|
+
# --- Block name ---
|
|
208
|
+
name_edit = QLineEdit(session.instance.name)
|
|
209
|
+
name_edit.textChanged.connect(
|
|
210
|
+
lambda val: self._on_param_changed(val, "name", session, readonly)
|
|
211
|
+
)
|
|
212
|
+
form.addRow(QLabel("Block name:"), name_edit)
|
|
213
|
+
if readonly:
|
|
214
|
+
name_edit.setReadOnly(True)
|
|
215
|
+
session.name_edit = name_edit
|
|
216
|
+
|
|
217
|
+
# --- Parameters ---
|
|
218
|
+
for param_meta in self.parameters:
|
|
219
|
+
param_name = param_meta.name
|
|
220
|
+
|
|
221
|
+
label, widget = self._create_param_row(session, param_meta, readonly)
|
|
222
|
+
if widget is None:
|
|
223
|
+
continue
|
|
224
|
+
if readonly:
|
|
225
|
+
self._set_readonly_style(widget)
|
|
226
|
+
|
|
227
|
+
form.addRow(label, widget)
|
|
228
|
+
|
|
229
|
+
session.param_widgets[param_name] = widget
|
|
230
|
+
session.param_labels[param_name] = label
|
|
231
|
+
|
|
232
|
+
|
|
233
|
+
# ------------------------------------------------------------
|
|
234
|
+
def build_post_param(self,
|
|
235
|
+
session: BlockDialogSession,
|
|
236
|
+
form: QFormLayout,
|
|
237
|
+
readonly: bool = False):
|
|
238
|
+
""" Default: no post-parameter widgets. Children override if needed. """
|
|
239
|
+
pass
|
|
240
|
+
|
|
241
|
+
# ------------------------------------------------------------
|
|
242
|
+
def build_file_param_row(
|
|
243
|
+
self,
|
|
244
|
+
session: BlockDialogSession,
|
|
245
|
+
form: QFormLayout,
|
|
246
|
+
pmeta: ParameterMeta,
|
|
247
|
+
readonly: bool = False,
|
|
248
|
+
file_filter: str = "Python files (*.py);;All files (*)",
|
|
249
|
+
) -> None:
|
|
250
|
+
edit = self._create_edit_widget(session, pmeta, readonly)
|
|
251
|
+
if readonly:
|
|
252
|
+
self._set_readonly_style(edit)
|
|
253
|
+
|
|
254
|
+
browse_btn = QPushButton("...")
|
|
255
|
+
browse_btn.setToolTip("Select file from disk")
|
|
256
|
+
browse_btn.setEnabled(not readonly)
|
|
257
|
+
browse_btn.clicked.connect(
|
|
258
|
+
lambda: self._browse_and_set_relative_file(edit, session.project_dir, file_filter)
|
|
259
|
+
)
|
|
260
|
+
|
|
261
|
+
row_widget = QWidget()
|
|
262
|
+
row_layout = QHBoxLayout(row_widget)
|
|
263
|
+
row_layout.setContentsMargins(0, 0, 0, 0)
|
|
264
|
+
row_layout.addWidget(edit)
|
|
265
|
+
row_layout.addWidget(browse_btn)
|
|
266
|
+
|
|
267
|
+
label = QLabel(f"{pmeta.name}:")
|
|
268
|
+
if pmeta.description:
|
|
269
|
+
label.setToolTip(pmeta.description)
|
|
270
|
+
|
|
271
|
+
form.addRow(label, row_widget)
|
|
272
|
+
session.param_widgets[pmeta.name] = row_widget
|
|
273
|
+
session.param_labels[pmeta.name] = label
|
|
274
|
+
|
|
275
|
+
# ------------------------------------------------------------
|
|
276
|
+
def refresh_form(self, session: BlockDialogSession):
|
|
277
|
+
"""
|
|
278
|
+
Refresh the parameter widgets visibility based on
|
|
279
|
+
BlockMeta.is_parameter_active and current local_params.
|
|
280
|
+
"""
|
|
281
|
+
|
|
282
|
+
for param_name, widget in session.param_widgets.items():
|
|
283
|
+
label = session.param_labels[param_name]
|
|
284
|
+
|
|
285
|
+
active = self.is_parameter_active(param_name, session.local_params)
|
|
286
|
+
|
|
287
|
+
widget.setVisible(active)
|
|
288
|
+
label.setVisible(active)
|
|
289
|
+
|
|
290
|
+
|
|
291
|
+
# --------------------------------------------------------------------------
|
|
292
|
+
# Private methods
|
|
293
|
+
# --------------------------------------------------------------------------
|
|
294
|
+
def _create_param_row(self,
|
|
295
|
+
session: BlockDialogSession,
|
|
296
|
+
pmeta: ParameterMeta,
|
|
297
|
+
readonly: bool = False
|
|
298
|
+
) -> tuple[QLabel, QWidget]:
|
|
299
|
+
|
|
300
|
+
# ENUM
|
|
301
|
+
if pmeta.type == "enum":
|
|
302
|
+
widget = self._create_enum_widget(session, pmeta, readonly)
|
|
303
|
+
|
|
304
|
+
else: # Default: text edit
|
|
305
|
+
widget = self._create_edit_widget(session, pmeta, readonly)
|
|
306
|
+
|
|
307
|
+
label = QLabel(f"{pmeta.name}:")
|
|
308
|
+
if pmeta.description:
|
|
309
|
+
label.setToolTip(pmeta.description)
|
|
310
|
+
|
|
311
|
+
return label, widget
|
|
312
|
+
|
|
313
|
+
|
|
314
|
+
# ------------------------------------------------------------
|
|
315
|
+
def _create_edit_widget(self,
|
|
316
|
+
session: BlockDialogSession,
|
|
317
|
+
pmeta: ParameterMeta,
|
|
318
|
+
readonly: bool = False) -> QLineEdit:
|
|
319
|
+
edit = QLineEdit()
|
|
320
|
+
value = session.local_params.get(pmeta.name)
|
|
321
|
+
if value is not None:
|
|
322
|
+
edit.setText(str(value))
|
|
323
|
+
elif pmeta.default is not None:
|
|
324
|
+
edit.setText(str(pmeta.default))
|
|
325
|
+
edit.textChanged.connect(
|
|
326
|
+
lambda val: self._on_param_changed(val, pmeta.name, session, readonly)
|
|
327
|
+
)
|
|
328
|
+
return edit
|
|
329
|
+
|
|
330
|
+
# ------------------------------------------------------------
|
|
331
|
+
def _create_enum_widget(self,
|
|
332
|
+
session: BlockDialogSession,
|
|
333
|
+
pmeta: ParameterMeta,
|
|
334
|
+
readonly: bool = False) -> QComboBox:
|
|
335
|
+
combo = QComboBox()
|
|
336
|
+
for v in pmeta.enum:
|
|
337
|
+
combo.addItem(str(v), userData=v)
|
|
338
|
+
value = session.local_params.get(pmeta.name)
|
|
339
|
+
if value is not None:
|
|
340
|
+
combo.setCurrentText(str(value))
|
|
341
|
+
combo.currentTextChanged.connect(
|
|
342
|
+
lambda val: self._on_param_changed(val, pmeta.name, session, readonly)
|
|
343
|
+
)
|
|
344
|
+
return combo
|
|
345
|
+
|
|
346
|
+
# ------------------------------------------------------------
|
|
347
|
+
def _browse_and_set_relative_file(
|
|
348
|
+
self,
|
|
349
|
+
edit: QLineEdit,
|
|
350
|
+
project_dir: Path | None,
|
|
351
|
+
file_filter: str,
|
|
352
|
+
) -> None:
|
|
353
|
+
if project_dir is None:
|
|
354
|
+
return
|
|
355
|
+
|
|
356
|
+
base_dir = project_dir.expanduser()
|
|
357
|
+
start_dir = base_dir if base_dir.is_dir() else Path.cwd()
|
|
358
|
+
|
|
359
|
+
selected_file, _ = QFileDialog.getOpenFileName(
|
|
360
|
+
edit,
|
|
361
|
+
"Select file",
|
|
362
|
+
str(start_dir),
|
|
363
|
+
file_filter,
|
|
364
|
+
)
|
|
365
|
+
if not selected_file:
|
|
366
|
+
return
|
|
367
|
+
|
|
368
|
+
selected_path = Path(selected_file).resolve()
|
|
369
|
+
base_resolved = base_dir.resolve()
|
|
370
|
+
try:
|
|
371
|
+
relative_path = selected_path.relative_to(base_resolved)
|
|
372
|
+
except ValueError:
|
|
373
|
+
try:
|
|
374
|
+
relative_path = Path(os.path.relpath(str(selected_path), str(base_resolved)))
|
|
375
|
+
except ValueError:
|
|
376
|
+
# Windows cross-drive case (e.g. C: -> D:): keep absolute path.
|
|
377
|
+
relative_path = selected_path
|
|
378
|
+
|
|
379
|
+
edit.setText(relative_path.as_posix())
|
|
380
|
+
|
|
381
|
+
# ------------------------------------------------------------
|
|
382
|
+
def _on_param_changed( self, val: str, name: str, session: BlockDialogSession, readonly: bool,):
|
|
383
|
+
if readonly:
|
|
384
|
+
return
|
|
385
|
+
|
|
386
|
+
if name == "name":
|
|
387
|
+
session.local_params["name"] = val
|
|
388
|
+
else:
|
|
389
|
+
text = str(val).strip()
|
|
390
|
+
try:
|
|
391
|
+
session.local_params[name] = ast.literal_eval(text)
|
|
392
|
+
except (ValueError, SyntaxError):
|
|
393
|
+
session.local_params[name] = text
|
|
394
|
+
self.refresh_form(session)
|
|
395
|
+
|
|
396
|
+
# ------------------------------------------------------------
|
|
397
|
+
def _set_readonly_style(self, widget: QWidget):
|
|
398
|
+
if isinstance(widget, QLineEdit):
|
|
399
|
+
widget.setReadOnly(True)
|
|
400
|
+
widget.setStyleSheet("""
|
|
401
|
+
QLineEdit {
|
|
402
|
+
background-color: #2b2b2b;
|
|
403
|
+
color: #888888;
|
|
404
|
+
border: 1px solid #444444;
|
|
405
|
+
}
|
|
406
|
+
""")
|
|
407
|
+
elif isinstance(widget, QComboBox):
|
|
408
|
+
widget.setEnabled(False)
|
|
@@ -0,0 +1,116 @@
|
|
|
1
|
+
# ******************************************************************************
|
|
2
|
+
# pySimBlocks
|
|
3
|
+
# Copyright (c) 2026 Université de Lille & INRIA
|
|
4
|
+
# ******************************************************************************
|
|
5
|
+
# This program is free software: you can redistribute it and/or modify it
|
|
6
|
+
# under the terms of the GNU Lesser General Public License as published by
|
|
7
|
+
# the Free Software Foundation, either version 3 of the License, or (at your
|
|
8
|
+
# option) any later version.
|
|
9
|
+
#
|
|
10
|
+
# This program is distributed in the hope that it will be useful, but WITHOUT
|
|
11
|
+
# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
|
12
|
+
# FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License
|
|
13
|
+
# for more details.
|
|
14
|
+
#
|
|
15
|
+
# You should have received a copy of the GNU Lesser General Public License
|
|
16
|
+
# along with this program. If not, see <https://www.gnu.org/licenses/>.
|
|
17
|
+
# ******************************************************************************
|
|
18
|
+
# Authors: see Authors.txt
|
|
19
|
+
# ******************************************************************************
|
|
20
|
+
|
|
21
|
+
from typing import Any, Dict
|
|
22
|
+
|
|
23
|
+
from pySimBlocks.gui.blocks.block_meta import BlockMeta
|
|
24
|
+
from pySimBlocks.gui.blocks.parameter_meta import ParameterMeta
|
|
25
|
+
from pySimBlocks.gui.blocks.port_meta import PortMeta
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
class PIDMeta(BlockMeta):
|
|
29
|
+
|
|
30
|
+
def __init__(self):
|
|
31
|
+
self.name = "PID"
|
|
32
|
+
self.category = "controllers"
|
|
33
|
+
self.type = "pid"
|
|
34
|
+
self.summary = "Discrete SISO PID controller."
|
|
35
|
+
self.description = (
|
|
36
|
+
"$$\n"
|
|
37
|
+
"u[k] = K_p e[k] + K_i x_i[k] + K_d \frac{e[k]-e[k-1]}{dt}\n"
|
|
38
|
+
"$$\n"
|
|
39
|
+
)
|
|
40
|
+
|
|
41
|
+
self.parameters = [
|
|
42
|
+
ParameterMeta(
|
|
43
|
+
name="controller",
|
|
44
|
+
type="enum",
|
|
45
|
+
required=True,
|
|
46
|
+
autofill=True,
|
|
47
|
+
default="PID",
|
|
48
|
+
enum=["P", "I", "PI", "PD", "PID"]
|
|
49
|
+
),
|
|
50
|
+
ParameterMeta(
|
|
51
|
+
name="Kp",
|
|
52
|
+
type="scalar",
|
|
53
|
+
autofill=True,
|
|
54
|
+
default=1.0,
|
|
55
|
+
description="Proportionnal gain."
|
|
56
|
+
),
|
|
57
|
+
ParameterMeta(
|
|
58
|
+
name="Ki",
|
|
59
|
+
type="scalar",
|
|
60
|
+
default=1.0,
|
|
61
|
+
description="Integral gain."
|
|
62
|
+
),
|
|
63
|
+
ParameterMeta(
|
|
64
|
+
name="Kd",
|
|
65
|
+
type="scalar",
|
|
66
|
+
default=1.0,
|
|
67
|
+
description="Derivative gain."
|
|
68
|
+
),
|
|
69
|
+
ParameterMeta(
|
|
70
|
+
name="integration_method",
|
|
71
|
+
type="enum",
|
|
72
|
+
default="euler forward",
|
|
73
|
+
enum=["euler forward", "euler backward"]
|
|
74
|
+
),
|
|
75
|
+
ParameterMeta(
|
|
76
|
+
name="u_min",
|
|
77
|
+
type="scalar"
|
|
78
|
+
),
|
|
79
|
+
ParameterMeta(
|
|
80
|
+
name="u_max",
|
|
81
|
+
type="scalar"
|
|
82
|
+
),
|
|
83
|
+
ParameterMeta(
|
|
84
|
+
name="sample_time",
|
|
85
|
+
type="float"
|
|
86
|
+
)
|
|
87
|
+
]
|
|
88
|
+
|
|
89
|
+
self.inputs = [
|
|
90
|
+
PortMeta(
|
|
91
|
+
name="e",
|
|
92
|
+
display_as="e",
|
|
93
|
+
shape=[1, 1],
|
|
94
|
+
description="Error signal."
|
|
95
|
+
)
|
|
96
|
+
]
|
|
97
|
+
|
|
98
|
+
self.outputs = [
|
|
99
|
+
PortMeta(
|
|
100
|
+
name="u",
|
|
101
|
+
display_as="u",
|
|
102
|
+
shape=[1, 1],
|
|
103
|
+
description="Control command."
|
|
104
|
+
)
|
|
105
|
+
]
|
|
106
|
+
|
|
107
|
+
def is_parameter_active(self, param_name: str, instance_params: Dict[str, Any]) -> bool:
|
|
108
|
+
|
|
109
|
+
if param_name == "Kp":
|
|
110
|
+
return instance_params["controller"] in ["P", "PI", "PD", "PID"]
|
|
111
|
+
elif param_name == "Ki":
|
|
112
|
+
return instance_params["controller"] in ["I", "PI", "PID"]
|
|
113
|
+
elif param_name == "Kd":
|
|
114
|
+
return instance_params["controller"] in ["PD", "PID"]
|
|
115
|
+
|
|
116
|
+
return super().is_parameter_active(param_name, instance_params)
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
# ******************************************************************************
|
|
2
|
+
# pySimBlocks
|
|
3
|
+
# Copyright (c) 2026 Université de Lille & INRIA
|
|
4
|
+
# ******************************************************************************
|
|
5
|
+
# This program is free software: you can redistribute it and/or modify it
|
|
6
|
+
# under the terms of the GNU Lesser General Public License as published by
|
|
7
|
+
# the Free Software Foundation, either version 3 of the License, or (at your
|
|
8
|
+
# option) any later version.
|
|
9
|
+
#
|
|
10
|
+
# This program is distributed in the hope that it will be useful, but WITHOUT
|
|
11
|
+
# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
|
12
|
+
# FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License
|
|
13
|
+
# for more details.
|
|
14
|
+
#
|
|
15
|
+
# You should have received a copy of the GNU Lesser General Public License
|
|
16
|
+
# along with this program. If not, see <https://www.gnu.org/licenses/>.
|
|
17
|
+
# ******************************************************************************
|
|
18
|
+
# Authors: see Authors.txt
|
|
19
|
+
# ******************************************************************************
|
|
20
|
+
|
|
21
|
+
from pySimBlocks.gui.blocks.block_meta import BlockMeta
|
|
22
|
+
from pySimBlocks.gui.blocks.parameter_meta import ParameterMeta
|
|
23
|
+
from pySimBlocks.gui.blocks.port_meta import PortMeta
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
class StateFeedBackMeta(BlockMeta):
|
|
27
|
+
def __init__(self):
|
|
28
|
+
self.name = "StateFeedback"
|
|
29
|
+
self.category = "controllers"
|
|
30
|
+
self.type = "state_feedback"
|
|
31
|
+
self.summary = "Discrete-time state-feedback controller."
|
|
32
|
+
self.description = (
|
|
33
|
+
"Implements the static control law:\n"
|
|
34
|
+
"$$\n"
|
|
35
|
+
"u[k] = G r[k] - K x[k]\n"
|
|
36
|
+
"$$\n"
|
|
37
|
+
)
|
|
38
|
+
|
|
39
|
+
self.parameters = [
|
|
40
|
+
ParameterMeta(
|
|
41
|
+
name="K",
|
|
42
|
+
type="matrix",
|
|
43
|
+
required=True,
|
|
44
|
+
description="State feedback gain matrix."
|
|
45
|
+
),
|
|
46
|
+
ParameterMeta(
|
|
47
|
+
name="G",
|
|
48
|
+
type="matrix",
|
|
49
|
+
required=True,
|
|
50
|
+
description="Reference feedforward gain matrix."
|
|
51
|
+
),
|
|
52
|
+
ParameterMeta(
|
|
53
|
+
name="sample_time",
|
|
54
|
+
type="float",
|
|
55
|
+
description="Block execution period."
|
|
56
|
+
),
|
|
57
|
+
]
|
|
58
|
+
|
|
59
|
+
self.inputs = [
|
|
60
|
+
PortMeta(
|
|
61
|
+
name="r",
|
|
62
|
+
display_as="r",
|
|
63
|
+
shape=["p", 1]
|
|
64
|
+
),
|
|
65
|
+
PortMeta(
|
|
66
|
+
name="x",
|
|
67
|
+
display_as="x",
|
|
68
|
+
shape=["n", 1],
|
|
69
|
+
description="State measurement vector."
|
|
70
|
+
)
|
|
71
|
+
]
|
|
72
|
+
|
|
73
|
+
self.outputs = [
|
|
74
|
+
PortMeta(
|
|
75
|
+
name="u",
|
|
76
|
+
display_as="u",
|
|
77
|
+
shape=["m", 1],
|
|
78
|
+
description="Control input vector."
|
|
79
|
+
),
|
|
80
|
+
]
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
# ******************************************************************************
|
|
2
|
+
# pySimBlocks
|
|
3
|
+
# Copyright (c) 2026 Université de Lille & INRIA
|
|
4
|
+
# ******************************************************************************
|
|
5
|
+
# This program is free software: you can redistribute it and/or modify it
|
|
6
|
+
# under the terms of the GNU Lesser General Public License as published by
|
|
7
|
+
# the Free Software Foundation, either version 3 of the License, or (at your
|
|
8
|
+
# option) any later version.
|
|
9
|
+
#
|
|
10
|
+
# This program is distributed in the hope that it will be useful, but WITHOUT
|
|
11
|
+
# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
|
12
|
+
# FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License
|
|
13
|
+
# for more details.
|
|
14
|
+
#
|
|
15
|
+
# You should have received a copy of the GNU Lesser General Public License
|
|
16
|
+
# along with this program. If not, see <https://www.gnu.org/licenses/>.
|
|
17
|
+
# ******************************************************************************
|
|
18
|
+
# Authors: see Authors.txt
|
|
19
|
+
# ******************************************************************************
|
|
20
|
+
|
|
21
|
+
from pySimBlocks.gui.blocks.block_meta import BlockMeta
|
|
22
|
+
from pySimBlocks.gui.blocks.parameter_meta import ParameterMeta
|
|
23
|
+
from pySimBlocks.gui.blocks.port_meta import PortMeta
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
class ExternalInputMeta(BlockMeta):
|
|
27
|
+
|
|
28
|
+
def __init__(self):
|
|
29
|
+
self.name = "ExternalInput"
|
|
30
|
+
self.category = "interfaces"
|
|
31
|
+
self.type = "external_input"
|
|
32
|
+
self.summary = "Pass-through block for external real-time measurements."
|
|
33
|
+
self.description = (
|
|
34
|
+
"Pass-through block intended to inject measurements coming from an\n"
|
|
35
|
+
"external real-time application (camera, robot sensors, network, etc.)\n"
|
|
36
|
+
"into a pySimBlocks model.\n\n"
|
|
37
|
+
"This block does not perform any synchronization. It only enforces the\n"
|
|
38
|
+
"pySimBlocks signal convention (column vectors of shape (n,1))."
|
|
39
|
+
)
|
|
40
|
+
|
|
41
|
+
self.parameters = [
|
|
42
|
+
ParameterMeta(
|
|
43
|
+
name="sample_time",
|
|
44
|
+
type="float"
|
|
45
|
+
)
|
|
46
|
+
]
|
|
47
|
+
|
|
48
|
+
self.inputs = [
|
|
49
|
+
PortMeta(
|
|
50
|
+
name="in",
|
|
51
|
+
display_as="in",
|
|
52
|
+
shape=["n", 1],
|
|
53
|
+
description="External input signal."
|
|
54
|
+
)
|
|
55
|
+
]
|
|
56
|
+
|
|
57
|
+
self.outputs = [
|
|
58
|
+
PortMeta(
|
|
59
|
+
name="out",
|
|
60
|
+
display_as="out",
|
|
61
|
+
shape=["m", 1],
|
|
62
|
+
description="Model data signal."
|
|
63
|
+
)
|
|
64
|
+
]
|