Introduction: Taylor Swift PixMob Wristband Synced Costume

For many concerts such as Taylor Swift and ColdPlay, they use a crowd lighting technology from a company called PixMob. They give you a wristband that ever concert goer has that allows them to create amazing light shows. I had seen a previous tiktok video of someone that had tapped into a pixmob wristband to make his vest light up in sync with the wristband. While he did eventually post a tiktok explaining in a general sense how he did it, he only went into the overarching concept. So anyone who might not have high level programming or electronics experience would not have any idea how he did it. I both wanted to see if I could figure it out as well, but also, if I did, I wanted to be able to share it with other fans (in my case, other swifties). So here's the comprehensive guide on how I did it. I realize it's late in the Eras tour so it might be difficult for the people attending the show before the end of the tour, but figured this will help anyone interested in doing this for any show that includes the pixmob wristbands.

Supplies

Required:

  1. Pixmob controller board. (Any version >2.3r1 will definitely work. I have not tested anything older. If you have not attended a show and gotten one previously, they are available online second hand for not terrible prices. I got my extra for like $25.)
  2. The latest version of the board is something like v2.6r1 which my wife had that I tested initially with. The second hand one I got was an older v2.3r1 which still worked exactly the same way
  3. Esp32 controller board. Any brand will work. In my case, I used an Adafruit Feather V2
  4. ADS1115 analog to digital converter. This is needed to convert the voltage signal from the RGB on the pixmob to data the Esp32 can analyze. The analog to digital pins on the esp32 simply weren't fast or accurate enough.
  5. Soldering Iron. Preferably one that has holders as well.
  6. Flux. An absolute must in order to not get shorts when attaching the wires to the rgb pins on the pixmob.
  7. Wire and lots of it
  8. Adruino IDE or equivalent. Needed to program the board.
  9. WS2812 LEDs. Any number should be fine provided you have enough power. I was able to get consistent color with a full 300 led strand.
  10. 5V Power source. You can do higher voltage, but I would recommend sticking with 5v as that is what the pixmob board wants

Optional:

  1. Second Esp32 board. This board is used as an IR transmitter to test that your pixmob is working as intended
  2. IR transmitter. Any works, I got mine off amazon.
  3. Breadboard. Makes testing wiring so much easier
  4. WS2812 male/female connectors
  5. Heat shrink tubing. Makes solder connections much more reliable
  6. 3D Printer. If you want to print the supplied box
  7. Clear PLA filament
  8. Toggle Switch (if you want to enable switching "modes" from wristband to programmed loop)
  9. 10k resistor (if adding toggle switch)
  10. Two wire USB C connector

Step 1: Confirm Pixmob Wristband Operation: Batteries

The following steps are optional but highly recommended as it will ensure your pixmob is working correctly. If you do not want to do these steps, skip to Step 3

You'll first need to make sure your wristband has working batteries. They are just two standard coin batteries. Just match with the existing ones within the wristband.

The wristbands simply pops open via small plastic clips. So just pry gently with a small flathead screwdriver.

Step 2: Confirm Pixmob Wristband Operation: IR Transmitter

In this step, you'll hook your IR transmitter board to your extra ESP32 board.

