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,162 @@
|
|
|
1
|
+
import time
|
|
2
|
+
from pathlib import Path
|
|
3
|
+
|
|
4
|
+
import numpy as np
|
|
5
|
+
import parameters as prm
|
|
6
|
+
from emioapi import EmioMotors
|
|
7
|
+
|
|
8
|
+
from pySimBlocks.project import load_simulator_from_project
|
|
9
|
+
from pySimBlocks.real_time import RealTimeRunner
|
|
10
|
+
|
|
11
|
+
try:
|
|
12
|
+
BASE_DIR = Path(__file__).parent.resolve()
|
|
13
|
+
except Exception:
|
|
14
|
+
BASE_DIR = Path("")
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
# ------------------------------------------------------------------------------
|
|
18
|
+
# Process
|
|
19
|
+
# ------------------------------------------------------------------------------
|
|
20
|
+
def process_block_diagram(shared_markers_pos, shared_ref_ol, shared_ref_cl,
|
|
21
|
+
shared_control_mode, shared_update,
|
|
22
|
+
event_frame, event_measure):
|
|
23
|
+
"""pySimBlocks runner process.
|
|
24
|
+
"""
|
|
25
|
+
init_angles = np.array([0.7, 0, 0.7, 0])
|
|
26
|
+
motors = setup_motors(init_angles)
|
|
27
|
+
|
|
28
|
+
# init pySimBlocks runner
|
|
29
|
+
runner = setup_block_diagram()
|
|
30
|
+
|
|
31
|
+
# Initialize variables
|
|
32
|
+
measure = np.zeros((prm.nb_markers * 2, 1))
|
|
33
|
+
ref_ol = np.zeros((2, 1))
|
|
34
|
+
ref_cl = np.zeros((2, 1))
|
|
35
|
+
control_mode = np.zeros((1, 1))
|
|
36
|
+
command = np.zeros((2, 1))
|
|
37
|
+
|
|
38
|
+
# Initial time for dt measurement
|
|
39
|
+
t = time.perf_counter()
|
|
40
|
+
dt_init = 1/60
|
|
41
|
+
dt_list = []
|
|
42
|
+
|
|
43
|
+
while True:
|
|
44
|
+
event_frame.wait()
|
|
45
|
+
event_frame.clear()
|
|
46
|
+
|
|
47
|
+
send_motors_command(motors, command, init_angles)
|
|
48
|
+
|
|
49
|
+
event_measure.wait()
|
|
50
|
+
event_measure.clear()
|
|
51
|
+
|
|
52
|
+
if dt_init is not None:
|
|
53
|
+
t = time.perf_counter()
|
|
54
|
+
dt = dt_init
|
|
55
|
+
dt_init = None
|
|
56
|
+
else:
|
|
57
|
+
t2 = time.perf_counter()
|
|
58
|
+
dt = t2 - t
|
|
59
|
+
t = t2
|
|
60
|
+
dt_list.append(dt)
|
|
61
|
+
|
|
62
|
+
# data from camera
|
|
63
|
+
with shared_markers_pos.get_lock():
|
|
64
|
+
measure[:, 0] = shared_markers_pos[:]
|
|
65
|
+
|
|
66
|
+
with shared_update.get_lock():
|
|
67
|
+
if shared_update.value:
|
|
68
|
+
with shared_ref_ol.get_lock():
|
|
69
|
+
ref_ol[:, 0] = shared_ref_ol[:]
|
|
70
|
+
with shared_ref_cl.get_lock():
|
|
71
|
+
ref_cl[:, 0] = shared_ref_cl[:]
|
|
72
|
+
with shared_control_mode.get_lock():
|
|
73
|
+
control_mode[0,0] = shared_control_mode.value
|
|
74
|
+
shared_update.value = False
|
|
75
|
+
|
|
76
|
+
outs = runner.tick(
|
|
77
|
+
inputs={
|
|
78
|
+
"Camera": measure,
|
|
79
|
+
"Ref_ol": ref_ol,
|
|
80
|
+
"Ref_cl": ref_cl,
|
|
81
|
+
"Mode": control_mode
|
|
82
|
+
},
|
|
83
|
+
dt=dt,
|
|
84
|
+
pace=False
|
|
85
|
+
)
|
|
86
|
+
command = outs["Cmd"]
|
|
87
|
+
|
|
88
|
+
|
|
89
|
+
# ------------------------------------------------------------------------------
|
|
90
|
+
# Helpers
|
|
91
|
+
# ------------------------------------------------------------------------------
|
|
92
|
+
def setup_block_diagram():
|
|
93
|
+
sim, _plot_cfg = load_simulator_from_project(BASE_DIR / "project.yaml")
|
|
94
|
+
runner = RealTimeRunner(
|
|
95
|
+
sim,
|
|
96
|
+
input_blocks=["Camera", "Ref_cl", "Ref_ol", "Mode"],
|
|
97
|
+
output_blocks=["Cmd"],
|
|
98
|
+
target_dt=1/60
|
|
99
|
+
)
|
|
100
|
+
runner.initialize()
|
|
101
|
+
return runner
|
|
102
|
+
|
|
103
|
+
|
|
104
|
+
# -------------------------------------------------------
|
|
105
|
+
def setup_motors(init_angles=[0, 0, 0, 0]):
|
|
106
|
+
motors = EmioMotors()
|
|
107
|
+
while not motors.open():
|
|
108
|
+
print("Waiting for motors to open...")
|
|
109
|
+
time.sleep(1)
|
|
110
|
+
print("Motors opened successfully.")
|
|
111
|
+
motors.position_p_gain = [2000, 800, 2000, 800]
|
|
112
|
+
motors.position_i_gain = [0, 0, 0, 0]
|
|
113
|
+
motors.position_d_gain = [50, 0, 50, 0]
|
|
114
|
+
time.sleep(1)
|
|
115
|
+
motors.angles = init_angles
|
|
116
|
+
return motors
|
|
117
|
+
|
|
118
|
+
# -------------------------------------------------------
|
|
119
|
+
def send_motors_command(motors, command, init_angles=np.array([0, 0, 0, 0])):
|
|
120
|
+
command = command.flatten()
|
|
121
|
+
motors.angles = [command[0] + init_angles[0], init_angles[1],
|
|
122
|
+
command[1] + init_angles[2], init_angles[3]]
|
|
123
|
+
|
|
124
|
+
# -------------------------------------------------------
|
|
125
|
+
def get_motors_position(motors, init_angles=np.array([0, 0, 0, 0])):
|
|
126
|
+
motors_pos = np.array(motors.angles) - init_angles
|
|
127
|
+
return motors_pos[[0, 2]].reshape((2, 1))
|
|
128
|
+
|
|
129
|
+
# ------------------------------------------------------------------------------
|
|
130
|
+
# BLOCK DIAGRAM FUNCTIONS
|
|
131
|
+
# ------------------------------------------------------------------------------
|
|
132
|
+
def select_cmd(t, dt, u_cl, u_ol, mode):
|
|
133
|
+
try:
|
|
134
|
+
mode = mode.item()
|
|
135
|
+
except Exception:
|
|
136
|
+
return {"u": np.zeros((2,1))}
|
|
137
|
+
|
|
138
|
+
if mode == 0:
|
|
139
|
+
return {"u": u_ol}
|
|
140
|
+
elif mode == 1:
|
|
141
|
+
return {"u": u_cl}
|
|
142
|
+
else:
|
|
143
|
+
return {"u": np.zeros((2,1))}
|
|
144
|
+
|
|
145
|
+
# -------------------------------------------------------
|
|
146
|
+
def filter_first_order(t, dt, u, u_prev):
|
|
147
|
+
"""Apply a first-order low-pass filter to the signal."""
|
|
148
|
+
if dt <= 0:
|
|
149
|
+
return {"u_filter" : np.zeros((2,1))}
|
|
150
|
+
cutoffFreq = 30. # Cutoff frequency in Hz
|
|
151
|
+
samplingFreq = 1 / dt
|
|
152
|
+
|
|
153
|
+
timeConstant = 1 / (2 * np.pi * cutoffFreq)
|
|
154
|
+
samplingPeriod = 1 / samplingFreq
|
|
155
|
+
alpha = samplingPeriod / (timeConstant + samplingPeriod)
|
|
156
|
+
uf = alpha * u + (1 - alpha) * u_prev
|
|
157
|
+
|
|
158
|
+
return {"u_filter" : uf}
|
|
159
|
+
|
|
160
|
+
|
|
161
|
+
if __name__ == "__main__":
|
|
162
|
+
setup_block_diagram()
|
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
import json
|
|
2
|
+
|
|
3
|
+
import cv2 as cv
|
|
4
|
+
import numpy as np
|
|
5
|
+
import parameters as prm
|
|
6
|
+
from emioapi._depthcamera import DepthCamera
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
# ------------------------------------------------------------------------------
|
|
10
|
+
# Process
|
|
11
|
+
# ------------------------------------------------------------------------------
|
|
12
|
+
def process_camera(shared_markers_pos, shared_start,
|
|
13
|
+
event_frame, event_measure):
|
|
14
|
+
"""Update tracker position."""
|
|
15
|
+
camera = setup_camera()
|
|
16
|
+
|
|
17
|
+
init_pos = np.zeros((2 * prm.nb_markers))
|
|
18
|
+
pos = np.zeros((2 * prm.nb_markers))
|
|
19
|
+
start = False
|
|
20
|
+
|
|
21
|
+
while True:
|
|
22
|
+
# get frame from camera
|
|
23
|
+
ret = camera.get_frame()
|
|
24
|
+
event_frame.set()
|
|
25
|
+
|
|
26
|
+
if not ret:
|
|
27
|
+
raise RuntimeError("Camera frame not received")
|
|
28
|
+
|
|
29
|
+
pos = process_frame(camera, pos)
|
|
30
|
+
|
|
31
|
+
if start:
|
|
32
|
+
pos -= init_pos
|
|
33
|
+
with shared_markers_pos.get_lock():
|
|
34
|
+
shared_markers_pos[:] = pos
|
|
35
|
+
event_measure.set()
|
|
36
|
+
|
|
37
|
+
else:
|
|
38
|
+
init_pos = pos
|
|
39
|
+
with shared_start.get_lock():
|
|
40
|
+
start = shared_start.value
|
|
41
|
+
|
|
42
|
+
k = cv.waitKey(1)
|
|
43
|
+
if k == ord('q'):
|
|
44
|
+
camera.quit()
|
|
45
|
+
break
|
|
46
|
+
|
|
47
|
+
|
|
48
|
+
# ------------------------------------------------------------------------------
|
|
49
|
+
# Helpers
|
|
50
|
+
# ------------------------------------------------------------------------------
|
|
51
|
+
def setup_camera():
|
|
52
|
+
json_path = prm.data_path / "cameraparameter.json"
|
|
53
|
+
with json_path.open('r') as fp:
|
|
54
|
+
json_parameters = json.load(fp)
|
|
55
|
+
camera = DepthCamera(
|
|
56
|
+
show_video_feed=True,
|
|
57
|
+
tracking=True,
|
|
58
|
+
compute_point_cloud=False,
|
|
59
|
+
parameter=json_parameters)
|
|
60
|
+
camera.set_fps(60)
|
|
61
|
+
camera.set_depth_min(0)
|
|
62
|
+
camera.set_depth_max(1000)
|
|
63
|
+
camera.open()
|
|
64
|
+
return camera
|
|
65
|
+
|
|
66
|
+
# -------------------------------------------------------
|
|
67
|
+
def pixel_to_mm(points, depth):
|
|
68
|
+
ppx, ppy = 319.475, 240.962
|
|
69
|
+
fx, fy = 382.605, 382.605
|
|
70
|
+
points[:, 0] = ((points[:, 0] - ppx) / fx) * depth
|
|
71
|
+
points[:, 1] = ((points[:, 1] - ppy) / fy) * depth
|
|
72
|
+
points = np.column_stack((points[:, 2], -points[:, 1], points[:, 0]))
|
|
73
|
+
return points.copy()
|
|
74
|
+
|
|
75
|
+
# -------------------------------------------------------
|
|
76
|
+
def camera_to_sofa_order(points):
|
|
77
|
+
i_ymax = np.argmax(points[:, 1])
|
|
78
|
+
i_rest = [i for i in range(prm.nb_markers) if i != i_ymax]
|
|
79
|
+
i_sorted_z = sorted(i_rest, key=lambda i: points[i, 2])
|
|
80
|
+
new_order = [i_sorted_z[1], i_sorted_z[0], i_ymax]
|
|
81
|
+
return points[new_order].flatten()
|
|
82
|
+
|
|
83
|
+
# -------------------------------------------------------
|
|
84
|
+
def process_frame(camera, last_pos):
|
|
85
|
+
indices = [1, 2, 4, 5, 7, 8]
|
|
86
|
+
camera.process_frame()
|
|
87
|
+
if len(camera.trackers_pos) == prm.nb_markers:
|
|
88
|
+
pos = np.array(camera.trackers_camera).reshape(prm.nb_markers, 3).copy()
|
|
89
|
+
pos = pos.astype(np.float64)
|
|
90
|
+
pos = pixel_to_mm(pos, 249)
|
|
91
|
+
markers_pos = camera_to_sofa_order(pos)
|
|
92
|
+
return markers_pos[indices]
|
|
93
|
+
return last_pos
|
|
@@ -0,0 +1,231 @@
|
|
|
1
|
+
import tkinter as tk
|
|
2
|
+
|
|
3
|
+
import numpy as np
|
|
4
|
+
import parameters as prm
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
# -------------------------------------------------------------------------------
|
|
8
|
+
# Process
|
|
9
|
+
# -------------------------------------------------------------------------------
|
|
10
|
+
def process_gui(shared_ref_ol, shared_ref_cl, shared_start,
|
|
11
|
+
shared_control_mode, shared_update):
|
|
12
|
+
root = tk.Tk()
|
|
13
|
+
app = EmioRealTimeGUI(root, shared_ref_ol, shared_ref_cl,
|
|
14
|
+
shared_start, shared_control_mode, shared_update)
|
|
15
|
+
root.protocol("WM_DELETE_WINDOW", app.close_app)
|
|
16
|
+
root.mainloop()
|
|
17
|
+
|
|
18
|
+
# -------------------------------------------------------------------------------
|
|
19
|
+
# GUI Class
|
|
20
|
+
# -------------------------------------------------------------------------------
|
|
21
|
+
class EmioRealTimeGUI:
|
|
22
|
+
MOTOR_SCALE = np.pi / 2 / 100 # slider [-100..100] -> radians
|
|
23
|
+
|
|
24
|
+
def __init__(self, root, shared_ref_ol, shared_ref_cl,
|
|
25
|
+
shared_start, shared_control_mode, shared_update):
|
|
26
|
+
self.root = root
|
|
27
|
+
self.root.title('Emio Real Time Control')
|
|
28
|
+
self.root.geometry("500x350")
|
|
29
|
+
|
|
30
|
+
# ------- Variables -------
|
|
31
|
+
# shared
|
|
32
|
+
self.shared_ref_ol = shared_ref_ol
|
|
33
|
+
self.shared_ref_cl = shared_ref_cl
|
|
34
|
+
self.shared_start = shared_start
|
|
35
|
+
self.shared_control_mode = shared_control_mode
|
|
36
|
+
self.shared_update = shared_update
|
|
37
|
+
|
|
38
|
+
# local
|
|
39
|
+
self.start = False
|
|
40
|
+
self.active = True
|
|
41
|
+
self.control_mode = prm.ControlMode.OPEN_LOOP
|
|
42
|
+
|
|
43
|
+
# ------- Information Frame -------
|
|
44
|
+
info_frame = tk.Frame(root)
|
|
45
|
+
info_frame.pack()
|
|
46
|
+
info = ["Start", "Active", "Control"]
|
|
47
|
+
info_values = ["False", "True", "Open Loop"]
|
|
48
|
+
self.label_info = {}
|
|
49
|
+
for i, item in enumerate(info):
|
|
50
|
+
self.label_info[item] = tk.Label(
|
|
51
|
+
info_frame,
|
|
52
|
+
text=f"{item}: {info_values[i]}", font=("Arial", 12))
|
|
53
|
+
self.label_info[item].pack(side="left", padx=5, pady=5)
|
|
54
|
+
|
|
55
|
+
# ------- Control Frame -------
|
|
56
|
+
self.build_sliders()
|
|
57
|
+
self.build_buttons()
|
|
58
|
+
|
|
59
|
+
|
|
60
|
+
# --------------------------------------------------------------------------
|
|
61
|
+
# Build methods
|
|
62
|
+
# --------------------------------------------------------------------------
|
|
63
|
+
def build_sliders(self):
|
|
64
|
+
self.label_motors = []
|
|
65
|
+
self.sliders_motor = []
|
|
66
|
+
motor_label_frame = tk.Frame(self.root)
|
|
67
|
+
motor_label_frame.pack()
|
|
68
|
+
motor_slider_frame = tk.Frame(self.root)
|
|
69
|
+
motor_slider_frame.pack()
|
|
70
|
+
|
|
71
|
+
self.label_ref = []
|
|
72
|
+
self.sliders_ref = []
|
|
73
|
+
ref_label_frame = tk.Frame(self.root)
|
|
74
|
+
ref_label_frame.pack()
|
|
75
|
+
ref_slider_frame = tk.Frame(self.root)
|
|
76
|
+
ref_slider_frame.pack()
|
|
77
|
+
|
|
78
|
+
for i in range(2):
|
|
79
|
+
self.label_motors.append(
|
|
80
|
+
self._build_label(motor_label_frame, text=f"Motor {i + 1}: 0.00 (rad)")
|
|
81
|
+
)
|
|
82
|
+
self.sliders_motor.append(
|
|
83
|
+
self._build_slider(
|
|
84
|
+
motor_slider_frame,
|
|
85
|
+
command=lambda val, idx=i: self._motor_action(val, idx),
|
|
86
|
+
)
|
|
87
|
+
)
|
|
88
|
+
|
|
89
|
+
self.label_ref.append(
|
|
90
|
+
self._build_label(ref_label_frame, text=f"Ref {i + 1}: 0.00 (mm)")
|
|
91
|
+
)
|
|
92
|
+
self.sliders_ref.append(
|
|
93
|
+
self._build_slider(
|
|
94
|
+
ref_slider_frame,
|
|
95
|
+
command=lambda val, idx=i: self._ref_action(val, idx),
|
|
96
|
+
)
|
|
97
|
+
)
|
|
98
|
+
|
|
99
|
+
control_label_frame = tk.Frame(self.root)
|
|
100
|
+
control_label_frame.pack()
|
|
101
|
+
control_slider_frame = tk.Frame(self.root)
|
|
102
|
+
control_slider_frame.pack()
|
|
103
|
+
|
|
104
|
+
self.label_control = self._build_label(
|
|
105
|
+
control_label_frame, text="Desired Control Mode: Open Loop"
|
|
106
|
+
|
|
107
|
+
)
|
|
108
|
+
self.slider_control = self._build_slider(
|
|
109
|
+
control_slider_frame,
|
|
110
|
+
command=self.slider_control_action,
|
|
111
|
+
from_=0, to=1
|
|
112
|
+
)
|
|
113
|
+
|
|
114
|
+
# ----------------------------------------------------------
|
|
115
|
+
def build_buttons(self):
|
|
116
|
+
button_frame = tk.Frame(self.root)
|
|
117
|
+
button_frame.pack(pady=10)
|
|
118
|
+
|
|
119
|
+
buttons = [
|
|
120
|
+
("Start", self.start_action),
|
|
121
|
+
("Switch Control", self.control_mode_action),
|
|
122
|
+
("Active", self.active_action),
|
|
123
|
+
]
|
|
124
|
+
for name, action in buttons:
|
|
125
|
+
tk.Button(button_frame, text=name, command=action).pack(side="left", padx=3)
|
|
126
|
+
|
|
127
|
+
|
|
128
|
+
# --------------------------------------------------------------------------
|
|
129
|
+
# Slider actions
|
|
130
|
+
# --------------------------------------------------------------------------
|
|
131
|
+
def _motor_action(self, val, index: int):
|
|
132
|
+
if not self.start:
|
|
133
|
+
self.sliders_motor[index].set(0)
|
|
134
|
+
return
|
|
135
|
+
cmd = float(val) * self.MOTOR_SCALE # radians
|
|
136
|
+
self.label_motors[index].config(text=f"Motor {index + 1}: {cmd:.2f} (rad)")
|
|
137
|
+
if self.active:
|
|
138
|
+
mode = prm.ControlMode(0)
|
|
139
|
+
self._push_current_commands(mode)
|
|
140
|
+
|
|
141
|
+
# ----------------------------------------------------------
|
|
142
|
+
def _ref_action(self, val, index: int):
|
|
143
|
+
if not self.start:
|
|
144
|
+
self.sliders_ref[index].set(0)
|
|
145
|
+
return
|
|
146
|
+
cmd = float(val) # mm
|
|
147
|
+
self.label_ref[index].config(text=f"Ref {index + 1}: {cmd:.2f} (mm)")
|
|
148
|
+
if self.active:
|
|
149
|
+
mode = prm.ControlMode(1)
|
|
150
|
+
self._push_current_commands(mode)
|
|
151
|
+
|
|
152
|
+
# ----------------------------------------------------------
|
|
153
|
+
def slider_control_action(self, val):
|
|
154
|
+
if not self.start:
|
|
155
|
+
self.slider_control.set(0)
|
|
156
|
+
return
|
|
157
|
+
self.control_mode = prm.ControlMode(int(val))
|
|
158
|
+
self.label_control.config(text=f"Desired Control Mode: {self.control_mode.label}")
|
|
159
|
+
|
|
160
|
+
# --------------------------------------------------------------------------
|
|
161
|
+
# Button actions
|
|
162
|
+
# --------------------------------------------------------------------------
|
|
163
|
+
def start_action(self):
|
|
164
|
+
if not self.start:
|
|
165
|
+
self.start = True
|
|
166
|
+
self.label_info["Start"].config(text="Start: True")
|
|
167
|
+
with self.shared_start.get_lock():
|
|
168
|
+
self.shared_start.value = self.start
|
|
169
|
+
mode = prm.ControlMode(0)
|
|
170
|
+
self._push_current_commands(mode)
|
|
171
|
+
|
|
172
|
+
# ----------------------------------------------------------
|
|
173
|
+
def active_action(self):
|
|
174
|
+
if not self.start:
|
|
175
|
+
return
|
|
176
|
+
self.active = not self.active
|
|
177
|
+
self.label_info["Active"].config(text=f"Active: {self.active}")
|
|
178
|
+
|
|
179
|
+
if self.active:
|
|
180
|
+
with self.shared_control_mode.get_lock():
|
|
181
|
+
mode = prm.ControlMode(int(self.shared_control_mode.value))
|
|
182
|
+
self._push_current_commands(mode)
|
|
183
|
+
|
|
184
|
+
# ----------------------------------------------------------
|
|
185
|
+
def control_mode_action(self):
|
|
186
|
+
if not self.start:
|
|
187
|
+
return
|
|
188
|
+
with self.shared_control_mode.get_lock():
|
|
189
|
+
self.shared_control_mode.value = int(self.control_mode)
|
|
190
|
+
with self.shared_update.get_lock():
|
|
191
|
+
self.shared_update.value = True
|
|
192
|
+
|
|
193
|
+
if self.control_mode == prm.ControlMode.OPEN_LOOP:
|
|
194
|
+
self.label_info["Control"].config(text="Control: Open Loop")
|
|
195
|
+
else:
|
|
196
|
+
self.label_info["Control"].config(text=f"Control: {self.control_mode.short}")
|
|
197
|
+
|
|
198
|
+
# --------------------------------------------------------------------------
|
|
199
|
+
# Other methods
|
|
200
|
+
# --------------------------------------------------------------------------
|
|
201
|
+
def close_app(self):
|
|
202
|
+
self.root.destroy()
|
|
203
|
+
|
|
204
|
+
# ----------------------------------------------------------
|
|
205
|
+
def _build_slider(self, frame, command, from_=-100, to=100):
|
|
206
|
+
slider = tk.Scale(frame, from_=from_, to=to, orient="horizontal", command=command)
|
|
207
|
+
slider.set(0)
|
|
208
|
+
slider.pack(side="left", padx=3, pady=5)
|
|
209
|
+
return slider
|
|
210
|
+
|
|
211
|
+
# ----------------------------------------------------------
|
|
212
|
+
def _build_label(self, frame, text):
|
|
213
|
+
label = tk.Label(frame, text=text)
|
|
214
|
+
label.pack(side="left", padx=3, pady=5)
|
|
215
|
+
return label
|
|
216
|
+
|
|
217
|
+
# ----------------------------------------------------------
|
|
218
|
+
def _push_current_commands(self, mode):
|
|
219
|
+
if mode == prm.ControlMode.OPEN_LOOP:
|
|
220
|
+
pos = [float(self.sliders_motor[i].get()) for i in range(2)]
|
|
221
|
+
with self.shared_ref_ol.get_lock():
|
|
222
|
+
self.shared_ref_ol[0] = pos[0] * self.MOTOR_SCALE
|
|
223
|
+
self.shared_ref_ol[1] = pos[1] * self.MOTOR_SCALE
|
|
224
|
+
else:
|
|
225
|
+
ref = [float(self.sliders_ref[i].get()) for i in range(2)]
|
|
226
|
+
with self.shared_ref_cl.get_lock():
|
|
227
|
+
self.shared_ref_cl[0] = ref[0]
|
|
228
|
+
self.shared_ref_cl[1] = ref[1]
|
|
229
|
+
|
|
230
|
+
with self.shared_update.get_lock():
|
|
231
|
+
self.shared_update.value = True
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
import numpy as np
|
|
2
|
+
import cvxpy as cp
|
|
3
|
+
import control as ct
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
def get_lmi_controller(A, B, C):
|
|
7
|
+
"""Compute the LMI controller.
|
|
8
|
+
"""
|
|
9
|
+
nx = A.shape[0]
|
|
10
|
+
nu = B.shape[1]
|
|
11
|
+
ny = C.shape[0]
|
|
12
|
+
|
|
13
|
+
# # Define the decay factor and matrices for the LQR problem
|
|
14
|
+
decay = 0.99
|
|
15
|
+
Qy = 1e0 * np.diag(ny//2 * [1, 0.5])
|
|
16
|
+
Q = C.T @ Qy @ C
|
|
17
|
+
eig = max(abs(np.linalg.eigvals(Q)))
|
|
18
|
+
Q = Q + 1e-3*eig * np.eye(nx)
|
|
19
|
+
R = 1e3 * np.eye(nu)
|
|
20
|
+
Q_inv = np.linalg.inv(Q)
|
|
21
|
+
R_inv = np.linalg.inv(R)
|
|
22
|
+
|
|
23
|
+
# Define the optimization variables
|
|
24
|
+
X = cp.Variable((nx, nx), symmetric=True)
|
|
25
|
+
Y = cp.Variable((nu, nx))
|
|
26
|
+
delta = cp.Variable(nonneg=True)
|
|
27
|
+
|
|
28
|
+
gamma_decay = cp.bmat([
|
|
29
|
+
[-X, 1/decay * (A @ X - B @ Y).T],
|
|
30
|
+
[1/decay * (A @ X - B @ Y), -X]
|
|
31
|
+
])
|
|
32
|
+
gamma_lqr = cp.bmat([
|
|
33
|
+
[-X, (A @ X - B @ Y).T, X, Y.T],
|
|
34
|
+
[(A @ X - B @ Y), -X, np.zeros((nx, nx)), np.zeros((nx, nu))],
|
|
35
|
+
[X, np.zeros((nx, nx)), -Q_inv, np.zeros((nx, nu))],
|
|
36
|
+
[Y, np.zeros((nu, nx)), np.zeros((nu, nx)), -R_inv]
|
|
37
|
+
])
|
|
38
|
+
|
|
39
|
+
matrix = [-X]
|
|
40
|
+
matrix.append(gamma_decay)
|
|
41
|
+
matrix.append(gamma_lqr)
|
|
42
|
+
matrix.append(cp.bmat([[-delta * np.eye(nx), np.eye(nx)], [np.eye(nx), -X]]))
|
|
43
|
+
constraints = [mat << -1e-5 * np.eye(mat.shape[0]) for mat in matrix]
|
|
44
|
+
|
|
45
|
+
# obj = cp.Minimize(0)
|
|
46
|
+
obj = cp.Minimize(delta)
|
|
47
|
+
pb = cp.Problem(obj, constraints)
|
|
48
|
+
pb.solve(solver="MOSEK")
|
|
49
|
+
print(f"Problem status: {pb.status}")
|
|
50
|
+
if pb.status != cp.OPTIMAL:
|
|
51
|
+
raise Exception(f"Control problem not solved. Status: {pb.status}")
|
|
52
|
+
|
|
53
|
+
P = np.linalg.inv(X.value)
|
|
54
|
+
K = Y.value @ P
|
|
55
|
+
# Feedforward gain
|
|
56
|
+
C_ctr = C[[0, 1]]
|
|
57
|
+
G = np.linalg.inv(C_ctr @ np.linalg.inv(np.eye(nx) - A + B @ K) @ B)
|
|
58
|
+
|
|
59
|
+
return K, G
|
|
60
|
+
|
|
61
|
+
def get_lmi_observer(A, C):
|
|
62
|
+
"""Compute the LMI observer.
|
|
63
|
+
"""
|
|
64
|
+
nx = A.shape[0]
|
|
65
|
+
ny = C.shape[0]
|
|
66
|
+
Q = 8e2 * np.eye(nx) # State error cost
|
|
67
|
+
R = 1e1 * np.eye(ny) # noise cost
|
|
68
|
+
L = ct.dlqr(A.T, C.T, Q, R)[0].T
|
|
69
|
+
return L
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
import time
|
|
2
|
+
import multiprocessing
|
|
3
|
+
|
|
4
|
+
import parameters as prm
|
|
5
|
+
from block_diagram import process_block_diagram
|
|
6
|
+
from camera import process_camera
|
|
7
|
+
from gui_emio import process_gui
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
def main():
|
|
11
|
+
|
|
12
|
+
# shared variables
|
|
13
|
+
shared_markers_pos = multiprocessing.Array("d", 2 * prm.nb_markers * [0.])
|
|
14
|
+
shared_ref_ol = multiprocessing.Array("d", 2 * [0.0])
|
|
15
|
+
shared_ref_cl = multiprocessing.Array("d", 2 * [0.0])
|
|
16
|
+
|
|
17
|
+
# shared bool
|
|
18
|
+
shared_start = multiprocessing.Value("b", False)
|
|
19
|
+
shared_control_mode = multiprocessing.Value("i", 0)
|
|
20
|
+
shared_update = multiprocessing.Value("b", False)
|
|
21
|
+
|
|
22
|
+
# shared event
|
|
23
|
+
event_frame = multiprocessing.Event()
|
|
24
|
+
event_measure = multiprocessing.Event()
|
|
25
|
+
|
|
26
|
+
# Create processes
|
|
27
|
+
p1 = multiprocessing.Process(target=process_camera, args=(
|
|
28
|
+
shared_markers_pos, shared_start,
|
|
29
|
+
event_frame, event_measure))
|
|
30
|
+
|
|
31
|
+
p2 = multiprocessing.Process(target=process_gui, args=(
|
|
32
|
+
shared_ref_ol, shared_ref_cl,
|
|
33
|
+
shared_start, shared_control_mode, shared_update))
|
|
34
|
+
|
|
35
|
+
p3 = multiprocessing.Process(target=process_block_diagram, args=(
|
|
36
|
+
shared_markers_pos, shared_ref_ol, shared_ref_cl,
|
|
37
|
+
shared_control_mode, shared_update, event_frame, event_measure))
|
|
38
|
+
|
|
39
|
+
p1.start()
|
|
40
|
+
p2.start()
|
|
41
|
+
p3.start()
|
|
42
|
+
|
|
43
|
+
try:
|
|
44
|
+
while True:
|
|
45
|
+
time.sleep(5)
|
|
46
|
+
except KeyboardInterrupt:
|
|
47
|
+
p1.terminate()
|
|
48
|
+
p2.terminate()
|
|
49
|
+
p3.terminate()
|
|
50
|
+
|
|
51
|
+
p1.join()
|
|
52
|
+
p2.join()
|
|
53
|
+
p3.join()
|
|
54
|
+
|
|
55
|
+
|
|
56
|
+
if __name__ == "__main__":
|
|
57
|
+
main()
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
from enum import IntEnum
|
|
2
|
+
from pathlib import Path
|
|
3
|
+
|
|
4
|
+
import numpy as np
|
|
5
|
+
|
|
6
|
+
from lmi import get_lmi_controller, get_lmi_observer
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
class ControlMode(IntEnum):
|
|
10
|
+
OPEN_LOOP = 0
|
|
11
|
+
STATE_FEEDBACK = 1
|
|
12
|
+
|
|
13
|
+
@property
|
|
14
|
+
def short(self) -> str:
|
|
15
|
+
return {0: "OL", 1: "SF"}[int(self)]
|
|
16
|
+
|
|
17
|
+
@property
|
|
18
|
+
def label(self) -> str:
|
|
19
|
+
return {
|
|
20
|
+
0: "Open Loop",
|
|
21
|
+
1: "State Feedback",
|
|
22
|
+
}[int(self)]
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
data_path = Path(__file__).parent / "data"
|
|
26
|
+
|
|
27
|
+
nb_markers = 3
|
|
28
|
+
|
|
29
|
+
# ctr_data = np.load(data_path / "controller_order5_linear.npz")
|
|
30
|
+
# K = ctr_data["feedback_gain"]
|
|
31
|
+
# G = ctr_data["feedforward_gain"]
|
|
32
|
+
|
|
33
|
+
obs_data = np.load(data_path / "observer_order5_linear.npz")
|
|
34
|
+
# L = obs_data["observer_gain"]
|
|
35
|
+
A = obs_data["state_matrix"]
|
|
36
|
+
B = obs_data["input_matrix"]
|
|
37
|
+
C = obs_data["output_matrix"]
|
|
38
|
+
|
|
39
|
+
x0 = np.zeros((A.shape[0], 1))
|
|
40
|
+
Abar = np.block([
|
|
41
|
+
[A, B],
|
|
42
|
+
[np.zeros((B.shape[1], A.shape[1] + B.shape[1]))]
|
|
43
|
+
])
|
|
44
|
+
Bbar = np.block([
|
|
45
|
+
[np.zeros((A.shape[0], B.shape[1]))],
|
|
46
|
+
[np.eye(B.shape[1])]
|
|
47
|
+
])
|
|
48
|
+
Cbar = np.block([
|
|
49
|
+
[C, np.zeros((C.shape[0], B.shape[1]))]
|
|
50
|
+
])
|
|
51
|
+
|
|
52
|
+
K, G = get_lmi_controller(Abar, Bbar, Cbar)
|
|
53
|
+
L = get_lmi_observer(A, C)
|
|
54
|
+
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
from numpy import array, dot
|
|
2
|
+
|
|
3
|
+
M = array([[1., 2., 0.], [-8., 3., 2.], [0., 1., 1.]])
|
|
4
|
+
P = M.T @ M
|
|
5
|
+
q = array([[3., 2., 3.]]) @ M
|
|
6
|
+
#
|
|
7
|
+
# from qpsolvers import solve_qp
|
|
8
|
+
# x = solve_qp(P, q, None, None, None, None, None, solver="clarabel")
|
|
9
|
+
# print(f"QP solution: x = {x}")
|
|
10
|
+
#
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
from pathlib import Path
|
|
2
|
+
from pySimBlocks.project import load_simulator_from_project
|
|
3
|
+
from pySimBlocks.project.plot_from_config import plot_from_config
|
|
4
|
+
|
|
5
|
+
try:
|
|
6
|
+
BASE_DIR = Path(__file__).parent.resolve()
|
|
7
|
+
except Exception:
|
|
8
|
+
BASE_DIR = Path("")
|
|
9
|
+
|
|
10
|
+
sim, plot_cfg = load_simulator_from_project(BASE_DIR / "project.yaml")
|
|
11
|
+
|
|
12
|
+
logs = sim.run()
|
|
13
|
+
if True and plot_cfg is not None:
|
|
14
|
+
plot_from_config(logs, plot_cfg)
|