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-candle-blower/birthday-candle-blower.jpg b/remix/birthday-candle-blower/birthday-candle-blower.jpg new file mode 100644 index 00000000..6ec28f6e Binary files /dev/null and b/remix/birthday-candle-blower/birthday-candle-blower.jpg differ diff --git a/remix/birthday-candle-blower/index.md b/remix/birthday-candle-blower/index.md new file mode 100644 index 00000000..1e2f3d06 --- /dev/null +++ b/remix/birthday-candle-blower/index.md @@ -0,0 +1,51 @@ +--- +title: "Birthday Bot: Candle Blower" +maintainer: + user: "TheVinhLuong102" + name: "The Lương-Phạm Family" +image: + local: "birthday-candle-blower.jpg" +video: + youtube: "PAk9mxus1Nk" +description: + "A cute robot who wheels around, sings Happy Birthday and blow candles by its powerful fan! Member of the Lương-Phạm family's Birthday Bots squad, alongside Birthday Cake Cutter and Birthday Gift Presenter." +building_instructions: + local: TODO +code: "#program" +--- + + +Control the Birthday Candle Blower by the Powered-Up Remote as follows: + +- Driving: (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 + +- Singing Happy Birthday: green Center button + +- Spinning the Fan: + - Clockwise (from robot's point of view): red Right button + - Counterclockwise: left Red button + + +## Technical Design Notes + +For the fan to spin fast enough to blow candles, we need a very large gear attached to the fan motor and a very small gear attached to the fan's axis. + +The fan blades' facing and spinning directions also matter. In the pictured design, the fan only generates strong forward wind when: +- the bottoms of the blade pieces face forward; AND +- the fan spins clockwise from the robot's point of view. + + +## Program + +{% include copy-code.html %} +```python +{% include_relative main.py %} +``` diff --git a/remix/birthday-candle-blower/main.py b/remix/birthday-candle-blower/main.py new file mode 100644 index 00000000..d9017cbe --- /dev/null +++ b/remix/birthday-candle-blower/main.py @@ -0,0 +1,134 @@ +from pybricks.hubs import PrimeHub +from pybricks.pupdevices import Motor, Remote +from pybricks.robotics import DriveBase +from pybricks.geometry import Axis +from pybricks.parameters import Button, 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 BirthdayCandleBlower(RemoteControlledDriveBase): + WHEEL_DIAMETER = 44 # milimeters + AXLE_TRACK = 100 # milimeters + + def __init__(self, + fan_motor_port: Port = Port.A, + left_motor_port: Port = Port.D, + right_motor_port: Port = Port.C): + 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 = PrimeHub(top_side=Axis.X, front_side=Axis.Z) + + self.fan_motor = Motor(fan_motor_port, Dir.CLOCKWISE) + + 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 spin_fan_by_remote_red_buttons(self): + remote_button_pressed = self.remote.buttons.pressed() + + if remote_button_pressed == (Button.LEFT,): + self.fan_motor.run(speed=-1000) + + elif remote_button_pressed == (Button.RIGHT,): + self.fan_motor.run(speed=1000) + + else: + self.fan_motor.stop() + + +# initialize Birthday Candle Blower +candle_blower = BirthdayCandleBlower() + +# make it smile +candle_blower.smile() + +# remote-control it to drive around, sing and blow candles +while True: + candle_blower.drive_by_remote() + candle_blower.sing_happy_birthday_by_remote_center_button() + candle_blower.spin_fan_by_remote_red_buttons() diff --git a/remix/index.md b/remix/index.md new file mode 100755 index 00000000..d44bfc88 --- /dev/null +++ b/remix/index.md @@ -0,0 +1,6 @@ +--- +title: "Remix" +image: + local: "birthday-candle-blower/birthday-candle-blower.jpg" +layout: remix +---