adafruit / Adafruit_CircuitPython_NeoPixel

CircuitPython drivers for neopixels.
MIT License
304 stars 98 forks source link

Threading + Architecture for async code #95

Closed objectiveSee closed 3 years ago

objectiveSee commented 3 years ago

Hey! Just checking out this library on my Raspberry Pi Zero. I am writing an Application that needs to handle async events from a Socket.io server. I have coded this up and included the code below in case you're interested. My concern is performance and best practices. Circuit Python uses blocking calls such as time.sleep() which apparently only blocks the thread and not the entire process. That's great! I coded this example which logs the thread ID for each async message received and calling sleep on one thread doesn't effect the Socket.io thread.

  1. What's the best architecture for programming LEDs in a "multithreaded" python script? Should I specifically dedicate a thread to CircuitPython... or can it just use whichever thread initially calls into it? In my case, that would be the thread which the SocketIO connect event is called on
  2. What sort of performance bottlenecks can I expect while using routines that call time.sleep()?
  3. Should I be considering something different?

Thanks! Hope these thoughts make sense! :)

# client.py
import socketio
import neopixelclient
import time
import threading

sio = socketio.Client()

connected = False

@sio.event
def connect():
    global connected
    connected = True
    print('connection established on thread', threading.current_thread().ident)
    while connected:
        print('LED loop', threading.current_thread().ident)
        neopixelclient.rainbow_cycle(0.001)
    # if we reached here... the client disconnected likely
    if not connected:
        neopixelclient.clearLEDs()
    else:
        print('client disconnected... but then reconnected?')

@sio.event
def message(data):
    print('received message on thread', threading.current_thread().ident)

@sio.event
def disconnect():
    print('disconnected from server')
    global connected
    connected = False

print('starting socket client on thread', threading.current_thread().ident)
sio.connect('http://localhost:5000')
sio.wait()
# neopixelclient.py
# Simple test for NeoPixels on Raspberry Pi
import time
import board
import neopixel

# Choose an open pin connected to the Data In of the NeoPixel strip, i.e. board.D18
# NeoPixels must be connected to D10, D12, D18 or D21 to work.
pixel_pin = board.D18

# The number of NeoPixels
num_pixels = 27

# The order of the pixel colors - RGB or GRB. Some NeoPixels have red and green reversed!
# For RGBW NeoPixels, simply change the ORDER to RGBW or GRBW.
ORDER = neopixel.GRB

pixels = neopixel.NeoPixel(
    pixel_pin, num_pixels, brightness=0.05, auto_write=False, pixel_order=ORDER
)

toggleState = False

# initally turn LEDs off
pixels.fill((0, 0, 0))
pixels.show()

def toggleLEDS():
    global toggleState
    toggleState = not toggleState
    if toggleState:
        pixels.fill((0, 0, 255))
    else:
        pixels.fill((0, 0, 0))
    pixels.show()

def clearLEDs():
    global toggleState
    toggleState = False
    pixels.fill((0, 0, 0))
    pixels.show()

def wheel(pos):
    # Input a value 0 to 255 to get a color value.
    # The colours are a transition r - g - b - back to r.
    if pos < 0 or pos > 255:
        r = g = b = 0
    elif pos < 85:
        r = int(pos * 3)
        g = int(255 - pos * 3)
        b = 0
    elif pos < 170:
        pos -= 85
        r = int(255 - pos * 3)
        g = 0
        b = int(pos * 3)
    else:
        pos -= 170
        r = 0
        g = int(pos * 3)
        b = int(255 - pos * 3)
    return (r, g, b) if ORDER in (neopixel.RGB, neopixel.GRB) else (r, g, b, 0)

def rainbow_cycle(wait):
    for j in range(255):
        for i in range(num_pixels):
            pixel_index = (i * 256 // num_pixels) + j
            pixels[i] = wheel(pixel_index & 255)
        pixels.show()
        time.sleep(wait)
// Server.js
var app = require("express")();
var http = require("http").createServer(app);
var io = require("socket.io")(http);

app.get("/", (req, res) => {
  res.sendFile(__dirname + "/index.html");
});

io.on("connection", (socket) => {
  console.log("a user connected");
  socket.emit(`message`, `welcome!`);
  setInterval(() => {
    console.log(`emit`);
    socket.emit(`message`, `foo`);
  }, 3000);

  socket.on("message", (msg) => {
    console.log("message: " + msg);
  });
});

http.listen(5000, () => {
  console.log("listening on *:3000");
});

OUTPUT:

starting socket client on thread 3069561056
connection established on thread 3049792608
LED loop 3049792608
LED loop 3049792608
received message on thread 3012547680
LED loop 3049792608
LED loop 3049792608
received message on thread 3012547680
LED loop 3049792608
received message on thread 3012547680
LED loop 3049792608
LED loop 3049792608
received message on thread 3012547680
LED loop 3049792608
LED loop 3049792608
received message on thread 3012547680
LED loop 3049792608
received message on thread 3012547680
LED loop 3049792608
ladyada commented 3 years ago

on a Pi you'd be running CPython not CircuitPython - we have a compatibility layer that lets you use CircuitPython libraries like this one. for performance questions, you can use any Python threading/optimization tutorial or online documentation - there's a ton of tutorials online for CPython!