22import ctypes
33import json
44import logging
5+ import math
56import os
67import time
78import warnings
@@ -279,11 +280,22 @@ async def set_bucket_1_position_to_current(self) -> None:
279280 _save_vspin_calibrations (device_id , remainder )
280281
281282 async def get_bucket_1_position (self ) -> int :
282- """Get the bucket 1 position based on calibration."""
283+ """Get the bucket 1 position based on calibration.
284+ Normally it is the home position minus the remainder (calibration).
285+ The bucket 1 position must be greater than the current position, so we find
286+ the first position greater than the current position by adding full rotations if needed.
287+ """
283288 if self ._bucket_1_remainder is None :
284289 raise bucket_1_not_set_error
285290 home_position = await self .get_home_position ()
286- bucket_1_position = (home_position - self .bucket_1_remainder ) % FULL_ROTATION
291+ bucket_1_position_mod_full_rotation = home_position - self .bucket_1_remainder
292+ # first number after current position that matches bucket 1 position mod FULL_ROTATION
293+ current_position = await self .get_position ()
294+ bucket_1_position = (
295+ FULL_ROTATION
296+ * math .floor ((current_position - bucket_1_position_mod_full_rotation ) / FULL_ROTATION + 1 )
297+ + bucket_1_position_mod_full_rotation
298+ )
287299 return bucket_1_position
288300
289301 async def stop (self ):
@@ -572,18 +584,21 @@ async def spin(
572584 await self ._send_command (byte_string )
573585
574586 # 3 - wait for acceleration to the set rpm
575- while await self .get_tachometer () < rpm * 0.95 :
587+ # we also check the position to avoid waiting forever if the speed is not reached (e.g. short spin...)
588+ while await self .get_tachometer () < rpm * 0.95 and await self .get_position () < final_position :
576589 await asyncio .sleep (0.1 )
577590
578591 # 4 - once the speed is reached, compute the position at which to start deceleration
579592 # this is different than computed above, because above we assumed constant acceleration from 0 to rpm.
580593 # however, in reality there is jerk and the acceleration is not constant, so we have to adjust as we go.
581594 # this is what the vendor software does too.
582- decel_start_position = await self .get_position () + distance_at_speed
595+ # if we are already past that position, we skip this part.
596+ if await self .get_position () < final_position :
597+ decel_start_position = await self .get_position () + distance_at_speed
583598
584- # then wait until we reach that position
585- while await self .get_position () < decel_start_position :
586- await asyncio .sleep (0.1 )
599+ # then wait until we reach that position
600+ while await self .get_position () < decel_start_position :
601+ await asyncio .sleep (0.1 )
587602
588603 # 5 - send deceleration command
589604 await self ._send_command (bytes .fromhex ("aa01e60500640000000000fd00803e01000c" ))
@@ -598,16 +613,31 @@ async def spin(
598613 await asyncio .sleep (2 )
599614
600615 # 6 - reset position back to 0ish
601- # this part is needed because otherwise calling go_to_position will not work after
602- await self ._send_command (bytes .fromhex ("aa0117021a" ))
603- await self ._send_command (bytes .fromhex ("aa01e6c800b00496000f004b00a00f050007" ))
604- await self ._send_command (bytes .fromhex ("aa0117041c" ))
605- await self ._send_command (bytes .fromhex ("aa01170119" ))
606- await self ._send_command (bytes .fromhex ("aa010b0c" ))
607- await self ._send_command (bytes .fromhex ("aa010001" )) # set position back to 0 (exactly)
608- await self ._send_command (bytes .fromhex ("aa01e605006400000000003200e80301006e" ))
609- await self ._send_command (bytes .fromhex ("aa0194b61283000012010000f3" ))
610- await self ._send_command (bytes .fromhex ("aa01192842" )) # it starts moving again
616+ # this part is aneeded because otherwise calling go_to_position will not work after
617+ async def _reset_to_zero ():
618+ await self ._send_command (bytes .fromhex ("aa0117021a" ))
619+ await self ._send_command (bytes .fromhex ("aa01e6c800b00496000f004b00a00f050007" ))
620+ await self ._send_command (bytes .fromhex ("aa0117041c" ))
621+ await self ._send_command (bytes .fromhex ("aa01170119" ))
622+ await self ._send_command (bytes .fromhex ("aa010b0c" ))
623+ await self ._send_command (bytes .fromhex ("aa010001" )) # set position back to 0 (exactly)
624+ await self ._send_command (bytes .fromhex ("aa01e605006400000000003200e80301006e" ))
625+ await self ._send_command (bytes .fromhex ("aa0194b61283000012010000f3" ))
626+ await self ._send_command (bytes .fromhex ("aa01192842" )) # it starts moving again
627+
628+ await _reset_to_zero ()
629+
630+ # 7 - wait for home position to change
631+ # go_to_bucket{1,2} does not work until the home position changes
632+ start = await self .get_home_position ()
633+ num_tries = 0
634+ while await self .get_home_position () == start :
635+ await asyncio .sleep (0.1 )
636+ num_tries += 1
637+ if num_tries % 25 == 0 :
638+ await _reset_to_zero ()
639+ if num_tries > 100 :
640+ raise RuntimeError ("Home position did not change after spin." )
611641
612642
613643# Deprecated alias with warning # TODO: remove mid May 2025 (giving people 1 month to update)
0 commit comments