Skip to content

Commit 4feca89

Browse files
committed
more status checks, refine
1 parent 7ec9a28 commit 4feca89

File tree

1 file changed

+120
-55
lines changed

1 file changed

+120
-55
lines changed

pylabrobot/centrifuge/vspin_backend.py

Lines changed: 120 additions & 55 deletions
Original file line numberDiff line numberDiff line change
@@ -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

Comments
 (0)