LIFX Z LAN not recieving packets from Python program

So I’ve been trying to make a music visualizer that actually works in real-time. I’ve struggled a lot to get it to work, but then, yesterday, out of nowhere, it started working when I added a small delay after sending packets. I took it off and voila, it was working in real-time.

So I brought them home later to find them still not working on my home network, which I thought was weird. Then I bring them back to school today to find that they are not even working at school anymore! What gives? I’m so stumped on why these things work like a charm out of nowhere, and then stop working again out of nowhere.

I try adding a delay, and it works, but the delay is so bad that it’s hard to tell that it’s even reacting to the microphone at all.

I’ve my GitHub here: https://github.com/HoldenGs/LIFX_controller

and also here’s the relevant code:

#!/usr/bin/env python3

import time
import socket
import argparse
import random
import queue
import sys
from tools import get_colour_zones_packet, APPLY

RETRIES = 1
UDP_PORT = 56700
SEQ_NUM = random.randint(0, 255)


def int_or_str(text):
    """Helper function for argument parsing."""
    try:
        return int(text)
    except ValueError:
        return text


parser = argparse.ArgumentParser(description=__doc__)
parser.add_argument(
    '-l', '--list-devices', action='store_true',
    help='show list of audio devices and exit')
parser.add_argument(
    '-d', '--device', type=int_or_str,
    help='input device (numeric ID or substring)')
parser.add_argument(
    '-w', '--window', type=float, default=200, metavar='DURATION',
    help='visible time slot (default: %(default)s ms)')
parser.add_argument(
    '-i', '--interval', type=float, default=30,
    help='minimum time between plot updates (default: %(default)s ms)')
parser.add_argument(
    '-b', '--blocksize', type=int, help='block size (in samples)')
parser.add_argument(
    '-r', '--samplerate', type=float, help='sampling rate of audio device')
parser.add_argument(
    '-n', '--downsample', type=int, default=10, metavar='N',
    help='display every Nth sample (default: %(default)s)')
parser.add_argument(
    'channels', type=int, default=[1], nargs='*', metavar='CHANNEL',
    help='input channels to plot (default: the first)')
args = parser.parse_args()
if any(c < 1 for c in args.channels):
    parser.error('argument CHANNEL: must be >= 1')

mapping = [c - 1 for c in args.channels]  # Channel numbers start with 1
q = queue.Queue(64)




def audio_callback(indata, frames, time, status):
    """This is called (from a separate thread) for each audio block."""
    if status:
        print(status, file=sys.stderr)
    # Fancy indexing with mapping creates a (necessary!) copy:
    q.put(indata[::args.downsample, mapping])


def update_lights(hue):
    """This is called by matplotlib for each plot update.
    Typically, audio callbacks happen more frequently than plot updates,
    therefore the queue tends to contain multiple blocks of audio data.
    """
    while True:
        try:
            data = q.get_nowait()
        except queue.Empty:
            break
        max_amp = max(data)[0] * 500
        if max_amp > 100:
            max_amp = 100
        if max_amp < 1:
            max_amp = 1
        print("amp: {}                 ".format(int(max_amp)), end="\r")
        bulb_ip = "192.168.1.5"
        start_index = 0
        end_index = 11
        sat = 0
        kel = 3500
        packet = get_colour_zones_packet(start_index, end_index,
                                        hue, sat, int(max_amp), kel, APPLY, SEQ_NUM)
        sock.sendto(packet, (bulb_ip, UDP_PORT))





if len(sys.argv) > 0:
    import numpy as np
    import sounddevice as sd
    sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)

    if args.list_devices:
        print(sd.query_devices())
        parser.exit(0)
    if args.samplerate is None:
        device_info = sd.query_devices(args.device, 'input')
        args.samplerate = device_info['default_samplerate']
    
    print("Starting...")
    # start stream
    stream = sd.InputStream(
        device=args.device, channels=max(args.channels),
        samplerate=args.samplerate, callback=audio_callback)
    try:
        with stream:
            hue = 0
            add = 1
            while True:
                # if add:
                #     hue += 0.00005
                #     if hue >= 255:
                #         add = 0
                #         hue = 255
                # else:
                #     hue -= 0.00005
                #     if hue <= 0:
                #         add = 1
                #         hue = 0
                update_lights(int(hue))
    except KeyboardInterrupt as e:
        parser.exit(type(e).__name__ + ': ' + str(e))
