diff --git a/.prospector.yaml b/.prospector.yaml new file mode 100644 index 00000000..a77729e4 --- /dev/null +++ b/.prospector.yaml @@ -0,0 +1,4 @@ +pylint: + disable: + - assignment-from-no-return + - too-many-arguments diff --git a/.vscode/settings.json b/.vscode/settings.json index 9e32897c..58235ea2 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -1,13 +1,43 @@ { "editor.rulers": [70], + "files.exclude": { + "**/.git": true, + "**/.mypy_cache": true, + "**/*.pyc": {"when": "$(basename).py"}, + "**/__pycache__": true, + "**/.ropeproject": true, + }, + + "python.analysis.diagnosticSeverityOverrides": { + "reportMissingImports": "none", + }, + "python.linting.enabled": true, + "python.linting.pylintEnabled": true, + "python.linting.pylintArgs": [ + "--disable", "assignment-from-no-return", + "--disable", "import-error", + "--disable", "missing-class-docstring", + "--disable", "missing-function-docstring", + "--disable", "missing-module-docstring", + "--disable", "no-name-in-module", + "--disable", "too-few-public-methods", + "--disable", "too-many-arguments", + ], + "python.linting.flake8Enabled": true, + "python.linting.mypyEnabled": true, + "python.linting.pydocstyleEnabled": false, + "python.linting.pycodestyleEnabled": true, + "python.linting.prospectorEnabled": true, + "python.linting.pylamaEnabled": true, - "python.linting.banditEnabled": true + + "python.linting.banditEnabled": true, } diff --git a/remix/birthday-cake-cutter/birthday-cake-cutter.jpg b/remix/birthday-cake-cutter/birthday-cake-cutter.jpg new file mode 100644 index 00000000..2f4b33b2 Binary files /dev/null and b/remix/birthday-cake-cutter/birthday-cake-cutter.jpg differ diff --git a/remix/birthday-cake-cutter/index.md b/remix/birthday-cake-cutter/index.md new file mode 100755 index 00000000..2e4e1986 --- /dev/null +++ b/remix/birthday-cake-cutter/index.md @@ -0,0 +1,57 @@ +--- +title: "Birthday Bot: Cake Cutter" +maintainer: + user: "TheVinhLuong102" + name: "The Lương-Phạm Family" +image: + local: "birthday-cake-cutter.jpg" +video: + youtube: "yckg0h2E-Ac" +description: + "Strong with a lot of torque, this robot is ready to cut the cake with a knife wielded by its powerful arm. It drives around on four wheels and also sings Happy Birthday. Member of the Lương-Phạm family's Birthday Bots squad, alongside Birthday Candle Blower and Birthday Gift Presenter." +building_instructions: + local: TODO +code: "#program" +--- + + +Control the Birthday Cake Cutter by the Powered-Up Remote as follows: + +- Switch between two modes: + - Left Red Button: Driving Mode (center button of Hub will turn green) + - Right Red Button: Cake-Cutting Mode (center button of Hub will turn red) + +- Driving Mode: (refer to the `RemoteControlledDriveBase` code below) + - 2 Top/Plus buttons together: drive forward + - 2 Bottom/Minus buttons together: drive backward + - Top-Left/Left-Plus and Bottom-Right/Right-Minus together: turn left on the spot + - Top-Right/Right-Plus and Bottom-Left/Left-Minus together: turn right on the spot + - Top-Left/Left-Plus: turn left forward + - Top-Right/Right-Plus: turn right forward + - Bottom-Left/Left-Minus: turn left backward + - Bottom-Right/Right-Minus: turn right backward + +- Cake-Cutting Mode: + - Arm Control: + - Bottom-Left/Left-Minus: bring arm backward + - Top-Left/Left-Plus: bring arm forward + - Knife Control: + - Bottom-Right/Right-Minus: bring knife backward + - Top-Right/Right-Plus: bring knife forward + +- Singing Happy Birthday: green Center button + + +## Technical Design and Safety Notes + +For the Arm and Knife to cut hard cakes, we design them with high torque. (The opposite is true for its friend Candle Blower, which needs high speed and low torque for its fan.) + +For safety, this robot uses a plastic butter knife. If you want a big, sharp metal knife instead, you have to re-design the knife holder (and operate at your own risk!). + + +## Program + +{% include copy-code.html %} +```python +{% include_relative main.py %} +``` diff --git a/remix/birthday-cake-cutter/main.py b/remix/birthday-cake-cutter/main.py new file mode 100644 index 00000000..16d9412f --- /dev/null +++ b/remix/birthday-cake-cutter/main.py @@ -0,0 +1,192 @@ +from pybricks.hubs import InventorHub +from pybricks.pupdevices import Motor, Remote +from pybricks.robotics import DriveBase +from pybricks.geometry import Axis +from pybricks.parameters import ( + Button, + Color, + Direction as Dir, + Icon, + Port +) + + +HAPPY_BIRTHDAY_SONG = [ + 'G3/8', 'G3/8', 'A3/4', 'G3/4', 'C4/4', 'B3/2', + 'G3/8', 'G3/8', 'A3/4', 'G3/4', 'D4/4', 'C4/2', + 'G3/8', 'G3/8', 'G4/4', 'E4/4', + 'C4/8', 'C4/8', 'B3/4', 'A3/4', + 'F4/8', 'F4/8', 'E4/4', 'C4/4', 'D4/4', 'C4/2' +] + + +class RemoteControlledDriveBase: + def __init__(self, + wheel_diameter: float, # milimeters + axle_track: float, # milimeters + left_motor_port: Port = Port.A, + left_motor_pos_dir: Dir = Dir.COUNTERCLOCKWISE, + right_motor_port: Port = Port.B, + right_motor_pos_dir: Dir = Dir.CLOCKWISE): + self.drive_base = \ + DriveBase(left_motor=Motor(left_motor_port, + left_motor_pos_dir), + right_motor=Motor(right_motor_port, + right_motor_pos_dir), + wheel_diameter=wheel_diameter, + axle_track=axle_track) + + self.remote = Remote() + print('Remote Connected!') + + def drive_by_remote(self, + speed: float = 1000, # mm/s + turn_rate: float = 90 # rotational deg/s + ): + remote_button_pressed = self.remote.buttons.pressed() + + # forward + if remote_button_pressed == (Button.LEFT_PLUS, + Button.RIGHT_PLUS): + self.drive_base.drive(speed=speed, turn_rate=0) + + # backward + elif remote_button_pressed == (Button.LEFT_MINUS, + Button.RIGHT_MINUS): + self.drive_base.drive(speed=-speed, turn_rate=0) + + # turn left on the spot + elif remote_button_pressed == (Button.RIGHT_MINUS, + Button.LEFT_PLUS): + self.drive_base.drive(speed=0, turn_rate=-turn_rate) + + # turn right on the spot + elif remote_button_pressed == (Button.LEFT_MINUS, + Button.RIGHT_PLUS): + self.drive_base.drive(speed=0, turn_rate=turn_rate) + + # turn left forward + elif remote_button_pressed == (Button.LEFT_PLUS,): + self.drive_base.drive(speed=speed, turn_rate=-turn_rate) + + # turn right forward + elif remote_button_pressed == (Button.RIGHT_PLUS,): + self.drive_base.drive(speed=speed, turn_rate=turn_rate) + + # turn left backward + elif remote_button_pressed == (Button.LEFT_MINUS,): + self.drive_base.drive(speed=-speed, turn_rate=turn_rate) + + # turn right backward + elif remote_button_pressed == (Button.RIGHT_MINUS,): + self.drive_base.drive(speed=-speed, turn_rate=-turn_rate) + + # otherwise stop + else: + self.drive_base.stop() + + +class BirthdayCakeCutter(RemoteControlledDriveBase): + WHEEL_DIAMETER = 44 # milimeters + AXLE_TRACK = 75 # milimeters + + def __init__(self, + left_motor_port: Port = Port.D, + right_motor_port: Port = Port.C, + arm_control_motor_port: Port = Port.A, + knife_control_motor_port: Port = Port.B): + super().__init__(wheel_diameter=self.WHEEL_DIAMETER, + axle_track=self.AXLE_TRACK, + left_motor_port=left_motor_port, + left_motor_pos_dir=Dir.COUNTERCLOCKWISE, + right_motor_port=right_motor_port, + right_motor_pos_dir=Dir.CLOCKWISE) + + self.hub = InventorHub(top_side=Axis.X, front_side=Axis.Z) + + self.arm_control_motor = \ + Motor(port=arm_control_motor_port, + positive_direction=Dir.CLOCKWISE) + + self.knife_control_motor = \ + Motor(port=knife_control_motor_port, + positive_direction=Dir.COUNTERCLOCKWISE) + + self.cake_cutting_mode = False + + self.switch_to_driving_mode() + + def switch_to_driving_mode(self): + self.cake_cutting_mode = False + + self.hub.light.on(color=Color.GREEN) + + def switch_to_cake_cutting_mode(self): + self.cake_cutting_mode = True + + self.hub.light.on(color=Color.RED) + + def switch_mode_by_remote_red_buttons(self): + remote_button_pressed = self.remote.buttons.pressed() + + if remote_button_pressed == (Button.LEFT,): + if self.cake_cutting_mode: + self.switch_to_driving_mode() + + elif remote_button_pressed == (Button.RIGHT,): + if not self.cake_cutting_mode: + self.switch_to_cake_cutting_mode() + + def smile(self): + self.hub.display.image(Icon.HAPPY) + + def sing_happy_birthday_by_remote_center_button(self): + if self.remote.buttons.pressed() == (Button.CENTER,): + self.hub.speaker.play_notes( + notes=HAPPY_BIRTHDAY_SONG, + tempo=120) + + def control_arm_by_remote_left_buttons(self): + remote_button_pressed = self.remote.buttons.pressed() + + if remote_button_pressed == (Button.LEFT_MINUS,): + self.arm_control_motor.run(speed=-100) + + elif remote_button_pressed == (Button.LEFT_PLUS,): + self.arm_control_motor.run(speed=100) + + else: + self.arm_control_motor.hold() + + def control_knife_by_remote_right_buttons(self): + remote_button_pressed = self.remote.buttons.pressed() + + if remote_button_pressed == (Button.RIGHT_MINUS,): + self.knife_control_motor.run(speed=-100) + + elif remote_button_pressed == (Button.RIGHT_PLUS,): + self.knife_control_motor.run(speed=100) + + else: + self.knife_control_motor.hold() + + +# initialize Birthday Cake Cutter +birthday_cake_cutter = BirthdayCakeCutter() + +# make it smile +birthday_cake_cutter.smile() + +# remote-control it to drive around, sing and cut the cake +while True: + birthday_cake_cutter.switch_mode_by_remote_red_buttons() + + birthday_cake_cutter.sing_happy_birthday_by_remote_center_button() + + if birthday_cake_cutter.cake_cutting_mode: + birthday_cake_cutter.control_arm_by_remote_left_buttons() + birthday_cake_cutter.control_knife_by_remote_right_buttons() + + else: + # drive slowly to reduce risk of colliding with cake + birthday_cake_cutter.drive_by_remote(speed=50) diff --git a/sets/mindstorms-robot-inventor/fan-inventions/print-and-scan/index.md b/sets/mindstorms-robot-inventor/fan-inventions/print-and-scan/index.md new file mode 100644 index 00000000..6cc6bd32 --- /dev/null +++ b/sets/mindstorms-robot-inventor/fan-inventions/print-and-scan/index.md @@ -0,0 +1 @@ +# [Disk Color Maze](https://docs.google.com/document/d/1hUiUxl1xtpDF-aG0GPg1tKvbY4T_vwGVMXpsKVLn4AI)