Joint-python-library 0.0.3__tar.gz → 0.0.5__tar.gz

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.
Files changed (21) hide show
  1. {joint_python_library-0.0.3 → joint_python_library-0.0.5}/Joint_python_library.egg-info/PKG-INFO +2 -2
  2. {joint_python_library-0.0.3 → joint_python_library-0.0.5}/Joint_python_library.egg-info/SOURCES.txt +7 -1
  3. joint_python_library-0.0.5/Joint_python_library.egg-info/entry_points.txt +2 -0
  4. {joint_python_library-0.0.3 → joint_python_library-0.0.5}/Joint_python_library.egg-info/top_level.txt +1 -0
  5. {joint_python_library-0.0.3 → joint_python_library-0.0.5}/PKG-INFO +2 -2
  6. {joint_python_library-0.0.3 → joint_python_library-0.0.5}/acrome_joint/Slave_Device.py +1 -1
  7. joint_python_library-0.0.5/acrome_joint/__init__.py +0 -0
  8. {joint_python_library-0.0.3 → joint_python_library-0.0.5}/acrome_joint/serial_port.py +31 -2
  9. joint_python_library-0.0.5/gui/__init__.py +0 -0
  10. joint_python_library-0.0.5/gui/joint_device.py +6 -0
  11. joint_python_library-0.0.5/gui/limits.py +17 -0
  12. joint_python_library-0.0.5/gui/main.py +858 -0
  13. joint_python_library-0.0.5/gui/ramp_trajectory.py +96 -0
  14. {joint_python_library-0.0.3 → joint_python_library-0.0.5}/setup.py +7 -2
  15. joint_python_library-0.0.3/acrome_joint/__init__.py +0 -1
  16. {joint_python_library-0.0.3 → joint_python_library-0.0.5}/Joint_python_library.egg-info/dependency_links.txt +0 -0
  17. {joint_python_library-0.0.3 → joint_python_library-0.0.5}/Joint_python_library.egg-info/requires.txt +0 -0
  18. {joint_python_library-0.0.3 → joint_python_library-0.0.5}/LICENSE +0 -0
  19. {joint_python_library-0.0.3 → joint_python_library-0.0.5}/README.md +0 -0
  20. {joint_python_library-0.0.3 → joint_python_library-0.0.5}/acrome_joint/joint.py +0 -0
  21. {joint_python_library-0.0.3 → joint_python_library-0.0.5}/setup.cfg +0 -0
@@ -1,7 +1,7 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: Joint-python-library
3
- Version: 0.0.3
4
- Summary: Python library for interfacing with Acrome Robotic Arm Joint BLDC Motor Controllers
3
+ Version: 0.0.5
4
+ Summary: Python library for interfacing with Acrome Robotic Arm Joint BLDC Motor Controllers. This Python library provides an easy-to-use interface for communication and control of BLDC motor controllers used in Acrome robotic arm joints. It is designed to simplify the integration of Acrome’s robotic joint actuators into custom applications, allowing developers and researchers to focus on building advanced robotic systems without dealing with low-level communication details.
5
5
  Home-page: https://github.com/Acrome-Smart-Motion-Devices/python-library-new
6
6
  Author: BeratComputer
7
7
  Author-email: beratdogan@acrome.net
@@ -4,9 +4,15 @@ setup.py
4
4
  Joint_python_library.egg-info/PKG-INFO
5
5
  Joint_python_library.egg-info/SOURCES.txt
6
6
  Joint_python_library.egg-info/dependency_links.txt
7
+ Joint_python_library.egg-info/entry_points.txt
7
8
  Joint_python_library.egg-info/requires.txt
8
9
  Joint_python_library.egg-info/top_level.txt
9
10
  acrome_joint/Slave_Device.py
10
11
  acrome_joint/__init__.py
11
12
  acrome_joint/joint.py
12
- acrome_joint/serial_port.py
13
+ acrome_joint/serial_port.py
14
+ gui/__init__.py
15
+ gui/joint_device.py
16
+ gui/limits.py
17
+ gui/main.py
18
+ gui/ramp_trajectory.py
@@ -0,0 +1,2 @@
1
+ [console_scripts]
2
+ joint_GUI = gui.main:main
@@ -1,7 +1,7 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: Joint-python-library
3
- Version: 0.0.3
4
- Summary: Python library for interfacing with Acrome Robotic Arm Joint BLDC Motor Controllers
3
+ Version: 0.0.5
4
+ Summary: Python library for interfacing with Acrome Robotic Arm Joint BLDC Motor Controllers. This Python library provides an easy-to-use interface for communication and control of BLDC motor controllers used in Acrome robotic arm joints. It is designed to simplify the integration of Acrome’s robotic joint actuators into custom applications, allowing developers and researchers to focus on building advanced robotic systems without dealing with low-level communication details.
5
5
  Home-page: https://github.com/Acrome-Smart-Motion-Devices/python-library-new
6
6
  Author: BeratComputer
7
7
  Author-email: beratdogan@acrome.net
@@ -3,7 +3,7 @@ import struct
3
3
  from crccheck.crc import Crc32Mpeg2 as CRC32
4
4
  import time
5
5
  import enum
