我一直试图向我的6岁女儿展示一种制造和制造简单玩具车的方法。我们有一个4电机底盘套件,Raspberry PI和一个可以操作这4个电机的motozero护罩。我们为电机放置了一个移动电源和一个单独的电池:

指示灯亮起/>现在,就编码而言,我已经创建了一个简单的Car类,其中包含forwardbackwardforward_leftforward_rightbackward_leftbackward_right操作。在大多数情况下,出于演示目的,我试图明确表达自己的观点,并违反了DRY原理。

我还初始化了一个无限循环,监听键盘输入-F代表前进,B代表前进向后和S键停止(尚未确定哪些键将用于那些“左移”和“右移”操作)。

from RPi import GPIO
from gpiozero import Motor, OutputDevice


class Car:
    def __init__(self):
        self.front_right = Motor(forward=24, backward=27)
        self.front_left = Motor(forward=6, backward=22)

        self.rear_right = Motor(forward=13, backward=18)
        self.rear_left = Motor(forward=23, backward=16)

        OutputDevice(5).on()
        OutputDevice(17).on()
        OutputDevice(25).on()
        OutputDevice(12).on()

    def forward(self):
        self.front_right.forward()
        self.front_left.backward()

        self.rear_right.backward()
        self.rear_left.backward()

    def backward(self):
        self.front_right.backward()
        self.front_left.forward()

        self.rear_right.forward()
        self.rear_left.forward()

    def backward_right(self):
        self.front_right.backward(1)
        self.front_left.forward(1)

        self.rear_right.forward(1)
        self.rear_left.forward(0.2)

    def backward_left(self):
        self.front_right.backward(1)
        self.front_left.forward(1)

        self.rear_right.forward(0.2)
        self.rear_left.forward(1)

    def forward_left(self):
        self.front_right.forward()
        self.front_left.backward(0.2)

        self.rear_right.backward(1)
        self.rear_left.backward(1)

    def forward_right(self):
        self.front_right.forward(0.2)
        self.front_left.backward(1)

        self.rear_right.backward(1)
        self.rear_left.backward(1)

    def stop(self):
        self.front_right.stop()
        self.front_left.stop()

        self.rear_right.stop()
        self.rear_left.stop()


if __name__ == '__main__':
    GPIO.setwarnings(False)
    GPIO.cleanup()

    commands = {
        "f": "forward",
        "b": "backward",
        "s": "stop"
    }
    try:
        car = Car()

        while True:
            command = raw_input()
            getattr(car, commands[command])()
    finally:
        try:
            GPIO.cleanup()
        except:  # ignore cleanup errors
            pass


(我ve还混合了一些电动机和板上的引脚,导致一些电动机接受“向后”作为“向前”。)

当然,很难将此类代码解释为6-今年三岁,因为当我向她展示Python的print()命令时,她仍然希望打印机能够打印某些东西,但是,您将如何更改此代码以使其对孩子更易理解?

我还要感谢有关代码组织和质量的任何观点,因为下一步将是“通过蓝牙”控制汽车。

评论

您是否考虑过像许多PC游戏一样使用WASD控件?在我看来,这似乎比F,B和S更自然。尽管对这个项目感到很荣幸,但这听起来像是教孩子建立东西的好方法!
@Phrancis谢谢! wasd会更好。.甚至可能是向上,向下,向左,向右的箭头-对于孩子来说更直接。.

你们都可以做到,我同意箭对于孩子来说会更简单。也许是刹车的空格键。干杯!

您难道不只是在程序中交换引脚号,而不是改变实际接线,仍然以正确的方向前进和后退吗?

我知道这可能需要付出更多的努力,但是您可以考虑尝试使用Google Blockly为汽车制作程序。孩子们喜欢颜色和拼图,这对孩子来说是很棒的工具,而且它可以直接编译为Python。要深入了解Blockly,请查看以下内容:developers.google.com/blockly/guides/create-custom-blocks/…

#1 楼

