MAR10

For X-mas, Cara got me this sweet Paladone Mario Build-A-Level Desk Light. Each block has an embedded LED and can be moved around It. The unit power in via a barrel jack and has a non-momentary switch on the side to turn the LEDs all on or off. Pretty cool, but I think we can make it cooler…

I pulled it apart and found a simple circuit board inside and a LOT of room for a power supply, Arduino, relays, or whatever I wanted! Yay!

I wanted to keep it looking as stock as possible, including keeping the 5v barrel jack and switch, so someone who recognized the device wouldn’t be easily able to tell the difference.

Modification of the non-momentary (locking) switch was pretty easy, just pull it apart and remove the retention pin. Boom! Now it’s a momentary (non-locking) push button that can easily be connected to a GPIO for control.


Arduino Code

/*******************************************************************************

  ▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒
  ▒▒▒▒▒▒▒▒▒▄▄▄▄▒▒▒▒▒▒▒                                                     ▒▒
  ▒▒▒▒▒▒▄▀▀▓▓▓▀█▒▒▒▒▒▒      March 10th 2022 - yo at joeltron dot com       ▒▒
  ▒▒▒▒▄▀▓▓▄██████▄▒▒▒▒      _______  _______  _______  __    _______       ▒▒
  ▒▒▒▄█▄█▀░░▄░▄░█▀▒▒▒▒     (       )(  ___  )(  ____ )/  \  (  __   )      ▒▒
  ▒▒▄▀░██▄░░▀░▀░▀▄▒▒▒▒     | () () || (   ) || (    )|\/) ) | (  )  |      ▒▒
  ▒▒▀▄░░▀░▄█▄▄░░▄█▄▒▒▒     | || || || (___) || (____)|  | | | | /   |      ▒▒
  ▒▒▒▒▀█▄▄░░▀▀▀█▀▒▒▒▒▒     | |(_)| ||  ___  ||     __)  | | | (/ /) |      ▒▒
  ▒▒▒▄▀▓▓▓▀██▀▀█▄▀▀▄▒▒     | |   | || (   ) || (\ (     | | |   / | |      ▒▒
  ▒▒█▓▓▄▀▀▀▄█▄▓▓▀█░█▒▒     | )   ( || )   ( || ) \ \____) (_|  (__) |      ▒▒
  ▒▒▀▄█░░░░░█▀▀▄▄▀█▒▒▒     |/     \||/     \||/   \__/\____/(_______)      ▒▒
  ▒▒▒▄▀▀▄▄▄██▄▄█▀▓▓█▒▒                                                     ▒▒
  ▒▒█▀▓█████████▓▓▓█▒▒    Paladone Super Mario Bros Build-A-Level Light    ▒▒
  ▒▒█▓▓██▀▀▀▒▒▒▀▄▄█▀▒▒           Making dumb things less dumb              ▒▒
  ▒▒▒▀▀▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒                                                     ▒▒
  ▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒

******************************************************************************/

#include <ESP8266WiFi.h>
#include <ESP8266mDNS.h>
#include <WiFiUdp.h>
#include <EEPROM.h>
#include <NTPClient.h>
#include <WiFiUdp.h>
#include <ESP_Mail_Client.h>
#include <ArduinoOTA.h>

/*****************************************************************************/
// settings

// gpio
int buttonPin = 2;
int relayCount = 4;
int relayPins[] = {5, 4, 12, 14};
// wifi
const char* ssid = "XXXXXXXXXXXXX";
const char* password = "XXXXXXXXXXXXX";

// ota
#define SENSORNAME "mar10_esp"
#define OTApassword "XXXXXXXXXXXXX"
int OTAport = 8266;

// email
#define IMAP_HOST "smtp.joeltron.com"
#define IMAP_FOLDER "INBOX"
#define IMAP_PORT 993
#define AUTHOR_EMAIL "yo@joeltron.com"
#define AUTHOR_PASSWORD "XXXXXXXXXXXXX"

// timezone
int timeOffset = 28800;  // GMT +8

/*****************************************************************************/

// Cycle time
int tickLength = 100;     // milisecond delay between cycles (1000=1sec)
int tickCount = 999;      // always trigger first time

