moons-motor 0.0.10__tar.gz → 0.1.2__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.
- {moons_motor-0.0.10 → moons_motor-0.1.2}/PKG-INFO +3 -2
- moons_motor-0.1.2/moons_motor/motor.py +368 -0
- {moons_motor-0.0.10 → moons_motor-0.1.2}/moons_motor.egg-info/PKG-INFO +3 -2
- {moons_motor-0.0.10 → moons_motor-0.1.2}/pyproject.toml +1 -1
- moons_motor-0.0.10/moons_motor/motor.py +0 -466
- {moons_motor-0.0.10 → moons_motor-0.1.2}/LICENSE +0 -0
- {moons_motor-0.0.10 → moons_motor-0.1.2}/README.md +0 -0
- {moons_motor-0.0.10 → moons_motor-0.1.2}/moons_motor/__init__.py +0 -0
- {moons_motor-0.0.10 → moons_motor-0.1.2}/moons_motor/observer.py +0 -0
- {moons_motor-0.0.10 → moons_motor-0.1.2}/moons_motor/simulate.py +0 -0
- {moons_motor-0.0.10 → moons_motor-0.1.2}/moons_motor/status.py +0 -0
- {moons_motor-0.0.10 → moons_motor-0.1.2}/moons_motor/subject.py +0 -0
- {moons_motor-0.0.10 → moons_motor-0.1.2}/moons_motor.egg-info/SOURCES.txt +0 -0
- {moons_motor-0.0.10 → moons_motor-0.1.2}/moons_motor.egg-info/dependency_links.txt +0 -0
- {moons_motor-0.0.10 → moons_motor-0.1.2}/moons_motor.egg-info/requires.txt +0 -0
- {moons_motor-0.0.10 → moons_motor-0.1.2}/moons_motor.egg-info/top_level.txt +0 -0
- {moons_motor-0.0.10 → moons_motor-0.1.2}/setup.cfg +0 -0
- {moons_motor-0.0.10 → moons_motor-0.1.2}/setup.py +0 -0
@@ -1,6 +1,6 @@
|
|
1
|
-
Metadata-Version: 2.
|
1
|
+
Metadata-Version: 2.4
|
2
2
|
Name: moons_motor
|
3
|
-
Version: 0.
|
3
|
+
Version: 0.1.2
|
4
4
|
Summary: This is a python library for controlling the Moons' motor through the serial port.
|
5
5
|
Author-email: miroc <mike8503111@gmail.com>
|
6
6
|
Project-URL: Repository, https://github.com/miroc99/moons_motor.git
|
@@ -16,6 +16,7 @@ Requires-Dist: pyserial
|
|
16
16
|
Requires-Dist: rich
|
17
17
|
Requires-Dist: python-socketio
|
18
18
|
Requires-Dist: requests
|
19
|
+
Dynamic: license-file
|
19
20
|
|
20
21
|
# Moons Motor
|
21
22
|
|
@@ -0,0 +1,368 @@
|
|
1
|
+
import serial
|
2
|
+
from serial.tools import list_ports
|
3
|
+
import re
|
4
|
+
import threading
|
5
|
+
from rich import print
|
6
|
+
from rich.console import Console
|
7
|
+
from rich.panel import Panel
|
8
|
+
import queue
|
9
|
+
from moons_motor.subject import Subject
|
10
|
+
import time
|
11
|
+
import threading
|
12
|
+
|
13
|
+
from dataclasses import dataclass
|
14
|
+
|
15
|
+
|
16
|
+
class StepperModules:
|
17
|
+
STM17S_3RN = "STM17S-3RN"
|
18
|
+
|
19
|
+
|
20
|
+
@dataclass(frozen=True)
|
21
|
+
class StepperCommand:
|
22
|
+
JOG: str = "CJ" # Start jogging
|
23
|
+
JOG_SPEED: str = "JS" # Jogging speed (Need to set before start jogging)
|
24
|
+
CHANGE_JOG_SPEED: str = "CS" # Change jogging speed while jogging
|
25
|
+
STOP_JOG: str = "SJ" # Stop jogging with deceleration
|
26
|
+
STOP: str = "ST" # Stop immediately (No deceleration)
|
27
|
+
STOP_DECEL: str = "STD" # Stop with deceleration
|
28
|
+
STOP_KILL: str = (
|
29
|
+
"SK" # Stop with deceleration(Control by AM) and kill all unexecuted commands
|
30
|
+
)
|
31
|
+
STOP_KILL_DECEL: str = (
|
32
|
+
"SKD" # Stop and kill all unexecuted commands with deceleration(Control by DE)
|
33
|
+
)
|
34
|
+
ENABLE: str = "ME" # Enable motor
|
35
|
+
DISABLE: str = "MD" # Disable motor
|
36
|
+
MOVE_ABSOLUTE: str = "FP" # Move to absolute position
|
37
|
+
MOVE_FIXED_DISTANCE: str = "FL" # Move to fixed distance
|
38
|
+
POSITION: str = "IP" # Motor absolute position(Calculated trajectory position)
|
39
|
+
TEMPERATURE: str = "IT" # Motor temperature
|
40
|
+
VOLTAGE: str = "IU" # Motor voltage
|
41
|
+
|
42
|
+
ENCODER_POSITION: str = "EP" # Encoder position
|
43
|
+
SET_POSITION: str = "SP" # Set encoder position
|
44
|
+
|
45
|
+
HOME: str = "SH" # Home position
|
46
|
+
VELOCITY: str = "VE" # Set velocity
|
47
|
+
|
48
|
+
ALARM_RESET: str = "AR" # Reset alarm
|
49
|
+
|
50
|
+
SET_RETURN_FORMAT_DECIMAL: str = "IFD" # Set return format to decimal
|
51
|
+
SET_RETURN_FORMAT_HEXADECIMAL: str = "IFH" # Set return format to hexadecimal
|
52
|
+
|
53
|
+
SET_TRANSMIT_DELAY: str = "TD" # Set transmit delay
|
54
|
+
|
55
|
+
|
56
|
+
class MoonsStepper(Subject):
|
57
|
+
motorAdress = [
|
58
|
+
"0",
|
59
|
+
"1",
|
60
|
+
"2",
|
61
|
+
"3",
|
62
|
+
"4",
|
63
|
+
"5",
|
64
|
+
"6",
|
65
|
+
"7",
|
66
|
+
"8",
|
67
|
+
"9",
|
68
|
+
"!",
|
69
|
+
'"',
|
70
|
+
"#",
|
71
|
+
"$",
|
72
|
+
"%",
|
73
|
+
"&",
|
74
|
+
"'",
|
75
|
+
"(",
|
76
|
+
")",
|
77
|
+
"*",
|
78
|
+
"+",
|
79
|
+
",",
|
80
|
+
"-",
|
81
|
+
".",
|
82
|
+
"/",
|
83
|
+
":",
|
84
|
+
";",
|
85
|
+
"<",
|
86
|
+
"=",
|
87
|
+
">",
|
88
|
+
"?",
|
89
|
+
"@",
|
90
|
+
]
|
91
|
+
|
92
|
+
def __init__(
|
93
|
+
self,
|
94
|
+
model: StepperModules,
|
95
|
+
VID,
|
96
|
+
PID,
|
97
|
+
SERIAL_NUM,
|
98
|
+
only_simlate=False,
|
99
|
+
universe=0,
|
100
|
+
):
|
101
|
+
super().__init__()
|
102
|
+
self.universe = universe
|
103
|
+
self.model = model # Motor model
|
104
|
+
self.only_simulate = only_simlate
|
105
|
+
self.device = "" # COM port description
|
106
|
+
self.VID = VID
|
107
|
+
self.PID = PID
|
108
|
+
self.SERIAL_NUM = SERIAL_NUM # ID for determent the deivice had same VID and PID, can be config using chips manufacturer tool
|
109
|
+
self.ser = None
|
110
|
+
self.Opened = False
|
111
|
+
self.recvQueue = queue.Queue()
|
112
|
+
self.sendQueue = queue.Queue()
|
113
|
+
self.pending_callbacks = {}
|
114
|
+
self.update_thread = None
|
115
|
+
self.is_updating = False
|
116
|
+
|
117
|
+
self.console = Console()
|
118
|
+
|
119
|
+
self.is_log_message = True
|
120
|
+
|
121
|
+
self.microstep = {
|
122
|
+
0: 200,
|
123
|
+
1: 400,
|
124
|
+
3: 2000,
|
125
|
+
4: 5000,
|
126
|
+
5: 10000,
|
127
|
+
6: 12800,
|
128
|
+
7: 18000,
|
129
|
+
8: 20000,
|
130
|
+
9: 21600,
|
131
|
+
10: 25000,
|
132
|
+
11: 25400,
|
133
|
+
12: 25600,
|
134
|
+
13: 36000,
|
135
|
+
14: 50000,
|
136
|
+
15: 50800,
|
137
|
+
}
|
138
|
+
|
139
|
+
# region connection & main functions
|
140
|
+
@staticmethod
|
141
|
+
def list_all_ports():
|
142
|
+
ports = list(list_ports.comports())
|
143
|
+
simple_ports = []
|
144
|
+
port_info = ""
|
145
|
+
for p in ports:
|
146
|
+
port_info += f"■ {p.device} {p.description} [blue]{p.usb_info()}[/blue]"
|
147
|
+
if p != ports[-1]:
|
148
|
+
port_info += "\n"
|
149
|
+
simple_ports.append(p.description)
|
150
|
+
print(Panel(port_info, title="All COMPorts"))
|
151
|
+
return simple_ports
|
152
|
+
|
153
|
+
@staticmethod
|
154
|
+
def process_response(response):
|
155
|
+
equal_sign_index = response.index("=")
|
156
|
+
address = response[0]
|
157
|
+
command = response[1:equal_sign_index]
|
158
|
+
value = response[equal_sign_index + 1 :]
|
159
|
+
|
160
|
+
if command == "IT" or command == "IU":
|
161
|
+
# Handle temperature response
|
162
|
+
value = int(value) / 10.0
|
163
|
+
return {
|
164
|
+
"address": address,
|
165
|
+
"command": command,
|
166
|
+
"value": value,
|
167
|
+
}
|
168
|
+
|
169
|
+
def __start_update_thread(self):
|
170
|
+
self.update_thread = threading.Thread(target=self.update, daemon=True)
|
171
|
+
self.is_updating = True
|
172
|
+
self.update_thread.start()
|
173
|
+
|
174
|
+
def connect(self, COM=None, baudrate=9600, callback=None):
|
175
|
+
# Simulate mode
|
176
|
+
if self.only_simulate:
|
177
|
+
self.Opened = True
|
178
|
+
self.device = f"Simulate-{self.universe}"
|
179
|
+
print(f"{self.device} connected")
|
180
|
+
if callback:
|
181
|
+
callback(self.device, self.Opened)
|
182
|
+
return
|
183
|
+
|
184
|
+
def attempt_connect(COM, baudrate):
|
185
|
+
try:
|
186
|
+
self.ser = serial.Serial(port=COM, baudrate=baudrate)
|
187
|
+
if self.ser is None:
|
188
|
+
self.Opened = False
|
189
|
+
if self.ser.is_open:
|
190
|
+
self.Opened = True
|
191
|
+
print(f"[bold green]Device connected[/bold green]: {self.device}")
|
192
|
+
|
193
|
+
except Exception as e:
|
194
|
+
print(f"[bold red]Device error:[/bold red] {e} ")
|
195
|
+
self.Opened = False
|
196
|
+
|
197
|
+
ports = list(list_ports.comports())
|
198
|
+
if COM is not None and not self.only_simulate:
|
199
|
+
attempt_connect(COM, baudrate)
|
200
|
+
if callback:
|
201
|
+
callback(self.device, self.Opened)
|
202
|
+
else:
|
203
|
+
for p in ports:
|
204
|
+
m = re.match(
|
205
|
+
r"USB\s*VID:PID=(\w+):(\w+)\s*SER=([A-Za-z0-9]*)", p.usb_info()
|
206
|
+
)
|
207
|
+
print(p.usb_info())
|
208
|
+
if (
|
209
|
+
m
|
210
|
+
and m.group(1) == self.VID
|
211
|
+
and m.group(2) == self.PID
|
212
|
+
# and m.group(3) == self.SERIAL_NUM
|
213
|
+
):
|
214
|
+
if m.group(3) == self.SERIAL_NUM or self.SERIAL_NUM == "":
|
215
|
+
print(
|
216
|
+
f"[bold yellow]Device founded:[/bold yellow] {p.description} | VID: {m.group(1)} | PID: {m.group(2)} | SER: {m.group(3)}"
|
217
|
+
)
|
218
|
+
|
219
|
+
self.device = p.description
|
220
|
+
|
221
|
+
attempt_connect(p.device, baudrate)
|
222
|
+
|
223
|
+
break
|
224
|
+
|
225
|
+
if self.only_simulate:
|
226
|
+
self.device = "Simulate"
|
227
|
+
self.Opened = True
|
228
|
+
time.sleep(0.5)
|
229
|
+
self.__start_update_thread()
|
230
|
+
if callback:
|
231
|
+
callback(self.device, self.Opened)
|
232
|
+
|
233
|
+
if not self.Opened:
|
234
|
+
print(f"[bold red]Device not found[/bold red]")
|
235
|
+
if callback:
|
236
|
+
callback(self.device, self.Opened)
|
237
|
+
|
238
|
+
def disconnect(self):
|
239
|
+
self.sendQueue.queue.clear()
|
240
|
+
self.recvQueue.queue.clear()
|
241
|
+
self.is_updating = False
|
242
|
+
self.update_thread = None
|
243
|
+
if self.only_simulate:
|
244
|
+
self.listen = False
|
245
|
+
self.is_sending = False
|
246
|
+
self.Opened = False
|
247
|
+
print(f"Simulate-{self.universe} disconnected")
|
248
|
+
return
|
249
|
+
if self.ser is not None and self.ser.is_open:
|
250
|
+
self.listen = False
|
251
|
+
self.is_sending = False
|
252
|
+
self.Opened = False
|
253
|
+
self.ser.flush()
|
254
|
+
self.ser.close()
|
255
|
+
print(f"[bold red]Device disconnected[/bold red]: {self.device}")
|
256
|
+
|
257
|
+
def send(self, command, eol=b"\r"):
|
258
|
+
if (self.ser != None and self.ser.is_open) or self.only_simulate:
|
259
|
+
self.temp_cmd = command + "\r"
|
260
|
+
|
261
|
+
if self.ser is not None or not self.only_simulate:
|
262
|
+
self.temp_cmd += "\r"
|
263
|
+
self.ser.write(self.temp_cmd.encode("ascii"))
|
264
|
+
if self.is_log_message:
|
265
|
+
print(
|
266
|
+
f"[bold green]Send to {self.device}:[/bold green] {self.temp_cmd}"
|
267
|
+
)
|
268
|
+
super().notify_observers(f"{self.universe}-{self.temp_cmd}")
|
269
|
+
else:
|
270
|
+
print(f"Target device is not opened. Command: {command}")
|
271
|
+
|
272
|
+
def send_command(self, address="", command="", value=None):
|
273
|
+
if command == "":
|
274
|
+
print("Command can't be empty")
|
275
|
+
return
|
276
|
+
if value is not None:
|
277
|
+
command = self.addressed_cmd(address, command + str(value))
|
278
|
+
else:
|
279
|
+
command = self.addressed_cmd(address, command)
|
280
|
+
|
281
|
+
self.sendQueue.put_nowait(command)
|
282
|
+
|
283
|
+
def update(self):
|
284
|
+
|
285
|
+
while self.is_updating:
|
286
|
+
if self.ser is not None:
|
287
|
+
if self.ser.in_waiting > 0:
|
288
|
+
response = self.ser.read(self.ser.in_waiting)
|
289
|
+
response = response.decode("ascii", errors="ignore").strip()
|
290
|
+
self.handle_recv(response)
|
291
|
+
time.sleep(0.01) # Send delay
|
292
|
+
if self.sendQueue.empty() != True:
|
293
|
+
while not self.sendQueue.empty():
|
294
|
+
cmd = self.sendQueue.get_nowait()
|
295
|
+
self.send(cmd)
|
296
|
+
self.sendQueue.task_done()
|
297
|
+
time.sleep(
|
298
|
+
0.01
|
299
|
+
) # Time for RS485 converter to switch between Transmit and Receive mode
|
300
|
+
|
301
|
+
def handle_recv(self, response):
|
302
|
+
if "*" in response:
|
303
|
+
print("buffered_ack")
|
304
|
+
elif "%" in response:
|
305
|
+
print(f"[bold green](v)success_ack[/bold green]")
|
306
|
+
elif "?" in response:
|
307
|
+
print(f"[bold red](x)fail_ack[/bold red]")
|
308
|
+
else:
|
309
|
+
print(f"[bold blue]Received from {self.device}: [/bold blue]", response)
|
310
|
+
self.recvQueue.put_nowait(response)
|
311
|
+
|
312
|
+
for command, callback in list(self.pending_callbacks.items()):
|
313
|
+
if command in response:
|
314
|
+
if callback:
|
315
|
+
callback(response)
|
316
|
+
del self.pending_callbacks[command]
|
317
|
+
break
|
318
|
+
|
319
|
+
# endregion
|
320
|
+
|
321
|
+
# region motor motion functions
|
322
|
+
|
323
|
+
def setup_motor(self, motor_address="", kill=False):
|
324
|
+
if kill:
|
325
|
+
self.stop_and_kill(motor_address)
|
326
|
+
self.set_transmit_delay(motor_address, 25)
|
327
|
+
self.set_return_format_dexcimal(motor_address)
|
328
|
+
|
329
|
+
def home(self, motor_address="", speed=0.3, onComplete=None):
|
330
|
+
self.send_command(
|
331
|
+
address=motor_address, command=StepperCommand.VELOCITY, value=speed
|
332
|
+
)
|
333
|
+
self.send_command(
|
334
|
+
address=motor_address, command=StepperCommand.HOME, value="3F"
|
335
|
+
)
|
336
|
+
self.send_command(
|
337
|
+
address=motor_address, command=StepperCommand.ENCODER_POSITION
|
338
|
+
)
|
339
|
+
self.send_command(
|
340
|
+
address=motor_address, command=StepperCommand.SET_POSITION, value=0
|
341
|
+
)
|
342
|
+
if onComplete:
|
343
|
+
self.get_status(
|
344
|
+
motor_address, StepperCommand.SET_POSITION, callback=onComplete
|
345
|
+
)
|
346
|
+
|
347
|
+
# endregion
|
348
|
+
def get_status(self, motor_address, command: StepperCommand, callback=None):
|
349
|
+
command = self.addressed_cmd(motor_address, command)
|
350
|
+
if callback:
|
351
|
+
self.pending_callbacks[command] = callback
|
352
|
+
self.sendQueue.put_nowait(command)
|
353
|
+
|
354
|
+
# endregion
|
355
|
+
|
356
|
+
# region utility functions
|
357
|
+
|
358
|
+
def addressed_cmd(self, motor_address, command):
|
359
|
+
return f"{motor_address}{command}"
|
360
|
+
|
361
|
+
|
362
|
+
# endregion
|
363
|
+
|
364
|
+
# SERIAL => 上次已知父系(尾巴+A) 或是事件分頁
|
365
|
+
# reg USB\s*VID:PID=(\w+):(\w+)\s*SER=([A-Za-z0-9]+)
|
366
|
+
|
367
|
+
# serial_num 裝置例項路徑
|
368
|
+
# TD(Tramsmit Delay) = 15
|
@@ -1,6 +1,6 @@
|
|
1
|
-
Metadata-Version: 2.
|
1
|
+
Metadata-Version: 2.4
|
2
2
|
Name: moons_motor
|
3
|
-
Version: 0.
|
3
|
+
Version: 0.1.2
|
4
4
|
Summary: This is a python library for controlling the Moons' motor through the serial port.
|
5
5
|
Author-email: miroc <mike8503111@gmail.com>
|
6
6
|
Project-URL: Repository, https://github.com/miroc99/moons_motor.git
|
@@ -16,6 +16,7 @@ Requires-Dist: pyserial
|
|
16
16
|
Requires-Dist: rich
|
17
17
|
Requires-Dist: python-socketio
|
18
18
|
Requires-Dist: requests
|
19
|
+
Dynamic: license-file
|
19
20
|
|
20
21
|
# Moons Motor
|
21
22
|
|
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
|
|
4
4
|
|
5
5
|
[project]
|
6
6
|
name = "moons_motor"
|
7
|
-
version = "0.
|
7
|
+
version = "0.1.2"
|
8
8
|
authors = [{ name = "miroc", email = "mike8503111@gmail.com" }]
|
9
9
|
description = "This is a python library for controlling the Moons' motor through the serial port."
|
10
10
|
readme = "README.md"
|
@@ -1,466 +0,0 @@
|
|
1
|
-
import serial
|
2
|
-
from serial.tools import list_ports
|
3
|
-
import re
|
4
|
-
import threading
|
5
|
-
from rich import print
|
6
|
-
from rich.console import Console
|
7
|
-
from rich.panel import Panel
|
8
|
-
import queue
|
9
|
-
from moons_motor.subject import Subject
|
10
|
-
import time
|
11
|
-
|
12
|
-
|
13
|
-
class StepperModules:
|
14
|
-
STM17S_3RN = "STM17S-3RN"
|
15
|
-
|
16
|
-
|
17
|
-
class MoonsStepper(Subject):
|
18
|
-
motorAdress = [
|
19
|
-
"0",
|
20
|
-
"1",
|
21
|
-
"2",
|
22
|
-
"3",
|
23
|
-
"4",
|
24
|
-
"5",
|
25
|
-
"6",
|
26
|
-
"7",
|
27
|
-
"8",
|
28
|
-
"9",
|
29
|
-
"!",
|
30
|
-
'"',
|
31
|
-
"#",
|
32
|
-
"$",
|
33
|
-
"%",
|
34
|
-
"&",
|
35
|
-
"'",
|
36
|
-
"(",
|
37
|
-
")",
|
38
|
-
"*",
|
39
|
-
"+",
|
40
|
-
",",
|
41
|
-
"-",
|
42
|
-
".",
|
43
|
-
"/",
|
44
|
-
":",
|
45
|
-
";",
|
46
|
-
"<",
|
47
|
-
"=",
|
48
|
-
">",
|
49
|
-
"?",
|
50
|
-
"@",
|
51
|
-
]
|
52
|
-
|
53
|
-
def __init__(
|
54
|
-
self,
|
55
|
-
model: StepperModules,
|
56
|
-
VID,
|
57
|
-
PID,
|
58
|
-
SERIAL_NUM,
|
59
|
-
only_simlate=False,
|
60
|
-
universe=0,
|
61
|
-
):
|
62
|
-
super().__init__()
|
63
|
-
self.universe = universe
|
64
|
-
self.model = model
|
65
|
-
self.only_simulate = only_simlate
|
66
|
-
self.device = ""
|
67
|
-
self.VID = VID
|
68
|
-
self.PID = PID
|
69
|
-
self.SERIAL_NUM = SERIAL_NUM
|
70
|
-
self.ser = None
|
71
|
-
self.listeningBuffer = ""
|
72
|
-
self.listeningBufferPre = ""
|
73
|
-
self.transmitDelay = 0.010
|
74
|
-
self.lock = False
|
75
|
-
self.Opened = False
|
76
|
-
self.new_data_event = threading.Event()
|
77
|
-
self.new_value_event = threading.Event()
|
78
|
-
self.on_send_event = threading.Event()
|
79
|
-
self.recvQueue = queue.Queue()
|
80
|
-
self.sendQueue = queue.Queue()
|
81
|
-
self.command_cache = queue.Queue()
|
82
|
-
self.usedSendQueue = queue.Queue()
|
83
|
-
|
84
|
-
self.console = Console()
|
85
|
-
|
86
|
-
self.is_log_message = True
|
87
|
-
|
88
|
-
self.microstep = {
|
89
|
-
0: 200,
|
90
|
-
1: 400,
|
91
|
-
3: 2000,
|
92
|
-
4: 5000,
|
93
|
-
5: 10000,
|
94
|
-
6: 12800,
|
95
|
-
7: 18000,
|
96
|
-
8: 20000,
|
97
|
-
9: 21600,
|
98
|
-
10: 25000,
|
99
|
-
11: 25400,
|
100
|
-
12: 25600,
|
101
|
-
13: 36000,
|
102
|
-
14: 50000,
|
103
|
-
15: 50800,
|
104
|
-
}
|
105
|
-
|
106
|
-
# region connection & main functions
|
107
|
-
@staticmethod
|
108
|
-
def list_all_ports():
|
109
|
-
ports = list(list_ports.comports())
|
110
|
-
simple_ports = []
|
111
|
-
port_info = ""
|
112
|
-
for p in ports:
|
113
|
-
port_info += f"■ {p.device} {p.description} [blue]{p.usb_info()}[/blue]"
|
114
|
-
if p != ports[-1]:
|
115
|
-
port_info += "\n"
|
116
|
-
simple_ports.append(p.description)
|
117
|
-
print(Panel(port_info, title="All COMPorts"))
|
118
|
-
return simple_ports
|
119
|
-
|
120
|
-
def connect(self, COM=None, baudrate=9600, callback=None):
|
121
|
-
if self.only_simulate:
|
122
|
-
self.Opened = True
|
123
|
-
self.device = f"Simulate-{self.universe}"
|
124
|
-
print(f"{self.device} connected")
|
125
|
-
if callback:
|
126
|
-
callback(self.device, self.Opened)
|
127
|
-
return
|
128
|
-
|
129
|
-
def attempt_connect(COM, baudrate):
|
130
|
-
try:
|
131
|
-
self.ser = serial.Serial(COM, baudrate)
|
132
|
-
if self.ser is None:
|
133
|
-
# print("> Device not found")
|
134
|
-
self.Opened = False
|
135
|
-
if self.ser.is_open:
|
136
|
-
# print(f"Device: {self.device} | COM: {COM} connected")
|
137
|
-
self.Opened = True
|
138
|
-
except:
|
139
|
-
print("> Device error")
|
140
|
-
self.Opened = False
|
141
|
-
|
142
|
-
if COM is not None and not self.only_simulate:
|
143
|
-
attempt_connect(COM, baudrate)
|
144
|
-
if callback:
|
145
|
-
callback(self.device, self.Opened)
|
146
|
-
return
|
147
|
-
ports = list(list_ports.comports())
|
148
|
-
for p in ports:
|
149
|
-
m = re.match(
|
150
|
-
r"USB\s*VID:PID=(\w+):(\w+)\s*SER=([A-Za-z0-9]*)", p.usb_info()
|
151
|
-
)
|
152
|
-
print(m, p.usb_info())
|
153
|
-
if (
|
154
|
-
m
|
155
|
-
and m.group(1) == self.VID
|
156
|
-
and m.group(2) == self.PID
|
157
|
-
# and m.group(3) == self.SERIAL_NUM
|
158
|
-
):
|
159
|
-
print("find vid pid match")
|
160
|
-
if m.group(3) == self.SERIAL_NUM or self.SERIAL_NUM == "":
|
161
|
-
print(
|
162
|
-
f"Device: {p.description} | VID: {m.group(1)} | PID: {m.group(2)} | SER: {m.group(3)} connected"
|
163
|
-
)
|
164
|
-
|
165
|
-
self.device = p.description
|
166
|
-
|
167
|
-
attempt_connect(p.device, baudrate)
|
168
|
-
if callback:
|
169
|
-
callback(self.device, self.Opened)
|
170
|
-
break
|
171
|
-
break
|
172
|
-
|
173
|
-
if self.only_simulate:
|
174
|
-
self.device = "Simulate"
|
175
|
-
self.Opened = True
|
176
|
-
if not self.Opened:
|
177
|
-
print("> Device not found")
|
178
|
-
if callback:
|
179
|
-
callback(self.device, self.Opened)
|
180
|
-
|
181
|
-
def disconnect(self):
|
182
|
-
if self.only_simulate:
|
183
|
-
self.listen = False
|
184
|
-
self.is_sending = False
|
185
|
-
self.Opened = False
|
186
|
-
print(f"Simulate-{self.universe} disconnected")
|
187
|
-
return
|
188
|
-
if self.ser is not None and self.ser.is_open:
|
189
|
-
self.listen = False
|
190
|
-
self.is_sending = False
|
191
|
-
self.Opened = False
|
192
|
-
self.ser.flush()
|
193
|
-
self.ser.close()
|
194
|
-
print(f"{self.device} Disconnected")
|
195
|
-
|
196
|
-
def send(self, command, eol=b"\r"):
|
197
|
-
if (self.ser != None and self.ser.is_open) or self.only_simulate:
|
198
|
-
self.temp_cmd = command + "\r"
|
199
|
-
|
200
|
-
if "~" in self.temp_cmd:
|
201
|
-
# remove ~ in self.temp_cmd
|
202
|
-
self.temp_cmd = self.temp_cmd[1:]
|
203
|
-
else:
|
204
|
-
self.usedSendQueue.put(self.temp_cmd)
|
205
|
-
if self.ser is not None or not self.only_simulate:
|
206
|
-
self.temp_cmd += "\r"
|
207
|
-
self.ser.write(self.temp_cmd.encode("ascii"))
|
208
|
-
if self.is_log_message:
|
209
|
-
print(
|
210
|
-
f"[bold green]Send to {self.device}:[/bold green] {self.temp_cmd}"
|
211
|
-
)
|
212
|
-
super().notify_observers(f"{self.universe}-{self.temp_cmd}")
|
213
|
-
else:
|
214
|
-
print(f"Target device is not opened. Command: {command}")
|
215
|
-
|
216
|
-
def read(self, timeout=1):
|
217
|
-
if self.ser is not None and self.ser.is_open:
|
218
|
-
print("reading...")
|
219
|
-
try:
|
220
|
-
start_time = time.time()
|
221
|
-
while time.time() - start_time < timeout:
|
222
|
-
if self.ser.in_waiting > 0:
|
223
|
-
response = self.ser.read(self.ser.in_waiting)
|
224
|
-
response = response.decode("utf-8").strip()
|
225
|
-
if self.is_log_message:
|
226
|
-
print(
|
227
|
-
f"[bold blue]Recv from {self.device} :[/bold blue] {response}"
|
228
|
-
)
|
229
|
-
return response
|
230
|
-
time.sleep(0.01)
|
231
|
-
print("reading timeout")
|
232
|
-
return None
|
233
|
-
except Exception as e:
|
234
|
-
print(f"Error when reading serial port: {str(e)}")
|
235
|
-
return None
|
236
|
-
elif self.only_simulate:
|
237
|
-
simulated_response = "simulate response"
|
238
|
-
if self.is_log_message:
|
239
|
-
print(
|
240
|
-
f"[bold blue]Recv from simulate device:[/bold blue] {simulated_response}"
|
241
|
-
)
|
242
|
-
return simulated_response
|
243
|
-
else:
|
244
|
-
print("Device not open, read fail.")
|
245
|
-
return None
|
246
|
-
|
247
|
-
# endregion
|
248
|
-
|
249
|
-
# region motor motion functions
|
250
|
-
def enable(self, motor_address="", enable=True):
|
251
|
-
cmd = "ME" if enable else "MD"
|
252
|
-
self.send(self.addressed_cmd(motor_address, cmd))
|
253
|
-
|
254
|
-
def move_absolute(self, motor_address="", position=0, speed=0.15):
|
255
|
-
self.send(self.addressed_cmd(motor_address, f"VE{speed}"))
|
256
|
-
self.send(self.addressed_cmd(motor_address, f"FP{position}"))
|
257
|
-
|
258
|
-
def move_fixed_distance(self, motor_address="", distance=100, speed=0.15):
|
259
|
-
self.send(self.addressed_cmd(motor_address, "VE{}".format(speed)))
|
260
|
-
self.send(self.addressed_cmd(motor_address, "FL{}".format(int(distance))))
|
261
|
-
|
262
|
-
def start_jog(self, motor_address="", speed=0.15, direction="CW"):
|
263
|
-
self.send(self.addressed_cmd(motor_address, "JS{}".format(speed)))
|
264
|
-
time.sleep(0.01)
|
265
|
-
self.send(self.addressed_cmd(motor_address, "CJ"))
|
266
|
-
# self.send(self.addressed_cmd(motor_address, "CS{}".format(speed)))
|
267
|
-
|
268
|
-
def change_jog_speed(self, motor_address="", speed=0.15):
|
269
|
-
self.send(self.addressed_cmd(motor_address, "CS{}".format(speed)))
|
270
|
-
|
271
|
-
def stop_jog(self, motor_address=""):
|
272
|
-
self.send(self.addressed_cmd(motor_address, "SJ"))
|
273
|
-
|
274
|
-
def stop(self, motor_address=""):
|
275
|
-
self.send(self.addressed_cmd(motor_address, "ST"))
|
276
|
-
|
277
|
-
def stop_with_deceleration(self, motor_address=""):
|
278
|
-
self.send(self.addressed_cmd(motor_address, "STD"))
|
279
|
-
|
280
|
-
def stop_and_kill(self, motor_address="", with_deceleration=True):
|
281
|
-
if with_deceleration:
|
282
|
-
self.send(self.addressed_cmd(motor_address, "SKD"))
|
283
|
-
else:
|
284
|
-
self.send(self.addressed_cmd(motor_address, "SK"))
|
285
|
-
|
286
|
-
def setup_motor(self, motor_address="", kill=False):
|
287
|
-
if kill:
|
288
|
-
self.stop_and_kill(motor_address)
|
289
|
-
self.set_transmit_delay(motor_address, 25)
|
290
|
-
self.set_return_format_dexcimal(motor_address)
|
291
|
-
|
292
|
-
def calibrate(self, motor_address="", speed=0.3, onStart=None, onComplete=None):
|
293
|
-
self.send(self.addressed_cmd(motor_address, "VE{}".format(speed)))
|
294
|
-
# time.sleep(0.01)
|
295
|
-
self.send(self.addressed_cmd(motor_address, "DI10"))
|
296
|
-
# time.sleep(0.01)
|
297
|
-
self.send(self.addressed_cmd(motor_address, "SH3F"))
|
298
|
-
# time.sleep(0.01)
|
299
|
-
self.send(self.addressed_cmd(motor_address, "EP0"))
|
300
|
-
# time.sleep(0.01)
|
301
|
-
self.send(self.addressed_cmd(motor_address, "SP0"))
|
302
|
-
|
303
|
-
def alarm_reset(self, motor_address=""):
|
304
|
-
self.send(self.addressed_cmd(motor_address, "AR"))
|
305
|
-
|
306
|
-
# speed slow= 0.25, medium=1, fast=5
|
307
|
-
def set_transmit_delay(self, motor_address="", delay=15):
|
308
|
-
self.send(self.addressed_cmd(motor_address, "TD{}".format(delay)))
|
309
|
-
|
310
|
-
# endregion
|
311
|
-
|
312
|
-
# region motor status functions
|
313
|
-
def get_position(self, motor_address):
|
314
|
-
self.send(self.addressed_cmd(motor_address, "IP"))
|
315
|
-
return self.read()
|
316
|
-
# self.new_value_event.wait(timeout=0.5)
|
317
|
-
# return self.get_value()
|
318
|
-
|
319
|
-
def get_temperature(self, motor_address):
|
320
|
-
self.send(self.addressed_cmd(motor_address, "IT"))
|
321
|
-
# self.new_value_event.wait(timeout=0.5)
|
322
|
-
return self.read()
|
323
|
-
# return int(self.get_value()) / 10
|
324
|
-
|
325
|
-
def get_sensor_status(self, motor_address):
|
326
|
-
self.send(self.addressed_cmd(motor_address, "IS"))
|
327
|
-
return self.read()
|
328
|
-
# self.new_value_event.wait(timeout=0.5)
|
329
|
-
# return self.get_value()
|
330
|
-
|
331
|
-
def get_votalge(self, motor_address):
|
332
|
-
self.send(self.addressed_cmd(motor_address, "IU"))
|
333
|
-
return self.read()
|
334
|
-
# self.new_value_event.wait(timeout=0.5)
|
335
|
-
# return self.get_value()
|
336
|
-
|
337
|
-
def get_acceleration(self, motor_address):
|
338
|
-
self.send(self.addressed_cmd(motor_address, "AC"))
|
339
|
-
return self.read()
|
340
|
-
# self.new_value_event.wait(timeout=0.5)
|
341
|
-
# return self.get_value()
|
342
|
-
|
343
|
-
def get_deceleration(self, motor_address):
|
344
|
-
self.send(self.addressed_cmd(motor_address, "DE"))
|
345
|
-
return self.read()
|
346
|
-
# self.new_value_event.wait(timeout=0.5)
|
347
|
-
# return self.get_value()
|
348
|
-
|
349
|
-
def get_velocity(self, motor_address):
|
350
|
-
self.send(self.addressed_cmd(motor_address, "VE"))
|
351
|
-
return self.read()
|
352
|
-
# self.new_value_event.wait(timeout=0.5)
|
353
|
-
# return self.get_value()
|
354
|
-
|
355
|
-
def get_distance(self, motor_address):
|
356
|
-
self.send(self.addressed_cmd(motor_address, "DI"))
|
357
|
-
return self.read()
|
358
|
-
# self.new_value_event.wait(timeout=0.5)
|
359
|
-
# return self.get_value()
|
360
|
-
|
361
|
-
def get_jog_speed(self, motor_address):
|
362
|
-
self.send(self.addressed_cmd(motor_address, "JS"))
|
363
|
-
# self.new_value_event.wait(timeout=0.5)
|
364
|
-
# return self.get_value()
|
365
|
-
return self.read()
|
366
|
-
|
367
|
-
def get_info(self, motor_address, progress=None):
|
368
|
-
self.set_return_format_dexcimal(motor_address)
|
369
|
-
self.motor_wait(motor_address, 0.1)
|
370
|
-
totalInfoCount = 7
|
371
|
-
pos = self.extractValueFromResponse(self.get_position(motor_address))
|
372
|
-
if progress:
|
373
|
-
progress(round(1 / totalInfoCount, 1))
|
374
|
-
temp = (
|
375
|
-
int(self.extractValueFromResponse(self.get_temperature(motor_address))) / 10
|
376
|
-
)
|
377
|
-
if progress:
|
378
|
-
progress(round(2 / totalInfoCount, 1))
|
379
|
-
vol = int(self.extractValueFromResponse(self.get_votalge(motor_address))) / 10
|
380
|
-
if progress:
|
381
|
-
progress(round(3 / totalInfoCount, 1))
|
382
|
-
accel = self.extractValueFromResponse(self.get_acceleration(motor_address))
|
383
|
-
if progress:
|
384
|
-
progress(round(4 / totalInfoCount, 1))
|
385
|
-
decel = self.extractValueFromResponse(self.get_deceleration(motor_address))
|
386
|
-
if progress:
|
387
|
-
progress(round(5 / totalInfoCount, 1))
|
388
|
-
jogsp = self.extractValueFromResponse(self.get_jog_speed(motor_address))
|
389
|
-
if progress:
|
390
|
-
progress(round(6 / totalInfoCount, 1))
|
391
|
-
info = {
|
392
|
-
"pos": pos,
|
393
|
-
"temp": temp,
|
394
|
-
"vol": vol,
|
395
|
-
"accel": accel,
|
396
|
-
"decel": decel,
|
397
|
-
"jogsp": jogsp,
|
398
|
-
}
|
399
|
-
if progress:
|
400
|
-
progress(round(7 / totalInfoCount))
|
401
|
-
|
402
|
-
return info
|
403
|
-
|
404
|
-
def get_status(self, motor_address) -> str:
|
405
|
-
self.set_return_format_dexcimal(motor_address)
|
406
|
-
self.send(self.addressed_cmd(motor_address, "RS"))
|
407
|
-
self.new_value_event.wait(timeout=0.5)
|
408
|
-
return str(self.get_value())
|
409
|
-
|
410
|
-
def set_return_format_dexcimal(self, motor_address):
|
411
|
-
self.send(self.addressed_cmd(motor_address, "IFD"))
|
412
|
-
|
413
|
-
def set_return_format_hexdecimal(self, motor_address):
|
414
|
-
self.send(self.addressed_cmd(motor_address, "IFH"))
|
415
|
-
|
416
|
-
# endregion
|
417
|
-
|
418
|
-
# region utility functions
|
419
|
-
def motor_wait(self, motor_address, wait_time):
|
420
|
-
self.send(self.addressed_cmd(motor_address, "WT{}".format(wait_time)))
|
421
|
-
|
422
|
-
def addressed_cmd(self, motor_address, command):
|
423
|
-
if motor_address == "":
|
424
|
-
return f"~{command}"
|
425
|
-
return f"{motor_address}{command}"
|
426
|
-
|
427
|
-
def extractValueFromResponse(self, response):
|
428
|
-
pattern = r"=(.*)"
|
429
|
-
if response == None:
|
430
|
-
return None
|
431
|
-
result = re.search(pattern, response)
|
432
|
-
if result:
|
433
|
-
return result.group(1)
|
434
|
-
else:
|
435
|
-
return None
|
436
|
-
|
437
|
-
def get_value(self):
|
438
|
-
print("Waiting for value")
|
439
|
-
self.new_data_event.wait(timeout=0.5)
|
440
|
-
print("Recv:" + self.listeningBufferPre)
|
441
|
-
self.new_data_event.clear()
|
442
|
-
return self.listeningBufferPre
|
443
|
-
# if "%" in self.listeningBufferPre:
|
444
|
-
# return "success_ack"
|
445
|
-
# if "?" in self.listeningBufferPre:
|
446
|
-
# return "fail_ack"
|
447
|
-
# if "*" in self.listeningBufferPre:
|
448
|
-
# return "buffered_ack"
|
449
|
-
# self.new_value_event.set()
|
450
|
-
# pattern = r"=(\w+(?:\.\w+)?|\d+(?:\.\d+)?)"
|
451
|
-
# result = re.search(pattern, self.listeningBufferPre)
|
452
|
-
# self.listeningBufferPre = ""
|
453
|
-
# self.new_value_event.clear()
|
454
|
-
# if result:
|
455
|
-
# return result.group(1)
|
456
|
-
# else:
|
457
|
-
# return "No_value_found"
|
458
|
-
|
459
|
-
|
460
|
-
# endregion
|
461
|
-
|
462
|
-
# SERIAL => 上次已知父系(尾巴+A) 或是事件分頁
|
463
|
-
# reg USB\s*VID:PID=(\w+):(\w+)\s*SER=([A-Za-z0-9]+)
|
464
|
-
|
465
|
-
# serial_num 裝置例項路徑
|
466
|
-
# TD(Tramsmit Delay) = 15
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|