Category: Smart Home

Living Room Forced-Airflow Old-Laptop-Rack Cabinet Build

Living Room Forced-Airflow Old-Laptop-Rack Cabinet Build

Building a Do-It-Yourself server rack, with old laptops as servers, airflow optimization, network and power distribution and low-noise ventilation for use in a living room while completely blending in and looking like a regular cabinet.

Introduction

What a title! Honestly – this is kind of hard to describe but you will get the idea. A few years ago I ditched my Chef-driven, hand-cooked Raspberry Pi farm and went for a more modern solution with less configuration. I wasted so much time trying to automate the software installation, OS maintenance and monitoring everything and yet couldn’t perform all the upgrades in time. It was terrible. So I moved to the everything is containerized operating system called “Rancher OS” and stacked those dusty old laptops I had lying around to a 2-server “Rack”.

Fast-forward 2 years – everything is running fine and the whole building and shipping happen on my workstation. No in-depth automation needed. When in use, the laptops got very hot and even switched themselves off as last resort. Even though the cabinet has no back wall there was just no good airflow. Leaving the door open did not help as well. Time for a better solution.

IMG_20180913_191835-e156f63b-5500-4aff-b932-50f4d5600bd2.jpg


Old cabinet: Over 29 °C with open back and front, laptops on the floor below, remains of the Raspberry Pi farm on the left

Taking measurements

I already have another one of those cabinets which is almost empty. I will transform this cheapo cabinet into a budget-friendly server cabinet. Before firing up the PC I take some measurements of the cabinet to bring it into my CAD software for further planning.

vlcsnap-2018-10-31-21h46m19s993-abe7d435-127e-4db4-8592-1f340de8af1d.png

3D Scan The Real Cabinet Into The CAD Software

Jk, it’s still 2018 so I have to re-create the cabinet manually using a Computer-Aided Drawing software. Being a complete beginner I heard a lot of good things about Autodesk’s Fusion 360 and already happily use Autodesk’s EAGLE software for PCB design so I will try this one out. They even let you register for the full version if you use it for non-commercial or low-volume purposes (Start-Up License) without any limits.

3D-CAD/CAM-Software für das Produktdesign | Fusion 360

Untitled-1c8b1625-d21d-465c-9f7f-1ee3bf0df830.png


First steps in Fusion 360: Creating the outer shell of the cabinet

Getting around in Fusion 360 is very easy and I did not need any tutorials or help. I’m totally amazed how easy it was. Autodesk has a very very in-depth tutorial series about absolutely everything, so if you feel like it, check it out on the Fusion 360 product page.

I will not cover every step of the creation of the 3D model. If you are interested in how I did the things I did or miss a step, you can inspect, download and change my project, which I released under an open-source license for you.

Server Cabinet

Untitled-30293efc-ba82-4669-a16f-665d67446cef.png


“Extruded” wooden block from the sketch above. Those are the outer measurements of the original cabinet

Untitled-108659ef-eaf0-4553-b59d-61649cf8e6b9.png


“Hollowed” the block and added a back wall. Thickness of the wood matches the original cabinet

Creation Of The Custom Back Wall And Cabinet Floors

Untitled-ebd6c0b3-c680-462a-ad8d-0d46b134e02b.png


Cutouts for the air inlet. Those have the size of 3 140×140 computer fan filters (only the filters) to reduce dust contamination

Untitled-a8c9e0a4-2e51-4e54-902c-c3f8e40112d0.png


Same for the fan air outlets on top and a cable inlet hole right above the air inlet

Alright. The basics of a server cabinet are there. Let’s now get to the really interesting part: Custom cabinet floors for each laptop model. To get the maximum cooling efficiency I will force the air through every air inlet of the laptops. There will be no chance of any heat jam in the cabinet itself and hot, used laptop air will be transported out of the cabinet right away. I have never seen something like this so let’s give it a try.

Untitled-19e0f0d4-7e23-45fc-9fb4-35ab2420a3d7.png
render-c6d1ca6b-d0d3-4f83-b401-73daa18fbf4b.png


