16 X 16 Pixel RGB LED Artboard Part 4

The fourth and final post in my 16 X 16 Pixel RGB LED Artboard project.

In this article the programming behind the project will be discussed. The code is run on a NodeMCU (ESP12) 8266 dev board. These little gems are available all over Ebay from China for a couple dollars, usually with free shipping. This link should get you started: NodeMcu boards on Ebay

I like the models with CP2102 usb-to-serial IC’s built in as they play nicely with windows 10, and driver installation is automatic and trouble free… I’m still not used to saying that about windows yet lol.

But its True 😉

If this is your first time programming a NodeMcu board using Arduino checkout this tutorial, your going to have to install the boards so they are available in the Arduino GUI.

See here: How to add NodeMcu and ESP8266 Boards to Arduino GUI

Next – Grab this code from GitHub: https://github.com/lincomatic/WS2812B

Now unzip that little gem and open the .ino file using Arduino.  Now depending on the NodeMcu dev board you purchased you may need to configure Arduino differently… for the upload. Normally choosing “NodeMcu 0.9” from the list of pre-configured boards just works, so I would suggest beginning there.

Ok so the code is pretty simple, I’m using good old Arduino to upload via USB cable to my pc as mentioned above, so by now you should be ready to configure some wifi network settings and get this thing lit up right?

After unzipping the library above open up the WS2812Artnet.ino file using Arduino and you should see something like this:

/*
Hacked by SCL from
 https://github.com/natcl/Artnet/blob/master/examples/ArtnetNeoPixel/ArtnetNeoPixel.ino
 https://github.com/overflo23/ESP8266_ARTNET_WS2801/tree/master/artnet_esp

 This example will receive multiple universes via Artnet and control a strip of ws2811 leds
*/

//#include <eagle_soc.h>
#include <EEPROM.h>
#include <ESP8266WiFi.h>
#include <WiFiUdp.h>
#include "./ArtnetWifi.h"
#include "./ArduinoOTAMgr.h"

//
// --- begin configuration
//
#define WIFI_MGR // use AP mode to configure via captive portal
#define OTA_UPDATE // OTA firmware update

#define PIXEL_CNT 255 //16 // number of LED's

#define PIN_DATA  15 
//#define PIN_LED    0
// n.b. pin 15 internal pullup doesn't seem to work so shouldn't be used for reset
#define PIN_FACTORY_RESET 12 // ground this pin to wipe out EEPROM & WiFi settings

#define AP_PREFIX "WS2812ArtNet_"

// OTA_UPDATE
#define OTA_HOST "WS2812ArtNet"
#define OTA_PASS "ws2812artnet"

#ifndef WIFI_MGR
//Wifi settings
const char* ssid = "yourssid";
const char* password = "yourpswd";
#endif // !WIFI_MGR


//
// --- end configuration
//

#define CHANNEL_CNT (PIXEL_CNT*3) // Total number of channels you want to receive (1 led = 3 channels)
#define BYTE_CNT (PIXEL_CNT*3)

// Artnet settings
int startUniverse = 0; 
const int maxUniverses = CHANNEL_CNT / 512 + ((CHANNEL_CNT % 512) ? 1 : 0);

#ifdef OTA_UPDATE
ArduinoOTAMgr AOTAMgr;
#endif

typedef struct pixel_grb {
  uint8_t g;
  uint8_t r;
  uint8_t b;
} PIXEL_GRB;
PIXEL_GRB g_BlkPixel = {0,0,0};

class WS2812Strand {
  PIXEL_GRB pixelBuffer[PIXEL_CNT];
  uint8_t *displayBuffer;
  uint32_t endTime;       // Latch timing reference
  uint8_t dataPin;

  inline bool canShow(void) { return (micros() - endTime) >= 50L; }
public:
  WS2812Strand(uint8_t datapin);

  void begin();
  void fill(PIXEL_GRB *pixel);
  void show();
  uint8_t *getDisplayBuffer() { return displayBuffer; }
  void setPixel(uint16_t idx,PIXEL_GRB *p);
  PIXEL_GRB *getPixel(uint16_t idx) { return pixelBuffer + idx; }
};

WS2812Strand::WS2812Strand(uint8_t datapin)
{
  displayBuffer = (uint8_t *)pixelBuffer;
  dataPin = datapin;
  endTime = 0;
}

