Philippe Libioulle
Published © GPL3+

RGB LED Strips Controller

You just bought a RGB LED strip kit and you would like to get rid of that cheap hand-held IR remote and to have REAL control.

IntermediateFull instructions provided8 hours52,816
RGB LED Strips Controller

Things used in this project

Hardware components

Aluminium enclosure
https://www.aliexpress.com/item/1-piece-power-aluminium-extruded-enclosure-electrical-junction-box-for-pcb-48-204-160mm/32548295254.html
×1
Arduino UNO
Arduino UNO
×1
Arduino Ethernet Shield 2
Arduino Ethernet Shield 2
×1
12 VDC power supply
https://www.aliexpress.com/item/30W-Dual-output-5V-12V-Switching-power-supply-AC-to-DC/628632725.html
×1
AC housing
https://www.aliexpress.com/item/New-Hot-5-Pcs-3P-IEC-320-C14-Male-Plug-Panel-Power-Inlet-Sockets-Connectors-AC/32656480250.html
×1
JST connectors
https://www.aliexpress.com/item/100sets-lot-SM-2-3-4-5-6Pin-2-54-Female-and-Male-JST-2-54MM/32351785873.html
×1
Extension PCB for Arduino
https://www.aliexpress.com/item/I-O-Extension-PCB-for-UNO-R3-Board-DIY-bte16-08/32676031613.html?spm=2114.13010608.0.0.Pb4lDe
×1
FQP47P06 MOSFET
×4
2N3904 transistor
×4
Resistor 1k ohm
Resistor 1k ohm
×8

Story

Read more

Code

Arduino script

C/C++
Note: the script does much more than controlling LED strips, just focus on that part for this tutorial
#include <ArduinoJson.h>
#include <avr/wdt.h>
#include <Ethernet.h>
#include <Network.h>
#include <PubSubClient.h>
#include <QueueArray.h>
#include <SPI.h>
#include <Timer.h>

/* Here is how the HW works

Therea are three subsystems:
- the main box :
    - an Arduino Uno with and Ethernet shield
    - an orange LED (alarm) 
          - steady when water is detected 
          - blinking when ...                  
          - off otherwise
    - a pink LED (fault)
          - blinking when MQTT broker is unreachable (for any reason),
		  - off otherwise
    - a blue LED (system) 
          - steady when system is monitoring 	                                        
          - blinking when commands are received from MQTT
		  - off is system is down
    - a push button : once pressed, a self-test is triggered 
    - a set of 3 MOSFETs to control a strip of RGB LEDs    
    - a MOSFET that control a strip of WHITE LEDs    
- a set of water detectors (all in parallel) 
- a set of 3 12VDC ligthing fixtures 
- a RGB led strip , 12VDC as well

*/
// Network
byte mac[] = { 0xDE, 0xAD, 0xBE, 0xAE, 0xFC, 0xEA };      // Arduino's MAC address
IPAddress ip(192, 168, 1, 218);                           // Arduino's IP address
IPAddress server(192, 168, 1, 100);                       // MQTT broker's address
EthernetClient ethClient;

// MQTT 
PubSubClient client(ethClient); 
#define mqttClientPrefix "GLX"                            // Prefix to use any MQTT publication/subscription 
#define mqttClientLocation "GROUNDFLOOR"                  // Second part of the client identifier
#define mqttClientUID "001"                               // Last part of the client identifier
#define mqttClientStatusTopic "Status"                    // Topic to be used to publish device status 
#define mqttClientFaultTopic "Fault"                      // Topic to be used to publish/subscribe to Faults
const int mqttInterval = 30;                              // determines how often the system will report to MQTT broker (ie. every mqttInterval * mainLoopDelay ms )
int mqttIntervalCnt = 0;                                  // local variable used to count down
int isConnectedToBroker = -1;                             // 1 when connected, -1 = unknown, 0 = unable to connected