else:
    print("No lights with MultiZone capability detected.")

Any tips are much appreciated. I’m presenting this for a talent fair at school in two days so I’ve got a lot of pressure to get this part working cleanly!

I’m getting an error trying to run your code:

$ python controller.py
  File "controller.py", line 62
    print(status, file=sys.stderr)
                      ^
SyntaxError: invalid syntax

I’m running this on macOS 10.13.5 (High Sierra) and Linux. If I fix that error, I get a few more. Was this developed on Windows perhaps? :slight_smile:

It was developed on MacOS. I did it on Python 3.6.5 if that makes any difference.

I’ve never gotten that error before though, or any others. That’s strange!

Ah, ok. Default Python on macOS and my server-grade Linux is Python 2.7. Let me fire up a VM and run it there instead.

So I changed my code to use a lifo queue instead, and that improved responsive over time A LOT, but I’m still feeling like there’s something wrong, because I still need a small delay in order for the packets not be dropped before getting to the lights. I’m pretty sure it’s on the firmware side, my loop seems to go super fast with no issue.

I’m having an issue with audio and the lack thereof in my VMs. :slight_smile: Might take me a while to get an input source working.

However, what wifi gear are you running? How many packets/sec are you trying to deliver? I’m wondering if you aren’t just saturating your wifi network.

That’s an interesting theory, I’ll look into that. I’m running a NETGEAR WNDR4500v3. Connecting over 2.4Ghz

Ok, hold out your hand so I can slap it for hard-coding your bulb IP address. :smiley:

1 Like

Can you update the git repo? My strip is not updating and I have Ubiquiti UniFi gear that can handle updating 30+ bulbs using Light DJ Pro or iLightShow, so I’m hoping it’s not my wifi. :slight_smile:

Also, does your code make any assumptions about the length of the strip, or does it check the zone count on the target strip first? My strip may be too short/long (depending your assumptions).

Updated the repo. So I only do the first 12 sections of the strip, no zone count at the moment. Shouldn’t be an issue since the minimum zone count on a strip is 12.

The minimum zone count is 8, not 12. There are 8 zones per strip. :slight_smile: I have 16 on mine. The updated repo makes my strip flash lots and lots, but I’m not sure how to tell if it’s “right” now or not.

Oh! Maybe that’s causing some of the issues. Yeah, so if you snap your fingers in front of the mic, you’ll notice that it only picks up the noise half of the time. The program will pick it up, but the packets won’t always manifest into light changes on the strip.

Also, you’ll notice that the hue is supposed to cycle, but it doesn’t. That’s another issue I’m trying to figure out. It may be related.

I see the strip changing for every snap of my fingers. Let me try and video it for you, so you can see it here.

1 Like


The snapping of my fingers wasn’t loud enough so I started slapping my glass desk instead. :slight_smile:

Note that the Z did respond to all the noise, it just may not be visible in the video. Recording a Z strip is tough.

That does look pretty good. I wish I could show you the responsiveness to music when it was working perfectly yesterday with no delay. To clarify, when I mean delay, I mean there’s a delay after sending a packet, so when the update_lights() function loops, it waits a split second before picking up the most recent mic data, so it will sometimes miss quick sounds. So there’s no delay, per se, just missed mic inputs. Maybe try it with some music to see if it happens to you. It’s good to see that it’s working pretty well though.

Playing with music doesn’t show any obvious delay beyond what I’d expect for standard speed of sound vs light and processing delay inside the script and then network delay to the Z, ie. it’s noticeable but you can’t break the laws of physics.

Which is why I’m still hoping someone out there develops an actual media player that analyses the audio first and controls the lights while the media is playing, instead of relying on post-processing of audio. :slight_smile:

Interesting project :slight_smile:

I think as mentioned your problem is probably just about saturating your network with messages.

P.S. I made a version using photons, https://gist.github.com/delfick/0ad394471d6d7dc3762e3cce923484bb :slight_smile: My version has rudimentary rate limiting so that it should send messages every 0.1 seconds, I think it works correctly but I didn’t spend much time on it. Also it supports sending the messages to multiple lights at the same time.

1 Like