Hackster is hosting Impact Spotlights: Smart Home. Watch the stream live on Thursday!Hackster is hosting Impact Spotlights: Smart Home. Stream on Thursday!
Haruto Sasajima
Published © GPL3+

Clap-on LED Light Strip

A unique LED system powered by clapping with warm colors, cool colors, and rainbow color pattern settings.

IntermediateWork in progress3 hours900
Clap-on LED Light Strip

Things used in this project

Hardware components

PocketBeagle
BeagleBoard.org PocketBeagle
×1
LED Light Strip
×1
USB Hub
×1
USB-A Mini Microphone
×1
Texas Instruments TXS0108E Level Shifter
×1
5 Pin Micro USB port
×1
Adafruit USB-A Male Plug to 5-pin Terminal Block
×1
USB-A to Micro-USB Cable
USB-A to Micro-USB Cable
×1
Micro USB to USB-A adapter
×1
Breadboard (generic)
Breadboard (generic)
×1
Push Button
×3
Jumper wires (generic)
Jumper wires (generic)
×11
Resistor 1k ohm
Resistor 1k ohm
×3
Breadboard Wire Kit
×1

Story

Read more

Schematics

Fritzing Diagram

Fritzing .fzz file

Code

opc-server

Python
No preview (download only).

project.py

Python
"""
--------------------------------------------------------------------------
Project
--------------------------------------------------------------------------
License:   
Copyright 2021 Haruto Sasajima

Redistribution and use in source and binary forms, with or without 
modification, are permitted provided that the following conditions are met:

1. Redistributions of source code must retain the above copyright notice, this 
list of conditions and the following disclaimer.

2. Redistributions in binary form must reproduce the above copyright notice, 
this list of conditions and the following disclaimer in the documentation 
and/or other materials provided with the distribution.

3. Neither the name of the copyright holder nor the names of its contributors 
may be used to endorse or promote products derived from this software without 
specific prior written permission.

THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE 
FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 
DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR 
SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER 
CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, 
OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGEself.
--------------------------------------------------------------------------
Code Overview:

This code should first connect the client to the server. It should then turn 
off all LED lights initially. Afterwards, it will then prompt the microphone to 
begin continuously collecting input data. Once a certain noise threshold from 
the environment is passed and power is on, the LED strip will turn on all 
white. If this threshold is passed while the LEDs are on, it will turn off all 
of the LEDs. When the LEDs are turned on, the user can press on the buttons to 
change the color settings of the LEDs. The three settings are warm colors, cool 
colors, and rainbow.  The code is formatted so that unless the LEDs are first 
turned on by a clap, it will not turn on even when the buttons are pressed. 
Once it recognizes that a certain button is pressed while the lights are on, 
the color settings corresponding to that button are sent into the LEDs and are 
kept at that setting. The color setting functions are threaded so that the user 
can switch between settings at any time while the LEDs are on. 
"""
import Adafruit_BBIO.GPIO as GPIO
import Adafruit_BBIO.ADC as ADC
import Adafruit_BBIO.PWM as PWM

import sounddevice as sd
import numpy as np

import time
import opc

power = False

sd.default.device = 0

device_list = sd.query_devices()

print(device_list[0])

ADDRESS = 'localhost:7890'

# Create a client object
client = opc.Client(ADDRESS)

# Test if it can connect
if client.can_connect():
    print ('connected to %s' % ADDRESS)
else:
    # We could exit here, but instead let's just print a warning
    # and then keep trying to send pixels in case the server
    # appears later
    print ('WARNING: could not connect to %s' % ADDRESS)

# Strip contains 240 LEDs
STR_LEN=240
# Turns off LEDs initially
for i in range(STR_LEN):
    leds = [(0, 0, 0)] * STR_LEN

if not client.put_pixels(leds, channel=0):
    print ('not connected')    

# Get input from the microphone
def print_sound(indata, outdata, frames, time):
    volume_norm = np.linalg.norm(indata)*10
    global power
        