void WS2812Strand::begin()
{
  pinMode(dataPin, OUTPUT);
  digitalWrite(dataPin, LOW);
  endTime = micros();
}

void WS2812Strand::fill(PIXEL_GRB *pixel)
{
  for (uint8_t i=0;i < PIXEL_CNT;i++) {
    pixelBuffer[i] = *pixel;
  }
}

void WS2812Strand::setPixel(uint16_t idx,PIXEL_GRB *p)
{
  if (idx < PIXEL_CNT) { pixelBuffer[idx] = *p; } } #ifdef ESP8266 // ESP8266 show() is external to enforce ICACHE_RAM_ATTR execution extern "C" void ICACHE_RAM_ATTR espShow( uint8_t pin, uint8_t *pixels, uint32_t numBytes, uint8_t type); #endif // ESP8266 void WS2812Strand::show() { // Data latch = 50+ microsecond pause in the output stream. Rather than // put a delay at the end of the function, the ending time is noted and // the function will simply hold off (if needed) on issuing the // subsequent round of data until the latch time has elapsed. This // allows the mainline code to start generating the next frame of data // rather than stalling for the latch. while(!canShow()); espShow(dataPin,displayBuffer,BYTE_CNT,1); endTime = micros(); // Save EOD time for latch on next call } #define BTN_PRESS_SHORT 50 // ms #define BTN_PRESS_LONG 500 // ms #define BTN_STATE_OFF 0 #define BTN_STATE_SHORT 1 // short press #define BTN_STATE_LONG 2 // long press class Btn { uint8_t btnGpio; uint8_t buttonState; unsigned long lastDebounceTime; // the last time the output pin was toggled public: Btn(uint8_t gpio); void init(); void read(); uint8_t shortPress(); uint8_t longPress(); }; Btn::Btn(uint8_t gpio) { btnGpio = gpio; buttonState = BTN_STATE_OFF; lastDebounceTime = 0; } void Btn::init() { pinMode(btnGpio,INPUT_PULLUP); } void Btn::read() { uint8_t sample; unsigned long delta; sample = digitalRead(btnGpio) ? 0 : 1; if (!sample && (buttonState == BTN_STATE_LONG) && !lastDebounceTime) { buttonState = BTN_STATE_OFF; } if ((buttonState == BTN_STATE_OFF) || ((buttonState == BTN_STATE_SHORT) && lastDebounceTime)) { if (sample) { if (!lastDebounceTime && (buttonState == BTN_STATE_OFF)) { lastDebounceTime = millis(); } delta = millis() - lastDebounceTime; if (buttonState == BTN_STATE_OFF) { if (delta >= BTN_PRESS_SHORT) {
	  buttonState = BTN_STATE_SHORT;
	}
      }
      else if (buttonState == BTN_STATE_SHORT) {
	if (delta >= BTN_PRESS_LONG) {
	  buttonState = BTN_STATE_LONG;
	}
      }
    }
    else { //!sample
      lastDebounceTime = 0;
    }
  }
}

uint8_t Btn::shortPress()
{
  if ((buttonState == BTN_STATE_SHORT) && !lastDebounceTime) {
    buttonState = BTN_STATE_OFF;
    return 1;
  }
  else {
    return 0;
  }
}

uint8_t Btn::longPress()
{
  if ((buttonState == BTN_STATE_LONG) && lastDebounceTime) {
    lastDebounceTime = 0;
    return 1;
  }
  else {
    return 0;
  }
}

#ifdef WIFI_MGR
#include "./WiFiManager.h"          // https://github.com/tzapu/WiFiManager

typedef struct config_parms {
int startUniverse;
  char artnet_universe[3];
  char staticIP[16]; // optional static IP
  char staticGW[16]; // optional static gateway
  char staticNM[16]; // optional static netmask
} CONFIG_PARMS;




//flag for saving data

class WifiConfigurator {
  CONFIG_PARMS configParms;
  // a flag for ip setup
  boolean set_static_ip;

  //network stuff
  //default custom static IP, changeable from the webinterface
  void resetConfigParms() { memset(&configParms,0,sizeof(configParms)); }
public:
  static bool shouldSaveConfig;

  WifiConfigurator();

  void StartManager(void);
  String getUniqueSystemName();
  void printMac();
  void readCfg();
  void resetCfg(int dowifi=0);
  uint8_t getConfigSize() { return sizeof(configParms); }
};


