Plex + Particle + IKEA + Baby = WIN

Hi all!

For those who have little once, you know the challenge. Our kids grow up in an digital age with phones, tablets, baby and kids TV channels, YouTube and tons more. A good friend and a child’s physiotherapist has seen kids coming in her practice with neck and shoulder complains. Most of these were related to playing with the parents tablet and looking down on the screen while the tablet sits in the child’s lap. So I had to fix this!

What I made is an IKEA box with an old iPad and three large buttons. When the baby pushes a button the box lights up int he color of the button and a TV of choice begins playing on the iPad. Each button is a different serie. I do this via Plex. When the button is pressed the Photon sends a HTTP GET request to my Plex server which in its turn send the request to the iPad which starts streaming the content from the Plex server.

in this way i can control what the baby sees. Also the box makes the baby able to sit upright in-front of it and it cannot carry around the iPad.

#Stuff Needed:

  1. 1x Particle Photon
  2. 1x Old 2gen iPad
  3. Plex Server
  4. Proxy Server
  5. 1x IKEA storage box
  6. 1x BMS Protection Board
  7. 3x 18650 cells (from old laptop battery)
  8. 1x 18650 3x cell holder
  9. 3x square arcade push buttons
  10. 44x WS2812B LEDs
  11. 1x big shottky diode
  12. 1x toggle switch
  13. 1x 5.5mm x 2.1mm DC Female Power Supply Jack
  14. 1x LM2596 Buck Step-down Power Converter
  15. 3x NPN transistors
  16. 3x 1k resistors
  17. 1x USB 2.1Amp charger
  18. PCB and etching materials
  19. PCB connectors
  20. Nuts and bolts
  21. Cables and shrink tubes
  22. Glue and others

#The challanges:
First; I didn’t want the internal battery pack to charge the iPad. So used an 3amp shottky diode inbetween the battery pack and the power supply jack. in this way the battery pack can be charge together with the iPad but does not leak back into the iPad.

Second; Plex is HTTPS and HTTPS is a pain in the **s from inside the Particle. So I used an already running NGINX server in my home as an HTTP to HTTPS proxy. The Photon sends all the requests to the NGINX server which in its turns doe HTTPS to the Plex Server

Third; The Plex App on the iPad must be only to let it work. It will only present itself as player to the Plex Server when the App is open on screen. Because of this full stand-alone is not possible.

Fourth; NOTHING sticks to the inside of an IKEA storage box. The plastic U frames where the iPad sits in kept coming lose. Only hot-glue was good enough to stick.

Below from left to right; USB charger for iPad, Custom PCB with Photon, Step-Down converter for contant 5v power:

The lid with WS2812B LED strips and the battery pack:

Inside of the IKEA box, all wired up:

One extremly happy baby!

The code:

// This #include statement was automatically added by the Particle IDE.
#include <neopixel.h>
// This #include statement was automatically added by the Particle IDE.
#include <clickButton.h>

// RGB Leds
void colorAll(uint32_t c, uint8_t wait);
void colorWipe(uint32_t c, uint8_t wait);
void rainbow(uint8_t wait);
void rainbowCycle(uint8_t wait);
uint32_t Wheel(byte WheelPos);

//## IMPORTANT: Set pixel COUNT, PIN and TYPE
#define PIXEL_COUNT 44
#define PIXEL_PIN D6
#define PIXEL_TYPE WS2812B

Adafruit_NeoPixel strip(PIXEL_COUNT, PIXEL_PIN, PIXEL_TYPE);

#define PIN3 D3
#define PIN4 D4
#define PIN5 D5
int stateD3 = 0;
int stateD4 = 0;
int stateD5 = 0;

// the Button
// Button settings
const int button_1_Pin0 = 0;
const int button_2_Pin1 = 1;
const int button_3_Pin2 = 2;
ClickButton button_1(button_1_Pin0, LOW, CLICKBTN_PULLUP);
ClickButton button_2(button_2_Pin1, LOW, CLICKBTN_PULLUP);
ClickButton button_3(button_3_Pin2, LOW, CLICKBTN_PULLUP);
int function_button_1 = 0; // Button results 
int function_button_2 = 0; // Button results 
int function_button_3 = 0; // Button results 

TCPClient client;
byte server[] = { xxx, xxx, xxx, xxx }; // Proxy server IP

Timer timer01(5000, downtime, true);
int downtime_state = 0;
Timer timer02(5000, blink);