// Pin-out
const int SystemLedPin = A0;                              // Blue led 
const int FaultLedPin = A1;                               // Pink led 
const int AlarmLedPin = A2;                               // Orange led 
const int ToggleButtonPin = 3;                            // goes to LOW when someone press on the button and then goes to HIGH when button is released, otherwise pull-down to GND
const int SdCardPin = 4;                                  // SD card on ethernet shield, not used
const int WaterDetectorPin = 7;                           // goes to LOW when water is detected, otherwise pulled-up to VCC
const int LEDFixturesCtrlPin = 2;                         // to control a set of 3 white LEDs
const int LEDStripRedControlPin = 5;                      // to control Red color 
const int LEDStripGreenControlPin = 6;                    // to control Green color
const int LEDStripBlueControlPin = 9;                     // to control Blue color
                                                          // Note do not use D10, D11, D12 and D13 since those pins are reserved for Ethernet shield
// WaterLeakage (local)                                                           
int isWaterDetected = 0;                                  // status as per the last good reading (0 = no water, 1 = water leakage detected)
                     
// Local queue
volatile QueueArray <byte> cmdQueue;                      // each command received from MQTT broker is stored here
byte* incoming;                                           // an incoming message to route to Insteon network
int outByte[26]= {0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0};     // a structured message with ligthing commands

// Manual RESET button
volatile boolean isResetRequested = 0;                    // this one will change when button triggers an interrupt

// Logic
const int mainLoopDelay = 500;                            // a fixed delay within main loop, in ms
void(* resetFunc) (void) = 0;
const int verbosity = 0;  
bool isKaraokeMode = 0;                                   // 0 = No, 1 = yes, repeat same sequence continuously

// Initialization 
void setup()
{
  wdt_disable(); //always good to disable it, if it was left 'on' or you need init time
  Serial.begin(19200);
  Serial.println(F("Begin of setup"));   
  // HW setup
  pinMode (SystemLedPin, OUTPUT);
  pinMode (FaultLedPin, OUTPUT);
  pinMode (AlarmLedPin, OUTPUT);
  pinMode (ToggleButtonPin, INPUT_PULLUP); 
  pinMode(SdCardPin, OUTPUT);
  digitalWrite(SdCardPin, HIGH); // to disable SD card since we do not use it
  pinMode (WaterDetectorPin, INPUT);
  pinMode (LEDFixturesCtrlPin, OUTPUT);
  pinMode (LEDStripRedControlPin, OUTPUT);
  pinMode (LEDStripGreenControlPin, OUTPUT);
  pinMode (LEDStripBlueControlPin, OUTPUT);
  digitalWrite(LEDFixturesCtrlPin, LOW);  
  analogWrite(LEDStripRedControlPin, LOW);  
  analogWrite(LEDStripGreenControlPin, LOW);  
  analogWrite(LEDStripBlueControlPin, LOW);  
  
  cmdQueue.setPrinter (Serial);                                    // setup local queue  
  
  // Self test
  testLeds(); 
  testLEDstrip();
  testLEDFixtures(); 
  
  // Network and MQTT setup
  client.setServer(server, 1883);
  client.setCallback(MQTTBrokerCallback);
  Ethernet.begin(mac, ip);
  Serial.print(F("Current IP is : "));
  Serial.print(Ethernet.localIP());  
  Serial.print(F(" - MQTT broker IP is : "));
  Serial.println(server);  
  enableInterruptOnResetButton(); 
  subscribeToToDoLists();                                           // from here, callbacks are trapped   
  delay(1500);  // allow hardware to sort itself out  
  Serial.println(F("End of setup"));       
}