bool WifiConfigurator::shouldSaveConfig; // instantiate

WifiConfigurator::WifiConfigurator()
{
  resetConfigParms();

  set_static_ip = false;
}



//creates the string that shows if the device goes into accces point mode
#define WL_MAC_ADDR_LENGTH 6
String WifiConfigurator::getUniqueSystemName()
{
  uint8_t mac[WL_MAC_ADDR_LENGTH];
  WiFi.softAPmacAddress(mac);


  String macID = String(mac[WL_MAC_ADDR_LENGTH - 2], HEX) + String(mac[WL_MAC_ADDR_LENGTH - 1], HEX);

  macID.toUpperCase();
  String UniqueSystemName = String(AP_PREFIX) + macID;

  return UniqueSystemName;
}





// displays mac address on serial port
void WifiConfigurator::printMac()
{
  uint8_t mac[WL_MAC_ADDR_LENGTH];
  WiFi.softAPmacAddress(mac);

  Serial.print("MAC: ");
  for (int i = 0; i < 5; i++){
    Serial.print(mac[i], HEX);
    Serial.print(":");
  }
  Serial.println(mac[5],HEX);
}

//callback notifying us of the need to save config
void saveConfigCallback () {
  Serial.println("Should save config");
  WifiConfigurator::shouldSaveConfig = true;
}


