Skip to main content

Python LED Server

quart & LED lib? Or Raspi pin lib?

#!/usr/bin/env python3
"""
Quart web server with colour picker + ON/OFF toggle for an RGB LED strip
on a Raspberry Pi 2 B.
"""

import asyncio
from quart import Quart, render_template_string, request, jsonify
import RPi.GPIO as GPIO

# --------------------------- GPIO CONFIG ---------------------------
RED_PIN   = 17   # Physical pin 11
GREEN_PIN = 27   # Physical pin 13
BLUE_PIN  = 22   # Physical pin 15
PWM_FREQ  = 1000  # Hz

GPIO.setmode(GPIO.BCM)
GPIO.setup(RED_PIN,   GPIO.OUT)
GPIO.setup(GREEN_PIN, GPIO.OUT)
GPIO.setup(BLUE_PIN,  GPIO.OUT)

red_pwm   = GPIO.PWM(RED_PIN,   PWM_FREQ)
green_pwm = GPIO.PWM(GREEN_PIN, PWM_FREQ)
blue_pwm  = GPIO.PWM(BLUE_PIN,  PWM_FREQ)

red_pwm.start(0)
green_pwm.start(0)
blue_pwm.start(0)

# --------------------------- GLOBAL STATE -------------------------
# Remember the last colour (as a hex string) and whether the strip is on.
last_colour = "#ff0000"
led_is_on   = True   # start powered on; change to False if you prefer off

# --------------------------- HELPERS ------------------------------
def hex_to_duty(hex_colour: str):
    """Convert '#RRGGBB' → (r,g,b) duty cycles 0‑100."""
    hex_colour = hex_colour.lstrip('#')
    r = int(hex_colour[0:2], 16)
    g = int(hex_colour[2:4], 16)
    b = int(hex_colour[4:6], 16)
    return (r / 255 * 100, g / 255 * 100, b / 255 * 100)


def apply_colour(col_hex: str):
    """Write PWM duties – respects the global on/off flag."""
    global led_is_on
    if not led_is_on:
        # Strip is forced off → set all duties to 0
        red_pwm.ChangeDutyCycle(0)
        green_pwm.ChangeDutyCycle(0)
        blue_pwm.ChangeDutyCycle(0)
        return

    r, g, b = hex_to_duty(col_hex)
    red_pwm.ChangeDutyCycle(r)
    green_pwm.ChangeDutyCycle(g)
    blue_pwm.ChangeDutyCycle(b)


# --------------------------- QUART APP ----------------------------
app = Quart(__name__)

@app.route('/')
async def index():
    # Render template with current colour and power state
    return await render_template(
        "home.html",
        colour=last_colour,
        power=led_is_on
    )

@app.route('/set', methods=['POST'])
async def set_colour():
    global last_colour
    payload = await request.get_json()
    colour = payload.get('colour')
    try:
        last_colour = colour            # remember for later “on” toggles
        apply_colour(colour)
        return jsonify(success=True)
    except Exception as exc:
        return jsonify(success=False, error=str(exc)), 400

@app.route('/power', methods=['POST'])
async def power():
    """Toggle the strip on/off."""
    global led_is_on
    payload = await request.get_json()
    state = payload.get('state')
    if state not in ('on', 'off'):
        return jsonify(success=False, error='Invalid state'), 400

    led_is_on = (state == 'on')
    # Apply the appropriate duty cycles (either colour or all‑off)
    apply_colour(last_colour)
    return jsonify(success=True, state=('on' if led_is_on else 'off'))

# --------------------------- CLEANUP -----------------------------
@app.before_serving
async def startup():
    print("RGB server started – strip is", "ON" if led_is_on else "OFF")

@app.after_serving
async def cleanup():
    print("\nShutting down – turning LEDs off")
    red_pwm.ChangeDutyCycle(0)
    green_pwm.ChangeDutyCycle(0)
    blue_pwm.ChangeDutyCycle(0)
    red_pwm.stop()
    green_pwm.stop()
    blue_pwm.stop()
    GPIO.cleanup()

# --------------------------- MAIN --------------------------------
if __name__ == '__main__':
    app.run(host='0.0.0.0', port=5000)
<!doctype html>
<html lang="en">
<head>
  <meta charset="utf-8">
  <title>RGB LED Controller</title>
  <style>
    body{font-family:sans-serif;text-align:center;margin-top:2rem}
    input[type=color]{width:150px;height:150px;border:none;cursor:pointer}
    .status{margin-top:1rem;font-size:1.2rem}
    button{padding:.6rem 1.2rem;font-size:1rem;margin-top:.8rem}
  </style>
</head>
<body>
  <h1>RGB LED Strip</h1>

  <input type="color" id="picker" value="{{ colour }}">
  <div class="status" id="msg">Current colour: {{ colour }}</div>

  <button id="toggle">{{ 'Turn OFF' if power else 'Turn ON' }}</button>
  <div class="status" id="powerMsg">
    Strip is {{ 'ON' if power else 'OFF' }}
  </div>

<script>
  const picker   = document.getElementById('picker');
  const msg      = document.getElementById('msg');
  const toggle   = document.getElementById('toggle');
  const powerMsg = document.getElementById('powerMsg');

  async function setColour(col){
    const resp = await fetch('/set', {
      method:'POST',
      headers:{'Content-Type':'application/json'},
      body:JSON.stringify({colour:col})
    });
    const data = await resp.json();
    if(data.success){ msg.textContent = `Current colour: ${col}`; }
    else           { msg.textContent = `Error: ${data.error}`; }
  }

  async function togglePower(){
    const newState = toggle.textContent.includes('ON') ? 'on' : 'off';
    const resp = await fetch('/power', {
      method:'POST',
      headers:{'Content-Type':'application/json'},
      body:JSON.stringify({state:newState})
    });
    const data = await resp.json();
    if(data.success){
      // Update UI according to returned state
      const on = data.state === 'on';
      toggle.textContent = on ? 'Turn OFF' : 'Turn ON';
      powerMsg.textContent = `Strip is ${on ? 'ON' : 'OFF'}`;
      // If we just turned it on, re‑apply the current colour
      if(on){ setColour(picker.value); }
    }else{
      powerMsg.textContent = `Error: ${data.error}`;
    }
  }

  // Event listeners
  picker.addEventListener('input', e=> setColour(e.target.value));
  toggle.addEventListener('click', togglePower);

  // Initialise with the colour stored on the server
  setColour(picker.value);
</script>
</body>
</html>