// Main loop
void loop()
{  
  // LEDs
  configureLedsWithInitialStates(); 
  
  // Part 1 : react to reset request
  if (isResetRequested == 1)
  {
     Serial.println(F("Someone pushed on the button to reset this device")); 
     publishStatus();  
     wdt_enable(WDTO_1S); //enable watchdog, will fire in 1 second   
     delay(5000); 
     Serial.println(F("this message should never appear")); 
  }  
  
  // Part 2 : check now whether any water leakage has been detected
  readLocalWaterSensor();     
  if (isWaterDetected == 1) 
  {
     publishWaterFault();
  }  

  // Part 3 : process messages originating from MQTT Broker. Those messages are waiting in a local queue
  while (!cmdQueue.isEmpty ())
  {
     if (parseMessage() == 0)
     {
        processLigthingCommand(); 
     };
  }    
  
  // Part 4 : report device status to MQTT broker
  
  if (mqttIntervalCnt == 0)
  { 
      if (isWaterDetected == 1){ publishWaterFault();}     
      publishStatus();        
      mqttIntervalCnt = mqttInterval;
  }    
  else
  {                   
      mqttIntervalCnt = mqttIntervalCnt - 1;      
  }  
  
  // Part 5 : play continuous sequence (in Karaoke mode only)
  if (isKaraokeMode) 
  { 
     playKaraoke();
  }
  else
  {
     delay(mainLoopDelay / 2 );  // Take some rest
  }
  client.loop();  
  
  // LEDs
  configureLedsWithFinalStates(); 
  displayProgressBar();
  delay(mainLoopDelay / 2);    
}

//
// Lighting commands management
//

void processLigthingCommand()
{
   if (outByte[0] != 0x0A) 
   {
       Serial.print(F("Unknown lighting command type: ")); 
	     Serial.println(outByte[0]);
       return;	   
   }
   isKaraokeMode = 0; 
   switch(outByte[2])
   {
       case 0x0B:                // On command
		       turnOn();
		       break;
		   case 0x0D:                // Off command
		       turnOff();
		       break;
		   case 0xAA:                // Karaoke command
		       isKaraokeMode = 1; 
		       playKaraoke();
		       break;
		   default:
		       Serial.print(F("Unknown command: ")); 
           Serial.println(outByte[2]);		   
   }   
}

void testLEDFixtures()
{
  Serial.println(F("Testing LED fixture - setting to LOW"));  
  digitalWrite(LEDFixturesCtrlPin, HIGH); 
  delay(1000);
  Serial.println(F("Testing LED fixture - setting to HIGH"));  
  digitalWrite(LEDFixturesCtrlPin, LOW);  
}

void testLEDstrip()
{
  Serial.println(F("Testing LED strip")); 
  setLEDStripColor(255, 0, 0);
  delay(200);
  setLEDStripColor(0, 0, 0);
  delay(200);    
  setLEDStripColor(0, 255, 0);  
  delay(200);
  setLEDStripColor(0, 0, 0);
  delay(200);    
  setLEDStripColor(0, 0, 255);
  delay(200);
  setLEDStripColor(0, 0, 0);
  Serial.println(F("End of testing LED strip")); 
}

void setLEDStripColor(int red, int green, int blue)
{
  analogWrite(LEDStripRedControlPin, red);
  analogWrite(LEDStripGreenControlPin, green);
  analogWrite(LEDStripBlueControlPin, blue); 
}

void turnOff()
{
   if (outByte[1] == 0x0A || outByte[1] == 0x00) { setLEDStripColor(0, 0, 0);}
   if (outByte[1] == 0x0B || outByte[1] == 0x00) { digitalWrite(LEDFixturesCtrlPin, LOW);}
}
void turnOn()
{
   if (outByte[1] == 0x0A ) { setLEDStripColor(outByte[3], outByte[4], outByte[5]);}
   if (outByte[1] == 0x0B ) { digitalWrite(LEDFixturesCtrlPin, LOW);}
}
void playKaraoke()
{
   if (outByte[1] == 0x0A ) 
   {    
        
	  for (int i=0;i<255;i++)                     // Changing Red brightness
      {
         setLEDStripColor(i, 0, 0);
		 for (int i=0;i<255;i++)                  // Changing Green brightness
         {
            setLEDStripColor(0, i, 0);
			for (int i=0;i<255;i++)               // Changing Blue brightness
            {
               setLEDStripColor(0, 0, i);
               delay (10);
            }
            delay (10);
         }
         delay (10);
      }  
   }
   else
   {
      Serial.print(F("Karaoke mode not supported by device : ")); 
	    Serial.println(outByte[1]);	
   }
}