void WifiConfigurator::StartManager(void)
{
  Serial.println("StartManager() called");

  shouldSaveConfig = false;

  // add parameter for artnet setup in GUI
  WiFiManagerParameter custom_artnet_universe("artnet_universe", "Art-Net Universe (Default: 0)", configParms.artnet_universe, sizeof(configParms.artnet_universe));

  // add parameters for IP setup in GUI
  WiFiManagerParameter custom_ip("ip", "Static IP (Blank for DHCP)", configParms.staticIP, sizeof(configParms.staticIP));
  WiFiManagerParameter custom_gw("gw", "Static Gateway (Blank for DHCP)", configParms.staticGW, sizeof(configParms.staticGW));
  WiFiManagerParameter custom_nm("nm", "Static Netmask (Blank for DHCP)", configParms.staticNM, sizeof(configParms.staticNM));
  //WiFiManager
  //Local intialization. Once its business is done, there is no need to keep it around
  WiFiManager wifiManager;

  // this is what is called if the webinterface want to save data, callback is right above this function and just sets a flag.
  wifiManager.setSaveConfigCallback(saveConfigCallback);

  // this actually adds the parameters defined above to the GUI
  wifiManager.addParameter(&custom_artnet_universe);

  // if the flag is set we configure a STATIC IP!
  if (set_static_ip) {
    //set static ip
    IPAddress _ip, _gw, _nm;
    _ip.fromString(configParms.staticIP);
    _gw.fromString(configParms.staticGW);
    _nm.fromString(configParms.staticNM);
    
    // this adds 3 fields to the GUI for ip, gw and netmask, but IP needs to be defined for this fields to show up.
    wifiManager.setSTAStaticIPConfig(_ip, _gw, _nm);
    
    Serial.println("Setting IP to:");
    Serial.print("IP: ");
    Serial.println(configParms.staticIP);
    Serial.print("GATEWAY: ");
    Serial.println(configParms.staticIP);
    Serial.print("NETMASK: ");
    Serial.println(configParms.staticNM);
  }
  else {
    // i dont want to fill these fields per default so i had to implement this workaround .. its ugly.. but hey. whatever.  
    wifiManager.addParameter(&custom_ip);
    wifiManager.addParameter(&custom_gw);
    wifiManager.addParameter(&custom_nm);
  }

  //sets timeout until configuration portal gets turned off
  //useful to make it all retry or go to sleep in seconds
  // also really annoying if you just connected and the damn thing resets in the middle of filling in the GUI..
  wifiManager.setConfigPortalTimeout(10*60);

  //fetches ssid and pass and tries to connect
  //if it does not connect it starts an access point with the specified name
  //here  "AutoConnectAP"
  //and goes into a blocking loop awaiting configuration
  if (!wifiManager.autoConnect(getUniqueSystemName().c_str())) {
    Serial.println("timed out and failed to connect");
    Serial.println("rebooting...");
    //reset and try again, or maybe put it to deep sleep
    reboot();
    //   we never get here.
  }

  //everything below here is only executed once we are connected to a wifi.

  //if you get here you have connected to the WiFi

  digitalWrite(LED_BUILTIN,LOW); // turn on LED
  Serial.print("CONNECTED to ");
  Serial.println(WiFi.SSID());
  Serial.print("IP address: ");
  Serial.println(WiFi.localIP());
  
  // this is true if we come from the GUI and clicked "save"
  // the flag is created in the callback above..
  if (shouldSaveConfig) {
    // connection worked so lets save all those parameters to the config file
    strcpy(configParms.artnet_universe, custom_artnet_universe.getValue());
    if (*configParms.artnet_universe) {
      startUniverse = atoi(configParms.artnet_universe);
    }
    else {
      startUniverse = 0;
    }
    strcpy(configParms.staticIP, custom_ip.getValue());
    strcpy(configParms.staticGW, custom_gw.getValue());
    strcpy(configParms.staticNM, custom_nm.getValue());
    
    // if we defined something in the gui before that does not work we might to get rid of previous settings in the config file
    // so if the form is transmitted empty, delete the entries.
    //if (strlen(ip) < 8) {
    //      resetConfigParms();
    //    }
    
    
    Serial.println("saving config");
    
    int eepidx = 0;
    int i;
    EEPROM.write(eepidx++,strlen(configParms.artnet_universe));
    for (i=0;i < (int)strlen(configParms.artnet_universe);i++) {
      EEPROM.write(eepidx++,configParms.artnet_universe[i]);
    }

    EEPROM.write(eepidx++,strlen(configParms.staticIP));
    for (i=0;i < (int)strlen(configParms.staticIP);i++) {
      EEPROM.write(eepidx++,configParms.staticIP[i]);
    }

    EEPROM.write(eepidx++,strlen(configParms.staticGW));
    for (i=0;i < (int)strlen(configParms.staticGW);i++) {
      EEPROM.write(eepidx++,configParms.staticGW[i]);
    }

    EEPROM.write(eepidx++,strlen(configParms.staticNM));
    for (i=0;i < (int)strlen(configParms.staticNM);i++) { EEPROM.write(eepidx++,configParms.staticNM[i]); } EEPROM.commit(); Serial.print("artnet_universe: "); Serial.println(*configParms.artnet_universe ? configParms.artnet_universe : "(default)"); Serial.print("IP: "); Serial.println(*configParms.staticNM ? configParms.staticNM : "(DHCP)"); Serial.print("GW: "); Serial.println(*configParms.staticGW ? configParms.staticGW : "(DHCP)"); Serial.print("NM: "); Serial.println(*configParms.staticNM ? configParms.staticNM : "(DHCP)"); //end save } } void WifiConfigurator::resetCfg(int dowifi) { Serial.print("resetCfg()"); // reset config parameters resetConfigParms(); // clear EEPROM EEPROM.write(0,0); EEPROM.commit(); if (dowifi) { // erase WiFi settings (SSID/passphrase/etc WiFiManager wifiManager; wifiManager.resetSettings(); } } void WifiConfigurator::readCfg() { int resetit = 0; int eepidx = 0; int i; resetConfigParms(); uint8_t len = EEPROM.read(eepidx++); if ((len == 0) || (len >= sizeof(configParms.artnet_universe))) {
    // assume uninitialized
    resetit = 1;
  }
  else {
    for (i=0;i < len;i++) { configParms.artnet_universe[i] = EEPROM.read(eepidx++); } configParms.artnet_universe[i] = 0; len = EEPROM.read(eepidx++); if ((len > 0) && (len < sizeof(configParms.staticIP))) {
      for (i=0;i < len;i++) { configParms.staticIP[i] = EEPROM.read(eepidx++); } configParms.staticIP[i] = 0; } else { resetit = 1; } if (!resetit) { len = EEPROM.read(eepidx++); if ((len > 0) && (len < sizeof(configParms.staticGW))) {
	for (i=0;i < len;i++) { configParms.staticGW[i] = EEPROM.read(eepidx++); } configParms.staticGW[i] = 0; } else { resetit = 1; } } if (!resetit) { len = EEPROM.read(eepidx++); if ((len > 0) && (len < sizeof(configParms.staticNM))) {
	for (i=0;i < len;i++) { configParms.staticNM[i] = EEPROM.read(eepidx++); } configParms.staticNM[i] = 0; } else { resetit = 1; } } } if (!resetit) { // valid config startUniverse = atoi(configParms.artnet_universe); if (*configParms.staticIP) { // lets use the IP settings from the config file for network config. Serial.println("setting static ip from config"); set_static_ip = 1; } else { Serial.println("using DHCP"); } } else { resetCfg(0); } } WifiConfigurator wfCfg; #endif // WIFI_MGR WS2812Strand leds(PIN_DATA); Btn btnReset(PIN_FACTORY_RESET); ArtnetWifi artnet; // Check if we got all universes bool universesReceived[maxUniverses]; bool sendFrame = 1; int previousDataLength = 0; #ifndef WIFI_MGR // connect to wifi – returns true if successful or false if not boolean ConnectWifi(void) { boolean state = true; int i = 0; WiFi.begin(ssid, password); Serial.println(""); Serial.println("Connecting to WiFi"); // Wait for connection Serial.print("Connecting"); while (WiFi.status() != WL_CONNECTED) { delay(500); Serial.print("."); if (i > 20){
      state = false;
      break;
    }
    i++;
  }
  if (state){
    digitalWrite(0,LOW); // turn on onboard LED

    Serial.println("");
    Serial.print("Connected to ");
    Serial.println(ssid);
    Serial.print("IP address: ");
    Serial.println(WiFi.localIP());
  } else {
    Serial.println("");
    Serial.println("Connection failed.");
  }
  
  return state;
}