6
- from acrome_joint.serial_port import SerialPort
6
+ from acrome_joint.serial_port import *
7
7
  '''
8
8
  COMMUNICATION PACKAGE =>
9
9
  HEADER, ID, DEVICE_FAMILY, PACKAGE_SIZE, COMMAND, STATUS, .............. DATA ................. , CRC
File without changes
@@ -1,4 +1,35 @@
1
1
  import serial
2
+ import serial.tools.list_ports
3
+ import platform
4
+
5
+ def whichOS():
6
+ return platform.system()
7
+
8
+
9
+ def USB_serial_port(keyword_for_WINDOWS:str='USB Serial Port', keyword_for_LINUX:str='/dev/ttyUSB'):
10
+
11
+ if whichOS() == "Windows":
12
+ ports = list(serial.tools.list_ports.comports())
13
+ if ports:
14
+ for port, desc, hwid in sorted(ports):
15
+ #print(f"{port}: {desc} [{hwid}]")
16
+ #print(type(port))
17
+ if keyword_for_WINDOWS in desc:
18
+ return port
19
+ else:
20
+ return None
21
+
22
+ if whichOS() == "Linux":
23
+ ports = list(serial.tools.list_ports.comports())
24
+ if ports:
25
+ for port, desc, hwid in sorted(ports):
26
+ #print(f"{port}: {desc} [{hwid}]")
27
+ #print(type(port))
28
+ if keyword_for_LINUX in port:
29
+ return port
30
+ else:
31
+ return None
32
+
2
33
 
3
34
  class SerialPort:
4
35
  def __init__(self, port_name, baudrate=921600, timeout=0.1, isTest:bool=False):
@@ -55,5 +86,3 @@ class SerialPort:
55
86
  else:
56
87
  self._ph.timeout = timeout
57
88
 
58
-
59
-
File without changes
@@ -0,0 +1,6 @@
1
+ from acrome_joint.joint import*
2
+
3
+ keyword_for_usb = "STMicroelectronics"
4
+ port = SerialPort(USB_serial_port(keyword_for_usb), baudrate=921600, timeout=0.01)
5
+
6
+ Device = Joint(0, port)
@@ -0,0 +1,17 @@
1
+
2
+ # SPEED = RPM, ACC= RPM/s
3
+ ENCODER_CPR = 4096
4
+
5
+ MOTOR_VEL_MAX = 150.0 # in rpm
6
+ MOTOR_ACC_MAX = 500.0
7
+
8
+
9
+ MOTOR_VEL_MAX_IN_ENC_TYPE = MOTOR_VEL_MAX * 68.2666
10
+ MOTOR_ACC_MAX_IN_ENC_TYPE = MOTOR_ACC_MAX * 68.2666
11
+
12
+ def rpm_to_tick_per_second(rpm:float):
13
+ return rpm*ENCODER_CPR/60
14
+
15
+ def tick_per_second_to_rpm(tick_per_second:float):
16
+ return tick_per_second*60/ENCODER_CPR
17
+
@@ -0,0 +1,858 @@
1
+ # app.py
2
+ # pip install PySide6 pyqtgraph numpy
3
+
4
+ from PySide6 import QtCore, QtGui, QtWidgets
5
+ import pyqtgraph as pg
6
+ import numpy as np
7
+ import time
8
+ from typing import Optional
9
+ from gui.joint_device import *
10
+ from gui.ramp_trajectory import * # <-- S-Curve Ramp sınıfını kullanacağız
11
+ from gui.limits import *
12
+
13
+ NUM_FMT = "{:.3f}" # tabloda göstereceğimiz sayısal format
14
+
15
+
16
+ # =========================
17
+ # OPERATION için parametre grupları
18
+ # =========================
19
+ OP_PARAM_GROUPS = {
20
+ 1: [ # Plot 1 yanındaki tablo (8 adet)
21
+ "current_Id",
22
+ "current_Iq",
23
+ "currentId_loop_kp",
24
+ "currentId_loop_ki",
25
+ "currentId_loop_kd",
26
+ "currentIq_loop_kp",
27
+ "currentIq_loop_ki",
28
+ "currentIq_loop_kd",
29
+ ],
30
+ 2: [ # Plot 2 yanındaki tablo (4 adet)
31
+ "current_velocity",
32
+ "velocity_loop_kp",
33
+ "velocity_loop_ki",
34
+ "velocity_loop_kd",
35
+ ],
36
+ 3: [ # Plot 3 yanındaki tablo (4 adet)
37
+ "current_position",
38
+ "position_loop_kp",
39
+ "position_loop_ki",
40
+ "position_loop_kd",
41
+ ],
42
+ }
43
+
44
+
45
+ # =========================
46
+ # Ortak Signal Bus
47
+ # =========================
48
+ class SignalBus(QtCore.QObject):
49
+ tabChanged = QtCore.Signal(int, str)
50
+ configParamChanged = QtCore.Signal(str, str)
51
+ operationParamChanged = QtCore.Signal(int, str, str)
52
+ refreshClicked = QtCore.Signal()
53
+ restartClicked = QtCore.Signal()
54
+ configSaveClicked = QtCore.Signal()
55
+ FactoryResetClicked = QtCore.Signal()
56
+ newTimedData = QtCore.Signal(float, float, float, float, float, float, float, float) # y1,y2,y3,y4, s1,s2,s3, t
57
+ idChanged = QtCore.Signal(str) # Uygulama kimliği değiştiğinde yayınlanır
58
+ enableToggled = QtCore.Signal(bool) # ON/OFF değişimi için
59
+ enableStateUpdated = QtCore.Signal(bool) # cihazdan gelen enable -> UI
60
+
61
+ # --- S-Curve eklentisi için yeni sinyaller ---
62
+ sCurveToggled = QtCore.Signal(bool) # S-Curve enable/disable
63
+ sCurvePlanRequested = QtCore.Signal(float, float, float, float) # target, vmax, a_des, t_des
64
+
65
+
66
+ # =========================
67
+ # Başlangıç ID Diyaloğu
68
+ # =========================
69
+ class StartupDialog(QtWidgets.QDialog):
70
+ def __init__(self, parent=None):
71
+ super().__init__(parent)
72
+ self.setWindowTitle("Cihaz ID Girişi")
73
+ self.setModal(True)
74
+ self.id_value: Optional[str] = None
75
+
76
+ layout = QtWidgets.QVBoxLayout(self)
77
+
78
+ form = QtWidgets.QFormLayout()
79
+ self.leID = QtWidgets.QLineEdit()
80
+ self.leID.setPlaceholderText("Örn: 42")
81
+ form.addRow("ID:", self.leID)
82
+
83
+ self.cbRemember = QtWidgets.QCheckBox("Bu ID'yi hatırla")
84
+ layout.addLayout(form)
85
+ layout.addWidget(self.cbRemember)
86
+
87
+ btns = QtWidgets.QDialogButtonBox(
88
+ QtWidgets.QDialogButtonBox.Ok | QtWidgets.QDialogButtonBox.Cancel
89
+ )
90
+ btns.accepted.connect(self._on_ok)
91
+ btns.rejected.connect(self.reject)
92
+ layout.addWidget(btns)
93
+
94
+ # QSettings: Son ID’yi yükle
95
+ sett = QtCore.QSettings("Acrome", "UartUiApp")
96
+ last_id = sett.value("last_id", "", type=str)
97
+ if last_id:
98
+ self.leID.setText(last_id)
99
+ self.cbRemember.setChecked(True)
100
+
101
+ def _on_ok(self):
102
+ text = self.leID.text().strip()
103
+ if not text:
104
+ QtWidgets.QMessageBox.warning(self, "Uyarı", "Lütfen bir ID girin.")
105
+ return
106
+ self.id_value = text
107
+ # Hatırlama
108
+ sett = QtCore.QSettings("Acrome", "UartUiApp")
109
+ if self.cbRemember.isChecked():
110
+ sett.setValue("last_id", text)
111
+ else:
112
+ sett.remove("last_id")
113
+ self.accept()
114
+
115
+
116
+ # =========================
117
+ # CONFIG Sekmesi
118
+ # =========================
119
+ class ConfigTab(QtWidgets.QWidget):
120
+ def __init__(self, bus: SignalBus, parent=None):
121
+ super().__init__(parent)
122
+ self.bus = bus
123
+ self._suppress_cell_signal = False
124
+
125
+ layout = QtWidgets.QHBoxLayout(self)
126
+
127
+ self.table = QtWidgets.QTableWidget(self)
128
+ self.table.setColumnCount(2)
129
+ self.table.setHorizontalHeaderLabels(["Parameter", "Value"])
130
+ self.table.horizontalHeader().setStretchLastSection(True)
131
+ self.table.verticalHeader().setVisible(False)
132
+ self.table.setEditTriggers(QtWidgets.QAbstractItemView.AllEditTriggers)
133
+
134
+ self.parameters = [
135
+ {"name": 'Header', "value": "", "read_only": True},
136
+ {"name": 'DeviceID', "value": "", "read_only": True},
137
+ {"name": 'DeviceFamily', "value": "", "read_only": True},
138
+ {"name": 'PackageSize', "value": "", "read_only": True},
139
+ {"name": 'Command', "value": "", "read_only": True},
140
+ {"name": 'Status', "value": "", "read_only": True},
141
+ {"name": 'HardwareVersion', "value": "", "read_only": True},
142
+ {"name": 'SoftwareVersion', "value": "", "read_only": True},
143
+ {"name": 'Baudrate', "value": "", "read_only": True},
144
+ {"name": 'OperationMode', "value": "", "read_only": False},
145
+ {"name": 'Enable', "value": "", "read_only": False},
146
+ {"name": 'Vbus_read', "value": "", "read_only": True},
147
+ {"name": 'Temprature_read', "value": "", "read_only": True},
148
+ {"name": 'currentId_loop_kp', "value": "", "read_only": False},
149
+ {"name": 'currentId_loop_ki', "value": "", "read_only": False},
150
+ {"name": 'currentId_loop_kd', "value": "", "read_only": False},
151
+ {"name": 'currentIq_loop_kp', "value": "", "read_only": False},
152
+ {"name": 'currentIq_loop_ki', "value": "", "read_only": False},
153
+ {"name": 'currentIq_loop_kd', "value": "", "read_only": False},
154
+ {"name": 'velocity_loop_kp', "value": "", "read_only": False},
155
+ {"name": 'velocity_loop_ki', "value": "", "read_only": False},
156
+ {"name": 'velocity_loop_kd', "value": "", "read_only": False},
157
+ {"name": 'position_loop_kp', "value": "", "read_only": False},
158
+ {"name": 'position_loop_ki', "value": "", "read_only": False},
159
+ {"name": 'position_loop_kd', "value": "", "read_only": False},
160
+ {"name": 'max_position', "value": "", "read_only": False},
161
+ {"name": 'min_position', "value": "", "read_only": False},
162
+ {"name": 'max_velocity', "value": "", "read_only": False},
163
+ {"name": 'max_current', "value": "", "read_only": False},
164
+ {"name": 'current_Va', "value": "", "read_only": True},
165
+ {"name": 'current_Vb', "value": "", "read_only": True},
166
+ {"name": 'current_Vc', "value": "", "read_only": True},
167
+ {"name": 'current_Ia', "value": "", "read_only": True},
168
+ {"name": 'current_Ib', "value": "", "read_only": True},
169
+ {"name": 'current_Ic', "value": "", "read_only": True},
170
+ {"name": 'current_Id', "value": "", "read_only": True},
171
+ {"name": 'current_Iq', "value": "", "read_only": True},
172
+ {"name": 'current_velocity', "value": "", "read_only": True},
173
+ {"name": 'current_position', "value": "", "read_only": True},
174
+ {"name": 'current_electrical_degree', "value": "", "read_only": True},
175
+ {"name": 'current_electrical_radian', "value": "", "read_only": True},
176
+ {"name": 'setpoint_current', "value": "", "read_only": False},
177
+ {"name": 'setpoint_velocity', "value": "", "read_only": False},
178
+ {"name": 'setpoint_position', "value": "", "read_only": False},
179
+ {"name": 'openloop_voltage_size', "value": "", "read_only": False},
180
+ {"name": 'openloop_angle_degree', "value": "", "read_only": False},
181
+ {"name": 'current_lock_angle_degree', "value": "", "read_only": False},
182
+ {"name": 'Config_TimeStamp', "value": "", "read_only": False},
183
+ {"name": 'Config_Description', "value": "", "read_only": False},
184
+ {"name": 'CRCValue', "value": "", "read_only": True},
185
+ ]
186
+
187
+ # İlk değerleri cihazdan çek
188
+ for var in self.parameters:
189
+ var["value"] = Device.get_variables(Index_Joint[var["name"]])[0]
190
+
191
+ self._populate_table()
192
+ self.table.cellChanged.connect(self._on_cell_changed)
193
+
194
+ # Sağ buton sütunu
195
+ btn_col = QtWidgets.QVBoxLayout()
196
+ self.btnRefresh = QtWidgets.QPushButton("Refresh")
197
+ self.btnRestart = QtWidgets.QPushButton("Restart")
198
+ self.btnConfigSave = QtWidgets.QPushButton("Config Save")
199
+ self.btnApply = QtWidgets.QPushButton("Apply")
200
+ for b in (self.btnRefresh, self.btnRestart, self.btnConfigSave, self.btnApply):
201
+ b.setMinimumHeight(40)
202
+ btn_col.addWidget(b)
203
+ btn_col.addStretch()
204
+
205
+ self.btnRefresh.clicked.connect(self.bus.refreshClicked)
206
+ self.btnRestart.clicked.connect(self.bus.restartClicked)
207
+ self.btnConfigSave.clicked.connect(self.bus.configSaveClicked)
208
+ self.btnApply.clicked.connect(self.bus.FactoryResetClicked)
209
+
210
+ layout.addWidget(self.table, 3)
211
+ layout.addLayout(btn_col, 1)
212
+
213
+ print("config tab init")
214
+
215
+ def _populate_table(self):
216
+ self._suppress_cell_signal = True
217
+ self.table.setRowCount(len(self.parameters))
218
+ for row, p in enumerate(self.parameters):
219
+ name_item = QtWidgets.QTableWidgetItem(p["name"])
220
+ name_item.setFlags(QtCore.Qt.ItemIsSelectable | QtCore.Qt.ItemIsEnabled)
221
+ self.table.setItem(row, 0, name_item)
222
+
223
+ val_item = QtWidgets.QTableWidgetItem(str(p["value"]))
224
+ if p["read_only"]:
225
+ val_item.setFlags(QtCore.Qt.ItemIsSelectable | QtCore.Qt.ItemIsEnabled)
226
+ self.table.setItem(row, 1, val_item)
227
+ self._suppress_cell_signal = False
228
+
229
+ def _on_cell_changed(self, row, column):
230
+ if self._suppress_cell_signal or column != 1:
231
+ return
232
+ name = self.table.item(row, 0).text()
233
+ value = self.table.item(row, 1).text()
234
+ if self.parameters[row]["read_only"]:
235
+ self._suppress_cell_signal = True
236
+ self.table.item(row, 1).setText(str(self.parameters[row]["value"]))
237
+ self._suppress_cell_signal = False
238
+ return
239
+ self.parameters[row]["value"] = value
240
+ self.bus.configParamChanged.emit(name, value)
241
+
242
+ def refresh_from_device(self, new_params: dict):
243
+ # Şu an parametreleri zaten self.parameters'tan alıyoruz
244
+ self._populate_table()
245
+
246
+
247
+ # =========================
248
+ # OPERATION Sekmesi
249
+ # =========================
250
+ class OperationPanel(QtWidgets.QGroupBox):
251
+ def __init__(self, bus: SignalBus, panel_id: int, title: str, plot_mode: str = "single",
252
+ extra_param_label: str | None = None, extra_param_key: str | None = None, parent=None):
253
+ super().__init__(title, parent)
254
+ self.bus = bus
255
+ self.panel_id = panel_id
256
+ self.plot_mode = plot_mode
257
+ self._suppress_cell_signal = False
258
+
259
+ # --- ekstra parametre meta ---
260
+ self.extra_param_label = extra_param_label # UI'da gösterilecek isim (ör. current_setpoint)
261
+ self.extra_param_key = extra_param_key # Index_Joint anahtarı (ör. setpoint_current)
262
+
263
+ layout = QtWidgets.QHBoxLayout(self)
264
+
265
+ # Plot
266
+ self.plot = pg.PlotWidget()
267
+ self.plot.showGrid(x=True, y=True, alpha=0.3)
268
+ self.plot.setLabel('bottom', 'Time', units='s')
269
+ self.plot.setLabel('left', 'Value', units='')
270
+
271
+ self.curve1 = self.plot.plot(pen=pg.mkPen(width=2))
272
+ self.curve2 = None
273
+ if self.plot_mode == "dual":
274
+ self.curve2 = self.plot.plot(pen=pg.mkPen(color='c', width=2))
275
+
276
+ # --- setpoint curve (yellow)
277
+ self.curve_sp = self.plot.plot(pen=pg.mkPen('y', width=2))
278
+
279
+ self.max_points = 1000
280
+ self.tbuf, self.y1buf, self.y2buf, self.spbuf = [], [], [], []
281
+
282
+ # Sağ kolon: tablo + alt setpoint alanı + (panel3 için S-Curve kutusu eklenecek)
283
+ self.right_col = QtWidgets.QVBoxLayout()
284
+
285
+ # Parametre tablosu (scrollable)
286
+ self.table = QtWidgets.QTableWidget()
287
+ self.table.setColumnCount(2)
288
+ self.table.setHorizontalHeaderLabels(["Parameter", "Value"])
289
+ self.table.verticalHeader().setVisible(False)
290
+ self.table.horizontalHeader().setStretchLastSection(True)
291
+ self.table.setRowCount(10)
292
+ for i in range(10):
293
+ name_item = QtWidgets.QTableWidgetItem(f"P{i+1}")
294
+ name_item.setFlags(QtCore.Qt.ItemIsSelectable | QtCore.Qt.ItemIsEnabled)
295
+ self.table.setItem(i, 0, name_item)
296
+ value_item = QtWidgets.QTableWidgetItem("0")
297
+ self.table.setItem(i, 1, value_item)
298
+ self.table.cellChanged.connect(self._on_cell_changed)
299
+
300
+ self.right_col.addWidget(self.table, 1)
301
+
302
+ # Alt sabit setpoint alanı (tablodan ayrı ve sabit)
303
+ if self.extra_param_label and self.extra_param_key:
304
+ self.extraBox = QtWidgets.QGroupBox("Setpoint")
305
+ form = QtWidgets.QHBoxLayout(self.extraBox)
306
+ self.lblExtra = QtWidgets.QLabel(self.extra_param_label)
307
+ self.leExtra = QtWidgets.QLineEdit()
308
+ self.btnExtraSet = QtWidgets.QPushButton("Set")
309
+ self.leExtra.setPlaceholderText("value")
310
+ form.addWidget(self.lblExtra)
311
+ form.addWidget(self.leExtra, 1)
312
+ form.addWidget(self.btnExtraSet)
313
+ self.right_col.addWidget(self.extraBox, 0)
314
+
315
+ # Enter veya buton ile SET
316
+ self.leExtra.returnPressed.connect(self._emit_extra_set)
317
+ self.btnExtraSet.clicked.connect(self._emit_extra_set)
318
+ else:
319
+ self.extraBox = None
320
+ self.lblExtra = None
321
+ self.leExtra = None
322
+ self.btnExtraSet = None
323
+
324
+ layout.addWidget(self.plot, 3)
325
+ layout.addLayout(self.right_col, 2)
326
+
327
+ # ---- alt setpoint alanını programatik güncellemek için (şu an otomatik yazmıyoruz)
328
+ def set_extra_param_value(self, text: str):
329
+ if self.leExtra is not None:
330
+ self.leExtra.setText(str(text))
331
+
332
+ def set_extra_enabled(self, enabled: bool):
333
+ """Setpoint giriş kutusu ve butonunu aç/kapat."""
334
+ if self.leExtra is not None:
335
+ self.leExtra.setEnabled(enabled)
336
+ if self.btnExtraSet is not None:
337
+ self.btnExtraSet.setEnabled(enabled)
338
+
339
+ def add_widget_to_side(self, w: QtWidgets.QWidget):
340
+ """Sağ kolona harici bir widget eklemek için yardımcı."""
341
+ self.right_col.addWidget(w, 0)
342
+
343
+ def _emit_extra_set(self):
344
+ if self.extra_param_key and self.leExtra:
345
+ value = self.leExtra.text()
346
+ # Panel kimliği ve INDEX anahtar adı ile yayınla
347
+ self.bus.operationParamChanged.emit(self.panel_id, self.extra_param_key, value)
348
+
349
+ # ---- OPERATION panel yardımcıları ----
350
+ def set_param_names(self, names: list[str]):
351
+ """Sağdaki tabloya verilen isimleri yazar; kalan satırlara P# bırakır."""
352
+ self._suppress_cell_signal = True
353
+ for i in range(10):
354
+ nm = names[i] if i < len(names) else f"P{i+1}"
355
+ self.table.item(i, 0).setText(str(nm))
356
+ self._suppress_cell_signal = False
357
+
358
+ def set_read_only(self, readonly_names: set[str]):
359
+ """Value hücresini verilen isimler için salt-okunur yapar."""
360
+ self._suppress_cell_signal = True
361
+ for r in range(self.table.rowCount()):
362
+ nm = self.table.item(r, 0).text()
363
+ val_item = self.table.item(r, 1)
364
+ if nm in readonly_names:
365
+ val_item.setFlags(QtCore.Qt.ItemIsSelectable | QtCore.Qt.ItemIsEnabled)
366
+ else:
367
+ val_item.setFlags(QtCore.Qt.ItemIsSelectable | QtCore.Qt.ItemIsEnabled | QtCore.Qt.ItemIsEditable)
368
+ self._suppress_cell_signal = False
369
+
370
+ def _on_cell_changed(self, row, column):
371
+ if self._suppress_cell_signal or column != 1:
372
+ return
373
+ name = self.table.item(row, 0).text()
374
+ value = self.table.item(row, 1).text()
375
+ self.bus.operationParamChanged.emit(self.panel_id, name, value)
376
+
377
+ def push_data(self, t: float, y1: float, y2: Optional[float] = None, sp: Optional[float] = None):
378
+ self.tbuf.append(t)
379
+ self.y1buf.append(y1)
380
+ if self.curve2 is not None:
381
+ self.y2buf.append(y2 if (y2 is not None) else 0.0)
382
+ self.spbuf.append(sp if (sp is not None) else 0.0)
383
+
384
+ if len(self.tbuf) > self.max_points:
385
+ self.tbuf = self.tbuf[-self.max_points:]
386
+ self.y1buf = self.y1buf[-self.max_points:]
387
+ if self.curve2 is not None:
388
+ self.y2buf = self.y2buf[-self.max_points:]
389
+ self.spbuf = self.spbuf[-self.max_points:]
390
+
391
+ self.curve1.setData(self.tbuf, self.y1buf)
392
+ if self.curve2 is not None:
393
+ self.curve2.setData(self.tbuf, self.y2buf)
394
+ self.curve_sp.setData(self.tbuf, self.spbuf)
395
+
396
+ def set_params_bulk(self, mapping: dict):
397
+ name_to_row = {self.table.item(r, 0).text(): r for r in range(self.table.rowCount())}
398
+ self._suppress_cell_signal = True
399
+ for name, val in mapping.items():
400
+ if name in name_to_row:
401
+ r = name_to_row[name]
402
+ self.table.item(r, 1).setText(str(val))
403
+ self._suppress_cell_signal = False
404
+
405
+
406
+ class OperationTab(QtWidgets.QWidget):
407
+ def __init__(self, bus: SignalBus, parent=None):
408
+ super().__init__(parent)
409
+ self.bus = bus
410
+ self._suppress_enable_signal = False # UI güncellerken sinyali bastırmak için
411
+ self.bus.enableStateUpdated.connect(self.set_enable_ui)
412
+
413
+ layout = QtWidgets.QVBoxLayout(self)
414
+
415
+ # ---- Üst bar: Enable switch
416
+ topbar = QtWidgets.QHBoxLayout()
417
+ topbar.addStretch()
418
+ self.enableSwitch = QtWidgets.QCheckBox("Enable")
419
+ self.enableSwitch.setTristate(False)
420
+ # (İsteğe bağlı) switch görünümü için basit bir stil:
421
+ self.enableSwitch.setStyleSheet("""
422
+ QCheckBox::indicator { width: 46px; height: 26px; }
423
+ QCheckBox::indicator:unchecked { image: none; border-radius: 13px; background: #bbb; }
424
+ QCheckBox::indicator:unchecked:hover { background: #aaa; }
425
+ QCheckBox::indicator:checked { image: none; border-radius: 13px; background: #4caf50; }
426
+ QCheckBox { padding: 4px; }
427
+ """)
428
+ self.enableSwitch.toggled.connect(self._on_enable_toggled)
429
+ topbar.addWidget(self.enableSwitch)
430
+ layout.addLayout(topbar)
431
+
432
+ # ---- Paneller
433
+ self.panel1 = OperationPanel(
434
+ bus, panel_id=1, title="Plot 1 (Two Traces)", plot_mode="dual",
435
+ extra_param_label="current_setpoint", extra_param_key="setpoint_current"
436
+ )
437
+ self.panel2 = OperationPanel(
438
+ bus, panel_id=2, title="Plot 2 (Single Trace)", plot_mode="single",
439
+ extra_param_label="velocity_setpoint", extra_param_key="setpoint_velocity"
440
+ )
441
+ self.panel3 = OperationPanel(
442
+ bus, panel_id=3, title="Plot 3 (Single Trace)", plot_mode="single",
443
+ extra_param_label="position_setpoint", extra_param_key="setpoint_position"
444
+ )
445
+ layout.addWidget(self.panel1)
446
+ layout.addWidget(self.panel2)
447
+ layout.addWidget(self.panel3)
448
+
449
+ # İsimleri yaz
450
+ self.panel1.set_param_names(OP_PARAM_GROUPS[1])
451
+ self.panel2.set_param_names(OP_PARAM_GROUPS[2])
452
+ self.panel3.set_param_names(OP_PARAM_GROUPS[3])
453
+
454
+ # --- Panel 3 (Position) için S-Curve Planner UI'sı ---
455
+ self._build_scurve_ui(self.panel3)
456
+
457
+ # Cihazdan veri akışı
458
+ self.bus.newTimedData.connect(self._on_new_timed_data)
459
+
460
+ print("operation tab init")
461
+
462
+ # S-Curve planner kutusu
463
+ def _build_scurve_ui(self, position_panel: OperationPanel):
464
+ gb = QtWidgets.QGroupBox("S-Curve Planner")
465
+ vbox = QtWidgets.QVBoxLayout(gb)
466
+
467
+ # Enable checkbox
468
+ self.cbSCurve = QtWidgets.QCheckBox("Enable S-Curve")
469
+ vbox.addWidget(self.cbSCurve)
470
+
471
+ # Form inputs
472
+ form = QtWidgets.QFormLayout()
473
+ self.leTarget = QtWidgets.QLineEdit()
474
+ self.leVmax = QtWidgets.QLineEdit()
475
+ self.leAmax = QtWidgets.QLineEdit()
476
+ self.leTdes = QtWidgets.QLineEdit()
477
+
478
+ self.leTarget.setPlaceholderText("target_position")
479
+ self.leVmax.setPlaceholderText("max_velocity")
480
+ self.leAmax.setPlaceholderText("desired_acceleration")
481
+ self.leTdes.setPlaceholderText("desired_time")
482
+
483
+ form.addRow("Target Position:", self.leTarget)
484
+ form.addRow("Max Velocity:", self.leVmax)
485
+ form.addRow("Desired Accel.:", self.leAmax)
486
+ form.addRow("Desired Time:", self.leTdes)
487
+ vbox.addLayout(form)
488
+
489
+ # Buttons
490
+ hbtn = QtWidgets.QHBoxLayout()
491
+ self.btnPlan = QtWidgets.QPushButton("Plan")
492
+ self.btnPlan.setToolTip("Plan now (x0 = current_position)")
493
+ hbtn.addStretch()
494
+ hbtn.addWidget(self.btnPlan)
495
+ vbox.addLayout(hbtn)
496
+
497
+ # Add to panel 3 right side
498
+ position_panel.add_widget_to_side(gb)
499
+
500
+ # Connections
501
+ self.cbSCurve.toggled.connect(self._on_scurve_toggled_ui)
502
+ self.btnPlan.clicked.connect(self._emit_plan_from_ui)
503
+ self.leTarget.returnPressed.connect(self._emit_plan_from_ui) # target girilince anında planla
504
+
505
+ def _on_scurve_toggled_ui(self, checked: bool):
506
+ # UI etkisi: S-Curve açıldığında manuel position_setpoint girişini kapat
507
+ self.bus.sCurveToggled.emit(checked)
508
+
509
+ def _emit_plan_from_ui(self):
510
+ def _as_float(le: QtWidgets.QLineEdit, default: float = 0.0) -> float:
511
+ try:
512
+ return float(le.text())
513
+ except Exception:
514
+ return default
515
+
516
+ target = _as_float(self.leTarget, 0.0)
517
+ vmax = _as_float(self.leVmax, 0.0)
518
+ a_des = _as_float(self.leAmax, 0.0)
519
+ t_des = _as_float(self.leTdes, 0.0)
520
+ self.bus.sCurvePlanRequested.emit(target, vmax, a_des, t_des)
521
+
522
+ def _on_new_timed_data(self, y1, y2, y3, y4, s1, s2, s3, t):
523
+ # Plot lines + yellow setpoints
524
+ self.panel1.push_data(t, y1, y2, s1) # Id, Iq, setpoint_current
525
+ self.panel2.push_data(t, y3, sp=s2) # current_velocity, setpoint_velocity
526
+ self.panel3.push_data(t, y4, sp=s3) # current_position, setpoint_position
527
+
528
+ # Keep the small tables synced with live values
529
+ self.panel1.set_params_bulk({
530
+ "current_Id": NUM_FMT.format(y1),
531
+ "current_Iq": NUM_FMT.format(y2),
532
+ })
533
+ self.panel2.set_params_bulk({
534
+ "current_velocity": NUM_FMT.format(y3),
535
+ })
536
+ self.panel3.set_params_bulk({
537
+ "current_position": NUM_FMT.format(y4),
538
+ })
539
+
540
+ def reload_from_device(self):
541
+ """Panel isimlerine göre Device'tan değer çek ve tabloları doldur."""
542
+ def pull(names: list[str]) -> dict:
543
+ out = {}
544
+ for nm in names:
545
+ if not nm or nm.startswith("P"):
546
+ continue
547
+ try:
548
+ idx = Index_Joint[nm]
549
+ out[nm] = Device.get_variables(idx)[0]
550
+ except Exception:
551
+ out[nm] = ""
552
+ return out
553
+
554
+ m1 = pull(OP_PARAM_GROUPS[1])
555
+ m2 = pull(OP_PARAM_GROUPS[2])
556
+ m3 = pull(OP_PARAM_GROUPS[3])
557
+
558
+ self.panel1.set_params_bulk(m1)
559
+ self.panel2.set_params_bulk(m2)
560
+ self.panel3.set_params_bulk(m3)
561
+
562
+ def _on_enable_toggled(self, checked: bool):
563
+ if self._suppress_enable_signal:
564
+ return
565
+ # Switch değişince bus üzerinden ana pencereye bildir
566
+ self.bus.enableToggled.emit(checked)
567
+
568
+ def set_enable_ui(self, checked: bool):
569
+ """Cihazdan okunan enable durumunu UI’a yaz (sinyal üretmeden)."""
570
+ self._suppress_enable_signal = True
571
+ try:
572
+ self.enableSwitch.setChecked(bool(checked))
573
+ finally:
574
+ self._suppress_enable_signal = False
575
+
576
+
577
+ # =========================
578
+ # Ana Pencere
579
+ # =========================
580
+ class MainWindow(QtWidgets.QMainWindow):
581
+ def __init__(self, device_id: str):
582
+ super().__init__()
583
+ self.setWindowTitle("ACROME JOINT BLDC DRIVER GUI")
584
+ self.bus = SignalBus()
585
+ self.current_id = device_id # Girilen ID
586
+ self.bus.idChanged.emit(self.current_id)
587
+
588
+ self.tabs = QtWidgets.QTabWidget()
589
+ self.setCentralWidget(self.tabs)
590
+
591
+ self.configTab = ConfigTab(self.bus)
592
+ self.operationTab = OperationTab(self.bus)
593
+ self.tabs.addTab(self.configTab, "CONFIG")
594
+ self.tabs.addTab(self.operationTab, "OPERATION")
595
+
596
+ self.tabs.currentChanged.connect(self._on_tab_changed)
597
+ self._connect_bus_handlers()
598
+
599
+ self.resize(1200, 900)
600
+
601
+ self._op_timer = QtCore.QTimer(self)
602
+ self._op_timer.timeout.connect(self._operation_tick)
603
+ self._op_t0 = None # OPERATION sekmesine girildiği anın referansı
604
+
605
+ # ---- Read-only bilgisini CONFIG'ten al ve OPERATION tablolara uygula
606
+ ro_names = {p["name"] for p in self.configTab.parameters if p.get("read_only", False)}
607
+ self.operationTab.panel1.set_read_only(ro_names)
608
+ self.operationTab.panel2.set_read_only(ro_names)
609
+ self.operationTab.panel3.set_read_only(ro_names)
610
+
611
+ # ---- S-Curve çalışma durumları ----
612
+ self._s_curve_enabled: bool = False
613
+ self._ramp: Optional[Ramp] = None
614
+ self._last_current_position: float = 0.0 # her tick'te güncellenir
615
+
616
+ print("main window init")
617
+
618
+ # ------- Hook Bağlantıları -------
619
+ def _connect_bus_handlers(self):
620
+ self.bus.tabChanged.connect(self._on_tab_hook)
621
+ self.bus.configParamChanged.connect(self._on_config_param_set)
622
+ self.bus.operationParamChanged.connect(self._on_operation_param_set)
623
+ self.bus.refreshClicked.connect(self._on_refresh_clicked)
624
+ self.bus.restartClicked.connect(self._on_restart_clicked)
625
+ self.bus.configSaveClicked.connect(self._on_config_save_clicked)
626
+ self.bus.FactoryResetClicked.connect(self._on_config_reset_clicked)
627
+ self.bus.enableToggled.connect(self._on_enable_toggled)
628
+
629
+ # S-Curve sinyalleri
630
+ self.bus.sCurveToggled.connect(self._on_scurve_toggled)
631
+ self.bus.sCurvePlanRequested.connect(self._on_scurve_plan_requested)
632
+
633
+ # ------- Tab Geçiş Kancası -------
634
+ def _on_tab_changed(self, index: int):
635
+ name = self.tabs.tabText(index)
636
+ self.bus.tabChanged.emit(index, name)
637
+
638
+ if name == "OPERATION":
639
+ # OPERATION'a girildi: zaman referansı ve periyodik GET
640
+ self._op_t0 = time.perf_counter()
641
+ self._op_timer.start(30) # ~33 Hz
642
+ else:
643
+ # OPERATION’dan çıkıldı: durdur
644
+ self._op_timer.stop()
645
+
646
+ def _operation_tick(self):
647
+ """
648
+ Periyodik GET: Device'tan değerleri çek ve zaman damgası ile yayınla.
649
+ Ayrıca S-Curve açıksa her tick'te step() çağırıp position setpoint yollar.
650
+ """
651
+ parameters = Device.get_FOC_parameters(3)
652
+ t_abs = time.perf_counter()
653
+ t_rel = t_abs - (self._op_t0 or t_abs)
654
+
655
+ y1 = parameters[1]
656
+ y2 = parameters[2]
657
+ y3 = parameters[3]
658
+ y4 = parameters[4]
659
+ self._last_current_position = float(y4) # plan için güncel x0
660
+
661
+ s1 = parameters[6]
662
+ s2 = parameters[7]
663
+ s3 = parameters[8]
664
+
665
+ # OPERATION sekmesine veri yay
666
+ self.bus.newTimedData.emit(
667
+ float(y1), float(y2), float(y3), float(y4),
668
+ float(s1), float(s2), float(s3),
669
+ float(t_rel)
670
+ )
671
+
672
+ # Enable durumunu UI'a yansıt
673
+ enable_state = parameters[0]
674
+ self.bus.enableStateUpdated.emit(enable_state)
675
+
676
+ # --- S-Curve aktifse: ramp step ve setpoint gönder ---
677
+ if self._s_curve_enabled and (self._ramp is not None) and (not self._ramp.done()):
678
+ try:
679
+ sp = float(self._ramp.step())
680
+ # Cihaza setpoint_position yaz
681
+ Device.set_variables([Index_Joint.setpoint_position, sp])
682
+ except Exception as e:
683
+ print(f"[S-CURVE] step/send failed: {e}")
684
+
685
+ def _on_tab_hook(self, index: int, name: str):
686
+ if name == "OPERATION":
687
+ Device.enter_operation()
688
+ # OPERATION panel tablolarını cihazdan güncelle
689
+ self.operationTab.reload_from_device()
690
+ else:
691
+ Device.enter_configuration()
692
+ print(f"[HOOK] Entered tab {index}: {name} (ID={self.current_id})")
693
+
694
+ # ------- CONFIG/OPERATION SET Kancaları -------
695
+ def _on_config_param_set(self, name: str, value: str):
696
+ Device.set_variables([Index_Joint[name], int(value)])
697
+ print(f"[CONFIG SET] {name} = {value} | ID={self.current_id}")
698
+
699
+ def _on_operation_param_set(self, panel_id: int, name: str, value: str):
700
+ """
701
+ Not: S-Curve açıkken position_setpoint kullanıcı tarafından girilemez (UI'da disable).
702
+ Diğer paneller (current/velocity) normal çalışır.
703
+ """
704
+ try:
705
+ idx = Index_Joint[name]
706
+ v = float(value)
707
+ if v.is_integer():
708
+ v = int(v)
709
+ Device.set_variables([idx, v])
710
+ print(f"[OP SET] Panel {panel_id} | {name} = {v} , idx = {idx}| ID={self.current_id}")
711
+ except Exception as e:
712
+ print(f"[OP SET] FAIL: idx = {idx if 'idx' in locals() else '?'} , {panel_id} {name} {value} -> {e}")
713
+
714
+ # ------- S-Curve event handlers -------
715
+ def _on_scurve_toggled(self, checked: bool):
716
+ self._s_curve_enabled = bool(checked)
717
+ # UI: manuel position_setpoint girişini aç/kapat
718
+ self.operationTab.panel3.set_extra_enabled(not self._s_curve_enabled)
719
+
720
+ if not checked:
721
+ # Kapandı -> planlayıcıyı bırak
722
+ self._ramp = None
723
+ print("[S-CURVE] Disabled and cleared ramp.")
724
+ return
725
+
726
+ # Açıldı; mevcut timer aralığına göre dt ayarla (saniye)
727
+ dt = (self._op_timer.interval() / 1000.0) if self._op_timer.isActive() else 0.03
728
+
729
+ # Ramp'i sadece bir kez yarat
730
+ try:
731
+ # Buradaki vmax/amax üst limit; asıl istek plan()'da vmax_des/a_des ile gelecek
732
+ self._ramp = Ramp(dt=dt, vmax=MOTOR_VEL_MAX_IN_ENC_TYPE, amax=MOTOR_ACC_MAX_IN_ENC_TYPE)
733
+ print(f"dt 1 = {dt}")
734
+ print("[S-CURVE] Enabled (created Ramp once).")
735
+ except Exception as e:
736
+ print(f"[S-CURVE] Ramp init failed: {e}")
737
+
738
+
739
+ def _on_scurve_plan_requested(self, target: float, vmax: float, a_des: float, t_des: float):
740
+ """
741
+ Kullanıcı 'Plan' dediğinde ya da target_position Enter ile girildiğinde çağrılır.
742
+ x0 = son okunan current_position
743
+ """
744
+ if not self._s_curve_enabled:
745
+ print("[S-CURVE] Plan requested but S-Curve is disabled.")
746
+ return
747
+
748
+ try:
749
+ dt = (self._op_timer.interval() / 1000.0) if self._op_timer.isActive() else 0.03
750
+ print(f"dt 2 = {dt}")
751
+ if self._ramp is None:
752
+ # Normalde buraya düşülmez; yine de güvenlik için:
753
+ self._ramp = Ramp(dt=dt, vmax=MOTOR_VEL_MAX_IN_ENC_TYPE, amax=MOTOR_ACC_MAX_IN_ENC_TYPE)
754
+ print("ERROR RAMP RECREATE")
755
+ else:
756
+ # Aynı ramp nesnesini koru; sadece parametrelerini güncelle
757
+ try:
758
+ self._ramp.dt = dt
759
+ except Exception:
760
+ pass
761
+ # Eğer Ramp sınıfı bu alanları public tutuyorsa güncelle:
762
+ try:
763
+ self._ramp.vmax = MOTOR_VEL_MAX_IN_ENC_TYPE
764
+ self._ramp.amax = MOTOR_ACC_MAX_IN_ENC_TYPE
765
+ except Exception:
766
+ # Public değilse sorun değil; plan() içindeki vmax_des/a_des zaten kullanılacak
767
+ pass
768
+
769
+ x0 = float(self._last_current_position)
770
+ xg = float(target)
771
+ t_d = float(t_des)
772
+ a_d = rpm_to_tick_per_second(float(a_des)) # conversion for unit (rpm/s)
773
+ v_d = rpm_to_tick_per_second(float(vmax)) # conversion for unit (rpm)
774
+
775
+ # Asıl hedef/istekler plan'a gidiyor
776
+ self._ramp.plan(x0=x0, xg=xg, t_des=t_d, a_des=a_d, vmax_des=v_d)
777
+ print(f"[S-CURVE] Planned: x0={x0}, xg={xg}, t_des={t_d}, a_des={a_d}, vmax_des={v_d}, dt={dt}")
778
+ print(f"t = {self._ramp.t}, a = {self._ramp.a}, dir ={self._ramp.dir}")
779
+ print(f"t1 = {self._ramp.t1}, t2 = {self._ramp.t2}, vp = {self._ramp.Vp}")
780
+ except Exception as e:
781
+ print(f"[S-CURVE] Plan failed: {e}")
782
+
783
+ # ------- Sağ Buton Kancaları (CONFIG) -------
784
+ def _on_refresh_clicked(self):
785
+ for var in self.configTab.parameters:
786
+ var["value"] = Device.get_variables(Index_Joint[var["name"]])[0]
787
+ print(var["value"])
788
+ print(f"[BTN] Refresh | ID={self.current_id}")
789
+
790
+ self.configTab.refresh_from_device(self.configTab.parameters)
791
+
792
+ def _on_restart_clicked(self):
793
+ Device.reboot()
794
+ print(f"[BTN] Restart | ID={self.current_id}")
795
+
796
+ def _on_config_save_clicked(self):
797
+ Device.eeprom_save()
798
+ print(f"[BTN] Config Save | ID={self.current_id}")
799
+
800
+ def _on_config_reset_clicked(self):
801
+ Device.factory_reset()
802
+ print(f"[BTN] Apply | ID={self.current_id}")
803
+
804
+ # (Opsiyonel) ID değiştirme API’sı
805
+ def change_id(self, new_id: str):
806
+ self.current_id = new_id
807
+ self.bus.idChanged.emit(new_id)
808
+ print(f"[INFO] Active ID changed to {new_id}")
809
+ # TODO: ID değişince yapılacak işler (örn. port yeniden açma)
810
+
811
+ def _on_enable_toggled(self, is_on: bool):
812
+ try:
813
+ Device.set_variables([Index_Joint.Enable, 1 if is_on else 0])
814
+ print(f"[OP ENABLE] -> {is_on}")
815
+ except Exception as e:
816
+ print(f"[OP ENABLE] FAIL -> {e}")
817
+ # Hata olursa UI’yı geri çevir
818
+ self.operationTab.set_enable_ui(not is_on)
819
+
820
+
821
+ # =========================
822
+ # Uygulama Başlatma Akışı
823
+ # =========================
824
+ def pre_start_handshake(device_id: str) -> bool:
825
+ try:
826
+ dev = Joint(int(device_id), port)
827
+ except Exception:
828
+ return False
829
+ print(f"[PRE-START] Handshake with ID={device_id}...")
830
+ return dev.ping()
831
+
832
+
833
+ def main():
834
+ app = QtWidgets.QApplication([])
835
+ # 1) Başlangıçta dialog göster
836
+ while True:
837
+ dlg = StartupDialog()
838
+ if dlg.exec() != QtWidgets.QDialog.Accepted:
839
+ return
840
+ device_id = dlg.id_value or ""
841
+ # 2) PRE-START HOOK (Handshake)
842
+ ok = pre_start_handshake(device_id)
843
+ if ok:
844
+ break
845
+ else:
846
+ QtWidgets.QMessageBox.critical(
847
+ None, "Bağlantı Hatası",
848
+ f"ID={device_id} için ön iletişim başarısız. Lütfen tekrar deneyin."
849
+ )
850
+ Device._id = int(device_id)
851
+ # 3) Ana pencereyi aç (CONFIG sekmesi ile başlayacak)
852
+ win = MainWindow(device_id=device_id)
853
+ win.show()
854
+ app.exec()
855
+
856
+
857
+ if __name__ == "__main__":
858
+ main()
@@ -0,0 +1,96 @@
1
+ from math import sqrt
2
+
3
+ class Ramp:
4
+ def __init__(self, dt=0.001, vmax=1.0, amax=1.0):
5
+ self.dt, self.vmax, self.amax = dt, vmax, amax
6
+ self.reset()
7
+
8
+ def reset(self):
9
+ self.x0 = self.xg = 0.0
10
+ self.t = self.t1 = self.t2 = self.tp = 0.0
11
+ self.Vp = self.a = 0.0
12
+ self.dir = 1
13
+ self._x = 0.0
14
+ self._x_t1 = 0.0 # hızlanma fazında alınan mesafe (t1_pos)
15
+
16
+ def plan(self, x0, xg, t_des=0.0, a_des=0.0, vmax_des=0.0):
17
+ """Trajektoriyi hazırla; ardından step() her çağrıda bir sonraki setpoint'i üretir."""
18
+ self.reset()
19
+ self.x0, self.xg = x0, xg
20
+ e = xg - x0
21
+ self.dir = 1 if e >= 0 else -1
22
+ e = abs(e)
23
+
24
+ Vmax = self.vmax if (vmax_des <= 0 or vmax_des > self.vmax) else vmax_des
25
+ acc = self.amax if (a_des <= 0 or a_des > self.amax) else a_des
26
+
27
+ # 1) ZAMAN+İVME verildiyse dene
28
+ if t_des > 0 and acc > 0:
29
+ D = t_des*t_des - 4.0*(e/acc)
30
+ if D >= 0:
31
+ t1 = (t_des - sqrt(D)) / 2.0 # her zaman <= t_des/2
32
+ Vp = acc * t1
33
+ if 0 <= t1 and Vp < Vmax:
34
+ t2 = t_des - 2.0*t1
35
+ self._finalize(t1, t2, Vp, acc)
36
+ return
37
+
38
+ # 2) Sadece ZAMAN verildiyse
39
+ if t_des > 0:
40
+ if Vmax * t_des > e:
41
+ Vp = 2.0*e / t_des
42
+ if Vp <= Vmax:
43
+ t1, t2 = t_des/2.0, 0.0
44
+ else:
45
+ t1 = t_des - e / Vmax
46
+ t2 = t_des - 2.0*t1
47
+ Vp = Vmax
48
+ a = Vp / max(t1, 1e-12)
49
+ self._finalize(t1, t2, Vp, a)
50
+ return
51
+
52
+ # 3) İVME (veya hiçbiri) verildiyse
53
+ if acc <= 0: # her ikisi de yoksa C kodundaki gibi varsayılan
54
+ acc = Vmax / 2.0
55
+ t1 = sqrt(e / acc)
56
+ Vp = acc * t1
57
+ if Vp > Vmax:
58
+ t1 = Vmax / acc
59
+ t2 = (e - Vmax*t1) / Vmax
60
+ Vp = Vmax
61
+ else:
62
+ t2 = 0.0
63
+ self._finalize(t1, t2, Vp, acc)
64
+
65
+ def _finalize(self, t1, t2, Vp, a):
66
+ self.t1, self.t2, self.Vp, self.a = t1, t2, Vp, a
67
+ self.tp = 2.0*t1 + t2
68
+ self._x = self.x0
69
+ self._x_t1 = 0.5 * a * t1 * t1 # hızlanma mesafesi
70
+
71
+ def step(self):
72
+ """Bir kontrol tikinde bir sonraki setpoint."""
73
+ t, a, d = self.t, self.a, self.dir
74
+ x0, xg = self.x0, self.xg
75
+ t1, t2, tp, Vp = self.t1, self.t2, self.tp, self.Vp
76
+
77
+ if t < t1: # hızlan
78
+ x = x0 + 0.5 * a * t*t * d
79
+ elif t < t1 + t2: # sabit hız
80
+ x = x0 + (self._x_t1 + Vp*(t - t1)) * d
81
+ elif t < tp: # yavaşla
82
+ dt = tp - t
83
+ x = xg - 0.5 * a * dt*dt * d
84
+ else:
85
+ x = xg
86
+
87
+ # hedefi aşma koruması
88
+ if (d > 0 and x > xg) or (d < 0 and x < xg):
89
+ x = xg
90
+
91
+ self._x = x
92
+ self.t += self.dt
93
+ return x
94
+
95
+ def done(self):
96
+ return self._x == self.xg
@@ -5,10 +5,10 @@ with open("README.md", "r", encoding="utf-8") as fh:
5
5
 
6
6
  setuptools.setup(
7
7
  name="Joint-python-library",
8
- version="0.0.3",
8
+ version="0.0.5",
9
9
  author="BeratComputer",
10
10
  author_email="beratdogan@acrome.net",
11
- description="Python library for interfacing with Acrome Robotic Arm Joint BLDC Motor Controllers \n \n This Python library provides an easy-to-use interface for communication and control of BLDC motor controllers used in Acrome robotic arm joints. It is designed to simplify the integration of Acrome’s robotic joint actuators into custom applications, allowing developers and researchers to focus on building advanced robotic systems without dealing with low-level communication details.",
11
+ description="Python library for interfacing with Acrome Robotic Arm Joint BLDC Motor Controllers. This Python library provides an easy-to-use interface for communication and control of BLDC motor controllers used in Acrome robotic arm joints. It is designed to simplify the integration of Acrome’s robotic joint actuators into custom applications, allowing developers and researchers to focus on building advanced robotic systems without dealing with low-level communication details.",
12
12
  long_description=long_description,
13
13
  long_description_content_type="text/markdown",
14
14
  url="https://github.com/Acrome-Smart-Motion-Devices/python-library-new",
@@ -21,6 +21,11 @@ setuptools.setup(
21
21
  "Operating System :: OS Independent",
22
22
  ],
23
23
  packages=setuptools.find_packages(exclude=['tests', 'test']),
24
+ entry_points={
25
+ "console_scripts": [
26
+ "joint_GUI=gui.main:main",
27
+ ]
28
+ },
24
29
  install_requires=["pyserial>=3.5", "stm32loader>=0.5.1", "crccheck>=1.3.0", "requests>=2.31.0", "packaging>=23.2"],
25
30
  python_requires=">=3.7"
26
31
  )
@@ -1 +0,0 @@
1
- from joint import *