forward的实现是可怕的。表达它的唯一正确方法是使所有四个电动机都前进。打个比方,如果您的键盘制造商告诉您“对不起,我们犯了一个错误,您必须每隔一个大写字母输入一个小写字母”,您会如何反应?这将是不直观的。

您提供的API必须尽可能简洁明了。您不希望您的女儿得知“程序员犯了错误并且懒于解决错误,因此我们必须忍受”。 ?您可能应该添加一些等待时间的示例代码,以允许使用诸如“向前,等待1s,向右,1s,向后”之类的组合命令。按键。对于机器人来说,这是不可接受的,因为在不受控制的情况下运行可能会伤害人或宠物(请参阅阿西莫夫定律)。那太累了。控制汽车必须简单,即时,以防止发生事故。如果您的成年街车要求您对油门或刹车的每一步进行确认,您会感觉如何?

评论


\ $ \ begingroup \ $
哇,超级好点。这是我们的开始,我们绝对准备从错误中学习。关于组合命令-好主意,我当时正在考虑为她创建一条路径,以便通过组合动作来尝试通过。有趣的还有安全性。非常感谢。
\ $ \ endgroup \ $
– alecxe
17年12月6日在12:43

\ $ \ begingroup \ $
我不确定玩具车是否需要执行阿西莫夫的法律。
\ $ \ endgroup \ $
– jwg
17年12月7日在13:54

\ $ \ begingroup \ $
@jwg当超级智能玩具车奴役我们所有人时,我一定会怪你。
\ $ \ endgroup \ $
–大卫
17年12月10日在4:54

\ $ \ begingroup \ $
阿西莫夫定律虽然非常聪明,但并不完全与实际的日常生活(和编码)相关,但除此之外,还有很多要点
\ $ \ endgroup \ $
– 13ros27
17/12/15在17:57

#2 楼


根据我的经验,孩子们对DRY的理解不加解释,事实上,他们对重复感到无聊。


这是一个可怕的错误。请尽快修复它,因为它可能会破坏孩子的直觉。即使我也无法理解为什么forward方法如此不对称。默认真的很难把握。

所有这些,我相信这个问题更适合CSE交换。

评论


\ $ \ begingroup \ $
还是CodeReview?
\ $ \ endgroup \ $
–沃尔夫特
17年12月6日在12:52

\ $ \ begingroup \ $
孩子们喜欢重复。这是教育的第一工具。 Teletubbies几乎会不断重复四次相同的过程(执行特定操作),每个Teletubby重复一次。在剧集中,探险家朵拉(Dora)不断重复相同的计划。小时候,我一遍又一遍地看我最喜欢的电影,再也没有厌倦过看它(而且我注意了,这不仅仅是背景噪音)。我仍然很清楚圣诞节前的噩梦剧本;)
\ $ \ endgroup \ $
–更
17年12月6日在13:27

\ $ \ begingroup \ $
@Flater有的做,有的没有。我一个人很快就厌倦了甚至是重复性的事情。
\ $ \ endgroup \ $
–user20300
17年12月7日在2:21

\ $ \ begingroup \ $
像Teletubbies的核心观众这样的2岁孩子比像OP的女儿这样的6岁孩子更喜欢重复。
\ $ \ endgroup \ $
– jwg
17年12月7日在13:57

#3 楼

我编写了一个Pi + python控制程序,因此可以快速了解自己的工作。
有趣的是,我注意到前进和后退似乎也有同样的问题。我发现了这个注释:

# Forwards and backwards are the same for left and right
# The trick is to reverse the motor connections for one  side :-) 


因此我通过在一侧交换两个电动机的连接来解决了这个问题。它在包含我的产品的网站上,我不想做广告。它使用不同的电机控制器,但原理相同。这是核心代码:

# Main program

# Get the curses screen
screen = curses.initscr()

..... <omitted code to set up the I/O>