Slightly outdated 3D render

Looks good to me. Now let’s go buy some wood. In Fusion 360 you can generate a drawing from your model. All the measurements are already known so you never have to type anything manually. Awesome. Let’s just put down some drawings and take these to the hardware store.

Untitled-c0feb212-08a6-48fc-9c45-9707ecaee64a.png


I did this for every new part I want to build. The only interesting measurements right now are the ones marked in red. I buy wood with the right thickness and dimensions and do all the cutouts with saws later by myself.

Now that I got myself some wood. Erm. Let’s just start doing the cutouts.

IMG_20180906_184719-ac9ef59b-aad2-4a5c-a340-f3dcd92f37c8.jpg


I use the jigsaw for all the rectangular and the three big fan outlet cutouts and a holesaw for the smaller, round ones.

20180903_193349-dcfc49f6-593d-46cb-b01c-8934b91d25e9.jpg
IMG_20180911_213322-7885416d-63d2-4b2f-b478-cbf8a9e991a8.jpg
IMG_20180911_213417-7813060e-b8fb-4fd2-acab-a8f2a42ca56a.jpg


The cable inlet gets an especially tight fit

IMG_20180911_215004-681abc7d-3e55-4b4b-b6c0-6683678e20df.jpg


I use some duct tape to fix the dust filters from the outer side to be able to easily dust it off if needed

IMG_20180911_220241-1f22e94c-617a-4b55-bd50-f0565a539649.jpg
IMG_20180911_220511-31445a39-278d-437c-8e74-7e04380433e6.jpg
IMG_20180909_144259-734f48ea-5596-45cc-9bfc-06039dd22bfb.jpg


After drilling new holes at the correct height for the cabinet floors, I can test if it fits. In case you wonder about that additional tiny hole in the back wall – The power connector of one laptop is in an awkward position and I have no chance to connect it from inside the cabinet. Looks weird, works well.

Adding Peripherals

IMG_20180911_200912-62041efb-9c59-4f25-b1e3-2359ce5b8742.jpg


Yes, I went shopping again.

I bought some peripherals to give the cabinet some life. The Amazon links below are affiliate links and you support me by clicking them and buying from there. Thanks for your support!

ARCTIC F14 PWM PST CO – 140 mm PWM PST Gehäuselüfter für Dauerbetrieb | Case Fan mit PST-Anschluss (PWM Sharing Technology) + Doppelkugellager | Reguliert RPM synchron

Novaato 2x Metall Kabeldurchführungen 60 mm Kabeldurchlass mit Bürste für mehr Ordnung auf dem Schreibtisch

ARCTIC Fan Grill – Lüfterabdeckung aus Stahl für 140 mm Lüfter I Lüftergitter Luftstrom-Durchlässig I Erhältlich in unterschiedlichen Größen

140mm PVC schwarze Computer PC Kühler Lüfter Lüftergitter Staubfilter Filtermatte Gehäuselüfter, 10 Stück

D-Link DGS-108 8-Port Layer2 Gigabit Switch (bis zu 2000 Mbit/s Datenübertragung pro Port, Non-Blocking-Architektur, lüfterlos, Metallgehäuse) schwarz

Brennenstuhl Eco-Line 6-fach Steckdosenleiste (mit Überspannungsschutz, Steckerleiste, Kindersicherung, Schalter und 5 m Kabel) anthrazit

The fans are especially interesting, because they are optimized for 24/7 operation and can be daisy-chained.

IMG_20180911_202405-71e34457-b51c-4faa-91c1-aae849080a1c.jpg
IMG_20180911_202937-082e6719-65ed-470a-968a-a98fb6fac7ba.jpg
IMG_20180911_203046_Bokeh-f0913180-e5a5-4abb-b78a-f6d6a3841f06.jpg
IMG_20180912_192852-bf45d37d-d786-4cf8-bc1d-b62f689b264d.jpg


After drilling holes for the fans, I use screws and nuts to fix the fan on the inside and the fan grill on the outside