//
// Local water sensor management
//
void readLocalWaterSensor()
{         
     isWaterDetected = !getDebouncedValue(WaterDetectorPin, 100, 10);  
     //Serial.print("Water : ");    
     //Serial.println(isWaterDetected); 
}

//
// Reset button management
//
void enableInterruptOnResetButton()
{
    isResetRequested = 0;
    attachInterrupt(1, onResetRequested, CHANGE);
}
void onResetRequested()
{
    detachInterrupt(1);
    isResetRequested = 1;
}

//
// Front panel LEDs management
//
void configureLedsWithInitialStates()
{
  clearLeds();
  // Re-evaluate
  if (isWaterDetected == 1 ) { digitalWrite(AlarmLedPin, HIGH);};
  if (isConnectedToBroker == 0) { digitalWrite(FaultLedPin, HIGH);}; 
  digitalWrite(SystemLedPin, HIGH);
}
void configureLedsWithFinalStates()
{      
  if (isConnectedToBroker == 0) { digitalWrite(FaultLedPin, LOW);};       
}
void clearLeds()
{
  digitalWrite(AlarmLedPin, LOW);
  digitalWrite(FaultLedPin, LOW);
  digitalWrite(SystemLedPin, LOW);
}
void testLeds()
{
  clearLeds();
  digitalWrite(AlarmLedPin, HIGH);
  delay(1000);
  digitalWrite(FaultLedPin, HIGH);
  delay(1000);
  digitalWrite(SystemLedPin, HIGH);
  delay(1000);
  clearLeds();
  delay(1000);
}

//
// MQTT related functions
//

// Subscribe to the MQTT broker to get list of commands to forward to Insteon devices (topic = GLI/Gateway/ToDo)

void subscribeToToDoLists()   
{ 
  if (connectToBroker() == true)
  {   	
	 char topic[50];
	 strcpy(topic, "GLX");  
     strcat(topic, "/");
     strcat(topic, "KitchenLighting");  
     strcat(topic,"/");
     strcat(topic, "ToDo");  
	 client.subscribe(topic);                  // otherwise subscriptions will growth forever..
     if (client.subscribe(topic) == true)
     {
        isConnectedToBroker = 1;    
        Serial.print(F("Registred to MQTT broker as a subscriber for the following topic: ")); 
        Serial.println(topic);
     }
     else
     {     
        Serial.println(F("Not registred to MQTT broker as a subscriber")); 
        isConnectedToBroker = 0;
     }    
     client.loop();          
  }
  else
  {
    isConnectedToBroker = 0;
	  Serial.println(F("Cannot subscribe to any topic since connection to MQTT broker is not established"));
  } 
}

// Handle incoming MQTT messages

void MQTTBrokerCallback(char* subscribedTopic, byte* payload, unsigned int msgLength)
{
  Serial.print(F("New message received from MQTT broker. Topic = "));
  Serial.print(subscribedTopic);
  Serial.print(F(", Length = "));
  Serial.println(msgLength);
  cmdQueue.enqueue('0');
  cmdQueue.enqueue('2');
  for (int i=0;i<msgLength;i++)
  {     
     cmdQueue.enqueue(payload[i]);   // store msg in local Queue     
  }  
  Serial.println();   
}

// Build the client identifier

String buildClientIdentifier()
{
  String data = mqttClientPrefix;
  data+="_";
  data+= mqttClientLocation;
  data+="_";
  data+= mqttClientUID; 
  return data; 
}

