Month: April 2018

Hacking Rocketbeans lamp with ESP32 to display current show type

Hacking Rocketbeans lamp with ESP32 to display current show type

Hey guys,

today I’d like to show you how I modded an ordinary lamp into a smart (IoT) one.

Foreword

I’m a huge Rocketbeans TV Fan. Rocketbeans is a big 24/7 Stream Show focused on gaming. Germans may know the hosts from “Giga” or “Game One”.

As they started selling a USB Lamp with their logo and some RGB-LED I thought about making it smart to display the current type of show running. At the moment there are three types of shows available: “Live”(=Red), “Premiere”(=Blue), “Playback”(=White). The unmodified lamp is able to switch colors on a button press.

via Rocketbeans Forum

Requirements

The Lamp

I bought one of them. As soon as it arrived I started to disassemble the lamp to check the circuit board. To my surprise, it’s straight forward.

The (smart) Microcontroller

Leroy told me about the ESP32 which is a very small Microcontroller with built-in Wifi and Bluetooth. This one seems to be a perfect match for the smart part of the lamp.

Loading

 

You can buy one via eBay

The Schedule of Rocketbeans TV

After viewing and manually scraping their Website I was able to find a Link which returns the current and four further shows as JSON object. (The following image only shows the current show also referred as Array[0]).

Now we gathered all requirements and start tinkering.

Reverse Engineering the Lamp Circuit

We started to measure some circuits and figured out how the embedded controller works. The controller has eight pins: GND, 5v, a Touch Button on the Case and three colors. The remaining ones may be used for infrared.

Next we removed the embedded microcontroller to attach the ESP32 controller.

Writing the Microcontroller Code

Since I never wrote any kind of ESP32 Code I decided to invest some time to read Tutorials/Blogs about “ESP32 Wifi”, “Arduino JSON” and “Arduino HTTP Client”.

After several compile errors and reading through manuals on how to use the above libraries I wrote the following code.

ESP32 Code (Version 1)
#include <WiFi.h>
#include <HTTPClient.h>
#include <ArduinoJson.h>

#define __product__    "Beanduino: Dynamic Color Lamp"
#define __model__      "DyCoLa"
#define __version__    "1000DEV"
#define __author__     "Marvyn Zalewski <[email protected]>"

#define __copyright__ "(c) 2015 The Senso Team"
#define __license__   "All rights reserved."

const char* ssid = "WIFI";
const char* password =  "WIFIPASSWORD";
const char* rbTvUri = "https://www.rocketbeans.tv/?next5Shows=true";
int status = WL_IDLE_STATUS;


void setup() {
  Serial.begin(115200);
  Serial.println(__product__);
  Serial.print(__model__);
  Serial.println(__version__);
  Serial.println(__author__);
  Serial.println((String)__copyright__ + ", " + (String)__license__ + "\n");
  while ( status != WL_CONNECTED) {
    Serial.println("Beanduino - Attempting to connect to Wifi network, SSID: " + String(ssid));
    status = WiFi.begin(ssid, password);
    delay(10000);
  }
  Serial.println("Beanduino - Connected to network");

}

String stateToColor(String state){
  const size_t showStateJsonBufferSize = JSON_OBJECT_SIZE(3);
  DynamicJsonBuffer showStateJsonBuffer(showStateJsonBufferSize);
  JsonObject& showStates = showStateJsonBuffer.createObject();
  showStates["live"] = "red";
  showStates["new"] = "blue";
  showStates["playback"] = "white";
  return showStates[state];
}

void setLampState(String state){
  String showState = state;
  String showColor = stateToColor(state);
  Serial.println(__product__ ";" + showState + ";" + showColor);
}

void loop() {
  if ((WiFi.status() == WL_CONNECTED)) {
      HTTPClient http;
      http.begin(rbTvUri);
      int httpCode = http.GET();
      if (httpCode > 0) {
          const size_t bufferSize = JSON_ARRAY_SIZE(5) + 5*JSON_OBJECT_SIZE(14) + 2970;
          DynamicJsonBuffer jsonBuffer(bufferSize);
          String payload = http.getString();
          JsonArray& root = jsonBuffer.parseArray(payload);
          if (!root.success()){
              Serial.println("PARSING FAILED");
              Serial.println(payload);
          }
          JsonArray& root_ = root;
          JsonObject& root_0 = root_[0];
          int isLive = root_0["isLive"];
          int isNew = root_0["isNew"];
          if (isLive == 1) {
              setLampState("live");
            } else {
            if (isNew == 1) {
                setLampState("new");
            } else {
                setLampState("playback");
             }    
          }
      }
      else {
        Serial.println("Error on HTTP request");
      }
      http.end();
    }
    delay(10000);
}

Running the code

