moons-motor 0.0.1__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.1/PKG-INFO +30 -0
- moons_motor-0.0.1/README.md +12 -0
- moons_motor-0.0.1/moons_motor/__init__.py +1 -0
- moons_motor-0.0.1/moons_motor/moons_motor.py +649 -0
- moons_motor-0.0.1/moons_motor.egg-info/PKG-INFO +30 -0
- moons_motor-0.0.1/moons_motor.egg-info/SOURCES.txt +10 -0
- moons_motor-0.0.1/moons_motor.egg-info/dependency_links.txt +1 -0
- moons_motor-0.0.1/moons_motor.egg-info/requires.txt +3 -0
- moons_motor-0.0.1/moons_motor.egg-info/top_level.txt +1 -0
- moons_motor-0.0.1/pyproject.toml +31 -0
- moons_motor-0.0.1/setup.cfg +4 -0
- moons_motor-0.0.1/setup.py +10 -0
@@ -0,0 +1,30 @@
|
|
1
|
+
Metadata-Version: 2.1
|
2
|
+
Name: moons_motor
|
3
|
+
Version: 0.0.1
|
4
|
+
Summary: This is a python library for controlling the Moons' motor through the serial port.
|
5
|
+
Author: miroc99
|
6
|
+
Author-email: miroc <mike8503111@gmail.com>
|
7
|
+
Project-URL: Repository, https://github.com/miroc99/moons_motor.git
|
8
|
+
Classifier: Development Status :: 3 - Alpha
|
9
|
+
Classifier: Intended Audience :: Developers
|
10
|
+
Classifier: Topic :: Terminals :: Serial
|
11
|
+
Classifier: License :: OSI Approved :: MIT License
|
12
|
+
Classifier: Programming Language :: Python :: 3.12
|
13
|
+
Requires-Python: >=3.12
|
14
|
+
Description-Content-Type: text/markdown
|
15
|
+
Requires-Dist: pyserial
|
16
|
+
Requires-Dist: rich
|
17
|
+
Requires-Dist: python-socketio
|
18
|
+
|
19
|
+
# Moons Motor
|
20
|
+
|
21
|
+
This is a python library for control moons motor through serial port.
|
22
|
+
|
23
|
+
## Compatibility
|
24
|
+
Now only support Windows.
|
25
|
+
|
26
|
+
## Installing
|
27
|
+
Install through `pip`
|
28
|
+
```bash
|
29
|
+
python -m pip install moons_motor
|
30
|
+
```
|
@@ -0,0 +1 @@
|
|
1
|
+
from moons_motor import *
|
@@ -0,0 +1,649 @@
|
|
1
|
+
import serial
|
2
|
+
from serial.tools import list_ports
|
3
|
+
import re
|
4
|
+
import time
|
5
|
+
import threading
|
6
|
+
import socketio
|
7
|
+
from rich import print
|
8
|
+
from rich.console import Console
|
9
|
+
from rich.panel import Panel
|
10
|
+
import queue
|
11
|
+
|
12
|
+
|
13
|
+
class moons_stepper_status:
|
14
|
+
def __init__(self, address=""):
|
15
|
+
|
16
|
+
# info
|
17
|
+
self.address = ""
|
18
|
+
self.position = 0 # IP
|
19
|
+
self.temperature = 0 # IT
|
20
|
+
self.sensor_status = 0 # IS
|
21
|
+
self.voltage = 0 # IU
|
22
|
+
self.acceleration = 0 # AC
|
23
|
+
self.deceleration = 0 # DE
|
24
|
+
self.velocity = 0 # VE
|
25
|
+
self.distance = 0 # DI
|
26
|
+
self.jog_speed = 0 # JS
|
27
|
+
|
28
|
+
# status
|
29
|
+
self.status_string = ""
|
30
|
+
self.alarm = False
|
31
|
+
self.disabled = False
|
32
|
+
self.drive_fault = False
|
33
|
+
self.moving = False
|
34
|
+
self.homing = False
|
35
|
+
self.jogging = False
|
36
|
+
self.motion_in_progess = False
|
37
|
+
self.ready = False
|
38
|
+
self.stoping = False
|
39
|
+
self.waiting = False
|
40
|
+
|
41
|
+
def update_info(self, info: dict) -> bool:
|
42
|
+
if info is None or len(info) < 1:
|
43
|
+
print("Update failed: input is None")
|
44
|
+
return False
|
45
|
+
self.position = info["pos"]
|
46
|
+
self.temperature = info["temp"]
|
47
|
+
self.sensor_status = info["sensor"]
|
48
|
+
self.voltage = info["vol"]
|
49
|
+
self.acceleration = info["accel"]
|
50
|
+
self.deceleration = info["decel"]
|
51
|
+
self.velocity = info["vel"]
|
52
|
+
self.distance = info["dis"]
|
53
|
+
self.jog_speed = info["jogsp"]
|
54
|
+
return True
|
55
|
+
|
56
|
+
def update_status(self, status_string) -> bool:
|
57
|
+
if status_string == None and status_string == "":
|
58
|
+
print("Update failed: input is empty or None")
|
59
|
+
return False
|
60
|
+
self.status_string = status_string
|
61
|
+
self.alarm = "A" in status_string
|
62
|
+
self.disabled = "D" in status_string
|
63
|
+
self.drive_fault = "E" in status_string
|
64
|
+
self.moving = "F" in status_string
|
65
|
+
self.homing = "H" in status_string
|
66
|
+
self.jogging = "J" in status_string
|
67
|
+
self.motion_in_progess = "M" in status_string
|
68
|
+
self.ready = "R" in status_string
|
69
|
+
self.stoping = "S" in status_string
|
70
|
+
self.waiting = "T" in status_string
|
71
|
+
return True
|
72
|
+
|
73
|
+
def get_info(self) -> str:
|
74
|
+
return f"""
|
75
|
+
Position: {self.position}
|
76
|
+
Temperature: {self.temperature}
|
77
|
+
Sensor Status: {self.sensor_status}
|
78
|
+
Voltage: {self.voltage}
|
79
|
+
Acceleration: {self.acceleration}
|
80
|
+
Deceleration: {self.deceleration}
|
81
|
+
Velocity: {self.velocity}
|
82
|
+
Distance: {self.distance}
|
83
|
+
Jog Speed: {self.jog_speed}"""
|
84
|
+
|
85
|
+
def get_status(self) -> str:
|
86
|
+
return f"""
|
87
|
+
Alarm: {self.alarm}
|
88
|
+
Disabled: {self.disabled}
|
89
|
+
Drive Fault: {self.drive_fault}
|
90
|
+
Moving: {self.moving}
|
91
|
+
Homing: {self.homing}
|
92
|
+
Jogging: {self.jogging}
|
93
|
+
Motion in Progress: {self.motion_in_progess}
|
94
|
+
Ready: {self.ready}
|
95
|
+
Stoping: {self.stoping}
|
96
|
+
Waiting: {self.waiting}"""
|
97
|
+
|
98
|
+
|
99
|
+
class moons_stepper:
|
100
|
+
motorAdress = [
|
101
|
+
"0",
|
102
|
+
"1",
|
103
|
+
"2",
|
104
|
+
"3",
|
105
|
+
"4",
|
106
|
+
"5",
|
107
|
+
"6",
|
108
|
+
"7",
|
109
|
+
"8",
|
110
|
+
"9",
|
111
|
+
"!",
|
112
|
+
'"',
|
113
|
+
"#",
|
114
|
+
"$",
|
115
|
+
"%",
|
116
|
+
"&",
|
117
|
+
"'",
|
118
|
+
"(",
|
119
|
+
")",
|
120
|
+
"*",
|
121
|
+
"+",
|
122
|
+
",",
|
123
|
+
"-",
|
124
|
+
".",
|
125
|
+
"/",
|
126
|
+
":",
|
127
|
+
";",
|
128
|
+
"<",
|
129
|
+
"=",
|
130
|
+
">",
|
131
|
+
"?",
|
132
|
+
"@",
|
133
|
+
]
|
134
|
+
|
135
|
+
def __init__(
|
136
|
+
self,
|
137
|
+
model,
|
138
|
+
VID,
|
139
|
+
PID,
|
140
|
+
SERIAL_NUM,
|
141
|
+
only_simlate=False,
|
142
|
+
universe=0,
|
143
|
+
simulate_ip="localhost",
|
144
|
+
simulate_port=3001,
|
145
|
+
):
|
146
|
+
self.universe = universe
|
147
|
+
self.model = model
|
148
|
+
self.only_simulate = only_simlate
|
149
|
+
self.device = ""
|
150
|
+
self.VID = VID
|
151
|
+
self.PID = PID
|
152
|
+
self.SERIAL_NUM = SERIAL_NUM
|
153
|
+
self.ser = None
|
154
|
+
self.listeningBuffer = ""
|
155
|
+
self.listeningBufferPre = ""
|
156
|
+
self.transmitDelay = 0.010
|
157
|
+
self.lock = False
|
158
|
+
self.Opened = False
|
159
|
+
self.new_data_event = threading.Event()
|
160
|
+
self.new_value_event = threading.Event()
|
161
|
+
self.on_send_event = threading.Event()
|
162
|
+
self.recvQueue = queue.Queue()
|
163
|
+
self.sendQueue = queue.Queue()
|
164
|
+
self.command_cache = queue.Queue()
|
165
|
+
self.usedSendQueue = queue.Queue()
|
166
|
+
self.simulator = moons_stepper_simulate(
|
167
|
+
self,
|
168
|
+
universe=universe,
|
169
|
+
server_address=f"http://{simulate_ip}:{simulate_port}",
|
170
|
+
)
|
171
|
+
|
172
|
+
self.console = Console()
|
173
|
+
|
174
|
+
self.is_log_message = True
|
175
|
+
|
176
|
+
self.microstep = {
|
177
|
+
0: 200,
|
178
|
+
1: 400,
|
179
|
+
3: 2000,
|
180
|
+
4: 5000,
|
181
|
+
5: 10000,
|
182
|
+
6: 12800,
|
183
|
+
7: 18000,
|
184
|
+
8: 20000,
|
185
|
+
9: 21600,
|
186
|
+
10: 25000,
|
187
|
+
11: 25400,
|
188
|
+
12: 25600,
|
189
|
+
13: 36000,
|
190
|
+
14: 50000,
|
191
|
+
15: 50800,
|
192
|
+
}
|
193
|
+
self.simulator.connect()
|
194
|
+
|
195
|
+
# region connection & main functions
|
196
|
+
@staticmethod
|
197
|
+
def list_all_ports():
|
198
|
+
# print("░░░░░░░░░░░░░░░░░░░ All COMPorts ░░░░░░░░░░░░░░░░░░░░░\n")
|
199
|
+
ports = list(list_ports.comports())
|
200
|
+
simple_ports = []
|
201
|
+
port_info = ""
|
202
|
+
for p in ports:
|
203
|
+
port_info += f"■ {p.device} {p.description} [blue]{p.usb_info()}[/blue]"
|
204
|
+
if p != ports[-1]:
|
205
|
+
port_info += "\n"
|
206
|
+
simple_ports.append(p.description)
|
207
|
+
# print("\n░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░")
|
208
|
+
print(Panel(port_info, title="All COMPorts"))
|
209
|
+
return simple_ports
|
210
|
+
|
211
|
+
def connect(self, COM=None, baudrate=9600, callback=None):
|
212
|
+
if self.only_simulate:
|
213
|
+
self.Opened = True
|
214
|
+
self.device = f"Simulate-{self.universe}"
|
215
|
+
print(f"{self.device} connected")
|
216
|
+
if callback:
|
217
|
+
callback(self.device, self.Opened)
|
218
|
+
return
|
219
|
+
|
220
|
+
def attempt_connect(COM, baudrate):
|
221
|
+
try:
|
222
|
+
self.ser = serial.Serial(COM, baudrate)
|
223
|
+
if self.ser is None:
|
224
|
+
print("> Device not found")
|
225
|
+
self.Opened = False
|
226
|
+
if self.ser.is_open:
|
227
|
+
print(f"Device: {self.device} | COM: {COM} connected")
|
228
|
+
self.Opened = True
|
229
|
+
except:
|
230
|
+
print("> Device error")
|
231
|
+
self.Opened = False
|
232
|
+
|
233
|
+
if COM is not None and not self.only_simulate:
|
234
|
+
attempt_connect(COM, baudrate)
|
235
|
+
if callback:
|
236
|
+
callback(self.device, self.Opened)
|
237
|
+
return
|
238
|
+
ports = list(list_ports.comports())
|
239
|
+
for p in ports:
|
240
|
+
m = re.match(
|
241
|
+
r"USB\s*VID:PID=(\w+):(\w+)\s*SER=([A-Za-z0-9]+)", p.usb_info()
|
242
|
+
)
|
243
|
+
if (
|
244
|
+
m
|
245
|
+
and m.group(1) == self.VID
|
246
|
+
and m.group(2) == self.PID
|
247
|
+
and m.group(3) == self.SERIAL_NUM
|
248
|
+
):
|
249
|
+
print(
|
250
|
+
f"Device: {p.description} | VID: {m.group(1)} | PID: {m.group(2)} | SER: {m.group(3)} connected"
|
251
|
+
)
|
252
|
+
|
253
|
+
self.device = p.description
|
254
|
+
|
255
|
+
attempt_connect(p.device, baudrate)
|
256
|
+
if callback:
|
257
|
+
callback(self.device, self.Opened)
|
258
|
+
break
|
259
|
+
# try:
|
260
|
+
# self.ser = serial.Serial(p.device, baudrate)
|
261
|
+
# except Exception as e:
|
262
|
+
# print(f"Permission Error: {e}")
|
263
|
+
# self.Opened = False
|
264
|
+
# return
|
265
|
+
|
266
|
+
# self.listeningThread = threading.Thread(target=self.listening)
|
267
|
+
# self.sendingThread = threading.Thread(target=self.sending)
|
268
|
+
# self.listeningThread.daemon = True
|
269
|
+
# self.sendingThread.daemon = True
|
270
|
+
# self.listeningThread.start()
|
271
|
+
# self.sendingThread.start()
|
272
|
+
|
273
|
+
break
|
274
|
+
|
275
|
+
if self.only_simulate:
|
276
|
+
self.device = "Simulate"
|
277
|
+
self.Opened = True
|
278
|
+
if not self.Opened:
|
279
|
+
print("> Device not found")
|
280
|
+
if callback:
|
281
|
+
callback(self.device, self.Opened)
|
282
|
+
# self.sendingThread = threading.Thread(target=self.sending)
|
283
|
+
# self.sendingThread.daemon = True
|
284
|
+
# self.sendingThread.start()
|
285
|
+
# self.device = "Target device"
|
286
|
+
# if self.ser is None and not self.only_simlate:
|
287
|
+
# print("> Device not found")
|
288
|
+
# self.Opened = False
|
289
|
+
|
290
|
+
def disconnect(self):
|
291
|
+
if self.only_simulate:
|
292
|
+
self.listen = False
|
293
|
+
self.is_sending = False
|
294
|
+
self.Opened = False
|
295
|
+
print(f"Simulate-{self.universe} disconnected")
|
296
|
+
return
|
297
|
+
if self.ser is not None and self.ser.is_open:
|
298
|
+
self.listen = False
|
299
|
+
self.is_sending = False
|
300
|
+
self.Opened = False
|
301
|
+
self.ser.flush()
|
302
|
+
self.ser.close()
|
303
|
+
# if self.device is not None:
|
304
|
+
# print("{} Disconnected".format(self.device))
|
305
|
+
# else:
|
306
|
+
print(f"{self.device} Disconnected")
|
307
|
+
|
308
|
+
def send(self, command, eol=b"\r"):
|
309
|
+
if (self.ser != None and self.ser.is_open) or self.only_simulate:
|
310
|
+
# self.sendQueue.put(command + "\r")
|
311
|
+
# self.temp_cmd = self.sendQueue.get()
|
312
|
+
self.temp_cmd = command + "\r"
|
313
|
+
|
314
|
+
if "~" in self.temp_cmd:
|
315
|
+
# remove ~ in self.temp_cmd
|
316
|
+
self.temp_cmd = self.temp_cmd[1:]
|
317
|
+
else:
|
318
|
+
self.usedSendQueue.put(self.temp_cmd)
|
319
|
+
# self.command_cache.put(self.temp_cmd)
|
320
|
+
if self.ser is not None or not self.only_simulate:
|
321
|
+
self.ser.write(self.temp_cmd.encode("ascii"))
|
322
|
+
if self.is_log_message:
|
323
|
+
print(
|
324
|
+
f"[bold green]Send to {self.device}:[/bold green] {self.temp_cmd}"
|
325
|
+
)
|
326
|
+
self.simulator.emit("motor", f"{self.universe}-{self.temp_cmd}")
|
327
|
+
else:
|
328
|
+
print(f"{self.device} is not open")
|
329
|
+
|
330
|
+
def sending(self):
|
331
|
+
self.is_sending = True
|
332
|
+
print("Sending thread started")
|
333
|
+
# start_time = time.time()
|
334
|
+
try:
|
335
|
+
while self.is_sending:
|
336
|
+
# start_time = time.time()
|
337
|
+
if self.sendQueue.empty():
|
338
|
+
continue
|
339
|
+
# self.temp_cmd = ""
|
340
|
+
self.temp_cmd = self.sendQueue.get_nowait()
|
341
|
+
|
342
|
+
if "~" in self.temp_cmd:
|
343
|
+
# remove ~ in self.temp_cmd
|
344
|
+
self.temp_cmd = self.temp_cmd[1:]
|
345
|
+
else:
|
346
|
+
self.usedSendQueue.put(self.temp_cmd)
|
347
|
+
# self.command_cache.put(self.temp_cmd)
|
348
|
+
|
349
|
+
if self.ser is not None and self.ser.is_open is not self.only_simulate:
|
350
|
+
self.ser.write(self.temp_cmd.encode("ascii"))
|
351
|
+
# print(
|
352
|
+
# f"[bold green]Send to {self.device}:[/bold green] {self.temp_cmd}"
|
353
|
+
# )
|
354
|
+
# self.simulate.emit("motor", f"{self.universe}-{self.temp_cmd}")
|
355
|
+
# print(f"[bold yellow]Cache:[/bold yellow]{self.temp_cmd}")
|
356
|
+
|
357
|
+
self.sendQueue.task_done()
|
358
|
+
|
359
|
+
time.sleep(0.05)
|
360
|
+
|
361
|
+
except Exception as e:
|
362
|
+
print("Error in sending thread:")
|
363
|
+
print(e)
|
364
|
+
self.is_sending = False
|
365
|
+
|
366
|
+
def listening(self):
|
367
|
+
self.listen = True
|
368
|
+
self.listeningBuffer = ""
|
369
|
+
print("Listening thread started")
|
370
|
+
# start_time = time.time()
|
371
|
+
try:
|
372
|
+
while True:
|
373
|
+
# start_time = time.time()
|
374
|
+
if not self.listen:
|
375
|
+
break
|
376
|
+
if self.only_simulate:
|
377
|
+
continue
|
378
|
+
if self.ser is not None and self.ser.is_open:
|
379
|
+
if self.ser.in_waiting > 0:
|
380
|
+
self.listeningBuffer += self.ser.read(1).decode(
|
381
|
+
"utf-8", "replace"
|
382
|
+
)
|
383
|
+
if self.listeningBuffer.endswith("\r"):
|
384
|
+
self.recvQueue.put(self.listeningBuffer)
|
385
|
+
self.listeningBuffer = ""
|
386
|
+
else:
|
387
|
+
print(f"{self.device} is not open")
|
388
|
+
if not self.recvQueue.empty():
|
389
|
+
self.temp_recv = self.recvQueue.get()
|
390
|
+
self.temp_used_cmd = self.usedSendQueue.get()
|
391
|
+
print(
|
392
|
+
f"[bold green]Recv:[/bold green] {self.temp_used_cmd}->{self.temp_recv}"
|
393
|
+
)
|
394
|
+
self.recvQueue.task_done()
|
395
|
+
self.usedSendQueue.task_done()
|
396
|
+
time.sleep(0.05)
|
397
|
+
# print(f"Time: {time.time()-start_time}")
|
398
|
+
except Exception as e:
|
399
|
+
print("Error in listening thread:")
|
400
|
+
print(e)
|
401
|
+
self.listen = False
|
402
|
+
print("Listening thread stopped")
|
403
|
+
|
404
|
+
# endregion
|
405
|
+
|
406
|
+
# region motor motion functions
|
407
|
+
def enable(self, motor_address="", enable=True):
|
408
|
+
cmd = "ME" if enable else "MD"
|
409
|
+
self.send(self.addressed_cmd(motor_address, cmd))
|
410
|
+
|
411
|
+
def move_absolute(self, motor_address="", position=0, speed=0.15):
|
412
|
+
# if speed > 0:
|
413
|
+
# self.set_velocity(motor_address, speed)
|
414
|
+
self.send(self.addressed_cmd(motor_address, f"VE{speed}"))
|
415
|
+
self.send(self.addressed_cmd(motor_address, f"FP{position}"))
|
416
|
+
|
417
|
+
def move_fixed_distance(self, motor_address="", distance=100, speed=0.15):
|
418
|
+
# if speed > 0:
|
419
|
+
# self.set_velocity(motor_address, speed)
|
420
|
+
self.send(self.addressed_cmd(motor_address, "VE{}".format(speed)))
|
421
|
+
self.send(self.addressed_cmd(motor_address, "FL{}".format(int(distance))))
|
422
|
+
|
423
|
+
def start_jog(self, motor_address="", speed=0.15, direction="CW"):
|
424
|
+
# if direction == "CW":
|
425
|
+
# self.send(self.addressed_cmd(motor_address, "DI1"))
|
426
|
+
# if direction == "CCW":
|
427
|
+
# self.send(self.addressed_cmd(motor_address, "DI-1"))
|
428
|
+
self.send(self.addressed_cmd(motor_address, "JS{}".format(speed)))
|
429
|
+
self.send(self.addressed_cmd(motor_address, "CJ"))
|
430
|
+
|
431
|
+
def change_jog_speed(self, motor_address="", speed=0.15):
|
432
|
+
self.send(self.addressed_cmd(motor_address, "CS{}".format(speed)))
|
433
|
+
|
434
|
+
def stop_jog(self, motor_address=""):
|
435
|
+
self.send(self.addressed_cmd(motor_address, "SJ"))
|
436
|
+
|
437
|
+
def stop(self, motor_address=""):
|
438
|
+
self.send(self.addressed_cmd(motor_address, "ST"))
|
439
|
+
|
440
|
+
def stop_with_deceleration(self, motor_address=""):
|
441
|
+
self.send(self.addressed_cmd(motor_address, "STD"))
|
442
|
+
|
443
|
+
def stop_and_kill(self, motor_address="", with_deceleration=True):
|
444
|
+
if with_deceleration:
|
445
|
+
self.send(self.addressed_cmd(motor_address, "SKD"))
|
446
|
+
else:
|
447
|
+
self.send(self.addressed_cmd(motor_address, "SK"))
|
448
|
+
|
449
|
+
def setup_motor(self, motor_address="", kill=False):
|
450
|
+
if kill:
|
451
|
+
self.stop_and_kill(motor_address)
|
452
|
+
# time.sleep(1)
|
453
|
+
self.set_transmit_delay(motor_address, 25)
|
454
|
+
self.set_return_format_dexcimal(motor_address)
|
455
|
+
# self.motor_wait(motor_address, 0.1)
|
456
|
+
# status = self.get_status(motor_address)
|
457
|
+
|
458
|
+
def calibrate(self, motor_address="", speed=0.3, onStart=None, onComplete=None):
|
459
|
+
self.send(self.addressed_cmd(motor_address, "VE{}".format(speed)))
|
460
|
+
# self.send(self.addressed_cmd(motor_address, "WT0.2"))
|
461
|
+
self.send(self.addressed_cmd(motor_address, "DI10"))
|
462
|
+
# time.sleep(self.transmitDelay)
|
463
|
+
# self.send(self.addressed_cmd(motor_address, "FS3F"))
|
464
|
+
self.send(self.addressed_cmd(motor_address, "SH3F"))
|
465
|
+
self.send(self.addressed_cmd(motor_address, "EP0"))
|
466
|
+
self.send(self.addressed_cmd(motor_address, "SP0"))
|
467
|
+
|
468
|
+
# speed slow= 0.25, medium=1, fast=5
|
469
|
+
def set_transmit_delay(self, motor_address="", delay=15):
|
470
|
+
self.send(self.addressed_cmd(motor_address, "TD{}".format(delay)))
|
471
|
+
|
472
|
+
# endregion
|
473
|
+
# region motor status functions
|
474
|
+
def get_position(self, motor_address):
|
475
|
+
self.send(self.addressed_cmd(motor_address, "IP"))
|
476
|
+
self.new_value_event.wait(timeout=0.5)
|
477
|
+
return self.get_value()
|
478
|
+
|
479
|
+
def get_temperature(self, motor_address):
|
480
|
+
self.send(self.addressed_cmd(motor_address, "IT"))
|
481
|
+
self.new_value_event.wait(timeout=0.5)
|
482
|
+
return int(self.get_value()) / 10
|
483
|
+
|
484
|
+
def get_sensor_status(self, motor_address):
|
485
|
+
self.send(self.addressed_cmd(motor_address, "IS"))
|
486
|
+
self.new_value_event.wait(timeout=0.5)
|
487
|
+
return self.get_value()
|
488
|
+
|
489
|
+
def get_votalge(self, motor_address):
|
490
|
+
self.send(self.addressed_cmd(motor_address, "IU"))
|
491
|
+
self.new_value_event.wait(timeout=0.5)
|
492
|
+
return self.get_value()
|
493
|
+
|
494
|
+
def get_acceleration(self, motor_address):
|
495
|
+
self.send(self.addressed_cmd(motor_address, "AC"))
|
496
|
+
self.new_value_event.wait(timeout=0.5)
|
497
|
+
return self.get_value()
|
498
|
+
|
499
|
+
def get_deceleration(self, motor_address):
|
500
|
+
self.send(self.addressed_cmd(motor_address, "DE"))
|
501
|
+
self.new_value_event.wait(timeout=0.5)
|
502
|
+
return self.get_value()
|
503
|
+
|
504
|
+
def get_velocity(self, motor_address):
|
505
|
+
self.send(self.addressed_cmd(motor_address, "VE"))
|
506
|
+
self.new_value_event.wait(timeout=0.5)
|
507
|
+
return self.get_value()
|
508
|
+
|
509
|
+
def get_distance(self, motor_address):
|
510
|
+
self.send(self.addressed_cmd(motor_address, "DI"))
|
511
|
+
self.new_value_event.wait(timeout=0.5)
|
512
|
+
return self.get_value()
|
513
|
+
|
514
|
+
def get_jog_speed(self, motor_address):
|
515
|
+
self.send(self.addressed_cmd(motor_address, "JS"))
|
516
|
+
self.new_value_event.wait(timeout=0.5)
|
517
|
+
return self.get_value()
|
518
|
+
|
519
|
+
def get_info(self, motor_address):
|
520
|
+
self.set_return_format_dexcimal(motor_address)
|
521
|
+
self.motor_wait(motor_address, 0.1)
|
522
|
+
info = {
|
523
|
+
"pos": str(self.get_position(motor_address)),
|
524
|
+
"temp": str(self.get_temperature(motor_address)),
|
525
|
+
"sensor": str(self.get_sensor_status(motor_address)),
|
526
|
+
"vol": str(self.get_votalge(motor_address)),
|
527
|
+
"accel": str(self.get_acceleration(motor_address)),
|
528
|
+
"decel": str(self.get_deceleration(motor_address)),
|
529
|
+
"vel": str(self.get_velocity(motor_address)),
|
530
|
+
"dis": str(self.get_distance(motor_address)),
|
531
|
+
"jogsp": str(self.get_jog_speed(motor_address)),
|
532
|
+
}
|
533
|
+
return info
|
534
|
+
|
535
|
+
def get_status(self, motor_address) -> str:
|
536
|
+
self.set_return_format_dexcimal(motor_address)
|
537
|
+
self.send(self.addressed_cmd(motor_address, "RS"))
|
538
|
+
self.new_value_event.wait(timeout=0.5)
|
539
|
+
return str(self.get_value())
|
540
|
+
|
541
|
+
def set_return_format_dexcimal(self, motor_address):
|
542
|
+
self.send(self.addressed_cmd(motor_address, "IFD"))
|
543
|
+
|
544
|
+
def set_return_format_hexdecimal(self, motor_address):
|
545
|
+
self.send(self.addressed_cmd(motor_address, "IFH"))
|
546
|
+
|
547
|
+
# endregion
|
548
|
+
|
549
|
+
# region utility functions
|
550
|
+
def motor_wait(self, motor_address, wait_time):
|
551
|
+
self.send(self.addressed_cmd(motor_address, "WT{}".format(wait_time)))
|
552
|
+
|
553
|
+
def addressed_cmd(self, motor_address, command):
|
554
|
+
if motor_address == "":
|
555
|
+
return f"~{command}"
|
556
|
+
return f"{motor_address}{command}"
|
557
|
+
|
558
|
+
def get_value(self):
|
559
|
+
print("Waiting for value")
|
560
|
+
self.new_data_event.wait(timeout=0.5)
|
561
|
+
print("Recv:" + self.listeningBufferPre)
|
562
|
+
self.new_data_event.clear()
|
563
|
+
if "%" in self.listeningBufferPre:
|
564
|
+
return "success_ack"
|
565
|
+
if "?" in self.listeningBufferPre:
|
566
|
+
return "fail_ack"
|
567
|
+
if "*" in self.listeningBufferPre:
|
568
|
+
return "buffered_ack"
|
569
|
+
self.new_value_event.set()
|
570
|
+
pattern = r"=(\w+(?:\.\w+)?|\d+(?:\.\d+)?)"
|
571
|
+
result = re.search(pattern, self.listeningBufferPre)
|
572
|
+
self.listeningBufferPre = ""
|
573
|
+
self.new_value_event.clear()
|
574
|
+
if result:
|
575
|
+
return result.group(1)
|
576
|
+
else:
|
577
|
+
return "No_value_found"
|
578
|
+
|
579
|
+
|
580
|
+
class moons_stepper_simulate:
|
581
|
+
def __init__(
|
582
|
+
self,
|
583
|
+
moons_motor: moons_stepper,
|
584
|
+
universe: int = 0,
|
585
|
+
server_address: str = "http://localhost:3001",
|
586
|
+
):
|
587
|
+
self.server_address = server_address
|
588
|
+
self.universe = universe
|
589
|
+
self.moons_motor = moons_motor
|
590
|
+
self.io = socketio.SimpleClient()
|
591
|
+
self.connected = False
|
592
|
+
self.is_log_message = True
|
593
|
+
|
594
|
+
def connect(self):
|
595
|
+
try:
|
596
|
+
self.is_log_message = False
|
597
|
+
self.io.connect(self.server_address)
|
598
|
+
self.connected = True
|
599
|
+
print(f"Socket connected to {self.server_address}[{self.io.sid}]")
|
600
|
+
# self.rederict_thread = threading.Thread(
|
601
|
+
# target=self.rederict_job,
|
602
|
+
# )
|
603
|
+
# self.rederict_thread.daemon = True
|
604
|
+
# self.rederict_thread.start()
|
605
|
+
except Exception as e:
|
606
|
+
print(f"Socket connection error: {e}")
|
607
|
+
self.connected = False
|
608
|
+
|
609
|
+
def rederict_job(self):
|
610
|
+
if not self.connected:
|
611
|
+
print("Socket not connected")
|
612
|
+
return
|
613
|
+
if self.moons_motor is None:
|
614
|
+
print("Motor is None")
|
615
|
+
return
|
616
|
+
while True:
|
617
|
+
# self.moons_motor.on_send_event.wait(timeout=0.5)
|
618
|
+
if self.moons_motor.command_cache.empty():
|
619
|
+
continue
|
620
|
+
cmd = self.moons_motor.command_cache.get_nowait()
|
621
|
+
# self.moons_motor.command_cache.task_done()
|
622
|
+
|
623
|
+
self.emit("motor", f"{self.universe}-{cmd}")
|
624
|
+
# self.moons_motor.command_cache = ""
|
625
|
+
# self.moons_motor.on_send_event.clear()
|
626
|
+
|
627
|
+
if not self.connected:
|
628
|
+
break
|
629
|
+
time.sleep(0.05)
|
630
|
+
|
631
|
+
def disconnect(self):
|
632
|
+
self.io.disconnect()
|
633
|
+
|
634
|
+
def emit(self, eventName: str, data):
|
635
|
+
if not self.connected:
|
636
|
+
print("Socket not connected")
|
637
|
+
return
|
638
|
+
self.io.emit(eventName, data)
|
639
|
+
if self.is_log_message:
|
640
|
+
print("[bold blue]Send to socket:[/bold blue] {}\n".format(data))
|
641
|
+
|
642
|
+
|
643
|
+
# endregion
|
644
|
+
|
645
|
+
# SERIAL => 上次已知父系(尾巴+A) 或是事件分頁
|
646
|
+
# reg USB\s*VID:PID=(\w+):(\w+)\s*SER=([A-Za-z0-9]+)
|
647
|
+
|
648
|
+
# serial_num 裝置例項路徑
|
649
|
+
# TD(Tramsmit Delay) = 15
|
@@ -0,0 +1,30 @@
|
|
1
|
+
Metadata-Version: 2.1
|
2
|
+
Name: moons_motor
|
3
|
+
Version: 0.0.1
|
4
|
+
Summary: This is a python library for controlling the Moons' motor through the serial port.
|
5
|
+
Author: miroc99
|
6
|
+
Author-email: miroc <mike8503111@gmail.com>
|
7
|
+
Project-URL: Repository, https://github.com/miroc99/moons_motor.git
|
8
|
+
Classifier: Development Status :: 3 - Alpha
|
9
|
+
Classifier: Intended Audience :: Developers
|
10
|
+
Classifier: Topic :: Terminals :: Serial
|
11
|
+
Classifier: License :: OSI Approved :: MIT License
|
12
|
+
Classifier: Programming Language :: Python :: 3.12
|
13
|
+
Requires-Python: >=3.12
|
14
|
+
Description-Content-Type: text/markdown
|
15
|
+
Requires-Dist: pyserial
|
16
|
+
Requires-Dist: rich
|
17
|
+
Requires-Dist: python-socketio
|
18
|
+
|
19
|
+
# Moons Motor
|
20
|
+
|
21
|
+
This is a python library for control moons motor through serial port.
|
22
|
+
|
23
|
+
## Compatibility
|
24
|
+
Now only support Windows.
|
25
|
+
|
26
|
+
## Installing
|
27
|
+
Install through `pip`
|
28
|
+
```bash
|
29
|
+
python -m pip install moons_motor
|
30
|
+
```
|
@@ -0,0 +1,10 @@
|
|
1
|
+
README.md
|
2
|
+
pyproject.toml
|
3
|
+
setup.py
|
4
|
+
moons_motor/__init__.py
|
5
|
+
moons_motor/moons_motor.py
|
6
|
+
moons_motor.egg-info/PKG-INFO
|
7
|
+
moons_motor.egg-info/SOURCES.txt
|
8
|
+
moons_motor.egg-info/dependency_links.txt
|
9
|
+
moons_motor.egg-info/requires.txt
|
10
|
+
moons_motor.egg-info/top_level.txt
|
@@ -0,0 +1 @@
|
|
1
|
+
|
@@ -0,0 +1 @@
|
|
1
|
+
moons_motor
|
@@ -0,0 +1,31 @@
|
|
1
|
+
[build-system]
|
2
|
+
requires = ["setuptools"]
|
3
|
+
build-backend = "setuptools.build_meta"
|
4
|
+
|
5
|
+
[project]
|
6
|
+
name = "moons_motor"
|
7
|
+
version = "0.0.1"
|
8
|
+
authors = [{ name = "miroc", email = "mike8503111@gmail.com" }]
|
9
|
+
description = "This is a python library for controlling the Moons' motor through the serial port."
|
10
|
+
readme = "README.md"
|
11
|
+
requires-python = ">=3.12"
|
12
|
+
dependencies = ["pyserial", "rich", "python-socketio"]
|
13
|
+
classifiers = [
|
14
|
+
# How mature is this project? Common values are
|
15
|
+
# 3 - Alpha
|
16
|
+
# 4 - Beta
|
17
|
+
# 5 - Production/Stable
|
18
|
+
"Development Status :: 3 - Alpha",
|
19
|
+
|
20
|
+
# Indicate who your project is intended for
|
21
|
+
"Intended Audience :: Developers",
|
22
|
+
"Topic :: Terminals :: Serial",
|
23
|
+
|
24
|
+
# Pick your license as you wish (see also "license" above)
|
25
|
+
"License :: OSI Approved :: MIT License",
|
26
|
+
|
27
|
+
# Specify the Python versions you support here.
|
28
|
+
"Programming Language :: Python :: 3.12",
|
29
|
+
]
|
30
|
+
[project.urls]
|
31
|
+
Repository = "https://github.com/miroc99/moons_motor.git"
|
@@ -0,0 +1,10 @@
|
|
1
|
+
from setuptools import setup, find_packages
|
2
|
+
|
3
|
+
setup(
|
4
|
+
name="moons_motor",
|
5
|
+
version="0.0.1",
|
6
|
+
description="A simple motor control library for the Moons stepper motor driver",
|
7
|
+
author="miroc99",
|
8
|
+
packages=find_packages(),
|
9
|
+
install_requires=["pyserial", "rich", "python-socketio"],
|
10
|
+
)
|