// Build the topic name to be used to publish status

String buildDeviceStatusTopic()
{
  String data = mqttClientPrefix;
  data+="/";
  data+=mqttClientLocation;
  data+="/";
  data+=mqttClientUID; 
  data+="/";
  data+=mqttClientStatusTopic;  
  return data; 
}

// Build the topic name to be used to publish/subscribe to Faults 

String buildFaultTopic()
{
  String data = mqttClientPrefix;
  data+="/";
  data+=mqttClientLocation;
  data+="/";
  data+=mqttClientUID; 
  data+="/";
  data+=mqttClientFaultTopic;  
  return data; 
}

// Build the topic name to be used to publish/subscribe to Faults 

String buildAnyFaultTopic()
{
  String data = mqttClientPrefix;
  data+="/";
  data+="+";
  data+="/";
  data+="+"; 
  data+="/";
  data+=mqttClientFaultTopic;  
  return data; 
}

// Build a JSON message to send to MQTT Broker
// NOTE : MQTT_MAX_PACKET_SIZE = 128 bytes.. therefore not more than 100 for the payload 
//        unless you change it in /Arduino/libraries/pubSubClient/src/PubSubClient.h

String buildDeviceStatusJson()
{ 
  String data = "{";
  data+="\n"; 
  data+="\"ResetByOperator\": ";
  data+=(int)isResetRequested;
  data+= ",";
  data+="\n"; 
  data+="\"WaterDetected\": ";
  data+=(int)isWaterDetected;  
  data+="\n";
  data+="}"; 
  return data; 
}

// Build a JSON message to send to MQTT Broker
// NOTE : MQTT_MAX_PACKET_SIZE = 128 bytes.. therefore not more than 100 for the payload 
//        unless you change it in /Arduino/libraries/pubSubClient/src/PubSubClient.h

String buildWaterFaultJson()
{ 
  String data = "{";
  data+="\n"; 
  data+="\"WaterDetected\": ";
  data+=(int)isWaterDetected;
  data+= ","; 
  data+="\n";
  data+="\"Location\": ";
  data+=mqttClientLocation;
  data+="\n";
  data+="}"; 
  return data; 
}

// Report to MQTT broker

void publishMsg(char (&topic)[200], char (&payload)[200] )  
{
  if (connectToBroker() == true)
  {       
     if (client.publish(topic, payload) == true)
     {
        isConnectedToBroker = 1;    
        Serial.print(F("Message sent to MQTT broker using the following topic "));  
        Serial.println(topic);
     }
     else
     {     
        Serial.print(F("Message NOT sent to MQTT broker using the following topic "));  
        Serial.println(topic);
        isConnectedToBroker = 0;
     }    
     client.loop();      
  }
}

// Report faults to MQTT broker

void publishWaterFault()
{
	// Topic
	char topicBuffer[200];
	buildFaultTopic().toCharArray(topicBuffer, 200);
	// Payload
	char payloadBuffer[200];
	buildWaterFaultJson().toCharArray(payloadBuffer, 200); ;
	// Publish message
	publishMsg(topicBuffer, payloadBuffer);
}

// Report faults to MQTT broker

void publishStatus()
{
	// Topic
	char topicBuffer[200];
	buildDeviceStatusTopic().toCharArray(topicBuffer, 200);
	// Payload
	char payloadBuffer[200];
	buildDeviceStatusJson().toCharArray(payloadBuffer, 200); ;
	// Publish message
	publishMsg(topicBuffer, payloadBuffer);
}

// Manage connection with MQTT broker

