Tower Status Light (On Air light) 🚦

'Working from home' during the COVID pandemic has created new challenges for many of us. I thought it would be a good idea to have a status light to indicate when I am on a call to reduce the chances of being interrupted.

Tower Status Light (On Air light) 🚦

I worked as an Engineer in my previous life, and I frequently saw 'tower status lights' on industrial equipment to indicate if they were working or had thrown faults. It's an easy way to spot from across a factory floor if something needs attention. You might have even see one on a self checkout in your local store.

'Working from home' during the COVID pandemic has created new challenges for many of us. I thought it would be a good idea to have a status light to indicate when I am on a call to reduce the chances of being interrupted... Do you remember this hilarious interview?

Children interrupt BBC News interview - BBC News
Please subscribe To our Channel HERE http://bit.ly/1rbfUog There was an unexpected distraction for Professor Robert Kelly when he was being interviewed live ...

There have been a few iterations of similar 'On Air' lights popping up recently as well, such as this one (there are loads of examples on Thingiverse - the theory is the same, just a different 3D printed device!);

On Air Sign [IoT] for Twitch by adafruit
In this project, we’re building an internet connected On Air Sign. It will light up whenever your favorite Twitch channel start streaming. You can use this as visual notification or if you’re a broadcaster, this will let the people around know to be courteous!It’s uses an Adafruit HUZZAH with ESP82…

The Tower

I decided to go for a tower style design. I started off by creating a design in Fusion 360 that would be smaller than a typical status light, but wide enough to fit a WS2812 circular LED matrix. You will see later that I might have over-done it a bit for the LED matrix...

The design I settled on is below, all 3D printed with PLA (with 'transparent' PLA in for the lens section). The parts all snap together.

Fusion360 Design for a status light

The WS2812 (aka 'neopixel') is connected directly to an ESP8266. I chose a 7-bit module for the tower. This gives full RGB, and as I'm sure you can guess if you have read my other posts, it can be controlled using MQTT via Home Assistant!

ESP8266 Code

The following code can be used with the Arduino IDE and uploaded to the ESP8266. You can add other code in here too, so the ESP does more than one job. The node I have connected also has a DHT22 to monitor temperature and humidity in the lab.

//
//  NODETWR - Lab Tower Light
//
//

#include <ESP8266WiFi.h>
#include <PubSubClient.h>
#include <ArduinoJson.h>
#include <Adafruit_NeoPixel.h>
#ifdef __AVR__
#include <avr/power.h> // Required for 16 MHz Adafruit Trinket
#endif

const int BUFFER_SIZE = 300;

// RGB LED
#define LEDs D2 // LED Module connected to D2
#define NUMPIXELS 7 // Number of BITs on the NeoPixel

Adafruit_NeoPixel pixels(NUMPIXELS, LEDs, NEO_GRB + NEO_KHZ800);

byte red = 255;
byte green = 255;
byte blue = 255;
byte brightness = 255;

byte realRed = 0;
byte realGreen = 0;
byte realBlue = 0;

bool stateOn = false;

const char* on_cmd = "ON";
const char* off_cmd = "OFF";

//Constants for WiFi and MQTT
const char* ssid = "CHANGE_ME";
const char* password = "CHANGE_ME";
const char* mqtt_server = "CHANGE_ME";
const char* mqttUser = "CHANGE_ME";
const char* mqttPassword = "CHANGE_ME";

WiFiClient espClient;
PubSubClient client(espClient);

//Vars for timing loop
unsigned long interval = 500; // the time we need to wait
unsigned long intervalLong = 60000; // update mqtt topics every minute
unsigned long now = 0; // init time
unsigned long timeOld = 0;
unsigned long timeVeryOld = 0;


