Short answer: It’s appears to be a bug in RPi.GPIO
There’s quite a lot at play with this apparently simple problem, but the crux of it is that Flask is fighting Explorer HAT for system resources, and you’re seeing the result of that fight manifest itself as flicker.
I’ll try and explain why this happens- it dates back to an issue with the Explorer HAT library crashing randomly which used to happen when it attempted to switch an output pin from PWM to regular ON/OFF too quickly, or at the wrong time.
This was fixed some time ago by treating ON/OFF states as just another level of PWM- ON being 100% duty cycle, and OFF being 0% duty cycle.
PWM - or Pulse Width Modulation - is a method of generating a variable voltage from a digital pin by turning it ON/OFF (usually) quickly and varying the ratio of on to off time. In RPi.GPIO - the library we use to do GPIO access - any pin can be a PWM pin, and the library will run a very simple loop in C to handle turning that pin on and off at the right times. The loop looks like this:
while (p->running)
{
if (p->dutycycle > 0.0)
{
output_gpio(p->gpio, 1);
full_sleep(&p->req_on);
}
if (p->dutycycle < 100.0)
{
output_gpio(p->gpio, 0);
full_sleep(&p->req_off);
}
}
Source: https://sourceforge.net/p/raspberry-gpio-python/code/ci/default/tree/source/soft_pwm.c
The duty cycle of a PWM signal is the amount of time the pin spends in its ON state. In this loop if the pin is at 100% duty cycle it will never be turned OFF but it will be turned ON repeatedly. This shouldn’t really do anything though, as it’s just writing the same value to the same register. I don’t know why it results in flicker.
I can rip Explorer HAT out of the equation and still get this same issue:
from flask import Flask
from flask import make_response
import RPi.GPIO as GPIO
YELLOW = 17
GPIO.setmode(GPIO.BCM)
GPIO.setup(YELLOW, GPIO.OUT)
pwm = GPIO.PWM(YELLOW, 1000)
pwm.start(0)
app = Flask(__name__)
@app.route("/on/<light>")
def turnon(light = 'None'):
if light == 'yellow':
pwm.ChangeDutyCycle(100)
return make_response(light + ' is on')
@app.route("/off/<light>")
def turnoff(light = 'None'):
if light == 'yellow':
pwm.ChangeDutyCycle(0)
return light + ' is off'
if __name__ == "__main__":
app.run(host='0.0.0.0', port=80, debug = True)
Unfortunately this doesn’t really lead us to a fix- you could remotely toggle the LEDs and some other things on Explorer HAT by doing away with the explorerhat library altogether and using RPi.GPIO to just write the pin states directly, like so:
from flask import Flask
from flask import make_response
import RPi.GPIO as GPIO
YELLOW = 17
GPIO.setmode(GPIO.BCM)
GPIO.setup(YELLOW, GPIO.OUT)
app = Flask(__name__)
@app.route("/on/<light>")
def turnon(light = 'None'):
if light == 'yellow':
GPIO.output(YELLOW, 1)
return make_response(light + ' is on')
@app.route("/off/<light>")
def turnoff(light = 'None'):
if light == 'yellow':
GPIO.output(YELLOW, 0)
return light + ' is off'
if __name__ == "__main__":
app.run(host='0.0.0.0', port=80, debug = True)
The above code will work because it sets the pin state precisely once- there’s no thread hanging around in the background making a nuisance of itself.
You can find the pins for the other LEDs here: https://pinout.xyz/pinout/explorer_hat
And maybe store them in a dictionary like so:
leds = {'yellow': 17, 'blue': 4}
Then you can get smart in your turnon and turnoff handlers, looking up the LED in the dictionary.