// mode
int mode = 0;             //0=normal 1=hour 2=email 3=random

// max number can display
int maxBinary = relayCount * relayCount - 1;

// Hour
int currentHour = 0;
int hourDelay = 100;

// Email
int currentEmails = 0;
int emailDelay = 100;
void printSelectedMailboxInfo(SelectedFolderInfo sFolder);
IMAPSession imap;
ESP_Mail_Session session;
IMAP_Config config;

// random lights
int randDelay = 12;

// count
int counter = 0;
int countDelay = 20;

// Buttons
bool buttonState = false;

// Relays
bool relayStates[] = {true, true, true, true};

// Blink between modes
int modeBlink = 4;        // 0 for no blink to indicate mode
int modeBlinkLength = 250;

// Time
WiFiUDP ntpUDP;
NTPClient timeClient(ntpUDP);

/*****************************************************************************/

void setup() {
  // start serial
  Serial.begin(9600);

  // eeprom
  EEPROM.begin(512);

  // button input
  pinMode(buttonPin, INPUT_PULLUP);

  // relay outputs
  for (int i = 0; i < relayCount; i++) {
    pinMode(relayPins[i], OUTPUT_OPEN_DRAIN);
    digitalWrite(relayPins[i], HIGH);
  }

  // network
  connectWiFi();

  // ota update
  ArduinoOTA.setPort(OTAport);
  ArduinoOTA.setHostname(SENSORNAME);
  ArduinoOTA.setPassword((const char *)OTApassword);

  ArduinoOTA.onStart([]() {
    Serial.println("Start");
  });
  ArduinoOTA.onEnd([]() {
    Serial.println("\nEnd");
  });
  ArduinoOTA.onProgress([](unsigned int progress, unsigned int total) {
    Serial.printf("Progress: %u%%\r", (progress / (total / 100)));
  });
  ArduinoOTA.onError([](ota_error_t error) {
    Serial.printf("Error[%u]: ", error);
    if (error == OTA_AUTH_ERROR) Serial.println("Auth Failed");
    else if (error == OTA_BEGIN_ERROR) Serial.println("Begin Failed");
    else if (error == OTA_CONNECT_ERROR) Serial.println("Connect Failed");
    else if (error == OTA_RECEIVE_ERROR) Serial.println("Receive Failed");
    else if (error == OTA_END_ERROR) Serial.println("End Failed");
  });
  ArduinoOTA.begin();

  // time
  timeClient.begin();
  timeClient.setTimeOffset(timeOffset);
  timeClient.begin();

  // email
  session.server.host_name = IMAP_HOST;
  session.server.port = IMAP_PORT;
  session.login.email = AUTHOR_EMAIL;
  session.login.password = AUTHOR_PASSWORD;
  session.network_connection_handler = connectWiFi;

  Serial.println("Ready");
  Serial.print("IP address: ");
  Serial.println(WiFi.localIP());

  loadMode();
}

void connectWiFi()
{
  Serial.println("Booting");
  WiFi.mode(WIFI_STA);
  WiFi.begin(ssid, password);
  while (WiFi.waitForConnectResult() != WL_CONNECTED) {
    Serial.println("Connection Failed! Rebooting...");
    delay(5000);
    ESP.restart();
  }
}

void loadMode()
// read from eeprom
{
  mode = char(EEPROM.read(0));
  if (mode > 3) mode = 0;
  Serial.println("Loading eeprom");
  Serial.println(mode);
}

void saveMode()
{
  EEPROM.write(0, mode);
  EEPROM.commit();

  Serial.println("Saving eeprom");
  Serial.println(mode);
  tickCount = 0;    // reset timer
}

