@@ -226,8 +226,10 @@ async def setup(self):
226226
227227 resp = 0x89
228228 while resp == 0x89 :
229+ stat = await self ._get_positions ()
229230 resp = stat [0 ]
230231
232+ # --- almost the same as go to position ---
231233 await self .send (bytes .fromhex ("aa0117021a" ))
232234 await self .send (bytes .fromhex ("aa01e6c800b00496000f004b00a00f050007" ))
233235 await self .send (bytes .fromhex ("aa0117041c" ))
@@ -236,10 +238,14 @@ async def setup(self):
236238 await self .send (bytes .fromhex ("aa010b0c" ))
237239 await self .send (bytes .fromhex ("aa01e6c800b00496000f004b00a00f050007" ))
238240 new_position = (0 ).to_bytes (4 , byteorder = "little" ) # arbitrary
241+ # rpm = 600,
242+ # acceleration = 75.09289617486338
239243 await self .send (bytes .fromhex ("aa01d497" ) + new_position + bytes .fromhex ("c3f52800d71a000049" ))
244+ # -----------------------------------------
240245
241246 resp = 0x08
242247 while resp != 0x09 :
248+ stat = await self ._get_positions ()
243249 resp = stat [0 ]
244250
245251 await self .send (bytes .fromhex ("aa0117021a" ))
@@ -276,34 +282,61 @@ async def stop(self):
276282 await self .configure_and_initialize ()
277283 await self .io .stop ()
278284
279- async def get_status (self ):
285+ async def _get_positions (self ):
280286 """Returns 14 bytes
281287
282288 Example:
283289 11 22 25 00 00 4f 00 00 18 e0 05 00 00 a4
284290
285291 - First byte (index 0):
286- - 11 = idle
287- - 13 = unknown
288- - 08 = spinning
289- - 09 = also spinning but different
290- - 19 = unknown
292+ - 11 = 0b0001011 = idle
293+ - 13 = 0b0001101 = unknown
294+ - 08 = 0b0001000 = spinning
295+ - 09 = 0b0001001 = also spinning but different
296+ - 19 = 0b0010011 = unknown
297+ - 88 = 0b1011000 = unknown
298+ - 89 = 0b1011001 = unknown
291299 - 2nd to 5th byte (index 1-4) = Position
292300 - 10th to 13th byte (index 9-12) = Homing Position
293301 - Last byte (index 13) = checksum
294302 """
303+ resp = await self .send (bytes .fromhex ("aa010e0f" ))
295304 if len (resp ) == 0 :
296305 raise IOError ("Empty status from centrifuge" )
297306 return resp
298307
299308 async def get_position (self ):
300- resp = await self .get_status ()
309+ resp = await self ._get_positions ()
301310 return int .from_bytes (resp [1 :5 ], byteorder = "little" )
302311
303312 async def get_home_position (self ):
304- resp = await self .get_status ()
313+ resp = await self ._get_positions ()
305314 return int .from_bytes (resp [9 :13 ], byteorder = "little" )
306315
316+ async def _get_status (self ):
317+ """
318+ examples:
319+ - 0080d0015
320+ - 0080f0015
321+ """
322+
323+ resp = await self .send (bytes .fromhex ("aa020e10" ))
324+ if len (resp ) == 0 :
325+ raise IOError ("Empty status from centrifuge" )
326+ return resp
327+
328+ async def get_bucket_locked (self ) -> bool :
329+ resp = await self ._get_status ()
330+ return resp [2 ] & 0b0001 != 0
331+
332+ async def get_door_open (self ) -> bool :
333+ resp = await self ._get_status ()
334+ return resp [2 ] & 0b0010 != 0
335+
336+ async def get_door_locked (self ) -> bool :
337+ resp = await self ._get_status ()
338+ return resp [2 ] & 0b0100 == 0
339+
307340 # Centrifuge communication: read_resp, send
308341
309342 async def read_resp (self , timeout = 20 ) -> bytes :
@@ -328,8 +361,8 @@ async def read_resp(self, timeout=20) -> bytes:
328361 logger .debug ("Read %s" , data .hex ())
329362 return data
330363
331- async def send (self , cmd : Union [ bytearray , bytes ] , read_timeout = 0.2 ) -> bytes :
332- written = await self .io .write (bytes (cmd )) # TODO: why decode? .decode("latin-1")
364+ async def send (self , cmd : bytes , read_timeout = 0.2 ) -> bytes :
365+ written = await self .io .write (bytes (cmd ))
333366
334367 if written != len (cmd ):
335368 raise RuntimeError ("Failed to write all bytes" )
@@ -356,26 +389,31 @@ async def initialize(self):
356389 # Centrifuge operations
357390
358391 async def open_door (self ):
359- await self .send (bytes .fromhex ("aa022600072f" ))
392+ # used to be: aa022600072f
393+ await self .send (bytes .fromhex ("aa022600062e" )) # same as unlock door
394+
360395 # we can't tell when the door is fully open, so we just wait a bit
361396 await asyncio .sleep (4 )
362397
363398 async def close_door (self ):
364- await self .send (bytes .fromhex ("aa022600052d" ))
399+ # used to be: aa022600052d
400+ await self .send (bytes .fromhex ("aa022600042c" )) # same as unlock door
365401 # we can't tell when the door is fully closed, so we just wait a bit
366402 await asyncio .sleep (2 )
367403
368404 async def lock_door (self ):
369- await self .send (bytes .fromhex ("aa0226000129" ))
405+ # used to be aa0226000129
406+ await self .send (bytes .fromhex ("aa0226000028" ))
370407
371408 async def unlock_door (self ):
372- await self .send (bytes .fromhex ("aa022600052d" ))
409+ # used to be aa022600052d
410+ await self .send (bytes .fromhex ("aa022600042c" )) # same as close door
373411
374412 async def lock_bucket (self ):
375413 await self .send (bytes .fromhex ("aa022600072f" ))
376414
377415 async def unlock_bucket (self ):
378- await self .send (bytes .fromhex ("aa022600062e" ))
416+ await self .send (bytes .fromhex ("aa022600062e" )) # same as open door
379417
380418 async def go_to_bucket1 (self ):
381419 await self .go_to_position (await self .get_bucket_1_position ())
@@ -396,9 +434,7 @@ async def go_to_position(self, position: int):
396434 sum_byte = (sum (byte_string ) - 0xAA ) & 0xFF
397435 byte_string += sum_byte .to_bytes (1 , byteorder = "little" )
398436 await self .send (bytes .fromhex ("aa0226000028" ))
399- await self .send (bytes .fromhex ("aa020e10" ))
400437 await self .send (bytes .fromhex ("aa0117021a" ))
401- await self .send (bytes .fromhex ("aa010e0f" ))
402438 await self .send (bytes .fromhex ("aa01e6c800b00496000f004b00a00f050007" ))
403439 await self .send (bytes .fromhex ("aa0117041c" ))
404440 await self .send (bytes .fromhex ("aa01170119" ))
@@ -408,9 +444,60 @@ async def go_to_position(self, position: int):
408444
409445 await asyncio .sleep (2 )
410446
447+ # TODO: needs a loop to confirm position reached
448+
411449 await self .send (bytes .fromhex ("aa0117021a" ))
412450 await self .open_door ()
413451
452+ async def _go_to_position (
453+ self ,
454+ position : int ,
455+ acceleration : float ,
456+ rpm : int ,
457+ ) -> None :
458+ """Internal method to go to a position with specified acceleration and rpm.
459+ This method is used both by `start_spin_cycle` and `go_to_position`.
460+ `go_to_position` is used by `go_to_bucket1` and `go_to_bucket2`.
461+ """
462+
463+ if position > 2 ** 32 - 1 :
464+ raise NotImplementedError (
465+ "We don't know what happens if the position exceeds 2^32-1. "
466+ "Please report this issue on discuss.pylabrobot.org."
467+ )
468+ position_b = position .to_bytes (4 , byteorder = "little" )
469+
470+ # 2 - encode the rpm
471+ rpm_b = int (rpm * 4473.925 ).to_bytes (4 , byteorder = "little" )
472+
473+ # 3 - encode the acceleration
474+ acceleration_b = int (9.15 * 100 * acceleration ).to_bytes (2 , byteorder = "little" )
475+
476+ byte_string = (
477+ bytes .fromhex ("aa01d497" ) + position_b + rpm_b + acceleration_b + bytes .fromhex ("0000" )
478+ )
479+ last_byte = (sum (byte_string ) - 0xAA ) & 0xFF
480+ byte_string += last_byte .to_bytes (1 , byteorder = "little" )
481+ print (
482+ f"position: { position } , RPM: { rpm } , Acceleration: { acceleration } , byte_string: { byte_string .hex ()} "
483+ )
484+
485+ await self .send (bytes .fromhex ("aa0226000028" ))
486+ await self .send (bytes .fromhex ("aa0117021a" ))
487+ await self .send (bytes .fromhex ("aa01e6c800b00496000f004b00a00f050007" ))
488+ await self .send (bytes .fromhex ("aa0117041c" ))
489+ await self .send (bytes .fromhex ("aa01170119" ))
490+ await self .send (bytes .fromhex ("aa010b0c" ))
491+ await self .send (bytes .fromhex ("aa01e60500640000000000fd00803e01000c" ))
492+
493+ # spin: aa01e60500640000000000fd00803e01000c
494+ # go to pos: aa01e6c800b00496000f004b00a00f050007
495+
496+ await self .send (byte_string )
497+
498+ # after spin
499+ # aa0117021a
500+
414501 async def start_spin_cycle (
415502 self ,
416503 g : float = 500 ,
@@ -423,8 +510,8 @@ async def start_spin_cycle(
423510 Args:
424511 g: relative centrifugal force, also known as g-force
425512 duration: time in seconds spent at speed (g)
426- acceleration: 1-100% of total acceleration
427- deceleration: 1-100% of total deceleration
513+ acceleration: 0-1 of total acceleration
514+ deceleration: 0-1 of total deceleration
428515
429516 Examples:
430517 Spin with 1000 g-force (close to 3000rpm) for 5 minutes at 100% acceleration
@@ -449,59 +536,37 @@ async def start_spin_cycle(
449536
450537 # compute the distance traveled during the acceleration period
451538 # distance = 1/2 * v^2 / a. area under 0 to t (triangle). t = a/v_max
452- # 12903.2 is 100% acceleration
539+ # 12903.2 ticks/s^2 is 100% acceleration
453540 acceleration_ticks_per_second2 = 12903.2 * acceleration
454- speed_per_second = rpm / 60
455- distance_during_acceleration = (speed_per_second * speed_per_second / acceleration ) // 2
541+ rounds_per_second = rpm / 60
542+ ticks_per_second = rounds_per_second * 8000
543+ distance_during_acceleration = int (0.5 * (ticks_per_second ** 2 ) / acceleration_ticks_per_second2 )
456544
457545 # compute the distance traveled at speed
458- distance_at_speed = speed_per_second * duration
546+ distance_at_speed = ticks_per_second * duration
459547
460548 current_position = await self .get_position ()
461549 final_position = current_position + distance_during_acceleration + distance_at_speed
462- if final_position > 2 ** 32 - 1 :
463- raise NotImplementedError (
464- "We don't know what happens if the position exceeds 2^32-1. "
465- "Please report this issue on discuss.pylabrobot.org."
466- )
467- position = final_position .to_bytes (4 , byteorder = "little" )
468550
469- # 2 - encode the rpm
470- rpm_b = int (rpm * 4473.925 ).to_bytes (4 , byteorder = "little" )
471-
472- # 3 - encode the acceleration
473- acc = int (9.15 * 10 * acceleration ).to_bytes (2 , byteorder = "little" )
474-
475- byte_string = bytes .fromhex ("aa01d497" ) + position + rpm_b + acc + bytes .fromhex ("0000" )
476- last_byte = (sum (byte_string ) - 0xAA ) & 0xFF
477- byte_string += last_byte .to_bytes (1 , byteorder = "little" )
478- print (
479- f"Final position: { final_position } , RPM: { rpm } , Acceleration: { acceleration } , current position: { current_position } , duration: { duration } , byte_string: { byte_string .hex ()} "
551+ await self ._go_to_position (
552+ position = int (final_position ),
553+ acceleration = acceleration ,
554+ rpm = rpm ,
480555 )
481556
482- await self .send (bytes .fromhex ("aa0226000028" ))
483- # await self.send(bytes.fromhex("aa020e10"))
484- await self .send (bytes .fromhex ("aa0117021a" ))
485- # await self.send(bytes.fromhex("aa010e0f"))
486- await self .send (bytes .fromhex ("aa01e6c800b00496000f004b00a00f050007" ))
487- await self .send (bytes .fromhex ("aa0117041c" ))
488- await self .send (bytes .fromhex ("aa01170119" ))
489- await self .send (bytes .fromhex ("aa010b0c" ))
490- # await self.send(bytes.fromhex("aa010e0f"))
491- await self .send (bytes .fromhex ("aa01e60500640000000000fd00803e01000c" ))
492- await self .send (byte_string )
493-
494- status_resp = await self .get_status ()
557+ status_resp = await self ._get_positions ()
495558 status = status_resp [0 ]
496559 while status == 0x08 :
497560 await asyncio .sleep (1 )
498- status_resp = await self .get_status ()
561+ status_resp = await self ._get_positions ()
499562 status = status_resp [0 ]
500563
501564 await self .send (bytes .fromhex ("aa01e60500640000000000fd00803e01000c" ))
502565 # aa0194b600000000dc02000029: decel at 80
503566 # aa0194b6000000000a03000058: decel at 85
504- decc = int (9.15 * 10 * deceleration ).to_bytes (2 , byteorder = "little" )
567+ # aa0194b61283000012010000f3: used in setup (30%)
568+ # decel must be between 0.0 and 1.0
569+ decc = int (9.15 * 100 * deceleration ).to_bytes (2 , byteorder = "little" )
505570 decel_command = bytes .fromhex ("aa0194b600000000" ) + decc + bytes .fromhex ("0000" )
506571 decel_command += ((sum (decel_command ) - 0xAA ) & 0xFF ).to_bytes (1 , byteorder = "little" )
507572 await self .send (decel_command )
0 commit comments