#endif // !WIFI_MGR


void reboot()
{
  WiFi.disconnect();
  delay(1000);
  ESP.reset();
}

void initTest()
{
  delay(100);
  leds.fill(&g_BlkPixel);
  leds.show();
  delay(100);

  PIXEL_GRB p;
  p.r = 127;
  p.g = 0;
  p.b = 0;
  leds.fill(&p);
  leds.show();
  delay(1000);

  p.r = 0;
  p.g = 127;
  p.b = 0;
  leds.fill(&p);
  leds.show();
  delay(1000);

  p.r = 0;
  p.g = 0;
  p.b = 127;
  leds.fill(&p);
  leds.show();
  delay(1000);

  leds.fill(&g_BlkPixel);
  leds.show();
}

void onDmxFrame(uint16_t universe, uint16_t length, uint8_t sequence, uint8_t* data)
{
  digitalWrite(LED_BUILTIN,LOW); // onboard LED on

  sendFrame = 1;
  /*
  // set brightness of the whole strip 
  if (universe == 15)
  {
    leds.setBrightness(data[0]);
    leds.show();
  }
  */

  // Store which universe has got in
  if ((universe - startUniverse) < maxUniverses)
    universesReceived[universe - startUniverse] = 1;

  for (int i = 0 ; i < maxUniverses ; i++)
  {
    if (universesReceived[i] == 0)
    {
      //Serial.println("Broke");
      sendFrame = 0;
      break;
    }
  }

  // read universe and put into the right part of the display buffer
  PIXEL_GRB *p = (PIXEL_GRB *)data;
  for (int i = 0; i < length / 3; i++)
  {
    int led = i + (universe - startUniverse) * (previousDataLength / 3);
    leds.setPixel(led,p++);
  }
  previousDataLength = length;     
  
  if (sendFrame)
  {
    leds.show();
    // Reset universeReceived to 0
    memset(universesReceived, 0, maxUniverses);
  }

  digitalWrite(LED_BUILTIN,HIGH); // onboard LED off
}

void factoryReset()
{
  Serial.println("Factory Reset");
  wfCfg.resetCfg(1);
  reboot();
}

void setup()
{
  // onboard leds also outputs
  pinMode(LED_BUILTIN, OUTPUT); // onboard LED
  digitalWrite(LED_BUILTIN,HIGH); // turn off onboard LED
  Serial.begin(115200);

  btnReset.init();

#ifdef WIFI_MGR
  EEPROM.begin(wfCfg.getConfigSize());  

  // display the MAC on Serial
  wfCfg.printMac();

  wfCfg.readCfg();
  Serial.println("readCfg() done.");

  wfCfg.StartManager();
  Serial.println("StartManager() done.");
#else
  ConnectWifi();
#endif // WIFI_MGR

#ifdef OTA_UPDATE
  AOTAMgr.boot(OTA_HOST,OTA_PASS);
#endif


  Serial.print("Art-Net universe: ");
  Serial.println(startUniverse);
  Serial.println("Starting Art-Net");
  artnet.begin();
  leds.begin();
  initTest();

  // this will be called for each packet received
  artnet.setArtDmxCallback(onDmxFrame);
}

