mirror of
https://github.com/undera/pylgbst.git
synced 2020-11-18 19:37:26 -08:00
* It's HUB ID * Rename file * Working with official doc * Some progress * AttachedIO msg * device action impl * some better device alert impl * restructuring * Some port commands handled * Some command feedback waiting * Some more request-reply things * Some more request-reply things * Reworked msg classes * Getting back to UTs * Getting back to UTs * Facing sync lock problems * Facing sync lock problems * Testing it * Covering more with tests * handle actions * Hub class is almost covered * done coverage for Hub * done coverage for MoveHub * Button is tested * remove debug server from examples * Current and voltage tested * color sensor basic test * cover tilt sensor * motor sensor tested * constant motor * motor is tested * hold_speed impl for motor * motor commands recorded * some cleanup * some cleanup * some cleanup * debug * debug * FIX a bug * acc/dec profiles figured out * UT motor ops * UT motor ops * Get rid of weird piggyback * fix UT * Fix encoding? * fix test mb * More robust * Checked demo works * cosmetics * cosmetics * Maybe better test * fetching and decoding some device caps * describing devs * describing devs works * Applying modes we've learned * Simple and extensible dev attach * Reworking peripherals based on modes * Applying modes we've learned * implemented getting sensor data * fixed port subscribe * Added led out cmds on vision sensor * Worked on color-distance sensor * Introduce some locking for consistency * Improved it all * Travis flags * improve * improve * improve docs
237 lines
7.5 KiB
Python
237 lines
7.5 KiB
Python
import logging
|
|
import math
|
|
import time
|
|
|
|
from pylgbst.peripherals import VisionSensor, COLOR_RED, COLOR_CYAN, COLORS
|
|
|
|
|
|
class Plotter(object):
|
|
MOTOR_RATIO = 1.425
|
|
ROTATE_UNIT = 2100
|
|
|
|
def __init__(self, hub, base_speed=1.0):
|
|
"""
|
|
|
|
:type hub: pylgbst.hub.MoveHub
|
|
"""
|
|
self._hub = hub
|
|
self.caret = self._hub.motor_A
|
|
self.wheels = self._hub.motor_B
|
|
self.both = self._hub.motor_AB
|
|
|
|
self.base_speed = float(base_speed)
|
|
self.xpos = 0
|
|
self.ypos = 0
|
|
self.is_tool_down = False
|
|
self._marker_color = False
|
|
self.__last_wheel_dir = 1
|
|
|
|
def initialize(self):
|
|
self._reset_caret()
|
|
self._compensate_wheels_backlash(1)
|
|
self._compensate_wheels_backlash(-1)
|
|
self.xpos = 0
|
|
self.ypos = 0
|
|
self.is_tool_down = False
|
|
|
|
def _reset_caret(self):
|
|
if not self._hub.vision_sensor:
|
|
logging.warning("No color/distance sensor, cannot center caret")
|
|
return
|
|
|
|
self._hub.vision_sensor.subscribe(self._on_distance, mode=VisionSensor.COLOR_DISTANCE_FLOAT)
|
|
self.caret.timed(0.5, 1)
|
|
try:
|
|
self.caret.start_power(-1)
|
|
count = 0
|
|
max_tries = 50
|
|
while self._marker_color not in (COLOR_RED, COLOR_CYAN) and count < max_tries:
|
|
time.sleep(30.0 / max_tries)
|
|
count += 1
|
|
self._hub.vision_sensor.unsubscribe(self._on_distance)
|
|
clr = COLORS[self._marker_color] if self._marker_color else None
|
|
logging.info("Centering tries: %s, color #%s", count, clr)
|
|
if count >= max_tries:
|
|
raise RuntimeError("Failed to center caret")
|
|
finally:
|
|
self.caret.stop()
|
|
self._hub.vision_sensor.unsubscribe(self._on_distance)
|
|
|
|
if self._marker_color == COLOR_CYAN:
|
|
self.move(-0.1, 0)
|
|
else:
|
|
self.move(-1.0, 0)
|
|
|
|
def _on_distance(self, color, distance):
|
|
self._marker_color = None
|
|
logging.debug("Color: %s, distance %s", COLORS[color], distance)
|
|
if color in (COLOR_RED, COLOR_CYAN):
|
|
if distance <= 3:
|
|
self._marker_color = color
|
|
|
|
def _compensate_wheels_backlash(self, movy):
|
|
"""
|
|
corrects backlash of wheels gear system
|
|
"""
|
|
if not movy:
|
|
return
|
|
wheel_dir = movy / abs(movy)
|
|
if wheel_dir == -self.__last_wheel_dir:
|
|
self.wheels.angled(270, -wheel_dir)
|
|
self.__last_wheel_dir = wheel_dir
|
|
|
|
def finalize(self):
|
|
if self.is_tool_down:
|
|
self._tool_up()
|
|
self.both.stop()
|
|
|
|
def _tool_down(self):
|
|
self.is_tool_down = True
|
|
self._hub.motor_external.angled(-270, 1)
|
|
time.sleep(1.0) # for laser to heat up
|
|
|
|
def _tool_up(self):
|
|
self._hub.motor_external.angled(270, 1)
|
|
self.is_tool_down = False
|
|
|
|
def move(self, movx, movy):
|
|
if self.is_tool_down:
|
|
self._tool_up()
|
|
self._transfer_to(movx, movy)
|
|
|
|
def line(self, movx, movy):
|
|
if not self.is_tool_down:
|
|
self._tool_down()
|
|
self._transfer_to(movx, movy)
|
|
|
|
def _transfer_to(self, movx, movy):
|
|
if not movy and not movx:
|
|
logging.warning("No movement, ignored")
|
|
return
|
|
|
|
self._compensate_wheels_backlash(movy)
|
|
|
|
self.xpos += movx
|
|
self.ypos += movy
|
|
|
|
length, speed_a, speed_b = self._calc_motor_angled(movx, movy * self.MOTOR_RATIO)
|
|
logging.info("Motors: %.3f with %.3f/%.3f", length, speed_a, speed_b)
|
|
|
|
if not speed_b:
|
|
self.caret.angled(length * 2.0, -speed_a * self.base_speed * 0.75) # slow down to cut better
|
|
elif not speed_a:
|
|
self.wheels.angled(length * 2.0, -speed_b * self.base_speed * 0.75) # slow down to cut better
|
|
else:
|
|
self.both.angled(length, -speed_a * self.base_speed, -speed_b * self.base_speed)
|
|
|
|
@staticmethod
|
|
def _calc_motor_timed(movx, movy):
|
|
amovx = float(abs(movx))
|
|
amovy = float(abs(movy))
|
|
|
|
length = max(amovx, amovy)
|
|
|
|
speed_a = (movx / float(amovx)) if amovx else 0.0
|
|
speed_b = (movy / float(amovy)) if amovy else 0.0
|
|
|
|
if amovx >= amovy:
|
|
speed_b = movy / amovx
|
|
else:
|
|
speed_a = movx / amovy
|
|
|
|
assert -1 <= speed_a <= 1
|
|
assert -1 <= speed_b <= 1
|
|
|
|
return length, speed_a, speed_b
|
|
|
|
@staticmethod
|
|
def _calc_motor_angled(movx, movy):
|
|
amovx = abs(movx)
|
|
amovy = abs(movy)
|
|
if amovx >= amovy:
|
|
ax = amovy / (amovx + amovy)
|
|
spd_b = ax
|
|
if spd_b < 0.05:
|
|
spd_b = 0
|
|
spd_a = (1.0 - spd_b)
|
|
rotate = Plotter.ROTATE_UNIT * amovx * (1.0 + spd_b / spd_a)
|
|
logging.info("A: movx=%s, movy=%s, ax=%s", movx, movy, ax)
|
|
else:
|
|
ax = amovx / (amovx + amovy)
|
|
spd_a = ax
|
|
if spd_a < 0.05:
|
|
spd_a = 0
|
|
spd_b = (1.0 - spd_a)
|
|
rotate = Plotter.ROTATE_UNIT * amovy * (1.0 + spd_a / spd_b)
|
|
logging.info("B: movx=%s, movy=%s, ax=%s", movx, movy, ax)
|
|
|
|
assert 0 <= spd_a <= 1
|
|
assert 0 <= spd_b <= 1
|
|
|
|
spd_a *= (movx / amovx) if amovx else 0
|
|
spd_b *= (movy / amovy) if amovy else 0
|
|
return rotate, spd_a, spd_b
|
|
|
|
def circle(self, radius):
|
|
if not self.is_tool_down:
|
|
self._tool_down()
|
|
|
|
parts = int(2 * math.pi * radius * 7)
|
|
dur = 0.025
|
|
logging.info("Circle of radius %s, %s parts with %s time", radius, parts, dur)
|
|
speeds = []
|
|
for x in range(0, parts):
|
|
speed_a = math.sin(x * 2.0 * math.pi / float(parts))
|
|
speed_b = math.cos(x * 2.0 * math.pi / float(parts))
|
|
speeds.append((speed_a, speed_b))
|
|
logging.debug("A: %s, B: %s", speed_a, speed_b)
|
|
speeds.append((0, 0))
|
|
|
|
for speed_a, speed_b in speeds:
|
|
spa = speed_a * self.base_speed
|
|
spb = -speed_b * self.base_speed * self.MOTOR_RATIO
|
|
logging.info("Motor speeds: %.3f / %.3f", spa, spb)
|
|
self.both.start_power(spa, spb)
|
|
time.sleep(dur)
|
|
|
|
def spiral(self, rounds, growth):
|
|
if not self.is_tool_down:
|
|
self._tool_down()
|
|
|
|
dur = 0.00
|
|
parts = 16
|
|
speeds = []
|
|
for r in range(0, rounds):
|
|
logging.info("Round: %s", r)
|
|
|
|
for x in range(0, parts):
|
|
speed_a = math.sin(x * 2.0 * math.pi / float(parts))
|
|
speed_b = math.cos(x * 2.0 * math.pi / float(parts))
|
|
dur += growth
|
|
speeds.append((speed_a, speed_b, dur))
|
|
logging.debug("A: %s, B: %s", speed_a, speed_b)
|
|
speeds.append((0, 0, 0))
|
|
|
|
for speed_a, speed_b, dur in speeds:
|
|
spa = speed_a * self.base_speed
|
|
spb = -speed_b * self.base_speed * self.MOTOR_RATIO
|
|
self.both.start_power(spa, spb)
|
|
logging.info("Motor speeds: %.3f / %.3f", spa, spb)
|
|
time.sleep(dur)
|
|
|
|
def rectangle(self, width, height, solid=False):
|
|
self.line(width, 0)
|
|
self.line(0, height)
|
|
self.line(-width, 0)
|
|
self.line(0, -height)
|
|
|
|
if solid:
|
|
max_step = 0.01
|
|
rounds = int(math.ceil(height / max_step))
|
|
step = height / rounds
|
|
flip = 1
|
|
for r in range(1, rounds):
|
|
self.line(0, step)
|
|
self.line(width * flip, 0)
|
|
flip = -flip
|