diff --git a/remix/birthday-gift-present3r/.gitignore b/remix/birthday-gift-present3r/.gitignore new file mode 100644 index 00000000..9b5f630a --- /dev/null +++ b/remix/birthday-gift-present3r/.gitignore @@ -0,0 +1,3 @@ +__pycache__/ +*.pyc +venv/ diff --git a/remix/birthday-gift-present3r/.vscode/extensions.json b/remix/birthday-gift-present3r/.vscode/extensions.json new file mode 100644 index 00000000..f395d79f --- /dev/null +++ b/remix/birthday-gift-present3r/.vscode/extensions.json @@ -0,0 +1,14 @@ +{ + // See http://go.microsoft.com/fwlink/?LinkId=827846 to learn about workspace recommendations. + // Extension identifier format: ${publisher}.${name}. Example: vscode.csharp + + // List of extensions which should be recommended for users of this workspace. + "recommendations": [ + "lego-education.ev3-micropython" + ], + + // List of extensions recommended by VS Code that should not be recommended for users of this workspace. + "unwantedRecommendations": [ + "ms-python.python" + ] +} diff --git a/remix/birthday-gift-present3r/.vscode/launch.json b/remix/birthday-gift-present3r/.vscode/launch.json new file mode 100644 index 00000000..4c6db1d7 --- /dev/null +++ b/remix/birthday-gift-present3r/.vscode/launch.json @@ -0,0 +1,17 @@ +{ + // Use IntelliSense to learn about possible attributes. + // Hover to view descriptions of existing attributes. + // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387 + + "version": "0.2.0", + + "configurations": [ + { + "name": "Download and Run", + "type": "ev3devBrowser", + "request": "launch", + "program": "/home/robot/${workspaceRootFolderName}/main.py", + "interactiveTerminal": false + } + ] +} diff --git a/remix/birthday-gift-present3r/.vscode/settings.json b/remix/birthday-gift-present3r/.vscode/settings.json new file mode 100644 index 00000000..1c545062 --- /dev/null +++ b/remix/birthday-gift-present3r/.vscode/settings.json @@ -0,0 +1,45 @@ +// Place your settings in this file to overwrite default and user settings. +{ + "editor.rulers": [70], + + "ev3devBrowser.download.exclude": "{**/*.md,**/*.jpg,**/*.jpeg,**/*.png,**/*.pdf,.vscode/*}", + "ev3devBrowser.download.include": "{**/*.py,**/*.json}", + + "files.exclude": { + "**/.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-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, +} diff --git a/remix/birthday-gift-present3r/Happy-Birthday-Song.json b/remix/birthday-gift-present3r/Happy-Birthday-Song.json new file mode 100644 index 00000000..7d14db68 --- /dev/null +++ b/remix/birthday-gift-present3r/Happy-Birthday-Song.json @@ -0,0 +1,7 @@ +[ + "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" +] diff --git a/remix/birthday-gift-present3r/birthday-gift-present3r.jpg b/remix/birthday-gift-present3r/birthday-gift-present3r.jpg new file mode 100644 index 00000000..0503a3b3 Binary files /dev/null and b/remix/birthday-gift-present3r/birthday-gift-present3r.jpg differ diff --git a/remix/birthday-gift-present3r/index.md b/remix/birthday-gift-present3r/index.md new file mode 100644 index 00000000..40d9ba34 --- /dev/null +++ b/remix/birthday-gift-present3r/index.md @@ -0,0 +1,49 @@ +--- +title: "Birthday Bot: Gift Presenter" +maintainer: + user: "TheVinhLuong102" + name: "The Lương-Phạm Family" +image: + local: "birthday-gift-present3r.jpg" +video: + youtube: "PAk9mxus1Nk" +description: + "A gentlemanly robot who wheels around, sings Happy Birthday and presents gifts with its arms! Member of the Lương-Phạm family's Birthday Bots squad, alongside Birthday Candle Blower and Birthday Cake Cutter." +building_instructions: + local: TODO +code: "#program" +--- + + +Control the Birthday Gift Present3r by the IR Beacon as follows: + +- Driving by IR Beacon channel #1: (refer to the `rc_tank_util.RemoteControlledTank` code below) + - 2 Top/Up buttons together: drive forward + - 2 Bottom/Down buttons together: drive backward + - Top-Left/Red-Up and Bottom-Right/Blue-Down together: turn left on the spot + - Top-Right/Blue-Up and Bottom-Left/Red-Down together: turn right on the spot + - Top-Left/Red-Up: turn left forward + - Top-Right/Blue-Up: turn right forward + - Bottom-Left/Red-Down: turn left backward + - Bottom-Right/Blue-Down: turn right backward + +- Singing Happy Birthday: IR Beacon button (any channel) + +- Controlling the arms by IR Beacon channel #4: + - Lowering the arms: Bottom-Left/Red-Down or Bottom-Right/Blue-Down + - Raising the arms: Top-Left/Red-Up or Top-Right/Blue-Up + + +## Program + +{% include copy-code.html %} +```python +{% include_relative main.py %} +``` + +`BirthdayGiftPresent3r` uses a remote-controlled tank driving utility whose code is in `rc_tank_util.py` as follows: + +{% include copy-code.html %} +```python +{% include_relative rc_tank_util.py %} +``` diff --git a/remix/birthday-gift-present3r/main.py b/remix/birthday-gift-present3r/main.py new file mode 100644 index 00000000..9524a85d --- /dev/null +++ b/remix/birthday-gift-present3r/main.py @@ -0,0 +1,120 @@ +#!/usr/bin/env pybricks-micropython + + +import json + +from pybricks.hubs import EV3Brick +from pybricks.ev3devices import Motor, TouchSensor +from pybricks.media.ev3dev import ImageFile +from pybricks.parameters import Button, Direction, Port + +from pybricks.experimental import run_parallel + +from rc_tank_util import RemoteControlledTank + + +HAPPY_BIRTHDAY_SONG = json.load(open('Happy-Birthday-Song.json')) + + +class BirthdayGiftPresent3r(RemoteControlledTank): + WHEEL_DIAMETER = 44 # milimeters + AXLE_TRACK = 88 # milimeters + + def __init__( + self, + left_motor_port: Port = Port.C, + right_motor_port: Port = Port.B, + medium_motor_port: Port = Port.A, + touch_sensor_port: Port = Port.S1, + ir_sensor_port: Port = Port.S4, + driving_ir_beacon_channel: int = 1, + non_driving_ir_beacon_channel: int = 4): + super().__init__( + wheel_diameter=self.WHEEL_DIAMETER, + axle_track=self.AXLE_TRACK, + left_motor_port=left_motor_port, + right_motor_port=right_motor_port, + polarity='inversed', + ir_sensor_port=ir_sensor_port, + ir_beacon_channel=driving_ir_beacon_channel) + + self.ev3_brick = EV3Brick() + + self.arm_control_motor = \ + Motor(port=medium_motor_port, + positive_direction=Direction.COUNTERCLOCKWISE) + + self.touch_sensor = TouchSensor(port=touch_sensor_port) + + self.non_driving_ir_beacon_channel = \ + non_driving_ir_beacon_channel + + def start_up(self): + self.ev3_brick.screen.load_image(ImageFile.NEUTRAL) + + self.ev3_brick.speaker.set_speech_options( + language='en', + voice='m3', + speed=None, + pitch=None) + + self.ev3_brick.speaker.set_volume( + volume=100, + which='_all_') + + def lower_or_raise_arms_by_ir_beacon(self): + non_driving_ir_beacon_button_pressed = \ + self.ir_sensor.buttons( + channel=self.non_driving_ir_beacon_channel) + + if {Button.LEFT_DOWN, Button.RIGHT_DOWN}.intersection( + non_driving_ir_beacon_button_pressed): + self.arm_control_motor.run(speed=-100) + + elif {Button.LEFT_UP, Button.RIGHT_UP}.intersection( + non_driving_ir_beacon_button_pressed): + self.arm_control_motor.run(speed=100) + + else: + self.arm_control_motor.hold() + + def keep_controlling_arms_by_ir_beacon(self): + while True: + self.lower_or_raise_arms_by_ir_beacon() + + def say_happy_birthday_if_touch_sensor_pressed(self): + if self.touch_sensor.pressed(): + self.ev3_brick.speaker.say( + text='Happy Birthday, My Love!') + + def say_happy_birthday_whenever_touch_sensor_pressed(self): + while True: + self.say_happy_birthday_if_touch_sensor_pressed() + + def sing_happy_birthday_if_ir_beacon_button_pressed(self): + if Button.BEACON in (self.ir_sensor.buttons(channel=1) + + self.ir_sensor.buttons(channel=2) + + self.ir_sensor.buttons(channel=3) + + self.ir_sensor.buttons(channel=4)): + self.ev3_brick.speaker.play_notes( + notes=HAPPY_BIRTHDAY_SONG, + tempo=120) + + def sing_whenever_ir_beacon_button_pressed(self): + while True: + self.sing_happy_birthday_if_ir_beacon_button_pressed() + + def main(self): + self.start_up() + + run_parallel( + self.keep_driving_by_ir_beacon, + self.keep_controlling_arms_by_ir_beacon, + self.say_happy_birthday_whenever_touch_sensor_pressed, + self.sing_whenever_ir_beacon_button_pressed) + + +if __name__ == '__main__': + birthday_gift_present3r = BirthdayGiftPresent3r() + + birthday_gift_present3r.main() diff --git a/remix/birthday-gift-present3r/rc_tank_util.py b/remix/birthday-gift-present3r/rc_tank_util.py new file mode 100644 index 00000000..64af28fe --- /dev/null +++ b/remix/birthday-gift-present3r/rc_tank_util.py @@ -0,0 +1,109 @@ +from pybricks.ev3devices import Motor, InfraredSensor +from pybricks.robotics import DriveBase +from pybricks.parameters import Button, Direction, Port + + +class RemoteControlledTank: + """ + This reusable mixin provides the capability of driving a robot + with a Driving Base by the IR beacon + """ + def __init__( + self, + wheel_diameter: float, # in milimeters + axle_track: float, # in milimeters + left_motor_port: Port = Port.B, + right_motor_port: Port = Port.C, + polarity: str = 'normal', + ir_sensor_port: Port = Port.S4, + ir_beacon_channel: int = 1): + motor_positive_direction = \ + Direction.CLOCKWISE \ + if polarity == 'normal' \ + else Direction.COUNTERCLOCKWISE + left_motor = \ + Motor(port=left_motor_port, + positive_direction=motor_positive_direction) + right_motor = \ + Motor(port=right_motor_port, + positive_direction=motor_positive_direction) + self.drive_base = DriveBase(left_motor=left_motor, + right_motor=right_motor, + wheel_diameter=wheel_diameter, + axle_track=axle_track) + + self.ir_sensor = InfraredSensor(port=ir_sensor_port) + self.ir_beacon_channel = ir_beacon_channel + + def drive_by_ir_beacon(self, + speed: float = 1000, # mm/s + turn_rate: float = 90 # rotational deg/s + ): + ir_beacon_button_pressed = \ + set(self.ir_sensor.buttons( + channel=self.ir_beacon_channel)) + + # forward + if ir_beacon_button_pressed == \ + {Button.LEFT_UP, Button.RIGHT_UP}: + self.drive_base.drive( + speed=speed, + turn_rate=0) + + # backward + elif ir_beacon_button_pressed == \ + {Button.LEFT_DOWN, Button.RIGHT_DOWN}: + self.drive_base.drive( + speed=-speed, + turn_rate=0) + + # turn left on the spot + elif ir_beacon_button_pressed == \ + {Button.LEFT_DOWN, Button.RIGHT_UP}: + self.drive_base.drive( + speed=0, + turn_rate=-turn_rate) + + # turn right on the spot + elif ir_beacon_button_pressed == \ + {Button.RIGHT_DOWN, Button.LEFT_UP}: + self.drive_base.drive( + speed=0, + turn_rate=turn_rate) + + # turn left forward + elif ir_beacon_button_pressed == {Button.RIGHT_UP}: + self.drive_base.drive( + speed=speed, + turn_rate=-turn_rate) + + # turn right forward + elif ir_beacon_button_pressed == {Button.LEFT_UP}: + self.drive_base.drive( + speed=speed, + turn_rate=turn_rate) + + # turn left backward + elif ir_beacon_button_pressed == {Button.RIGHT_DOWN}: + self.drive_base.drive( + speed=-speed, + turn_rate=turn_rate) + + # turn right backward + elif ir_beacon_button_pressed == {Button.LEFT_DOWN}: + self.drive_base.drive( + speed=-speed, + turn_rate=-turn_rate) + + # otherwise stop + else: + self.drive_base.stop() + + def keep_driving_by_ir_beacon(self, + speed: float = 1000, # mm/s + turn_rate: float = 90 # deg/s + ): + while True: + self.drive_by_ir_beacon( + speed=speed, + turn_rate=turn_rate)