void loop()
{
  btnReset.read();
  if (btnReset.longPress()) {
    Serial.println("long press");
    factoryReset();
  }


  // we call the read function inside the loop
  artnet.read();

#ifdef OTA_UPDATE
  AOTAMgr.handle();
#endif

}

PIN_DATA is your digital pin on the NodeMcu board, I have mine set to 15 (which is actually labelled D8 on the NodeMcu board itself…. see below, just be aware of this)

*I use a 200R 1/4 W resistor in series on the data wires coming from the NodeMcu board, this is to delay the pwm signal slightly and also current limiting, its recommended on the ws2812 datasheet*

Now you also need to set PIXEL_CNT to the number of WS2812 LED’s in your strip, array, etc…

Next change the SSID and password to match your local wifi network, you can also play around and set static IP’s. Just browse through the code, there places to change these settings as well as others.

Now just upload your code to the nodemcu and you should be able to use a program like Jinx! found HERE

Its pretty easy to use, although setting up your arrays are tricky… I will most likely do a post on that in the future, if you have any questions feel free to ask in the comment section below or shoot me an email.

Until Next Time – Happy Coding

 

 

MQTT IR Blaster for my OpenHab Setup (nodemcu+MQTT+openhab+IR)

A few months back I started to setup a dedicated home automation system to control my ever-growing number of “smart” devices.

Smart devices are great but they usually have separate interfaces and mobile programs to install and control each device. What is really needed is a single program to control all of my devices… from a single interface.

Well that in a nutshell is Openhab, a software suite developed specifically for controlling smart devices from a range of manufacturers and industries, utilizing many different communication protocols.

One of these communication protocols is MQTT. I wont go into the specifics of this protocol, only that it is widely used in industrial applications and has even been used for face-books messenger app.

Now the reason for designing an IR blaster that uses MQTT to trigger the IR LED’s is that I’m already using it to control several appliances in my apartment.

Specifically I’m using Nodemcu boards with Arduino compatible code to receive an MQTT command and do some action… i.e. turn on or off a relay

Except this time instead of turning on and off a relay I will use a separate Arduino library called IRsend.h to activate the IR leds through a digital pin on the nodemcu board.

Here is the Arduino code for the Nodemcu boards:

#include <ESP8266WiFi.h>
#include <PubSubClient.h>
#include <ESP8266mDNS.h>
#include <WiFiUdp.h>
#include <ArduinoOTA.h>
#include <IRsend.h>

IRsend irsend(14);  // An IR LED is controlled by GPIO pin 14 (D5) on nodemcu boards

// Update these with values suitable for your network.

//openhab MQQT settings
const char* ssid = "********";
const char* password = "********";
const char* mqtt_server = "xxx.xxx.xxx.xxx";
const char* brokerusername = "********";
const char* brokerpassword = "********";

IPAddress ip(192, 168, 0, 114); // where xx is the desired IP Address
IPAddress gateway(192, 168, 0, 1); // set gateway to match your network
IPAddress subnet(255, 255, 255, 0); // set subnet mask to match your network

WiFiClient espClient;
PubSubClient client(espClient);
long lastMsg = 0;
char msg[50];
int value = 0;

