moons-motor 0.1.3__py3-none-any.whl → 0.1.5__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
moons_motor/motor.py CHANGED
@@ -13,6 +13,66 @@ import threading
13
13
 
14
14
  from dataclasses import dataclass
15
15
 
16
+ import logging
17
+
18
+
19
+ class ColoredFormatter(logging.Formatter):
20
+ # Define ANSI escape codes for colors and reset
21
+ grey = "\x1b[38;21m"
22
+ yellow = "\x1b[33;21m"
23
+ red = "\x1b[31;21m"
24
+ bold_red = "\x1b[31;1m"
25
+ bold_yellow = "\x1b[33;1m"
26
+ bold_green = "\x1b[32;1m"
27
+ bold_blue = "\x1b[34;1m"
28
+ bold_cyan = "\x1b[36;1m"
29
+ bold_magenta = "\x1b[35;1m"
30
+ reset = "\x1b[0m"
31
+
32
+ # Define the base format string
33
+ format_time = "%(asctime)s"
34
+ format_header = " [%(levelname)s]"
35
+ format_base = " %(message)s"
36
+
37
+ # Map log levels to colored format strings
38
+ FORMATS = {
39
+ logging.DEBUG: format_time
40
+ + bold_cyan
41
+ + format_header
42
+ + reset
43
+ + format_base
44
+ + reset,
45
+ logging.INFO: format_time
46
+ + bold_green
47
+ + format_header
48
+ + reset
49
+ + format_base
50
+ + reset,
51
+ logging.WARNING: format_time
52
+ + bold_yellow
53
+ + format_header
54
+ + reset
55
+ + format_base
56
+ + reset,
57
+ logging.ERROR: format_time
58
+ + bold_red
59
+ + format_header
60
+ + reset
61
+ + format_base
62
+ + reset,
63
+ logging.CRITICAL: format_time
64
+ + bold_magenta
65
+ + format_header
66
+ + reset
67
+ + format_base
68
+ + reset,
69
+ }
70
+
71
+ def format(self, record):
72
+ log_fmt = self.FORMATS.get(record.levelno)
73
+ formatter = logging.Formatter(log_fmt)
74
+ return formatter.format(record)
75
+
16
76
 
17
77
  class StepperModules:
18
78
  STM17S_3RN = "STM17S-3RN"
@@ -22,6 +82,9 @@ class StepperModules:
22
82
  class StepperCommand:
23
83
  JOG: str = "CJ" # Start jogging
24
84
  JOG_SPEED: str = "JS" # Jogging speed (Need to set before start jogging)
85
+ JOG_ACCELERATION: str = (
86
+ "JA" # Jogging acceleration (Need to set before start jogging)
87
+ )
25
88
  CHANGE_JOG_SPEED: str = "CS" # Change jogging speed while jogging
26
89
  STOP_JOG: str = "SJ" # Stop jogging with deceleration
27
90
  STOP: str = "ST" # Stop immediately (No deceleration)
@@ -52,6 +115,7 @@ class StepperCommand:
52
115
  SET_RETURN_FORMAT_HEXADECIMAL: str = "IFH" # Set return format to hexadecimal
53
116
 
54
117
  SET_TRANSMIT_DELAY: str = "TD" # Set transmit delay
118
+ REQUEST_STATUS: str = "RS" # Request status
55
119
 
56
120
 
57
121
  class MoonsStepper(Subject):
@@ -89,6 +153,13 @@ class MoonsStepper(Subject):
89
153
  "?",
90
154
  "@",
91
155
  ]
156
+ # Configure logging
157
+ logger = logging.getLogger(__name__)
158
+ logger.setLevel(logging.INFO)
159
+
160
+ ch = logging.StreamHandler()
161
+ ch.setFormatter(ColoredFormatter())
162
+ logger.addHandler(ch)
92
163
 