Wiring is pretty basic here:

  1. V+ on IR connects to 3v pin on ESP32
  2. Gnd on IR to Gnd on ESP32
  3. In on IR connects to whatever GPIO pin you want on the ESP32 (in my case and in the code examples, its GPIO13

Once they are connected and the ESP32 is plugged into your computer via usb we have two options. Either a pre-programmed color loop provided or one that takes input via LED Lights Wristband Effects (ivanr3d.com).


Pre-programmed loop (PixMob_Loop_ESP32)

The values in this may look very odd. These values were pulled from the pixmob reverse engineering library here to mimic various colors. That library is here if you want to work with it further https://github.com/danielweidman/pixmob-ir-reverse-engineering/blob/main/README.md.

This code loops through blue, red, green, white, light blue, orange yellow and a few others. Should be more than enough to confirm your colors match.

Simply load the file in Arduino IDE and upload to your board.

Input based transmitter (Pixmob_Transmitter_ESP32_Simplified)

This is a simplified version of the provided code from the reverse engineering library above. I simply removed all the bluetooth code as I didn't super need it. If you want to connect via bluetooth, you can find the original file there.

Load the file in Arduino IDE and upload to your board.

Important: If using the ivanr3d.com site, you must disconnect your board from arduino completely (i usually just close the arduino window) otherwise, the site will not connect to the board correctly as the COM port won't be available)

Once you connect, you should see the connect button turn red and say disconnect.

You can select from the list of effects and colors to test things. Some will not work! This comes from a reverse engineered library so not all of them will work. This doesnt mean your board doesn't work. As long as some work and the colors match, you're good. I usually just use the Blink button at the bottom that quickly scrolls through a bunch of colors.

If everything works correctly, you should get something similar to the video below.

*Careful about copying the code below directly. The include files do not show in the embed. I'd suggest clicking the "view raw" at the bottom of the file.

PixMob-Loop

This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters. Learn more about bidirectional Unicode characters
Show hidden characters
#include
#include
#define IR_SEND_PIN 13
String incomingString = "";
void setup() {
IrSender.begin(IR_SEND_PIN);
Serial.begin(115200);
SerialBT.begin("PixMobSender");
}
void loop() {
for(int i =0; i<8; i++)
{
incomingString = Serial.readStringUntil('['); // read the incoming byte:
incomingString = Serial.readStringUntil(']'); // read the incoming byte:
String newVals;
if(i == 0) newVals = "11132114121112211211111113121221111114241";
if(i == 1) newVals = "22111214132111122424121221111114241";
if(i == 2) newVals = "22111124121212122424121221111114241";
if(i == 3) newVals = "1212112412121322132213241423241";
if(i == 4) newVals = "11132114121112111111112213121221111114241";
if(i == 5) newVals = "1111222413111212111324121221111114241";
if(i == 6) newVals = "22111124131112122424121221111114241";
if(i == 7) newVals = "1113211412111422131212121221111114241";
int newLength = i==0 || i == 4 || i == 6 ? 41 : 35;
if(i ==3) newLength = 31;
if(i == 5 || i == 7) newLength = 37;
uint16_t newRawData[newLength] = {};
for (int j = 0; j < newVals.length(); j++ ) {
int intVal = newVals.substring(j, j + 1).toInt() * 700;
newRawData[j] = intVal;
}
IrSender.sendRaw(newRawData, newLength, 38); // Send a raw data capture at 38kHz.
delay(3);
}
delay(500);
}
view raw PixMob-Loop hosted with ❤ by GitHub

PixMob-Transmitter

This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters. Learn more about bidirectional Unicode characters
Show hidden characters
#include
#include
#define IR_SEND_PIN 13
String incomingString = "";
void setup() {
IrSender.begin(IR_SEND_PIN);
Serial.begin(115200);
SerialBT.begin("PixMobSender");
}
void loop() {
if (Serial.available() > 0) {
incomingString = Serial.readStringUntil('['); // read the incoming byte:
incomingString = Serial.readStringUntil(']'); // read the incoming byte:
int newLength = incomingString.toInt();
uint16_t newRawData[newLength] = {};
String newVals = Serial.readStringUntil(',');
for (int i = 0; i < newVals.length(); i++ ) {
int intVal = newVals.substring(i, i + 1).toInt() * 700;
newRawData[i] = intVal;
}
IrSender.sendRaw(newRawData, newLength, 38); // Send a raw data capture at 38kHz.
delay(3);
} else if (SerialBT.available() > 0) {
incomingString = SerialBT.readStringUntil('['); // read the incoming byte:
incomingString = SerialBT.readStringUntil(']'); // read the incoming byte:
int newLength = incomingString.toInt();
uint16_t newRawData[newLength] = {};
String newVals = SerialBT.readStringUntil(',');
for (int i = 0; i < newVals.length(); i++ ) {
int intVal = newVals.substring(i, i + 1).toInt() * 700;
newRawData[i] = intVal;
}
IrSender.sendRaw(newRawData, newLength, 38); // Send a raw data capture at 38kHz.
delay(4);
}
}

Step 3: Preparing Your Circuit

I've provided two overall schematics of how everything needs to be wired. One is with the mode switch and one is without. If this doesn't follow exact circuit diagram rules, come at me, I don't care.

For my example, on the ADC, I used A1 for the Red channel, A2 for the blue, and A3 for the green. This is arbitrary, you can use whatever combination you want, you'll just need to adjust the code in the following steps.

Some resources suggested grounding the unused A input (A0) but I didn't see any issue with not. I did, however, ground ADDR.

For my example, I hooked the data out to the WS2812 leds to GPIO13. If you add the toggle switch, I used pin 12. Again, you can use whatever pins you want for these, you'll just need to make sure you match them in the code.


Step 4: Solder Wires to Pixmob

For some newer to soldering, this will probably be one of the most difficult steps. You'll need to attach 3 wires to each of the RGB connections on one of the LEDs. I'd use the exact connections shown on the diagram as pixmob appears to use common anode leds so one side will be common V+ (which the code will reflect).

I strongly suggest using flux here and pre-tinning both the pins on the led and the wire you're connecting. Don't overuse solder. You don't need much. Using too much will make it easier to short the pins together.

Once you have those connected, add the - and + connections. The diagram shows connecting to only one "dot" on the pixmob, but I'd definitely connect it to at least two. You can see what that looks like in the picture.

Step 5: Connect Everything Up

Follow the diagram and connect your wires, paying close attention to your grounds and Vin's. I'd strongly suggest using a breadboard here as it'll make everything much easier to manage.

There is no need necessary to connect your WS2812 LEDs up just yet. You'll be able to use the Serial monitor in the arduino IDE to see the values you are getting from the ADC and the color values being outputted to the neopixel library in the following steps.

Step 6: Programming the ESP32

Depending on your ESP32 board, ADS1115, pixmob boards, or your voltage input (if not 5v), or if you use a higher ADS1115 gain (not recommended). There may be some trial and error on the values in code.

Two files are provided: One with the toggle, and one without.

The goal here is you will take the signal values from the ADS inputs and convert them into their 0-255 value equivalents for the color values. There are debug print statements to help you figure out what your high (led off) values are, and what your low (led fully on) values are.

Given the LEDs are common anode, when the LED is off, the ADS will be receiving a high value. In my case, given the default gain set, was around 20200 for each. So I mapped that value to a 0 color value.

The low values are more difficult to get right and will probably require you do watch the output values as the led turns to each color. For me, fully on blue was around 17400. Red was also 17400, and green was much lower at 11400. So for each of those maps, those values were mapped to 255.

Now, given these numbers are not exact, I hacked the boundaries a bit given we do not want the WS2812 led being given a value above 255 or below 0. I simply upper bounded the 255 and lower bounded the 0 for each color. There is definitely a more accurate way to do this, but I had limited time and didn't feel the need as the colors matched enough with this code.

One last note, I utilized millis() instead of delay as that made the ADS communication much faster and the color sync much more accurate.

*Careful about copying the code below directly. The include files do not show in the embed. I'd suggest clicking the "view raw" at the bottom of the file.

wristband-no-toggle

This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters. Learn more about bidirectional Unicode characters
Show hidden characters
#include
#include
#include
Adafruit_ADS1115 ads;
// Define the number of LEDs and the data pin for the output (if using NeoPixels)
#define NUM_LEDS 300
#define DATA_PIN 13 // Pin to the WS2812/NeoPixel strip (if using digital LEDs)
Adafruit_NeoPixel pixels = Adafruit_NeoPixel(300, 13, NEO_GRB + NEO_KHZ800);
unsigned long startMillis; //some global variables available anywhere in the program
unsigned long currentMillis;
const unsigned long period = 2;
void setup() {
Serial.begin(115200);
//If you are getting "Failed to initialize", then most likely your ADS is not at address 0x48. You'll need to look up code online to find the address of your ADS1115
if (!ads.begin(0x48)) {
Serial.println("Failed to initialize ADS.");
while (1);
}
ads.setDataRate(RATE_ADS1115_128SPS); //Any higher than this rate seemed to cause too much blinking
pixels.begin();
pixels.clear();
pixels.setBrightness(100);
pixels.show();
}
void loop() {
unsigned long currentMillis = millis();
if ((currentMillis - startMillis >= period))
{
int16_t adc0, adc1, adc2, adc3;
adc0 = ads.readADC_SingleEnded(0);
adc1 = ads.readADC_SingleEnded(1);
adc2 = ads.readADC_SingleEnded(2);
adc3 = ads.readADC_SingleEnded(3);
int blue = map(adc2, 17400, 20200, 255, 0);
if(blue > 255)
blue = 255;
if(blue < 5)
blue = 0;
int red = map(adc1, 17400, 20200, 255, 0);
if(red > 255)
red = 255;
if(red < 5)
red = 0;
int green = map(adc3, 11400, 20200, 255, 0);
if(green > 255)
green = 255;
if(green < 5)
green = 0;
for (int i = 0; i < NUM_LEDS; i++) {
pixels.setPixelColor(i, red, green, blue);
}
//Remove or comment these two lines once you have confirmed the values work
Serial.println("-----");
Serial.print("Red: "); Serial.print(adc1); Serial.print(" --- "); Serial.print(red);Serial.print(" Green: "); Serial.print(adc3); Serial.print(" Blue: "); Serial.print(adc2);
pixels.show();
startMillis = currentMillis;
}
}

wristband-with-toggle

This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters. Learn more about bidirectional Unicode characters
Show hidden characters
#include
#include
#include
Adafruit_ADS1115 ads;
// Define the number of LEDs and the data pin for the output (if using NeoPixels)
#define NUM_LEDS 300
#define DATA_PIN 13 // Pin to the WS2812/NeoPixel strip (if using digital LEDs)
Adafruit_NeoPixel pixels = Adafruit_NeoPixel(300, 13, NEO_GRB + NEO_KHZ800); //If you notice your LEDs are the wrong colors, you might need to change to NEO_RGB, NEO_GBR, etc. You have to play around to find the right one.
unsigned long startMillis; //some global variables available anywhere in the program
unsigned long currentMillis;
const unsigned long period = 2;
int positionStep = 0;
float divisor = float(215)/float(255);
#define SWITCH_PIN 12
void setup() {
Serial.begin(115200);
//If you are getting "Failed to initialize", then most likely your ADS is not at address 0x48. You'll need to look up code online to find the address of your ADS1115
if (!ads.begin(0x48)) {
Serial.println("Failed to initialize ADS.");
while (1);
}
ads.setDataRate(RATE_ADS1115_128SPS); //Any higher than this rate seemed to cause too much blinking
pixels.begin();
pixels.clear();
pixels.setBrightness(100);
pixels.show();
}
void loop() {
unsigned long currentMillis = millis();
int inputSwitch = digitalRead(SWITCH_PIN);
if ((currentMillis - startMillis >= period))
{
if(inputSwitch == 0) {
int16_t adc0, adc1, adc2, adc3;
float volts0, volts1, volts2, volts3;
adc0 = ads.readADC_SingleEnded(0);
adc1 = ads.readADC_SingleEnded(1);
adc2 = ads.readADC_SingleEnded(2);
adc3 = ads.readADC_SingleEnded(3);
int blue = map(adc2, 17400, 20200, 255, 0);
if(blue > 255)
blue = 255;
if(blue < 5)
blue = 0;
int red = map(adc1, 17400, 20200, 255, 0);
if(red > 255)
red = 255;
if(red < 5)
red = 0;
int green = map(adc3, 11400, 20200, 255, 0);
if(green > 255)
green = 255;
if(green < 5)
green = 0;
for (int i = 0; i < NUM_LEDS; i++) {
pixels.setPixelColor(i, red, green, blue);
}
Serial.println("-----");
Serial.print("Red: "); Serial.print(adc1); Serial.print(" --- "); Serial.print(red);Serial.print(" Green: "); Serial.print(adc3); Serial.print(" Blue: "); Serial.print(adc2);
pixels.show();
}
else if(inputSwitch == 1){
colorChange(currentMillis);
}
startMillis = currentMillis;
}
}
void colorChange(long time) {
//input code to create whatever preprogrammed pattern you want here
}

Step 7: Testing Everything

As this point, you should be getting color values the make sense and match the color you're seeing on the pixmob. If so, connected up your WS2812 leds and they should be matching. If not, go back to step 6 and see if you can understand why. If the colors seem to be "switched", change the NEO_GRB value on line 9.


Step 8: Finalizing Connections

At this point, you just need to connect everything in a more "permanent" way from the breadboard. This is completely up to you on how is best for you. I went with male pins on the two boards with JST connectors to easily connect and disconnect if need be.

The only requirement for this will be if you want to move on to the optional modular 3d printed box in the next steps. If so, you'll need to try and make everything as compact as possible or you'll need to adjust the size of the box to compensate.

Step 9: Optional: Modular Box

This box makes it easy to both protect your boards and connections, while also providing a great way to quickly disconnect power and LEDs to it for portability.

I've provided the blender file as well as the STL files. Another important note if you use the STL files directly. The scale I modeled at in blender was set a magnitude lower, so you'll need to scale those by 1000% in your slicer. Otherwise you can adjust the blender project and export your own STLs.

The model provides a slide in lid, opening for a JST 3 pin LED connector (for your WS2812 leds), and a usb-c connector. The one I used can be found here.

I added the toggle switch after the fact so there isn't a cutout for that, so if you do want that, you'll need to make that cutout on your own.

I would STRONGLY suggest using clear filament so the IR signal is easily received by the pixmob board. You also need to make sure that the IR receiver on the pixmob board is point out on one of the inner sides of the box so it will receive the signal from the concert transmitters.


*Instructables does not allow me to upload a blender file, so you'll have to download from my github here: https://github.com/ttothebrown/pixmob-led-sync/blob/main/circuitbox.blend

Step 10: Go to the Concert and Have Fun!

Connect everything up, switch to loop mode on your way to the concert, then switch it when the concert starts and turn heads!


One FINAL note. be nice and try to limit the LEDs you add pointed backwards. You don't want to ruin the experience for the people behind you shine a bunch of bright LEDs in their face the whole concert! This is why mine were only on the front.