void setup_wifi() {

  delay(10);
  // We start by connecting to a WiFi network
  Serial.println();
  Serial.print("Connecting to ");
  Serial.println(ssid);

  WiFi.config(ip, gateway, subnet);

  WiFi.mode(WIFI_STA);
 
  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 callback(char* topic, byte* payload, unsigned int length) {
  Serial.print("Message arrived [");
  Serial.print(topic);
  Serial.print("] ");
  
  String p_payload;
  
  for (int i = 0; i < length; i++) {

    p_payload.concat((char)payload[i]);
         
  }
    Serial.println(p_payload);
    
    // Convert from String HEX to Hex long
    long hexCmd;
    hexCmd = strtol(&p_payload[0], NULL, 16);
    irsend.sendNEC(hexCmd, 32);
       
    Serial.print("INT:");
    Serial.println(hexCmd);
      
}

void reconnect() {
  // Loop until we're reconnected
  while (!client.connected()) {
    Serial.print("Attempting MQTT connection...");
    // Create a random client ID
    String clientId = "ESP8266Client-";
    clientId += String(random(0xffff), HEX);
    // Attempt to connect
    if (client.connect(clientId.c_str(), brokerusername, brokerpassword)) {
      Serial.println("connected");
      client.subscribe("apartment/livingroom/ir_blaster/state");
    } else {
      Serial.print("failed, rc=");
      Serial.print(client.state());
      Serial.println(" try again in 5 seconds");
      // Wait 5 seconds before retrying
      delay(5000);
    }
  }
}

void setup() {
  Serial.begin(115200);
  setup_wifi();
  client.setServer(mqtt_server, 1883);
  client.setCallback(callback);
  irsend.begin();
  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();
  
}

void loop() {
    
   ArduinoOTA.handle();

  if (!client.connected()) {
    reconnect();
  }
  client.loop();
  }

The code posted above is pretty simple, its using an MQTT communication library for receiving commands from Openhab. It subscribes to the “apartment/livingroom/ir_blaster/state” topic. Openhab publishes an IR code to this topic and the code here receives that command, and uses the IRsend library to pulse pin 14 (D5) on the nodemcu boards.

I decided on using 6 IR LED’s arranged in a semicircular pattern to entirely cover my living room. Each LED is 30 degrees from the next, with 6 LED’s its able to cover more than 180 degrees. I used a single PN222A transistor driven through pin 14, which switches the IR LED’s. There are three series pairs of IR leds in parallel running through the PN222A, with this configuration I do not require current limiting resistors.

Here is the circuit:

And then I designed and 3D printed this case:

Now all that’s left to do is send some IR codes to the device through the subscribed MQQT channel.

I recorded the IR codes from several of my favourite devices remotes and then set up some items and button in my Openhab UI.

UI currently looks like this:

It works perfectly!! Next I need to design some nice UI’s to allow control of my home entertainment system through a tablet.

If anyone wants a tutorial on how to record IR codes from remotes let me know.

Here is a shot of the IR codes being recorded:

SolidWorks Sheet Metal Tutorial – Electrical Enclosure (STEP-BY-STEP)

Step-by-step video tutorial showing how to make a small electrical enclosure from sheet metal.

You will learn how to create a base flange, add edge flanges with k factor allowances, use stamping and forming tools, add a fan vent all using built-in SolidWorks library tools. Finally, I will show how to flatten the parts into a format which could be sent for cutting/CNC Bending or used to produce engineering drawings of the enclosure.

Total Time Approx 25min

Working on a Logo!

Coming up with an animated logo for the BlackRock3D website…. Its a work in progress.

Let me know what you think!!

 

 

Fix those pesky Solid part Fillet errors in Solidworks using basic Surfacing Tools

I’ve come across this problem more than a few times and never had a good solution. Sure you can mess around with variable radius fillets, or change the base geometry…. but sometimes that’s just not possible. It just has to work within the given constraints. And you either need rounded edges for manufacturing purposes, or perhaps its safety. Or maybe you just want those beautiful smooth edges that catch the light to make your product stand out in a photoshoot!

At some point this happens:

 

But it doesn’t have to be… It can be like this, easily:

 

Check out my Tutorial on YouTube below that shows how to fix this in minutes using the surfacing tools in SolidWorks:

 

Thanks for dropping by, stay tuned for more solidworks content on this website and my youtube channel here: BlackRock3D Youtube Channel

16 X 16 Pixel RGB LED Artboard Part 3

This is the third post in the 16 X 16 Pixel RGB LED Artboard project, Part1 and Part2  links just in case you showed up late!

In this post I will discuss the building of a pine framed box to house the pixel array. It has a glass cover on the front for easy cleaning and finished look.

First part was cutting the frame pieces, used a miter box and hand saw to cut all four sides.

Frame of pine

Next I carved out a groove for the glass, the location of the grooves was marked onto the wood with a pencil, square and metal ruler.

Then I used a small 1/4″ wood chisel to slowly carve out the groove to a depth of approximately 1/4″ deep. The glass was cut 1/2″ (13mm) larger on both sides than the wood frame. So a groove about 3/16″ to 1/4″ holds the glass in place and hides the edge.

Laying out the grooves
Cutting the grooves
Fitting the glass

Ok so that went fairly quickly, it was a lot of fun carving the wood. I love the smell of fresh cut wood.

Once I was totally satisfied with the fit, wood glue was applied to each joint and painters tape used to hold the sides in tension with each other.

Holding the frame together while glue dries

The panel is held in place with offcuts from making the sides.

Completed frame with led array in place

Right away I tested it out!!

Very first full panel tests!!

And the first tests were successful!

This panel is power hunnngggry though!! The small bench power supply I used for this test immediately started to smell like burning electronics, so I shut it down… Need to find a 5V 4A power supply, 10A would be even better!

The pattern shown on the panel  is being fed via wifi to an ESP8266 mounted on the back of the panel. The ESP8266 is running an Artnet Node with two universes. I’m feeding the data from a windows PC running Jinx…. ive also tested it with GLEDiator as well and it works fine.

To be continued…. Programming, ESP8266, Artnet in the next post.

16 X 16 Pixel RGB LED Artboard Part 2

So continuing from Part 1 of the Series on designing and building a 16 X 16 RGB LED Artboard...

At first, I had such grandiose ideas of this stunning foam core frame for the LED panel… well this didn’t work out for me. Let me explain why.

Have you ever tried to cut intricate foam core shapes with an exacto knife… the foam either gummed up the blade resulting in horrible edges that basically ended up pulling and ripping that paper backings on both sides…

Grid design for holding WS2812B RGB LED’sI tried multiple techniques and decided life was too short, plus I have a 3D printer at my disposal!! ….. 

Look it just wasn’t my thing. So instead I went with a 3D printed design. Now I could make precise squares, with thin walls. And I could make the exact shape of the LED as  cut out and pop the suckers in…. it all made sense now and I began to come up with printable design…

With this design the LEDS simply “snap” into place on the side opposing the open face of the array.

Here’s a shot of the first piece being printed on my FDM printer:

Populating the first panel with LED’s:

Laying down copper:

Testing the first panel:

Four panels tested and ready for final connections:

After wiring all four panels together, tested finished 16 X 16 panel using Arduino Duemilanove and a 5V 4A power supply 😀

Conclusion of Series in part 3 – Programming and Finishing in Wood/Glass Frame

16 X 16 Pixel RGB LED Artboard Part 1

A few months back I saw some very interesting NeoPixel coffee table designs on YouTube. Right away I knew that I must get my hands on some of these amazing little lights and start to experiment.

Instead of going for a large coffee table design I decided on a smaller project, a 16 x 16 pixel NeoPixel Artboard that could display static images or animations. I was inspired by this video:

And thus the project began, first by purchasing some WS2812B addressable RGB LED’s from an online vendor. The parts showed up after waiting a couple weeks and I dove in right away!

In the past I’ve played around with classic thru hole RGB LED’s, but these were quite different. The nice part with these LED’s is that each pixel in a strip is individually addressable, meaning they can be controlled separately using a very simple one wire communication protocol. No extra shift registers are required which keeps this project compact and the wiring very simple, although tedious if you use the individual LED’s on pcb approach that I did here.

In the future I would definitely use and suggest using the pre-wired strips for a project like this.

My first experiment was wiring up a 10×10 array for testing. Here is a picture of the LED’s as they arrived at my doorstep.

Here’s how I wired it up for testing, except I did add a 100 Ohm current limiting resistor in series with the data line as it was suggested in the manufacturers documentation online.

 

Looking back I really should have ordered the prewired strips for this project. Oh well such is life, live and learn… Needless to say I wasn’t looking forward to soldering for at least a week after this. Wiring by hand, this took a few hours. I used solid 22awg copper wire for power, gnd and data.

This went smoothly and in no time I was up and running, I downloaded the acclaimed AdafruitNeoPixelLibrary and installed it in Arduino IDE. Then I wired up the panel to an Arduino Nano and suitable DC power supply (these things are power hungry so beware!).

Here I’m testing each row individually as they are wired.

And finally success, the whole panel works… But why is my small bench supply making funny smells. This thing is drawing easily 2A at 5V DC, depending on the color produced. White is the worst as it requires all three LED’s of each pixel to run at full brightness.

Continued in Part 2 – Designing and Building the Array…