Hardware components | ||||||
| × | 1 | ||||
| × | 1 | ||||
| × | 1 | ||||
| × | 1 | ||||
| × | 1 | ||||
| × | 1 | ||||
| × | 1 | ||||
| × | 4 | ||||
| × | 4 | ||||
| × | 8 |
In LED strips world, there are (so) many models.
As explained on Wikipedia... "The most common design differences are in how individual LEDs are controlled, specifically differences in color and whether or not each LED is addressable.
- Single Color, non-addressable: Every LED on the strand is a single white colour, typically ranging from 2700K to 6500K in colour temperature, or any of several monochrome colors covering the range of the visible spectrum (generally from 400-700 nanometers in wavelength).
- Multicolor, non addressable: Each LED is capable of displaying red, green, blue, or all three (white), driven by three input power rails. All the LEDs display the same colour at any one time, but the colour can be manipulated by varying the voltage applied to each of the three power inputs.
- RGB, addressable: Multiple colours and addresses. Each LED has its own chip meaning they can be individually triggered for chasing, strobing, and colour changing"
As a generic rule of thumb, you can consider that "cheap RGB LED strip" = "non addressable". There are numerous tutorials on addressable LED strips but not that much on simple, non addressable ones.
LED StripHere is how my LED strip looks like. The strip is divided into segments, each segment hosting 3 RGB LEDs:
Here is how my LED strip is wired, internally:
Some models share a common POSITIVE, other shares a common NEGATIVE. My LED strip is as per the following:
Input Voltage
The next topic to consider is .... input voltage. LED strip lights most commonly operate on 12 or 24 volts DC from a power supply. Mine was 12 VDC. Depending on the strip length, you should pay attention to current as well and select a power supply that can handle LED strip current requirements.
If you just want to turn LED strip on/off (as a whole or per color), relays are okay and easy to setup but if you want to control intensity as well (dimming), then MOSFETs are a great solution.
In order to control colors (and intensity), I need to control A1, A2 and A3. The LED strip requires 12 VDC feed and, even if you had a 5 VDC LED strip, it would require to much current to be controlled directly, that's why I need some kind of circuit based on transistors.
Control CircuitHere is how my control circuit looks like (duplicate it 3 times, one for each color):
Here is how it looks like once integrated in a box:
- This tutorial is about "how to control LED strips". In the picture, you may notice that it does more than that and you are totally right. I will come back with other tutorials with more information on this.
- The LED strip control circuit is on the right: 3 MOSFETS are used to control a RGB LED strip and there is a fourth one to control another B/W LED strip.
Arduino script
C/C++#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;
}
Comments