IMG_20180912_192743_Bokeh-fe3eebd0-ba37-4a8e-9504-96b5cffdd907.jpg
IMG_20180912_201436-21d09be0-1fce-4e3f-bac4-06185bc069c5.jpg


Daisy-chained fans on the back wall and the turned-around cabinet behind it

IMG_20180912_202538-9b6cf1c8-d68f-4b43-a477-97e6c3616081.jpg


Nailed it

IMG_20180912_203127-dbd1f830-1a83-4fa0-954a-479bfae3af4c.jpg
IMG_20180913_182333-3d555208-31a5-4d26-96e3-a8a46bba1043.jpg


Add power and network

The network cable comes directly from the central switch. The 12 V fans are powered by a 9 V power supply to keep the noise low. The power comes from a discrete output of a UPS which can supply the whole cabinet (together with the rest of the infrastructure) for about 20 minutes allowing for a clean shutdown for all servers.

https://hub.docker.com/r/gersilex/apcupsd

My Docker image to shut down the Docker host from an APCUPSd container

APC Back-UPS BX – Unterbrechungsfreie Stromversorgung 700VA, BX700U-GR (AVR, 4 Schuko Ausgänge, USB) schwarz

Amazon affiliate link: The UPS I use, for a clean power output to the infrastructure. Worth it.

Alright. Now I have a stable network and power connection and I can even hear the air being pulled inside when closing the door. I think it pulls a lot of air through the cracks of that cheap cabinet.

IMG_20180913_182643-13d97aec-4c61-40a1-a826-41565b8a8de9.jpg
IMG_20180913_182650-23e01d34-32bf-40a2-b918-075883ddafe0.jpg
IMG_20180913_182637-ed76d404-7328-42ec-ae69-30eabc5ebdee.jpg


I use Door/Window insulation foam band and put it into or behind all the cracks

IMG_20180913_204354-8e10b905-3d2e-48eb-b4e4-56773102f106.jpg
IMG_20180913_183832-237787e2-8d6b-4c6c-aa6e-3a7d87c6cfe4.jpg


Just the usual cabinet. Or is it? It is. Or is it?

Conclusion

I can barely hear the new cabinet. In fact – it’s quieter than before because of the closed system. And thanks to the airflow and the cold air the laptop fans don’t need to spin high at all. Win-win.

IMG_20181101_003710-61a83da4-616d-4c46-a0b3-c0c86dd0a86c.jpg


The precise trusty old Chinese Alecto thermometer knock-off confirms our success. We now have about 1 °C ambient-to-cabinet difference as opposed to the 8 °C from before.

What’s Next?

I expect to add one or two more laptops and maybe some other small electronics. I will test with a pegboard next, which is basically all holes. This will put the directed cooling method I used in contrast with an overall cooling airflow concept. I will definitely report back as soon as I have new information.

I hope you enjoyed my biggest project to date. Have a great one!

Rocketbeans Lamp – Update #1

Rocketbeans Lamp – Update #1

Hey guys,
recently Rocketbeans updated their Website and also updated the API Endpoint for our beautiful lamp.

If you haven’t read about the Smart RGB-LED Lamp before you better take a look at my older Post here.

Changes

  • The API endpoint changed to https://api.rocketbeans.tv/v1/frontend/init
  • Added all possible colors (Cyan, Yellow and Purple)
  • Added error signals
    • Cyan = If JSON parsing failed
    • Purple = If show type not matching [“rerun”, “preview”, “live”]
    • Yellow = If HTTP connection failed
  • Added a switch to enable or disable error signals
  • I needed to dig deeper into the JSON to get the current show type

Code

As usual you are able to watch or star my Github repository to keep updated.

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

/*
* Coniguration
** CONNECTION_INDICATOR - blinking led during startup when defined
** ERROR_INDICATOR - blinking led sequence when parsing or http error occured
*/
#define CONNECTION_INDICATOR
#define ERROR_INDICATOR
#define LED_RED GPIO_NUM_14
#define LED_GREEN GPIO_NUM_27
#define LED_BLUE GPIO_NUM_26