void setup() {
pinMode(PIN3, OUTPUT);
pinMode(PIN4, OUTPUT);
pinMode(PIN5, OUTPUT);

strip.begin(); // RGB leds; // Initialize all pixels to 'off'


void loop() {

// Save click codes in LEDfunction, as click codes are reset at next Update()
if(button_1.clicks != 0) function_button_1 = button_1.clicks;
if(button_2.clicks != 0) function_button_2 = button_2.clicks;
if(button_3.clicks != 0) function_button_3 = button_3.clicks;

if(function_button_1 == 1) { 

if(function_button_2 == 1) {

if(function_button_3 == 1) {

function_button_1 = 0;
function_button_2 = 0;
function_button_3 = 0;

void blink() {
digitalWrite(PIN3, (stateD3) ? HIGH : LOW);
stateD3 = !stateD3;
digitalWrite(PIN4, (stateD4) ? HIGH : LOW);
stateD4 = !stateD4;
digitalWrite(PIN5, (stateD5) ? HIGH : LOW);
stateD5 = !stateD5;
//colorAll(strip.Color(random(0,255), random(0,255), random(0,255)), 50); // random color

void downtime() {
downtime_state = 0;
digitalWrite(PIN3, HIGH);
digitalWrite(PIN4, HIGH);
digitalWrite(PIN5, HIGH);
colorAll(strip.Color(0, 0, 0), 0); // OFF

void plex(int button){
if (downtime_state == 0){
digitalWrite(PIN3, LOW);
digitalWrite(PIN4, LOW);
digitalWrite(PIN5, LOW);
downtime_state = 1;
        case 1:
            if (client.connect(server, 88)) {
                client.println("GET /plex/player/playback/playMedia?<YOUR REQUEST> HTTP/1.0");
                client.println("Host: localhost");
                client.println("Content-Length: 0");
                digitalWrite(PIN3, HIGH);
                colorAll(strip.Color(255, 0, 0), 255); // Red
        case 2:
            if (client.connect(server, 88)) {
                client.println("GET /plex/player/playback/playMedia?<YOUR REQUEST> HTTP/1.0");
                client.println("Host: localhost");
                client.println("Content-Length: 0");
                digitalWrite(PIN4, HIGH);
                colorAll(strip.Color(255, 80, 0), 255); // Yellow
        case 3:
            if (client.connect(server, 88)) {
                client.println("GET /plex/player/playback/playMedia?<YOUR REQUEST> HTTP/1.0");
                client.println("Host: localhost");
                client.println("Content-Length: 0");
                digitalWrite(PIN5, HIGH);
                colorAll(strip.Color(0, 0, 255), 255); // Blue

// Set all pixels in the strip to a solid color, then wait (ms)
void colorAll(uint32_t c, uint8_t wait) {
  uint16_t i;

  for(i=0; i<strip.numPixels(); i++) {
strip.setPixelColor(i, c);

// Fill the dots one after the other with a color, wait (ms) after each one
void colorWipe(uint32_t c, uint8_t wait) {
  for(uint16_t i=0; i<strip.numPixels(); i++) {
strip.setPixelColor(i, c);;

void rainbow(uint8_t wait) {
  uint16_t i, j;

  for(j=0; j<256; j++) {
for(i=0; i<strip.numPixels(); i++) {
  strip.setPixelColor(i, Wheel((i+j) & 255));

// Slightly different, this makes the rainbow equally distributed throughout, then wait (ms)
void rainbowCycle(uint8_t wait) {
  uint16_t i, j;

  for(j=0; j<256; j++) { // 1 cycle of all colors on wheel
for(i=0; i< strip.numPixels(); i++) {
  strip.setPixelColor(i, Wheel(((i * 256 / strip.numPixels()) + j) & 255));

// Input a value 0 to 255 to get a color value.
// The colours are a transition r - g - b - back to r.
uint32_t Wheel(byte WheelPos) {
  if(WheelPos < 85) {
   return strip.Color(WheelPos * 3, 255 - WheelPos * 3, 0);
  } else if(WheelPos < 170) {
   WheelPos -= 85;
   return strip.Color(255 - WheelPos * 3, 0, WheelPos * 3);
  } else {
   WheelPos -= 170;
   return strip.Color(0, WheelPos * 3, 255 - WheelPos * 3);

The PCB design: (Fritzing)


This is amazing!!! Thanks for sharing this with us!