# Tell user what to expect
# in curses print does not work anymore. use addstr
screen.addstr("<prouduct name> example program for Python\n")
screen.addstr("Use numeric keypad keys control\n")
screen.addstr("Left  Both  Right\n")
screen.addstr("     Forward     \n")
screen.addstr("  7     8     9  \n")
screen.addstr("                 \n")
screen.addstr("  4    Stop   6  \n")
screen.addstr("                 \n")
screen.addstr("  1     2     3  \n")
screen.addstr("     Reverse     \n")
screen.addstr("Left  Both  Right\n")
screen.addstr("Don't forget to set numlock on!!!!\n")
screen.addstr("Use Q or q to quit\n")
screen.addstr("\n")


run = 1
while run==1 :
  key = screen.getch() # Key?
  if key==ord('q') :
    run = 0 # stop running

  if key==ord('1') : 
     gb.move_brushed(BOARD,LEFT,BACKW) # Left backwards 

  if key==ord('2') : 
     gb.move_brushed(BOARD,LEFT,BACKW)  # Left backwards 
     gb.move_brushed(BOARD,RIGHT,BACKW) # Right backwards 

  if key==ord('3') : 
     gb.move_brushed(BOARD,RIGHT,BACKW) # Right backwards 

  if key==ord('4') :
     gb.move_brushed(BOARD,LEFT,STOP) # Left stop 

  if key==ord('5') : 
     gb.move_brushed(BOARD,LEFT,STOP)  # Left stop 
     gb.move_brushed(BOARD,RIGHT,STOP) # Right stop 

  if key==ord('6') :
     gb.move_brushed(BOARD,RIGHT,STOP) # Right stop 

  if key==ord('7') :
     gb.move_brushed(BOARD,LEFT,FORWD) # Left forwards 

  if key==ord('8') :
     gb.move_brushed(BOARD,LEFT,FORWD)  # Left forwards 
     gb.move_brushed(BOARD,RIGHT,FORWD) # Right forwards 

  if key==ord('9') :
     gb.move_brushed(BOARD,RIGHT,FORWD) # Right forwards 

# on exist stop everything
gb.emerg_stop()
# Set terminal behaviour normal again
curses.endwin()


评论


\ $ \ begingroup \ $
啊,很好。我偶然发现了阅读用户输入的诅咒-我想我终于应该尝试一下解决这个问题。并感谢您提供更换连接的提示。
\ $ \ endgroup \ $
– alecxe
2017年12月7日14:51



#4 楼

换个说法,为了不必在每个命令后都按回车,可以在代码的主要部分添加以下修改以使用pygame,尽管这样做会使它难以理解,所以我肯定会保留两个版本。 >
if __name__ == '__main__':
    GPIO.setwarnings(False)
    GPIO.cleanup()

    try:
        car = Car()
        import pygame

        while True:
            events = pygame.event.get()
            for event in events:
                if event.type == pygame.KEYDOWN:
                    if event.key == pygame.K_UP or event.key == pygame.K_w:
                        car.forward()
                    elif event.key == pygame.K_LEFT or event.key == pygame.K_a:
                        car.forward_left()
                    elif event.key == pygame.K_RIGHT or event.key == pygame.K_d:
                        car.forward_right()
                    elif event.key == pygame.K_DOWN or event.key == pygame.K_s:
                        car.backward()
                    elif event.key == pygame.K_SPACE:
                        car.stop()
    finally:
        try:
            GPIO.cleanup()
        except:  # ignore cleanup errors
            pass


用wasd或箭头键控制其移动,而空格则停止它。 / 16044229 /如何在pygame中获取键盘输入

评论


\ $ \ begingroup \ $
使用pygame是一个好主意-开箱即用的思想,谢谢!
\ $ \ endgroup \ $
– alecxe
17年12月15日17:55

#5 楼

我认为import this的建议将在这里为您提供帮助,尤其是“显式胜于隐式”,“简单胜于复杂”,“平面胜于嵌套”和“可读性”。特别要注意的是,您正在创建一个只能用作单例的Car类。但是Python模块已经是完全有效的单例类,因此您可以通过直接在模块级别实现所有功能来消除抽象和嵌套级别。

