Функции кнопки Python странно не делают то же самое

В настоящее время у меня есть две кнопки, подключенные к моей малиновой Pi (это те, у кого есть светодиод кольца), и я пытаюсь выполнить этот код

#!/usr/bin/env python
import RPi.GPIO as GPIO
import time

GPIO.setmode(GPIO.BCM)
GPIO.setwarnings(False)
GPIO.setup(17, GPIO.OUT) #green LED
GPIO.setup(18, GPIO.OUT) #red LED
GPIO.setup(4, GPIO.IN, GPIO.PUD_UP) #green button
GPIO.setup(27, GPIO.IN, GPIO.PUD_UP) #red button

def remove_events():
        GPIO.remove_event_detect(4)
        GPIO.remove_event_detect(27)

def add_events():
        GPIO.add_event_detect(4, GPIO.FALLING, callback=green, bouncetime=800)
        GPIO.add_event_detect(27, GPIO.FALLING, callback=red, bouncetime=800)

def red(pin):
        remove_events()
        GPIO.output(17, GPIO.LOW)
        print "red pushed"
        time.sleep(2)
        GPIO.output(17, GPIO.HIGH)
        add_events()

def green(pin):
        remove_events()
        GPIO.output(18, GPIO.LOW)
        print "green pushed"
        time.sleep(2)
        GPIO.output(18, GPIO.HIGH)
        add_events()

def main():
    while True:
        print "waiting"
        time.sleep(0.5)

GPIO.output(17, GPIO.HIGH)
GPIO.output(18, GPIO.HIGH)
GPIO.add_event_detect(4, GPIO.FALLING, callback=green, bouncetime=800)
GPIO.add_event_detect(27, GPIO.FALLING, callback=red, bouncetime=800)

if __name__ == "__main__":
    main()

На поверхности это выглядит довольно легко script. При обнаружении нажатия кнопки:

  • удалить события
  • напечатать сообщение
  • подождите 2 секунды перед добавлением событий и включением светодиода

Что обычно отлично работает, когда я нажимаю зеленую кнопку. Я пробовал его несколько раз подряд, и он работает непременно. С красным, однако, он работает хорошо в первый раз, и второй раз, но после того, как он завершил второй цикл красного (pin), script просто останавливается.

Учитывая, что оба события довольно похожи, я не могу объяснить, почему он терпит неудачу в конце второй красной кнопки.

EDIT: я изменил контакты с красного и зеленого соответственно (либо на разные контакты полностью, либо заменил их). В любом случае, всегда красный код кнопки (на самом деле теперь зеленая кнопка) вызывает ошибку. Таким образом, кажется, что это не проблема с физической красной кнопкой, а также проблема с штырем, это просто приводит к ошибке кода...

Ответ 1

Я смог воспроизвести вашу проблему на моей малине Pi 1, модель B, запустив ваш script и подключив перемычку между землей и GPIO27 для имитации красных нажатий кнопок. (Это штыри 25 и 13 на моей конкретной модели Pi.)

Интерпретатор python сбой с ошибкой сегментации в потоке, предназначенном для опроса событий GPIO после того, как red возвращается с обработки нажатия кнопки. Посмотрев на реализацию модуля Python GPIO, мне ясно, что из-за обратного вызова обработчика события небезопасно вызывать remove_event_detect, и это вызывает сбой. В частности, удаление обработчика событий, в то время как этот обработчик событий в настоящее время работает, может привести к повреждению памяти, что приведет к сбоям (как вы видели) или другим странным поведением.

Я подозреваю, что вы удаляете и повторно добавляете обработчики событий, потому что вы обеспокоены получением обратного вызова в то время, когда вы передаете нажатие кнопки. Нет необходимости делать это. Модуль GPIO объединяет один поток опроса для мониторинга событий GPIO и будет ожидать возврата одного обратного вызова перед вызовом другого, независимо от количества просматриваемых событий GPIO.

Я предлагаю вам просто звонить на add_event_detect при запуске script и никогда не удалять обратные вызовы. Простое удаление add_events и remove_events (и их invocations) из вашего script исправит проблему.

Если вас интересует подробная информация о проблеме в модуле GPIO, вы можете ознакомиться с исходным кодом C для этого модуля. Взгляните на run_callbacks и remove_callbacks в файле RPi.GPIO-0.6.2/source/event_gpio.c. Обратите внимание, что обе эти функции используют глобальную цепочку узлов struct callback. run_callbacks выполняет цепочку обратного вызова, захватывая один node, вызывая обратный вызов, а затем следуя этой ссылке node для следующего обратного вызова в цепочке. remove_callbacks будет проходить одну и ту же цепочку обратного вызова и освободить память, связанную с обратными вызовами на конкретном выводе GPIO. Если remove_callbacks вызывается в середине run_callbacks, node, в настоящее время удерживаемый run_callbacks, может быть освобожден (и его память может быть повторно использована и перезаписана) до того, как будет следовать указатель на следующий node.

Причина, по которой вы видите эту проблему только для красной кнопки, скорее всего, связана с порядком вызовов add_event_detect и remove_event_detect, заставляет память, ранее использованную обратным вызовом node, для красной кнопки, которая будет исправлена ​​для некоторых другая цель и перезаписанная ранее память, используемая в обратном вызове зеленой кнопки node, аналогично исправлена. Однако будьте уверены, что проблема существует для обеих кнопок - просто удача в том, что память, связанная с обратным вызовом зеленой кнопки, не изменяется до того, как будет следовать указатель на следующий обратный вызов node.

В целом, существует проблема отсутствия синхронизации потоков вокруг использования цепочки обратного вызова в модуле GPIO в целом, и я подозреваю, что подобные проблемы могут возникнуть, если вызываются remove_event_detect или add_event_detect во время работы обработчика событий, даже если события удалены из другого потока! Я бы предположил, что автор модуля RPi.GPIO должен использовать некоторую синхронизацию, чтобы гарантировать, что цепочка обратного вызова не может быть изменена при выполнении обратных вызовов. (Возможно, помимо проверки того, изменяется ли цепочка в самой теме опроса, pthread_mutex_lock и pthread_mutex_unlock могут использоваться для предотвращения изменения других цепочек обратного вызова, когда он используется в потоке опроса.)

К сожалению, на данный момент это не так, и по этой причине я предлагаю вам полностью не называть remove_event_detect, если вы можете его избежать.