Beanduino: Dynamic Color Lamp
DyCoLa1000DEV
Marvyn Zalewski <[email protected]>
(c) 2015 The Senso Team, All rights reserved.

Beanduino – Attempting to connect to Wifi network, SSID: WIFI
Beanduino – Attempting to connect to Wifi network, SSID: WIFI
Beanduino – Connected to network
Beanduino: Dynamic Color Lamp;live;red
Beanduino: Dynamic Color Lamp;live;red

Putting it all together

Now we know everything to set up the smart lamp. At first, we’re soldering all needed wires and stick them into the battery compartment.

As you can see there are six wires coming from the Lamp Circuit. (VCC which is 5V, GND, directly from the Button and all three colors.)

Due to space issues, we soldered the wires directly onto the ESP32.

Wire Map:

  • ESP32 VIN -> Lamp Circuit VCC
  • ESP32 GND -> Lamp Circuit GND
  • ESP32 D14 -> Lamp Circuit LED Green (the left resistor R5)
  • ESP32 D22 -> Lamp Case Button (without any function at the moment)
  • ESP32 D26 -> Lamp Circuit LED Blue (the middle resistor R6)
  • ESP32 D33 -> Lamp Circuit LED Red (the right resistor R7)

 

After a nearly whole code refactoring, I finished it and moved it to Github.

Beanlamp Code
#include <Arduino.h>
#include <WiFi.h>
#include <HTTPClient.h>
#include <ArduinoJson.h>

/*
* Coniguration
** CONNECTION_INDICATOR - blinking led during startup when defined
*/
#define CONNECTION_INDICATOR
#define LED_RED GPIO_NUM_14
#define LED_GREEN GPIO_NUM_27
#define LED_BLUE GPIO_NUM_26

#define WIFI_SSID "WLAN"
#define WIFI_PASSWORD "WLAN_PASSWORD"
#define ROCKETBEANS_API "https://www.rocketbeans.tv/?next5Shows=true"

/*
* Information
*/
#define __product__ "Beanlight"
#define __version__ "1000DEV"
#define __author__ "Marvyn Zalewski <[email protected]>"
#define __copyright__ "(c) 2018 KeyboardInterrupt.org"

/*
* Global Variables
*/
int status = WL_IDLE_STATUS;

/*
* Functions
*/
void setLampToRed()
{
    digitalWrite(LED_RED, HIGH);
    digitalWrite(LED_GREEN, LOW);
    digitalWrite(LED_BLUE, LOW);
}

void setLampToBlue()
{
    digitalWrite(LED_BLUE, HIGH);
    digitalWrite(LED_GREEN, LOW);
    digitalWrite(LED_RED, LOW);
}

void setLampToGreen()
{
    digitalWrite(LED_GREEN, HIGH);
    digitalWrite(LED_BLUE, LOW);
    digitalWrite(LED_RED, LOW);
}

void setLampToWhite()
{
    digitalWrite(LED_GREEN, HIGH);
    digitalWrite(LED_BLUE, HIGH);
    digitalWrite(LED_RED, HIGH);
}

void setLampToBlack()
{
    digitalWrite(LED_GREEN, LOW);
    digitalWrite(LED_BLUE, LOW);
    digitalWrite(LED_RED, LOW);
}

/*
* Init
*/
void setup()
{
    Serial.begin(115200);
    Serial.println(__product__);
    Serial.println(__version__);
    Serial.println(__author__);
    Serial.println((String)__copyright__ + "\n");
    pinMode(LED_RED, OUTPUT);
    pinMode(LED_BLUE, OUTPUT);
    pinMode(LED_GREEN, OUTPUT);
    while (status != WL_CONNECTED)
    {
        Serial.println((String)__product__ + " attempting to connect to Wifi network, WIFI_SSID: " + (String)WIFI_SSID);
        status = WiFi.begin(WIFI_SSID, WIFI_PASSWORD);
        #ifdef CONNECTION_INDICATOR
        setLampToRed();
        delay(2000);
        setLampToBlue();
        delay(2000);
        setLampToGreen();
        delay(2000);
        #else
        delay(6000);
        #endif
    };
    #ifdef CONNECTION_INDICATOR
    for (int i = 0; i <= 3; i++)
    {
        setLampToGreen();
        delay(250);
        setLampToBlack();
        delay(250);
    };
    #endif
    Serial.println((String)__product__ + " connected to network");
}