//Requirements for WiFi and MQTT
void setup_wifi() {

  delay(10);
  Serial.println();
  Serial.print("Connecting to ");
  Serial.println(ssid);

  WiFi.enableAP(0);
  WiFi.begin(ssid, password);

  while (WiFi.status() != WL_CONNECTED) {
    delay(500);
    Serial.print(".");
  }

  randomSeed(micros());

  Serial.println("");
  Serial.println("WiFi connected");
  Serial.println("IP address: ");
  Serial.println(WiFi.localIP());
}

void reconnect() {
  // Loop until we're reconnected
  while (!client.connected()) {
    Serial.print("Attempting MQTT connection...");
    // Create a random client ID
    String clientId = "TowerNode-";
    clientId += String(random(0xffff), HEX);
    // Attempt to connect
    if (client.connect(clientId.c_str(), mqttUser, mqttPassword)) {
      Serial.println("connected");
      // Once connected, publish an announcement...
      digitalWrite(2, LOW);   // Turn the LED on by making the voltage LOW
      delay(1000);            // Wait for a second
      digitalWrite(2, HIGH);   // Turn the LED off by making the voltage HIGH
      delay(1000);
      digitalWrite(2, LOW);   // Turn the LED on by making the voltage LOW
      delay(1000);            // Wait for a second
      digitalWrite(2, HIGH);   // Turn the LED off by making the voltage HIGH
      delay(1000);
      client.publish("nodeTwr/avail", "online");
      client.subscribe("nodeTwr/lightset");

    } else {

      Serial.print("failed, rc=");
      Serial.print(client.state());
      Serial.println(" try again in 5 seconds");
      digitalWrite(2, LOW);   // Turn the LED on by making the voltage LOW
      delay(1000);            // Wait for a second
      digitalWrite(2, HIGH);   // Turn the LED off by making the voltage HIGH
      delay(1000);
      digitalWrite(2, LOW);   // Turn the LED on by making the voltage LOW
      delay(1000);            // Wait for a second
      digitalWrite(2, HIGH);   // Turn the LED off by making the voltage HIGH
      delay(1000);
      digitalWrite(2, LOW);   // Turn the LED on by making the voltage LOW
      delay(1000);            // Wait for a second
      digitalWrite(2, HIGH);   // Turn the LED off by making the voltage HIGH
      delay(1000);
      // Wait 5 seconds before retrying
      delay(5000);
    }
  }
}
// Get MQTT messages in topic 'nodeTwr/lightset'
void callback(char* topic, byte* payload, unsigned int length) {

  String payloadData;             // we start, assuming open
  String topicData;

  Serial.print("Message arrived in topic: ");
  Serial.println(topic);
  topicData = String(topic).c_str();
  Serial.print("Message:");

  char message[length + 1];
  for (int i = 0; i < length; i++) {
    message[i] = (char)payload[i];
  }
  message[length] = '\0';
  Serial.println(message);

  if (topicData == "nodeTwr/lightset") {

    if (!processJson(message)) {
      return;
    }

    if (stateOn) {
      // Update lights
      realRed = map(red, 0, 255, 0, brightness);
      realGreen = map(green, 0, 255, 0, brightness);
      realBlue = map(blue, 0, 255, 0, brightness);
    }
    else {
      realRed = 0;
      realGreen = 0;
      realBlue = 0;
    }

    pixels.clear(); // clear the pixel buffer
    setColor(realRed, realGreen, realBlue); // set the pixel buffer 
    pixels.setBrightness(brightness); // set the pixel brightness
    pixels.show(); // send to the pixels
    sendState(); // send feedback to HA about new state

  }

  Serial.println();
  Serial.println("-----------------------");
}

bool processJson(char* message) {
  StaticJsonBuffer<BUFFER_SIZE> jsonBuffer;

  JsonObject& root = jsonBuffer.parseObject(message);

  if (!root.success()) {
    Serial.println("parseObject() failed");
    return false;
  }

  if (root.containsKey("state")) {
    if (strcmp(root["state"], on_cmd) == 0) {
      stateOn = true;
    }
    else if (strcmp(root["state"], off_cmd) == 0) {
      stateOn = false;
    }
  }

  if (root.containsKey("color")) {
    red = root["color"]["r"];
    green = root["color"]["g"];
    blue = root["color"]["b"];
  }

  if (root.containsKey("brightness")) {
    brightness = root["brightness"];
  }

  return true;
}