#define WIFI_SSID "Your Wifi"
#define WIFI_PASSWORD "Your Wifi Password"
#define ROCKETBEANS_API "https://api.rocketbeans.tv/v1/frontend/init"

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

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

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

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

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

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

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

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

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

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

/*
* 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(4) + JSON_ARRAY_SIZE(8) + 2 * JSON_OBJECT_SIZE(2) + 10 * JSON_OBJECT_SIZE(3) + 5 * JSON_OBJECT_SIZE(4) + JSON_OBJECT_SIZE(8) + 1190;
            DynamicJsonBuffer jsonBuffer(bufferSize);
            String payload = http.getString();
            JsonObject &root = jsonBuffer.parseObject(payload);
            JsonObject &data = root["data"];
            JsonObject &data_streamInfo = data["streamInfo"];
            JsonObject &data_streamInfo_info = data_streamInfo["info"];
            String data_streamInfo_info_type = data_streamInfo_info["type"].as<String>();

            if (!root.success())
            {
                Serial.println((String)__product__ + " parsing failed.");
                Serial.println(payload);
                #ifdef ERROR_INDICATOR
                for (int i = 0; i <= 6; i++)
                {
                    for (int i = 0; i <= 3; i++)
                    {
                        setLampToCyan();
                        delay(100);
                        setLampToBlack();
                        delay(100);
                    };
                    delay(500);
                }
                #endif
                setLampToCyan();
            } else
            {
                /* When current show is live */
                if (data_streamInfo_info_type == "live")
                    setLampToRed();
                /* When current show is new */
                else if (data_streamInfo_info_type == "premiere")
                    setLampToBlue();
                /* When current show is playback */
                else if (data_streamInfo_info_type == "rerun")
                    setLampToWhite();
                /* Graceful handling if no type matched */
                else {
                    Serial.println((String)__product__ + " unsupported show type -> "+ data_streamInfo_info_type +".");
                    #ifdef ERROR_INDICATOR
                    for (int i = 0; i <= 3; i++)
                    {
                        for (int i = 0; i <= 3; i++)
                        {
                            setLampToPurple();
                            delay(100);
                            setLampToBlack();
                            delay(100);
                        };
                        delay(500);
                    }
                    #endif
                    setLampToPurple();
                }
            }
        }
        else
        {
            Serial.println((String)__product__ + " error on HTTP request");
            #ifdef ERROR_INDICATOR
            for (int i = 0; i <= 3; i++)
            {
                for (int i = 0; i <= 3; i++)
                {
                    setLampToRed();
                    delay(100);
                    setLampToBlack();
                    delay(100);
                    setLampToYellow();
                    delay(100);
                    setLampToBlack();
                    delay(100);
                };
                delay(500);
            }
            #endif
            setLampToYellow();
        }
        http.end();
    }
    delay(60000);
}
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. 🙂

Save your SD cards: Raspberry Pi on a network file system

Save your SD cards: Raspberry Pi on a network file system

If you work a lot with your Raspberry Pi, you probably have burned one or another SD card. While SD flash cards are great for storing photos or music, they suck at being written to very often. This is exactly the case if you run a whole operating system on them. Hundreds of thousands of read and write operations wear your SD cards out and if you do not have a solid backup concept you have probably lost some data, too.

In my home, a couple of systems are running on the cheap, embedded hardware by the Raspberry Pi Foundation because they are both small and power-saving at a very affordable price point. And so are the accessories like cases and add-on-boards. At the time of writing, these are the jobs that are done by my Raspberries:

  • SSH server (as gateway and tunnel from the internet into the LAN)
  • Monitoring
  • OpenHAB smart home controller
  • TV Server
  • Backup
  • Ambilight

 

When living alone, having all this run on SD card-based Raspberries is fine and you know what to do when something breaks. As soon as you have a partner to share your home with, you probably need to agree on an SLA to ensure a high WAF:

SLA (Service Level Agreement):

If you want to have a smart home controlled by a computer, you have a very short acceptable “TTR” if something breaks. Our Time-To-Recover is usually as short as 1 day. Honestly, it is more like same-day. Do not go to sleep until it is fixed.