void loop()
{
    if ((WiFi.status() == WL_CONNECTED))
    {
        HTTPClient http;
        http.begin(ROCKETBEANS_API);
        int httpCode = http.GET();
        if (httpCode > 0)
        {
            const size_t bufferSize = JSON_ARRAY_SIZE(5) + 5 * JSON_OBJECT_SIZE(14) + 2970;
            DynamicJsonBuffer jsonBuffer(bufferSize);
            String payload = http.getString();
            JsonArray &root = jsonBuffer.parseArray(payload);
            if (!root.success())
            {
                Serial.println((String)__product__ + " parsing failed.");
                Serial.println(payload);
            }
            /* When current show is live */
            if (root[0]["isLive"] == 1)
                setLampToRed();
            /* When current show is new */
            else if (root[0]["isNew"] == 1)
                setLampToBlue();
            /* When current show is playback */
            else
                setLampToWhite();
        }
        else
        {
            Serial.println((String)__product__ + " error on HTTP request");
        }
        http.end();
    }
    delay(60000);
}

Result

After working nearly ten hours on the project I would say it was a lot of fun.

If you have any questions or improvements, let me know in the comments. 🙂

Portable HDMI Screen Using Your Smartphone

Portable HDMI Screen Using Your Smartphone

When it comes to Raspberry Pies, it’s all fun and games until one of them doesn’t boot anymore. If you are lucky, you can attach your TV or a Computer screen to the HDMI or Composite port and watch the boot messages. My recent routine got a little uncomfortable as I tend to have my devices in a stacking enclosure and cannot carry them to the TV. It now mostly got like this:

  1. Power off the problematic Raspberry Pi
  2. Power off the Hyperion (Ambilight-Clone) Raspberry Pi attached behind the TV
  3. Change the cabling of the TV to output Hyperion’s boot messages.
  4. Swap the SD cards
  5. Power it up and watch the boot messages of the problematic Raspberry Pi’s SD Card in the Hyperion Pi
  6. Write it down
  7. Try to fix it
  8. Swap & repeat

Wow. This takes a lot of time and only works with the same hardware. This was no permanent solution anymore. But where can I get a small HDMI-capable screen, ideally battery-powered and cheap? Everything I found were some DVD-Players with HDMI input, starting at around 100 European bucks or car reverse cameras at 12 Volts with open-ended cables or lighter plugs. Both are weird and too expensive. But wait a second: While Ebaying for these devices I noticed I actually am staring at such a screen right now and it’s even battery-powered!

DIY HDMI Screen

Alright, we can’t have it completely free, but we don’t need a new display-and-battery whatever. Look at this:

Different USB video grabbers
Different USB video grabbers

I recently found a very cheap HDMI USB grabber on Ebay. If you already built an Ambilight clone, you may recognize the hardware on the center and right hand side. They are a combination of a (2) Composite USB Grabber and a (3) HDMI to Composite Converter, which – in combination – resemble a pretty ugly HDMI USB Grabber. The Converter even needs extra power. That’s not a good portable solution. Also the quality of the image sucks, because it is a digital signal converted to 480p analog video and then back to HDMI (still at 480p, because the pixels are lost forever).

There is an (1) All-In-One solution available now. Unfortunately it doesn’t have better specs, but it does not need extra power and is a lot smaller and handier. I took it apart and it uses almost the same circuitry as the combination of (2) and (3). It even converts it down to analog and back to digital, but without the signal loss of the connectors and cables:

USB HDMI grabber disassembled
USB HDMI grabber disassembled

Some of the chips are unmarked. I appreciate any identification hints.

If you don’t use a tablet with an USB A port, you might also need a microUSB to USB A (or USB C to USB A, if you are not using a 2014 phone) adapter. They are very cheap and (at least in the case of Micro-USB) only consist of wire traces without any logic or ICs. Some of mine look like this:

Smartphone, USB OTG Adapter, USB HDMI Grabber
Smartphone, USB OTG Adapter, USB HDMI Grabber
Smartphone, USB OTG Adapter, USB HDMI Grabber
Smartphone, USB OTG Adapter, USB HDMI Grabber
USB OTG Adapter, USB HDMI Grabber plugged into the Smartphone
USB OTG Adapter, USB HDMI Grabber plugged into the Smartphone

 

Fits acceptably. Now we still need a piece of software to view the grabber’s video input. All of my tested grabbers use EasyCap-compatible chipsets. The modules are usually included in the linux-firmware and work with any Video-For-Linux compatible software. If you use an Android smartphone there are multiple apps that work just fine. I chose “USB Camera” by ShenYao China which is free but shows banner advertisements.

Let’s plug in a Raspberry Pi and start it up:

The video quality is pretty bad. I recommend to reduce the default HDMI resolution of the Raspi to something lower to increase readability. After that, and with the pinch-to-zoom functionality of the app, I can now use it for debugging misbehaving hardware without the hassle of disconnecting and moving stuff. Great!

I also switched to this USB HDMI Grabber for my Hyperion-based Ambilight clone. It produces absolutely no green flashes (grabber dropouts) and saves a lot of space behind the TV. Do you have any other applications in mind? Let me know in the comments below!

Links

USB HDMI Grabber

MicroUSB OTG Adapter

Raspberry Pi Docs

Google Play Store Apps