# Turns LEDs off if noise level threshold is passed and if LEDs are currently 
# on. Else, it turns them on all white            
    if volume_norm > 20:
        if power: 
            STR_LEN=240
            for i in range(STR_LEN):
                leds = [(0, 0, 0)] * STR_LEN
            
            if not client.put_pixels(leds, channel=0):
                print ('not connected')
            power = False
            sd.sleep(300)
        else:
        # Define Pixel String
            STR_LEN=240
            for i in range(STR_LEN):
                leds = [(255, 255, 255)] * STR_LEN
            
            if not client.put_pixels(leds, channel=0):
                print ('not connected')
            power = True   
            sd.sleep(300)


class ledsetting():
    warm_button     = None
    cold_button     = None
    rain_button     = None
    
    def __init__(self, warm_button="P2_2", cold_button="P2_4", rain_button="P2_6",):
        """ Initialize variables"""
        self.warm_button     = warm_button
        self.cold_button     = cold_button
        self.rain_button     = rain_button
        self._setup()

    # End def
    
    def _setup(self):
        """Setup the hardware components."""
        # Initialize Buttons
        GPIO.setup(self.warm_button, GPIO.IN)
        GPIO.setup(self.cold_button, GPIO.IN)
        GPIO.setup(self.rain_button, GPIO.IN)
        
    # End def

    def warm(self):
        while(1): 
            while(GPIO.input(self.warm_button) == 1): #waiting for press
                time.sleep(0.1)
            if power:
                for i in range(0,STR_LEN,3): #change color array
                    leds[i] = (255, 0, 0)
                    leds[i+1] = (255, 64, 0)
                    leds[i+2] = (255, 128, 0)
                if not client.put_pixels(leds, channel=0): #sends color array over
                    print ('not connected')
            # Wait for button release
            while(GPIO.input(self.warm_button) == 0): 
                # Sleep for a short period of time to reduce CPU load
                time.sleep(0.1)
    # End def
    
    def cold(self):
        while(1): #so the program doesn't stop after one press
            while(GPIO.input(self.cold_button) == 1): #waiting for press
                time.sleep(0.1)
            if power:
                for i in range(0,STR_LEN,3): #change color array
                    leds[i] = (200, 0, 255)
                    leds[i+1] = (0, 0, 255)
                    leds[i+2] = (0, 255, 0)
                if not client.put_pixels(leds, channel=0): #sends color array over
                    print ('not connected')
            # Wait for button release
            while(GPIO.input(self.cold_button) == 0): 
                # Sleep for a short period of time to reduce CPU load
                time.sleep(0.1)
    # End def
    
    def rainbow(self):
        while(1): #so the program doesn't stop after one press
            while(GPIO.input(self.rain_button) == 1): #waiting for press
                time.sleep(0.1)
            if power:
                for i in range(0,STR_LEN,6): #change color array
                    leds[i] = (255, 0, 0)
                    leds[i+1] = (255, 127, 0)
                    leds[i+2] = (255, 255, 0)
                    leds[i+3] = (0, 255, 0)
                    leds[i+4] = (0, 0, 255)
                    leds[i+5] = (75, 0, 130)
                if not client.put_pixels(leds, channel=0): #sends color array over
                    print ('not connected')
            # Wait for button release
            while(GPIO.input(self.rain_button) == 0): 
                # Sleep for a short period of time to reduce CPU load
                time.sleep(0.1)
    # End def
        
if __name__ == '__main__': 
    # prompts microphone to start collecting input data
    with sd.InputStream(channels = 1, callback=print_sound):
        import threading
        
        color_settings = ledsetting()
        # threading allows for buttons to be switched between 
        try:
            thread1 = threading.Thread(target=color_settings.warm)
            thread2 = threading.Thread(target=color_settings.cold)
            thread3 = threading.Thread(target=color_settings.rainbow)
            thread1.start()
            thread2.start()
            thread3.start()
            thread1.join()
            thread2.join()
            thread3.join()
            
        except KeyboardInterrupt:
            pass
    
        print("Program Complete.")        

opc.py

Python
#!/usr/bin/env python

"""Python Client library for Open Pixel Control
http://github.com/zestyping/openpixelcontrol

Sends pixel values to an Open Pixel Control server to be displayed.
http://openpixelcontrol.org/

Recommended use:

    import opc

    # Create a client object
    client = opc.Client('localhost:7890')

    # Test if it can connect (optional)
    if client.can_connect():
        print('connected to %s' % ADDRESS)
    else:
        # We could exit here, but instead let's just print a warning
        # and then keep trying to send pixels in case the server
        # appears later
        print('WARNING: could not connect to %s' % ADDRESS)

    # Send pixels forever at 30 frames per second
    while True:
        my_pixels = [(255, 0, 0), (0, 255, 0), (0, 0, 255)]
        if client.put_pixels(my_pixels, channel=0):
            print('...')
        else:
            print('not connected')
        time.sleep(1/30.0)

"""