93
164
  def __init__(
94
165
  self,
@@ -178,7 +249,7 @@ class MoonsStepper(Subject):
178
249
  if self.only_simulate:
179
250
  self.Opened = True
180
251
  self.device = f"Simulate-{self.universe}"
181
- print(f"{self.device} connected")
252
+ MoonsStepper.logger.info(f"{self.device} connected")
182
253
  if callback:
183
254
  callback(self.device, self.Opened)
184
255
  return
@@ -205,10 +276,10 @@ class MoonsStepper(Subject):
205
276
  self.Opened = False
206
277
  if self.ser.is_open:
207
278
  self.Opened = True
208
- print(f"[bold green]Device connected[/bold green]: {self.device}")
279
+ MoonsStepper.logger.info(f"Device connected: {self.device}")
209
280
 
210
281
  except Exception as e:
211
- print(f"[bold red]Device error:[/bold red] {e} ")
282
+ MoonsStepper.logger.error(f"Device error: {e} ")
212
283
  self.Opened = False
213
284
 
214
285
  ports = list(list_ports.comports())
@@ -221,7 +292,7 @@ class MoonsStepper(Subject):
221
292
  m = re.match(
222
293
  r"USB\s*VID:PID=(\w+):(\w+)\s*SER=([A-Za-z0-9]*)", p.usb_info()
223
294
  )
224
- print(p.usb_info())
295
+ MoonsStepper.logger.info(p.usb_info())
225
296
  if (
226
297
  m
227
298
  and m.group(1) == self.VID
@@ -229,8 +300,8 @@ class MoonsStepper(Subject):
229
300
  # and m.group(3) == self.SERIAL_NUM
230
301
  ):
231
302
  if m.group(3) == self.SERIAL_NUM or self.SERIAL_NUM == "":
232
- print(
233
- f"[bold yellow]Device founded:[/bold yellow] {p.description} | VID: {m.group(1)} | PID: {m.group(2)} | SER: {m.group(3)}"
303
+ MoonsStepper.logger.info(
304
+ f"Device founded: {p.description} | VID: {m.group(1)} | PID: {m.group(2)} | SER: {m.group(3)}"
234
305
  )
235
306
 
236
307
  self.device = p.description
@@ -248,7 +319,7 @@ class MoonsStepper(Subject):
248
319
  callback(self.device, self.Opened)
249
320
 
250
321
  if not self.Opened:
251
- print(f"[bold red]Device not found[/bold red]")
322
+ MoonsStepper.logger.error(f"Device not found")
252
323
  if callback:
253
324
  callback(self.device, self.Opened)
254
325
 
@@ -263,7 +334,7 @@ class MoonsStepper(Subject):
263
334
  self.listen = False
264
335
  self.is_sending = False
265
336
  self.Opened = False
266
- print(f"Simulate-{self.universe} disconnected")
337
+ MoonsStepper.logger.info(f"Simulate-{self.universe} disconnected")
267
338
  return
268
339
  if self.ser is not None and self.ser.is_open:
269
340
  self.listen = False
@@ -271,7 +342,7 @@ class MoonsStepper(Subject):
271
342
  self.Opened = False
272
343
  self.ser.flush()
273
344
  self.ser.close()
274
- print(f"[bold red]Device disconnected[/bold red]: {self.device}")
345
+ MoonsStepper.logger.info(f"Device disconnected: {self.device}")
275
346
 
276
347
  def send(self, command, eol=b"\r"):
277
348
  if (self.ser != None and self.ser.is_open) or self.only_simulate:
@@ -280,16 +351,16 @@ class MoonsStepper(Subject):
280
351
  if self.ser is not None or not self.only_simulate:
281
352
  self.ser.write(self.temp_cmd.encode("ascii"))
282
353
  if self.is_log_message:
283
- print(
284
- f"[bold green]Send to {self.device}:[/bold green] {self.temp_cmd}"
285
- )
354
+ MoonsStepper.logger.debug(f"Send to {self.device}: {self.temp_cmd}")
286
355
  super().notify_observers(f"{self.universe}-{self.temp_cmd}")
287
356
  else:
288
- print(f"Target device is not opened. Command: {command}")
357
+ MoonsStepper.logger.debug(
358
+ f"Target device is not opened. Command: {command}"
359
+ )
289
360
 
290
361
  def send_command(self, address="", command="", value=None):
291
362
  if command == "":
292
- print("Command can't be empty")
363
+ MoonsStepper.logger.warning("Command can't be empty")
293
364
  return
294
365
  if value is not None:
295
366
  command = self.addressed_cmd(address, command + str(value))
@@ -326,13 +397,13 @@ class MoonsStepper(Subject):
326
397
 
327
398
  def handle_recv(self, response):
328
399
  if "*" in response:
329
- print(f"[bold green](o)buffered_ack[/bold green]")
400
+ MoonsStepper.logger.info(f"(o)buffered_ack")
330
401
  elif "%" in response:
331
- print(f"[bold green](v)success_ack[/bold green]")
402
+ MoonsStepper.logger.info(f"(v)success_ack")
332
403
  elif "?" in response:
333
- print(f"[bold red](x)fail_ack[/bold red]")
404
+ MoonsStepper.logger.info(f"(x)fail_ack")
334
405
  else:
335
- print(f"[bold blue]Received from {self.device}: [/bold blue]", response)
406
+ MoonsStepper.logger.info(f"Received from {self.device}: ", response)
336
407
  self.recvQueue.put_nowait(response)
337
408
 
338
409
  if "=" in response:
@@ -357,6 +428,32 @@ class MoonsStepper(Subject):
357
428
  # self.set_return_format_dexcimal(motor_address)
358
429
 
359
430
  def home(self, motor_address="", speed=0.3, onComplete=None):
431
+ homing_complete = threading.Event() # Shared event to signal completion
432
+
433
+ def check_status(response):
434
+ result = MoonsStepper.process_response(response)
435
+ MoonsStepper.logger.info(f"Status check result: {result}")
436
+ if "H" not in result["value"]:
437
+ MoonsStepper.logger.info("Motor is homed.")
438
+ if onComplete: # Call the onComplete callback if provided
439
+ onComplete(result)
440
+ homing_complete.set() # Signal that homing is complete
441
+ else:
442
+ MoonsStepper.logger.info("Motor is not homed yet.")
443
+
444
+ def check_homing_complete():
445
+ while not homing_complete.is_set(): # Loop until homing is complete
446
+ self.get_status(
447
+ motor_address=motor_address,
448
+ command=StepperCommand.REQUEST_STATUS,
449
+ callback=check_status,
450
+ )
451
+ time.sleep(0.3)
452
+
453
+ home_thread = threading.Thread(
454
+ target=check_homing_complete,
455
+ daemon=True,
456
+ )
360
457
  self.send_command(
361
458
  address=motor_address, command=StepperCommand.VELOCITY, value=speed
362
459
  )
@@ -364,15 +461,12 @@ class MoonsStepper(Subject):
364
461
  address=motor_address, command=StepperCommand.HOME, value="3F"
365
462
  )
366
463
  self.send_command(
367
- address=motor_address, command=StepperCommand.ENCODER_POSITION
464
+ address=motor_address, command=StepperCommand.ENCODER_POSITION, value=0
368
465
  )
369
466
  self.send_command(
370
467
  address=motor_address, command=StepperCommand.SET_POSITION, value=0
371
468
  )
372
- if onComplete:
373
- self.get_status(
374
- motor_address, StepperCommand.SET_POSITION, callback=onComplete
375
- )
469
+ home_thread.start()
376
470
 
377
471
  # endregion
378
472
  def get_status(self, motor_address, command: StepperCommand, callback=None):
@@ -381,6 +475,33 @@ class MoonsStepper(Subject):
381
475
  self.pending_callbacks.put_nowait(callback)
382
476
  self.sendQueue.put_nowait(command)
383
477
 
478
+ def decode_status(status_code):
479
+ """
480
+ Decode the status code from the motor.
481
+ """
482
+ status = {
483
+ "A": "An Alarm code is present (use AL command to see code, AR command to clear code)",
484
+ "D": "Disabled (the drive is disabled)",
485
+ "E": "Drive Fault (drive must be reset by AR command to clear this fault)",
486
+ "F": "Motor moving",
487
+ "H": "Homing (SH in progress)",
488
+ "J": "Jogging (CJ in progress)",
489
+ "M": "Motion in progress (Feed & Jog Commands)",
490
+ "P": "In position",
491
+ "R": "Ready (Drive is enabled and ready)",
492
+ "S": "Stopping a motion (ST or SK command executing)",
493
+ "T": "Wait Time (WT command executing)",
494
+ "W": "Wait Input (WI command executing)",
495
+ }
496
+ status_string = ""
497
+ for char in status_code:
498
+ if char in status:
499
+ status_string += status[char]
500
+ status_string += "\n"
501
+ else:
502
+ status_string += f"Unknown status code: {char}"
503
+ return status_string
504
+
384
505
  # endregion
385
506
 
386
507
  # region utility functions
@@ -394,5 +515,6 @@ class MoonsStepper(Subject):
394
515
  # SERIAL => 上次已知父系(尾巴+A) 或是事件分頁
395
516
  # reg USB\s*VID:PID=(\w+):(\w+)\s*SER=([A-Za-z0-9]+)
396
517
 
518
+
397
519
  # serial_num 裝置例項路徑
398
520
  # TD(Tramsmit Delay) = 15
@@ -1,10 +1,10 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: moons_motor
3
- Version: 0.1.3
3
+ Version: 0.1.5
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
7
- Classifier: Development Status :: 3 - Alpha
7
+ Classifier: Development Status :: 4 - Beta
8
8
  Classifier: Intended Audience :: Developers
9
9
  Classifier: Topic :: Terminals :: Serial
10
10
  Classifier: License :: OSI Approved :: MIT License
@@ -38,7 +38,7 @@ python -m pip install moons_motor
38
38
  ## Usage
39
39
 
40
40
  ```python
41
- from motor import MoonsStepper, StepperModules
41
+ from motor import MoonsStepper, StepperModules, StepperCommand
42
42
  import simulate
43
43
  from time import sleep
44
44
 
@@ -47,9 +47,9 @@ motor = MoonsStepper(StepperModules.STM17S_3RN, "0403", "6001", "TESTA")
47
47
  MoonsStepper.list_all_ports()
48
48
  motor.connect()
49
49
 
50
- motor.start_jog("", 10)
50
+ motor.send_command(address="@", command=StepperCommand.JOG)
51
51
  sleep(5)
52
- motor.stop_jog()
52
+ motor.send_command(address="@", command=StepperCommand.STOP_JOG)
53
53
 
54
54
  ```
55
55
 
@@ -1,11 +1,11 @@
1
1
  moons_motor/__init__.py,sha256=qOpsRwizV-DpKSvNzyvj8ju3cs6vwgIICur1Oe6sxOA,27
2
- moons_motor/motor.py,sha256=rpunJXViqvSpMW9uRJDctBPyfSJnZh5WZtBtci16-Ck,13308
2
+ moons_motor/motor.py,sha256=LujQRwyndbeowiGcjvfPt_L8j3B2582hDJLYMD-8suM,17381
3
3
  moons_motor/observer.py,sha256=PXzuPYKRb2HpjArJcD8HakYIPfFGAs1uBDIL8PSizgA,124
4
4
  moons_motor/simulate.py,sha256=J0y1fZhoOim9i-BAkprxnPern1SAdkDfKPqT2MWyDwU,2561
5
5
  moons_motor/status.py,sha256=jXQZFZTt9ugHktkWKLII8MpEQQaeO-UjlwTrrP4LJNE,2872
6
6
  moons_motor/subject.py,sha256=L_GS6fvJTeX7X23o3T92oiZ4rtLVKA2OEd9GpHn_Dz4,445
7
- moons_motor-0.1.3.dist-info/licenses/LICENSE,sha256=nsYjO800SjIjI85y2kVHR5mC3tca2vs4kK_BhNe89bM,1074
8
- moons_motor-0.1.3.dist-info/METADATA,sha256=Zaj25TLKPvBv0qJWRInaALURr8bcOtDLNWfxQymFdtY,1304
9
- moons_motor-0.1.3.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
10
- moons_motor-0.1.3.dist-info/top_level.txt,sha256=0dE-CR5_NYBw34jHIDGQNWpMllzO6mtUIuKyRv_rJLg,12
11
- moons_motor-0.1.3.dist-info/RECORD,,
7
+ moons_motor-0.1.5.dist-info/licenses/LICENSE,sha256=nsYjO800SjIjI85y2kVHR5mC3tca2vs4kK_BhNe89bM,1074
8
+ moons_motor-0.1.5.dist-info/METADATA,sha256=enjd1RbUxi1c2mzwIAXenUEu-KegcywTe70SoIXiwQs,1403
9
+ moons_motor-0.1.5.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
10
+ moons_motor-0.1.5.dist-info/top_level.txt,sha256=0dE-CR5_NYBw34jHIDGQNWpMllzO6mtUIuKyRv_rJLg,12
11
+ moons_motor-0.1.5.dist-info/RECORD,,