WAF (Wife Acceptance Factor):

If you stick to the SLA, you are free to do all this crazy stuff; your partner accepts it and maybe even likes it.

 

And that is basically the reason I am writing this. It is completely OK if the ambilight stops working, it is also acceptable if the backups do not run for some days. But if the smart home controller fails, … well, you better have a backup plan. You do not want to deal with someone who spent two hours trying to turn the lights up or the television on and ultimately failed because it simply does not work anymore without the central controlling instance.

So we should reduce these outages to a minimum:

The Objective

  • Increase device uptime / reduce device outages
  • save data on a central storage for easy backups or snapshots

 

There are so many other things that can fail. The processor may overheat and hang, forcing a reboot by the watchdog, the power consumption could be too high and disable the USB bus (including the Ethernet Port), the cheap phone charger you use for powering may fail as well, but the single biggest problem is – without a doubt – the SD card.
So today I am showing you how simple it is to boot and run your Pi completely from the network.

 

The Requirements

  • always-on network storage with NFS server software
  • stable network connection to the storage (wired whenever possible)
  • 16+ MB SD card for the Raspberry Pi bootloader and config file that points to your network storage
    (Note: Starting with the Raspberry Pi 3 there are ways to modify the internal bootloader to boot from network without an SD card. I am not going to cover this here; this method will work with all the Raspberry Pi versions)
  • 3 beers of time

Everything I do is based on a Linux computer (I use Arch Linux and Manjaro) but you can also do this on OS X and maybe on Windows with a lot of tools. I really recommend you to use a Linux computer or a linux VM.

The Network Storage

You need a network storage capable of running an nfs-server. You can even use a Raspberry Pi as sever but that would not really solve the problem here, would it? 😉

Ensure the NFS service is globally enabled

Most commercial NAS systems support NFS and so does my Seagate BlackArmor NAS 4000. I highly recommend to create a seperate share for the filesystems of your Raspberries to be able to add stricter permissions later.

You may need to enable the NFS service first, because a lot of people do not need it and use CIFS only.

Create a share where you will put the filesystems for your Raspis

The resulting config, that my NAS generates, looks like this:

~ $ grep rootfs /etc/exports
/nfs/rootfs *(rw,async,insecure,no_root_squash,no_subtree_check)

Please note that the options rw, no_root_squash and no_subtree_check are obligatory for what we want to achieve. For a detailed description of all possible options, please consult the man page on your system or here.

 

The Extraction Of The SD Card Image Content

You have your NFS Server up and running and configured.

I already stumbled across some distributions of Raspberry Pi SD card images, that depend on and wait for partitions on the local sd card – even if you boot them from NFS. If you see your Raspberry Pi failing at boot, because it waited for a local partition or device for too long, simply flash the sd card image onto that sd card like you would normally. Then, change the settings to have it boot over NFS. Your local SD card still will not be touched, but the boot scripts and dependencies will work now.

This topic can be split into two subtopics: Using a freshly downloaded image to start from scratch, or converting a normal SD-card-based Raspberry Pi to a NFS-based one. Skip to Convert SD-Card-Based Raspberry Pi To NFS if you already have an SD card image, which you want to convert.

 

Create A RootFS From Scratch (New Download)

Now you can download any SD card image like you would do normally. But instead of flashing it onto an SD card, you will mount it and copy the content to your NFS share.

In this example I will download the official Raspberry Pi Foundation Raspbian Stretch Lite Image over the Torrent protocol on my Manjaro Linux machine.

[[email protected] Downloads]$ transmission-cli https://downloads.raspberrypi.org/raspbian_lite_latest.torrent
2017-09-07-raspbian-stretch-lite.zip: State changed from "Incomplete" to "Complete"

Be nice and give something back to the torrent community by leaving this open seeding (uploading) for the other downloaders. Meanwhile in another terminal window:

Check the checksum to check if the data was downloaded successfully and was not modified:

[[email protected] Downloads]$ sha256sum 2017-09-07-raspbian-stretch-lite.zip 
bd2c04b94154c9804cc1f3069d15e984c927b750056dd86b9d86a0ad4be97f12 2017-09-07-raspbian-stretch-lite.zip

The checksum equals the checksum from the website. Great! Let’s unpack it and have a look inside the partition table. It is a complete device image, so it contains everything from (or for) the SD card, including partition table.

[[email protected] Downloads]$ unzip 2017-09-07-raspbian-stretch-lite.zip 
Archive: 2017-09-07-raspbian-stretch-lite.zip
 inflating: 2017-09-07-raspbian-stretch-lite.img
[[email protected] Downloads]$ fdisk -l 2017-09-07-raspbian-stretch-lite.img 
Disk 2017-09-07-raspbian-stretch-lite.img: 1.7 GiB, 1854590976 bytes, 3622248 sectors
Units: sectors of 1 * 512 = 512 bytes
Sector size (logical/physical): 512 bytes / 512 bytes
                                ^^^-----<<< write this down! <<<-------
I/O size (minimum/optimal): 512 bytes / 512 bytes
Disklabel type: dos
Disk identifier: 0x11eccc69

Device                           Boot Start  End      Sectors Size Id Type
2017-09-07-raspbian-stretch-lite.img1 8192   93813    85622   41.8M c W95 FAT32 (LBA)
2017-09-07-raspbian-stretch-lite.img2 94208  3622247  3528040 1.7G 83 Linux
                                      ^^^^^-----<<< and this! <<<-------

In the output of fdisk -l you see some important information. As we are not interested in putting the boot partition onto our NFS server (but the rootfs partition, the second partition) instead, we need to know the offset where the interesting partition starts. To get this offset you simply multiply the sector size (usually always 512 bytes) with the start sector of the second partition (both are marked above).

[[email protected] Downloads]$ echo $(( 512 * 94208 ))
48234496

Let’s create a loopback device with this information so we can mount it and steal the data from it.

[[email protected] Downloads]$ sudo losetup -f --show -o $((512*94208)) 2017-09-07-raspbian-stretch-lite.img 
/dev/loop1

You need to be root to create loopback devices. -f found us a free loop device, -o took the offset we calculated and –show made sure we get to know which loop device losetup used. Now we can go and mount it like any other partition.

[[email protected] Downloads]$ mkdir /tmp/fresh-pi-rootfs
[[email protected] Downloads]$ sudo mount -v /dev/loop1 /tmp/fresh-pi-rootfs
mount: /dev/loop1 mounted on /tmp/fresh-pi-rootfs.
[[email protected] Downloads]$ cd /tmp/fresh-pi-rootfs
[[email protected] fresh-pi-rootfs]$ ls
bin boot dev etc home lib lost+found media mnt opt proc root run sbin srv sys tmp usr var

Now let’s copy that data to your NFS share. Refer to the code snippet in Convert SD-Card-Based Raspberry Pi To NFS, but copy from /tmp/fresh-pi-rootfs instead of /.

Afterwards, burn the SD card image to your SD card like you would normally. This ensures that you have the proper boot partition (the first partition in the partition table) on your card. If you feel fancy or have a tiny SD card, you can also extract the partition data like we did above and put it onto a fresh FAT partition on the SD card with the same start sector.

 

Convert SD-Card-Based Raspberry Pi To NFS

If you already have a running Raspberry Pi, you probably do not want to rebuild everything from scratch but move your existing data to the network share. All you need to do is to log into your Pi, install rsync (or any other application that reliably copies filesystem attributes like scp), and copy all your stuff over. Do not forget to stop your applications to have them in a defined (stopped) state.

[email protected] ~ $ mkdir /tmp/rootfs
[email protected] ~ $ mount nas:/nfs/rootfs /tmp/rootfs
[email protected] ~ $ rsync -Phax --numeric-ids / /tmp/rootfs/openhab

There are about 31.000 files to copy, so this might take a while. Go brew some coffee or grab a beer.

 

The Configuration