import socket
import struct
import sys

SET_PIXEL_COLOURS = 0  # "Set pixel colours" command (see openpixelcontrol.org)


class Client(object):
    def __init__(self, server_ip_port, long_connection=True, verbose=False):
        """Create an OPC client object which sends pixels to an OPC server.

        server_ip_port should be an ip:port or hostname:port as a single string.
        For example: '127.0.0.1:7890' or 'localhost:7890'

        There are two connection modes:
        * In long connection mode, we try to maintain a single long-lived
          connection to the server.  If that connection is lost we will try to
          create a new one whenever put_pixels is called.  This mode is best
          when there's high latency or very high framerates.
        * In short connection mode, we open a connection when it's needed and
          close it immediately after.  This means creating a connection for each
          call to put_pixels. Keeping the connection usually closed makes it
          possible for others to also connect to the server.

        A connection is not established during __init__.  To check if a
        connection will succeed, use can_connect().

        If verbose is True, the client will print debugging info to the console.

        """
        self.verbose = verbose

        self._long_connection = long_connection

        self._ip, self._port = server_ip_port.split(':')
        self._port = int(self._port)

        self._socket = None  # will be None when we're not connected

    def _debug(self, m):
        if self.verbose:
            print('    %s' % str(m))

    def _ensure_connected(self):
        """Set up a connection if one doesn't already exist.

        Return True on success or False on failure.

        """
        if self._socket:
            self._debug('_ensure_connected: already connected, doing nothing')
            return True

        try:
            self._debug('_ensure_connected: trying to connect...')
            self._socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
            self._socket.settimeout(1)
            self._socket.connect((self._ip, self._port))
            self._debug('_ensure_connected:    ...success')
            return True
        except socket.error:
            self._debug('_ensure_connected:    ...failure')
            self._socket = None
            return False

    def disconnect(self):
        """Drop the connection to the server, if there is one."""
        self._debug('disconnecting')
        if self._socket:
            self._socket.close()
        self._socket = None

    def can_connect(self):
        """Try to connect to the server.

        Return True on success or False on failure.

        If in long connection mode, this connection will be kept and re-used for
        subsequent put_pixels calls.

        """
        success = self._ensure_connected()
        if not self._long_connection:
            self.disconnect()
        return success

    def put_pixels(self, pixels, channel=0):
        """Send the list of pixel colors to the OPC server on the given channel.

        channel: Which strand of lights to send the pixel colors to.
            Must be an int in the range 0-255 inclusive.
            0 is a special value which means "all channels".

        pixels: A list of 3-tuples representing rgb colors.
            Each value in the tuple should be in the range 0-255 inclusive. 
            For example: [(255, 255, 255), (0, 0, 0), (127, 0, 0)]
            Floats will be rounded down to integers.
            Values outside the legal range will be clamped.

        Will establish a connection to the server as needed.

        On successful transmission of pixels, return True.
        On failure (bad connection), return False.

        The list of pixel colors will be applied to the LED string starting
        with the first LED.  It's not possible to send a color just to one
        LED at a time (unless it's the first one).

        """
        self._debug('put_pixels: connecting')
        is_connected = self._ensure_connected()
        if not is_connected:
            self._debug('put_pixels: not connected.  ignoring these pixels.')
            return False

        # build OPC message
        command = SET_PIXEL_COLOURS
        header = struct.pack('>BBH', channel, SET_PIXEL_COLOURS, len(pixels)*3)
        pieces = [struct.pack(
                      'BBB',
                      min(255, max(0, int(r))),
                      min(255, max(0, int(g))),
                      min(255, max(0, int(b)))
                  ) for r, g, b in pixels]
        if bytes is str:
            message = header + ''.join(pieces)
        else:
            message = header + b''.join(pieces)

        self._debug('put_pixels: sending pixels to server')
        try:
            self._socket.send(message)
        except socket.error:
            self._debug('put_pixels: connection lost.  could not send pixels.')
            self._socket = None
            return False

        if not self._long_connection:
            self._debug('put_pixels: disconnecting')
            self.disconnect()

        return True

