Cameron Coward
Published © CC BY-NC-SA

SubscriberBoard

A Home Assistant-connected oversized 7-segment display

IntermediateFull instructions provided4 hours1,367
SubscriberBoard

Things used in this project

Hardware components

Wemos D1 Mini
Espressif Wemos D1 Mini
×1
LED Strip, NeoPixel Digital RGB
LED Strip, NeoPixel Digital RGB
×1
basswood sheet
×1

Software apps and online services

Fusion 360
Autodesk Fusion 360

Hand tools and fabrication machines

Laser cutter (generic)
Laser cutter (generic)
3D Printer (generic)
3D Printer (generic)

Story

Read more

Custom parts and enclosures

Frame Pieces

Shields

Diffusers

Dot Diffusers

Schematics

Front board DXF

Code

Arduino Code

Arduino
The code for the ESP32
#include <FastLED.h>
#include <WiFi.h>
#include <ArduinoHA.h>

#define DATA_PIN 16                                   // the pin we have the LED strip connected to
#define MQTT_SENSOR_DATA_LENGTH_MAX 6                 // the maximum number of characters we will receive in a payload, plus one for null terminator
#define LEDS_PER_SEG 4                                // the nummber of LEDs per segment on the physical display

const int ledCount = (LEDS_PER_SEG * 28) + 2;         // calculate the total number of LEDs, plus two for the time colon dots
CRGB leds[ledCount];                                  // create FastLED strip
bool dotsOn = false;                                  // toggles where the time dots are on or off

WiFiClient client;                                    // for WiFi
HADevice device;                                      // create Home Assistant device
HAMqtt mqtt(client, device);                          // create MQTT thing
byte mac[] = {0x00, 0x12, 0xFB, 0x6E, 0x3E, 0x5B};    // make up a MAC address for networking. Needs to be unique on the network.

String wifiSSID = "ssid";                             // WiFi network SSID
String wifiPass = "password";                         // WiFi password

// each row controls the segements to turn on for that character,
// Order is: start at the bottom left, going counterclockwise and ending with the center segment
const bool display[12][7] = {
  {1, 1, 1, 1, 1, 1, 0},        // 0
  {0, 0, 1, 1, 0, 0, 0},        // 1
  {1, 1, 0, 1, 1, 0, 1},        // 2
  {0, 1, 1, 1, 1, 0, 1},        // 3
  {0, 0, 1, 1, 0, 1, 1},        // 4
  {0, 1, 1, 0, 1, 1, 1},        // 5
  {1, 1, 1, 0, 1, 1, 1},        // 6
  {0, 0, 1, 1, 1, 0, 0},        // 7
  {1, 1, 1, 1, 1, 1, 1},        // 8
  {0, 0, 1, 1, 1, 1, 1},        // 9
  {1, 0, 0, 0, 1, 1, 1},        // F (we only need the F for temperature)
  {0, 0, 0, 0, 0, 0, 0},        // _ (blank, all off)
};

void setup() {
  FastLED.addLeds<NEOPIXEL, DATA_PIN>(leds, 114);       // initialize FastLED strip
  CRGB color = CRGB(127,0,0);                           // start with all LEDs on
  fill_solid(leds, 114, color);                         
  FastLED.show();                                       
  delay(500);
  Serial.begin(115200);                                 // initialize serial
  while(!Serial);                                       // wait for serial to begin
  Serial.println("Trying to start...");
  WiFi.macAddress(mac);                                 // turn on WiFi adapter with our custom MAC address
  device.setUniqueId(mac, sizeof(mac));                 // give this Home Assistant device the same MAC address

  WiFi.begin(wifiSSID, wifiPass);                       // connect to WiFi using your credentials
  while (WiFi.status() != WL_CONNECTED) {               // don't proceed unless we're connected
      delay(500);                                       // waiting for the connection
  }

  device.setName("SubscriberBoard");                    // give a name and software version to Home Assistant for this device
  device.setSoftwareVersion("1.0.0");

  mqtt.onMessage(onMqttMessage);                        // call the onMessage function when MQTT message received event occurs
  mqtt.onConnected(onMqttConnected);                    // call the onConnected function on successful connection with MQTT broker

  mqtt.begin("ip", "user", "password");                 // initialize MQTT with your credentials
}