All your data are belong to the NFS share. The last step is to tell the Raspberry Pi to boot from the share instead of the local SD card. One widespread method that works on almost every linux device is to modify the kernel command line. This is usually done in the bootloader (Syslinux,  Grub, U-Boot). The Raspberry Pi got a configurable U-Boot bootloader starting with version 3. In the versions before you have to have a partition on your SD-Card, containing the boot files and a file called “cmdline.txt” that contains the kernel command line. This also works with Version 3 and newer, if you do not want to bother with U-Boot and you are fine with using an SD card for that.

Modify the “cmdline.txt” file on the boot partition. You can do this directly on your Raspberry Pi, or by plugging the SD card into another computer. When using another computer, you’ll find the file on the first Partition of the card, usually called “boot” partition. When editing the file directly on the Pi, it is located at “/boot/cmdline.txt”.

This is an example cmdline.txt I use in production:

dwc_otg.lpm_enable=0 console=serial0,115200 console=tty1 root=/dev/nfs nfsroot=192.168.21.1:/nfs/rootfs/openhab ip=dhcp elevator=deadline

This is basically the default cmdline.txt with additions of the root, nfsroot and ip options. If you want to define a static IP address, use something like

ip=192.168.21.50::192.168.21.1:255.255.255.0:raspberrypi:eth0:off

(ip=<client-IP-number>::<gateway-IP-number>:<netmask>:<client-hostname>:eth0:off)

Please keep in mind, that there must only be exactly one line in the cmdline.txt file. No comments, nothing else. Just one line.

 

The First Boot

Save it, unmount the boot partition (Apple and Microsoft call this “eject securely” or similar), put the SD card into your Pi and fire it up. If you have a screen attached, you will see the communication with the DHCP server and the handover to the NFS filesystem. This usually takes only a second. My boot time increases by 50% which is most likely the case because of my very old and slow NAS. Results may vary.

Congratulations, you just separated hardware from software resources and made your IT a lot more agile. Now, if your Pi explodes, you can just swap it with a new one from you Raspberry-Pi-drawer and be up again in no time. I am not responsible for exploding Pis, though.

 

The Remaining Questions

People asked me about this a lot so maybe this is interesting for you as well:

Q: Wait! What happens, if my NAS crashes, or is offline in some other way?
A: As soon as the NFS server doesn’t answer anymore, your devices will just stop completely. The root filesystem is essential so your Raspberry Pi will just wait until it is reachable again. It will not crash and it won’t reboot or panic. It just waits until your server is back. I think this is quite nice.

 

If you have any other questions, feel free to ask in the comments section below!

 

Read Also

Handcrafted St(r)eam Machine – not as easy as you think

Handcrafted St(r)eam Machine – not as easy as you think

Hey guys,

today I’d like to show you how I build my Steam Machine and why it isn’t as easy as it seems like.

I had the following conditions for the Steam Machine (which are no particularly high requirements):

  • Have to match into the closet
    • width: 50cm
    • height: 30cm
    • depth: 40cm
  • Most bang for bucks – upper limit 1000€

Hardware Parts

I end up choosing the following Parts for the Computing (Server) Part of my Setup:

Nr. Manufacturer Part Price in €
1 Corsair DIMM 16GB DDR4-2133 Kit, RAM 119,9
2 Samsung MZ-75E250B 250 GB, SSD 99,9
3 Fractal Design NODE 304, Case 74,9
4 Sharkoon SilentStorm SFX Gold 500W, PS 79,9
5 Intel Core i5 6500 4x 3.20GHz So.1151 BOX 202,3
6 Asus H110I-PLUS Intel H110 So.1151 Dual Channel DDR4 Mini-ITX Retail 70,34
7 Nvidia 6GB Inno3D GeForce GTX 1060 iChill X3 Aktiv PCIe 3.0 x16 259,00

As Clients, I used two Steam links located in the Bed and also Living Room. They work out of the Box and are able to customize with the Steam link SDK.

Now we miss two parts. Windows 10 disables the Cursor if no Mouse is connected. You are able to install any kind of software to emulate a mouse or plug in a very cheap one.