int connectToBroker() 
{
  Serial.println(F(""));        
  Serial.print(F("Connecting to network and to MQTT Broker... "));    
  char tempBuffer[200];
  buildClientIdentifier().toCharArray(tempBuffer,200);
  if (client.connect(tempBuffer) == true)
  {        
    Serial.print(F("connected as ")); 
    Serial.println(tempBuffer);
  } 
  else
  {
     switch (client.state())
     {
        case -4:
          Serial.println(F("MQTT_CONNECTION_TIMEOUT - the server didn't respond within the keepalive time"));
          break;
        case -3:
          Serial.println(F("MQTT_CONNECTION_LOST - the network connection was broken"));
          break;
        case -2:
          Serial.println(F("MQTT_CONNECT_FAILED - the network connection failed"));
          break;
        case -1:
          Serial.println(F("MQTT_DISCONNECTED - the client is disconnected cleanly"));
          break;
        case 0:
          break;
        case 1:
          Serial.println(F("MQTT_CONNECT_BAD_PROTOCOL - the server doesn't support the requested version of MQTT"));
          break;
        case 2:
          Serial.println(F("MQTT_CONNECT_BAD_CLIENT_ID - the server rejected the client identifier"));
          break;
        case 3:
          Serial.println(F("MQTT_CONNECT_UNAVAILABLE - the server was unable to accept the connection"));
          break;
        case 4:
          Serial.println(F("MQTT_CONNECT_BAD_CREDENTIALS - the username/password were rejected"));
          break;
        case 5:
          Serial.println(F("MQTT_CONNECT_UNAUTHORIZED - the client was not authorized to connect"));
          break;
        default:
          Serial.print("failed, rc=");
          Serial.println(client.state()); 
          break;        
     }
  }
  return client.connected();    
}

// Local queue management
// Parse a message stored in the local queue

int parseMessage()
{
  byte b;
  int hex_digit_to_read = 2;
  bool reading_hex = true;
  byte hex = 0;
  for(int j=0;j<26;j++) outByte[j] = 0xFF;
  int j;
  while (!cmdQueue.isEmpty ())
  { 
     b = cmdQueue.dequeue(); 
     if (b == ':')   // delimiter between each set of 2 characters 
     {
         hex = 0;
         hex_digit_to_read = 2;
         reading_hex = true;
         continue;
     };
     if (hex_digit_to_read > 0)  
     {
        hex = (hex << 4) | CharToHex(b);
        hex_digit_to_read--;
     };
     if (reading_hex && hex_digit_to_read == 0)
     {
        reading_hex = false;
        if (hex == 0x02)   // end of message
        {
          hex_digit_to_read = 2;
          reading_hex = true;
          hex = 0;
          j = 0;
          return 0;
        }
        else
        {
          outByte[j] = hex;
          Serial.print(hex, HEX);
          Serial.print(' ');
          j++;
        };
     };     
  };
}

//
// Utilities
//

// This routine helps to avoid false alarms

int getDebouncedValue(int inputPin, int intervalInMs, int requiredConfirmations)
{
    int confirmations = 1;
    int currentValue = digitalRead(inputPin);
    while (confirmations <= requiredConfirmations)
    {      
      delay(intervalInMs);
      if (currentValue == digitalRead(inputPin)) 
      {
         confirmations = confirmations + 1;
      }
      else
      {       
        confirmations = 1;
        currentValue = digitalRead(inputPin);
      }
      //Serial.print(currentValue);
    }  
    return currentValue;
}

// A simple routine that helps to format serial log

void displayProgressBar()
{
    if (isWaterDetected == 1) 
    {
        Serial.print(F("W"));        
    }
    else
    {
        Serial.print(F("."));        
    };      
}

byte CharToHex(char c)
{
    byte out = 0;
    if( c >= '0' && c <= '9'){
        out = (byte)(c - '0');
    } 
    else if ( c >= 'A' && c <= 'F'){
        out = (byte) (c - 'A') + 10;
    }
    else if ( c >= 'a' && c <= 'f'){
        out = (byte) (c - 'a') + 10;
    }

    return out;
}

Credits

Philippe Libioulle

Philippe Libioulle

7 projects • 49 followers

Comments