void readButton()
// read the state of the button and switch modes
{
  buttonState = !digitalRead(buttonPin);
  //Serial.println(buttonState);

  // inc mode
  if (buttonState) {
    mode++;
    if (mode > 3) mode = 0;

    Serial.print("Mode: ");
    Serial.println(mode);
    saveMode();

    // turn off all relays
    for (int i = 0; i < relayCount; i++)
      relayStates[i] = false;

    // blink to show what mode you are in
    for (int j = 0; j < modeBlink; j++) {
      // just this mode on
      relayStates[mode] = true;
      setRelays();
      delay(modeBlinkLength);

      relayStates[mode] = false;
      setRelays();
      delay(modeBlinkLength);

    }
  }


  /*
    for (int j = 0; j < modeBlink; j++) {
    for (int i = 0; i < relayCount; i++) {
      if (i == mode)
        relayStates[i] = true;
      else
        relayStates[i] = false;
    }

    for (int i = 0; i < relayCount; i++) {
      relayStates[i] = false;
      setRelays();
      delay(modeBlinkLength);
      setRelays();
      delay(modeBlinkLength);
    }
    }
    }
  */
}

void setRelays()
// turn on/off relays according to relayStates array
{
  // Serial.println("Setting Relays");
  for (int i = 0; i < relayCount; i++) {
    /*Serial.print("LED");
      Serial.print(i);
      Serial.print(": ");
      Serial.println(relayStates[i]);*/

    if (!relayStates[i])    // Normally Closed logic
      digitalWrite(relayPins[i], LOW);
    else
      digitalWrite(relayPins[i], HIGH);
  }
}

void intToBinaryRelays(int input)
{
  // dont go over max
  if (input > maxBinary)
    input = maxBinary;

  Serial.println("");
  for (byte i = 0; i < relayCount; i++) {
    byte state = bitRead(input, i);
    relayStates[i] = state;
    Serial.print(state);
  }
}

void modeCount()
// counts up to max
{
  if (tickCount > countDelay) {
    tickCount = 0;
    intToBinaryRelays(counter);

    counter++;
    if (counter > maxBinary)
      counter = 0;
  } else {
    tickCount++;
  }
}

void modeAllOn()
// turn all on
{
  for (int i = 0; i < relayCount; i++)
    relayStates[i] = true;
}

void modeHour()
// shows hour in binary
{
  // only delay after first run
  if (tickCount > hourDelay) {
    tickCount = 0;
    timeClient.update();
    currentHour = timeClient.getHours();

    // 24hrs is for cool kids only
    if (currentHour > 12)
      currentHour = currentHour - 12;

    Serial.println(currentHour);
    Serial.println("");

    intToBinaryRelays(currentHour);
  } else {
    tickCount++;
  }
}

void modeEmails()
// displays email count in binary
{
  if (tickCount > emailDelay) {
    tickCount = 0;
    if (!imap.connect(&session, &config))
      return;

    if (!imap.selectFolder(F(IMAP_FOLDER)))
      return;

    currentEmails = imap.selectedFolder().msgCount();
    intToBinaryRelays(currentEmails);
    Serial.println(currentEmails);
  } else {
    tickCount++;
  }
}

void modeTest()
// swaps relays on/off
{
  if (tickCount > randDelay) {
    tickCount = 0;
    for (int i = 0; i < relayCount; i++) {
      relayStates[i] = !relayStates[i];
    }
  } else {
    tickCount++;
  }
}

void modeRun()
// climbs up through relays
{
  if (tickCount > randDelay) {
    tickCount = 0;

    for (int i = 0; i < relayCount; i++) {
      relayStates[i] = (counter == i);
    }
    counter++;
    if (counter > 3)
      counter = 0;

  } else {
    tickCount++;
  }
}

void modeRandom()
// randomly sets on/off
{
  if (tickCount > randDelay) {
    tickCount = 0;
    for (int i = 0; i < relayCount; i++) {
      relayStates[i] = random(0, 2);
    }
  } else {
    tickCount++;
  }
}

void loop() {
  ArduinoOTA.handle();

  // now do stuff
  readButton();
  switch (mode) {
    case 0:
      modeAllOn();
      break;
    case 1:
      modeHour();
      break;
    case 2:
      modeEmails();
      break;
    case 3:
      //modeRandom();
      modeCount();
      //modeRun();
      //modeTest();
      break;
  }

  // trigger relays
  setRelays();

  // waitin
  delay(tickLength);
}

/******************************************************************************
                      le end. yo at joeltron dot com
******************************************************************************/