void loop() {
  mqtt.loop();                                          // all we do in the main loop is wait for MQTT messages
}

void onMqttMessage(const char* topic, const uint8_t* payload, uint16_t length) {
  // This callback is called when message from MQTT broker is received.

  String topicStr = topic;                              // not necessary to convert, but makes life easier

  Serial.print(topicStr);
  Serial.print(": ");
  
  char payloadData[MQTT_SENSOR_DATA_LENGTH_MAX];        // gets rid of "noise" characters at the end of actual payload
  strncpy(payloadData, (const char *)payload, length);  // copies just the chars we want
  payloadData[length] = '\0';                           // terminate the payloadData as strncpy does not.

  Serial.println(payloadData);
  Serial.println(length);

  int displayChars[4];                                  // an array to store each RELEVANT MQTT payload char as an integer, for use in display algorithm

  CRGB color = CRGB(127,127,127);                       // set initial display color to white

  if (topicStr == "Time") {                             // when the topic is "Time," we know to remove the colon in the middle
    Serial.println("Processing payload as Time...");
    displayChars[0] = payloadData[0] - '0';
    displayChars[1] = payloadData[1] - '0';
    displayChars[2] = payloadData[3] - '0';
    displayChars[3] = payloadData[4] - '0';  
    color = CRGB(0,0,127);                              // set time color to blue
    dotsOn = true;
  }

  if (topicStr == "Weather") {                          // when the topic is "Weather," we know to add the F character to the end (F is 10th row of the array)
    Serial.println("Processing payload as Weather...");
    displayChars[0] = payloadData[0] - '0';
    displayChars[1] = payloadData[1] - '0';
    displayChars[2] = payloadData[2] - '0';
    displayChars[3] = 10;
    color = CRGB(0,127,0);                              // set the weather color to green
    dotsOn = false;
  }

  if (topicStr == "YT") {                               // when the topic is "YT," we don't need to do anything special to the data
    Serial.println("Processing payload as YT...");
    displayChars[0] = payloadData[0] - '0';
    displayChars[1] = payloadData[1] - '0';
    displayChars[2] = payloadData[2] - '0';
    displayChars[3] = payloadData[3] - '0'; 
    color = CRGB(127,0,0);                              // set the YouTube subscriber count to red
    dotsOn = false;
  }

  for (int i = 0; i < 4; i++) {                         // run the display function for each of the four characters
    updateDisplay(displayChars[i], i, color);
  }

}

void onMqttConnected() {                               
  // runs when first connected to broker
  
  Serial.println("Connected to the broker!");

  // subscribe to each topic
  mqtt.subscribe("Time");
  mqtt.subscribe("Weather");
  mqtt.subscribe("YT");
}

void updateDisplay(int charIn, int digit, CRGB color) {
  // this takes a character, the digit (0-3), and the color,
  // then illuminates each segment's LEDs based on the character
  // arrays with the maps (0-9 and F)
  if (charIn < 0 || charIn > 11) {charIn = 11;}                // ignores anything that isn't a character we've mapped

  Serial.print("Drawing ");
  Serial.print(charIn);
  Serial.print(" on digit ");
  Serial.println(digit);

  for (int i = 0; i < 7; i++) {                                 // loop through all seven segments, so we can turn them on or off
    int start = (digit * LEDS_PER_SEG * 7) + (i * LEDS_PER_SEG);    // calculate the starting position of LEDs to address, based on the specified digit and current segment in the loop
    if (display[charIn][i] == true) {                           // check if that segment should be on
      fill_solid(leds + start, LEDS_PER_SEG, color);            // if so, set LEDs to the color specified
    } else {
      fill_solid(leds + start, LEDS_PER_SEG, CRGB::Black);      // if not, set the LEDs to black (off)
    }
  }

  if (dotsOn == true) {                                         // turns the dots on or off
    fill_solid(leds + (ledCount - 2), 2, color);
  } else {
    fill_solid(leds + (ledCount - 2), 2, CRGB::Black);
  }

  FastLED.show();                                               // illuminates the LEDs
}

Credits

Cameron Coward

Cameron Coward

16 projects • 1338 followers
Writer for Hackster News. Proud husband and dog dad. Maker and serial hobbyist. Check out my YouTube channel: Serial Hobbyism

Comments