The second part is a virtual monitor which is very important. Some games try to get the Monitor configuration to set up the resolution. Either you try to install any kind of software to emulate a Monitor or you buy a HDMI Stick which emulates a monitor. You can use e.g. this Model.

Software Part

My Server will be running on Windows 10 because most games in Steam are compatible with Windows. To keep control of the server I use Teamviewer.

Windows 10 need some tweaks to work properly as streaming Machine because e.g. the login keeps appearing after reboot so that Steam isn’t able to start.

Disabling Login Screen on boot up

  • Why?
    • As soon as the Steam machine boot up, steam cannot start because the computer is still locked.
  • How?
    • a. Press [WINDOWS] Button or open start menu
    • b. Enter “netplwiz”
    • c. Remove the checkmark of “Users must enter a username and password to use this computer.”
    • d. Click OK to keep the option

Disabling Lock Screen on Wake on Lan

  • Why?
    • After Waking up the Machine it keeps stuck in a lock screen.
  • How?
    • a. Press [WINDOWS] + [X]
    • b. Select “Command Prompt (admin)”
    • c. Enter the following command
      • powercfg /SETACVALUEINDEX SCHEME_CURRENT SUB_NONE CONSOLELOCK 0

Disabling UAC (User Account Control)

  • Why?
    • The Steam link is not able to work through the UAC when e.g. installing a program.
  • How?
    • First Method:
      • a. Press [WINDOWS] + [X]
      • b. Go to “Control Panel\User Accounts and Family Safety\User Accounts”
      • c. Click on “Change User Account Control settings”
      • d. Move the slider to the bottom to “Never Notify”
      • e. Click ok
    • Second Method:
      • a. Open the Start Menu
      • b. enter ua c and hit [ENTER]
      • c. Go ahead with “First Method” at (d.)
    • Third Method:
      • a. Open Registry Editor (open Start Menu; enter Regedit; hit [ENTER]
      • b. Navigate to “HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows\CurrentVersion\Policies\System”
      • c. Modify the value of the “EnableLUA” DWORD and set it to 0

Putting all together

Does it even perform?

To perform any kind of performance tests I always use Novabench because it’s free with all features included. Here are my results.

Yes. It does! This score is really good in relation to the total costs.

Brief overview of my Homelab

Brief overview of my Homelab

Hey guys,

today I like to tell and also show you something about my home lab including my well-known, not done yet, security system and the not so smart ‘Smart Home’ which obviously becomes smarter as time passes by.

Infrastructure

The infrastructure consists of a Fritzbox Router (which I received from my Internet Service Provider) with a 1GB connection to a Cisco Tier 2 switch (the core switch). Bed- and Livingroom is also equipped with a 1GB switch (of course not cisco :-)). The switches are connected with CAT 7 cables which are jailed into a cable channel.

Security

As I told before my security system consists of two hardware components: an IP Cam and a cable connected motion detector. No siren at all. The motion detector is connected to my Crestron (will be explained later) Smarthome Base into an I/O Port. As soon as the motion detector detects motion and the Security system is armed, a Raspberry Pi receives a xinetd command and executes a script which takes a picture, sends it as mail, and records a video afterward which will be uploaded to my Dropbox account.

Smart Home

My Smarthome stuff contains everything. It starts from Crestron (a commercial solution) and ends with OpenHAB2 (a opensource solution). I started with Crestron as my apprenticeship begun and it only controls my current security system and all infrared devices. OpenHAB2 is way easier and quite cheap to implement different smart home techniques like Z-Wave or Mysensors. At the moment OpenHAB2 control all Z-Wave Devices and it started to take over the infrared devices in the Bedroom.

Where the way leads us to?

I want to completely redesign my security system and exchange Crestron with OpenHAB2.
Appendix

Crestron -> https://www.crestron.com/
OpenHAB2 -> http://docs.openhab.org/
Mysensors -> https://www.mysensors.org/

 

Thanks for reading. See you soon!

 

via Jason Brown and Shawn Douglas (Creative Commons Attribution-NonCommercial license)