我也同意其他人的观点,即必须解决前进/后退问题,并且始终指定电动机速度而不是依靠默认值是一个好主意。我喜欢您的事件堆栈方法,但是我会直接引用函数(而不是按名称),并且我会添加一个额外的步骤,以使其更容易了解正在发生的事情。

from RPi import GPIO
from gpiozero import Motor, OutputDevice

# setup GPIO
GPIO.setwarnings(False)
GPIO.cleanup()
OutputDevice(5).on()
OutputDevice(17).on()
OutputDevice(25).on()
OutputDevice(12).on()

# setup motor references (with correct forward/backward settings)
front_right = Motor(forward=24, backward=27)
front_left = Motor(forward=22, backward=6)
rear_right = Motor(forward=18, backward=13)
rear_left = Motor(forward=16, backward=23)

# define available commands
def forward(self):
    front_right.forward(1)
    front_left.forward(1)
    rear_right.forward(1)
    rear_left.forward(1)

def backward(self):
    front_right.backward(1)
    front_left.backward(1)
    rear_right.backward(1)
    rear_left.backward(1)

def backward_right(self):
    front_right.backward(1)
    front_left.backward(1)
    rear_right.backward(1)
    rear_left.backward(0.2)

def backward_left(self):
    front_right.backward(1)
    front_left.backward(1)
    rear_right.backward(0.2)
    rear_left.backward(1)

def forward_left(self):
    front_right.forward(1)
    front_left.forward(0.2)
    rear_right.forward(1)
    rear_left.forward(1)

def forward_right(self):
    front_right.forward(0.2)
    front_left.forward(1)
    rear_right.forward(1)
    rear_left.forward(1)

def stop(self):
    front_right.stop()
    front_left.stop()
    rear_right.stop()
    rear_left.stop()

commands = {
    "f": forward,
    "b": backward,
    "s": stop
}

try:
    while True:
        # better to use another library that can use arrow keys and maybe spacebar for stop
        command = raw_input() 
        func = commands[command]
        func()
finally:
    try:
        GPIO.cleanup()
    except:  # ignore cleanup errors
        pass


如果您想要DRYer方法,则可以这样做(以更多抽象为代价):

评论


\ $ \ begingroup \ $
导入这个是什么意思
\ $ \ endgroup \ $
– 13ros27
17年12月15日在18:47

\ $ \ begingroup \ $
@ 13ros27,尝试一下!或参阅Python PEP 20:python.org/dev/peps/pep-0020
\ $ \ endgroup \ $
–马蒂亚斯·弗里普(Matthias Fripp)
17年12月15日在19:01

\ $ \ begingroup \ $
PEP20只是我的人生宣言!我非常喜欢您提出的其他抽象。谢谢,马蒂亚斯。
\ $ \ endgroup \ $
– alecxe
17年12月15日在19:04

\ $ \ begingroup \ $
哦,是的,很酷,我从不知道
\ $ \ endgroup \ $
– 13ros27
17/12/15在19:46



\ $ \ begingroup \ $
@alexce,很高兴为您提供帮助!但是我认为大多数六岁的孩子都会遇到字典和负数的字典问题(可能会帮助使用abs(speed)而不是-speed)。我的肯定还没有准备好这些!但是至少“ API”应该非常易于使用。
\ $ \ endgroup \ $
–马蒂亚斯·弗里普(Matthias Fripp)
17年12月15日在20:06

#6 楼

class Car:
    def __init__(self):
        self.front_right = Motor(forward=24, backward=27)
        self.front_left = Motor(forward=22, backward=6)

        self.rear_right = Motor(forward=18, backward=13)
        self.rear_left = Motor(forward=16, backward=23)


__init__方法的这种修改应该意味着不要混淆引脚,因为我只是颠倒了那些引脚。注意:仍然需要一些代码来打开它们。