void setColor(int inR, int inG, int inB) {

  for (int i = 0; i < NUMPIXELS; i++)
  {
    pixels.setPixelColor(i, inR, inG, inB);
  }

  Serial.println("Setting LEDs:");
  Serial.print("r: ");
  Serial.print(inR);
  Serial.print(", g: ");
  Serial.print(inG);
  Serial.print(", b: ");
  Serial.println(inB);
}

// This sends feedback to Home Assistant to inform it of the new light state, brightness and RGB colour
void sendState() {
  StaticJsonBuffer<BUFFER_SIZE> jsonBuffer;

  JsonObject& root = jsonBuffer.createObject();

  root["state"] = (stateOn) ? on_cmd : off_cmd;
  JsonObject& color = root.createNestedObject("color");
  color["r"] = red;
  color["g"] = green;
  color["b"] = blue;

  root["brightness"] = brightness;

  char buffer[root.measureLength() + 1];
  root.printTo(buffer, sizeof(buffer));

  Serial.println(buffer);
  client.publish("nodeTwr/sendstate", buffer);

}

void setup() {
  pixels.begin();

  timeOld = millis();
  Serial.begin(115200);
  delay(1000);
  Serial.print("Start time: ");
  Serial.println(timeOld);
  setup_wifi();
  client.setServer(mqtt_server, 1883);
  client.setCallback(callback);

  pinMode(2, OUTPUT);     // Initialize GPIO2 pin as an output (for the integrated LED flash during setup)
  pinMode(LEDs, OUTPUT); // set the digital pin as output.

  setColor(0, 0, 0);

}

void loop() {
  unsigned long now = millis(); // grab current time

  //Ensures MQTT client is connected
  {
    if (!client.connected()) {
      reconnect();
    }
    client.loop();

    //Timer
    if ((unsigned long)(now - timeOld) >= interval) {
      Serial.print("time is now: ");
      Serial.println(now);
      timeOld = millis();
	
    //this routine runs every minute to let home assistant know the node is still alive
      if ((unsigned long)(now - timeVeryOld) >= intervalLong) {
        Serial.println();
        Serial.println("60 Second MQTT Update running");
        client.publish("nodeTwr/avail", "online");
        Serial.println("...complete");
        timeVeryOld = millis();
        sendState();
      }

    }
  }
}
ESP8266 Code 'NodeTwr'

To add this to Home Assistant, you will need to add some config code (and a working MQTT broker;

sensor: 
  - platform: mqtt
    state_topic: 'nodeTwr/avail'
    name: 'NodeMCU4 Availability'
    expire_after: 150
    
light:
  - platform: mqtt
    schema: json
    name: "Tower Light"  
    state_topic: "nodeTwr/sendstate"  
    command_topic: "nodeTwr/lightset"  
    brightness: true  
    rgb: true  
    optimistic: false  
    qos: 0  
Home Assistant Config Code

Result & upgrades

The end product is simple but works very well.
If the light is red, I'm on a call. If it's amber, it's a webinar (and I can probably be interrupted). I set the light manually using my desk macro keyboard - another post to follow for this.

There are a few ways this project could be improved with automation. These could be triggered by Home Assistant, or possibly another node or script.

The light colour could be changed based on different events, for example...

  • UV or pollen meter as a reminder for sun-cream or anti-histamines
  • Continuous integration build status (see below!)
  • Screen time reminder (red = time's up!)
  • Automated call status (Teams / Webex integration)
  • Email inbox indicator
Create a Jenkins build status light — The MagPi magazine
Jenkins is a great tool to monitor code quality. For greater visibility of the build status, you can build an awesome tower light that uses it

Did you build this? Or have any comments? Get in touch: hi@xga.ie