run

Python
#!/bin/bash
# --------------------------------------------------------------------------
# Project - Run Script
# --------------------------------------------------------------------------
# License:   
# Copyright 2021 Haruto Sasajima
# 
# Redistribution and use in source and binary forms, with or without 
# modification, are permitted provided that the following conditions are met:
# 
# 1. Redistributions of source code must retain the above copyright notice, this 
# list of conditions and the following disclaimer.
# 
# 2. Redistributions in binary form must reproduce the above copyright notice, 
# this list of conditions and the following disclaimer in the documentation 
# and/or other materials provided with the distribution.
# 
# 3. Neither the name of the copyright holder nor the names of its contributors 
# may be used to endorse or promote products derived from this software without 
# specific prior written permission.
# 
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 
# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 
# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 
# DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE 
# FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 
# DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR 
# SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER 
# CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, 
# OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 
# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
# --------------------------------------------------------------------------

cd /var/lib/cloud9/ENGI301/python/project
./configure_pins.sh
#PYTHONPATH=/var/lib/cloud9/ENGI301/python/led_strip run-opc-server
PYTHONPATH=/var/lib/cloud9/ENGI301/python/project python3 project.py

run-opc-server

Python
#!/bin/bash
# --------------------------------------------------------------------------
# Project - Server Run Script
# --------------------------------------------------------------------------
# License:   
# Copyright 2021 Haruto Sasajima
# 
# Redistribution and use in source and binary forms, with or without 
# modification, are permitted provided that the following conditions are met:
# 
# 1. Redistributions of source code must retain the above copyright notice, this 
# list of conditions and the following disclaimer.
# 
# 2. Redistributions in binary form must reproduce the above copyright notice, 
# this list of conditions and the following disclaimer in the documentation 
# and/or other materials provided with the distribution.
# 
# 3. Neither the name of the copyright holder nor the names of its contributors 
# may be used to endorse or promote products derived from this software without 
# specific prior written permission.
# 
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 
# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 
# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 
# DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE 
# FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 
# DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR 
# SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER 
# CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, 
# OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 
# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
# --------------------------------------------------------------------------

cd /var/lib/cloud9/project/

./opc-server --config config.json

ws281x-original-ledscape-pru0.bin

Python
No preview (download only).

ws281x-original-ledscape-pru1.bin

Python
No preview (download only).

config.json

Python
{
	"outputMode": "ws281x",
	"outputMapping": "original-ledscape",
	"demoMode": "none",
	"ledsPerStrip": 240,
	"usedStripCount": 1,
	"colorChannelOrder": "BRG",
	"opcTcpPort": 7890,
	"opcUdpPort": 7890,
	"enableInterpolation": false,
	"enableDithering": false,
	"enableLookupTable": true,
	"lumCurvePower": 2.0000,
	"whitePoint": {
		"red": 0.9000,
		"green": 1.0000,
		"blue": 1.0000
	}
}

configure_pins.sh

Python
#!/bin/bash
# --------------------------------------------------------------------------
# LED Strip Light - Configure Pins
# --------------------------------------------------------------------------
# License:
# Copyright 2021 Erik Welsh
#
# Redistribution and use in source and binary forms, with or without
# modification, are permitted provided that the following conditions are met:
#
# 1. Redistributions of source code must retain the above copyright notice, this
# list of conditions and the following disclaimer.
#
# 2. Redistributions in binary form must reproduce the above copyright notice,
# this list of conditions and the following disclaimer in the documentation
# and/or other materials provided with the distribution.
#
# 3. Neither the name of the copyright holder nor the names of its contributors
# may be used to endorse or promote products derived from this software without
# specific prior written permission.
#
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
# DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
# FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
# DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
# SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
# CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
# OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
# --------------------------------------------------------------------------
#
# --------------------------------------------------------------------------

# Data Pin
config-pin P1_08 gpio

Credits

Haruto Sasajima
1 project • 1 follower
Contact
Thanks to Erik Welsh.

Comments

Please log in or sign up to comment.