Controllable downlights part two
Date posted:
In my original post I played around with a now discontinued tri-color (warm/cool/natural whites) LED downlight. And a DMX decoder LED driver, but was unable to obtain the correct DMX address writer and the seller wasn't responding. So I've moved onto RGB downlights and dip-switch address DMX decoder/driver - parts linked below.
Here's a video of the lights in action.
Hardware
- DMX decoder LED driver x1 per downlight
- 24VDC RGB downlight 18W (without RF wirless controller)
- 24VDC switch-mode mains adaptor “wall wart” - x1 per downlight
- EtherTen - Arduino Uno compatible with onboard Ethernet
- POE module for EtherTen
- DMX arduino shield
- DMX to CAT5 adaptor
Software
I've released my first Node JS package, for Node-RED. It converts HomeKit's light accessory's HSB values into RGB values. View on Node-RED website. To be used by the Arduino DMX controller.
Node-RED function node
Take the three RGB values and pad them into 3 digit values for use with the Arduino. And set the starting address. In this example it's address 001.
msg.payload[0] = String(msg.payload[0]).padStart(3, '0');
msg.payload[1] = String(msg.payload[1]).padStart(3, '0');
msg.payload[2] = String(msg.payload[2]).padStart(3, '0');
msg.payload = "001" + msg.payload[0] + "" + msg.payload[1] + "" + msg.payload[2];
return msg;
Arduino code
Convert MQTT string into 4 integer values. First address, Red value, Green value, Blue value.
Eg: 001255255255 = address: 001, red: 255, green: 255, blue: 255. Which assumes the addresses of 001, 002, 003; based on the given starting address.
#include <Ethernet.h>
#include <PubSubClient.h>
#include <DmxSimple.h>
// Ethernet https://www.miniwebtool.com/mac-address-generator/
byte device_mac[] = {0x16,0xB8,0x90,0x93,0x56,0xDA};
EthernetClient device;
// MQTT
IPAddress mqtt_server(XXX, XXX, XXX, XXX);
PubSubClient mqtt(device);
String mqtt_client = "dmx";
const char mqtt_user[] = "XXXX";
const char mqtt_pass[] = "XXXX";
const char mqtt_sub[] = "device/house/lights";
unsigned long mqtt_timmer = 0;
/**
* Main setup function, called once per boot
*
* @return void
*/
void setup()
{
// Configure DMX
DmxSimple.usePin(4);
DmxSimple.maxChannel(128);
// Dont know why this is needed for DMX to work with network?!
// Without this the DMX shield stops responding
Serial.begin(9600);
// Sync DMX shield with Arduino
pinMode (2, OUTPUT);
digitalWrite (2, HIGH);
// Just for visual testing of network connection
// All downlights 1% while we wait for network connection
for (int i = 0; i < 128; i++)
{
DmxSimple.write(i, 1);
}
// Wait for network connection
while (Ethernet.begin(device_mac) == 0)
{
delay(1000);
}
// Initializes the pseudo-random number generator
randomSeed(micros());
// Configure MQTT
mqtt.setServer(mqtt_server, 1883);
mqtt.setCallback(mqttReceive);
}
/**
* Main program loop
* @return void
*/
void loop()
{
// Keep network connection
Ethernet.maintain();
// Reconnect to MQTT server
if (!mqtt.connected())
{
if (!mqttReconnect())
{
// Exit early of loop
return;
}
}
// Check for incoming MQTT messages
mqtt.loop();
}
/**
* MQTT reconnect
* @return bool True on successful connection
* @return bool False on failure to connect
*/
void mqttReconnect()
{
// Only attempt every 1 seconds to prevent congesting the network
if (!getTimer(mqtt_timmer, 1000))
{
return false;
}
// Attempt to connect with random client name
if (mqtt.connect(String(mqtt_client + String(random(0xffff), HEX)).c_str(), mqtt_user, mqtt_pass))
{
// Subscribe to topic
mqtt.subscribe(mqtt_sub);
// Subscribed to topic: All downlights off
for (int i = 0; i < 128; i++)
{
DmxSimple.write(i, 0);
}
// Connection was successful
return true;
}
// Still unable to connect
return false;
}
/**
* Receive MQTT message
*
* @param char* topic The incomnig topic
* @param byte* payload The incoming message
* @param int leng Length of payload
* @return void
*/
void mqttReceive(char* topic, byte* payload, unsigned int leng)
{
char channel[4] = "";
char val1[4] = "";
char val2[4] = "";
char val3[4] = "";
// Determine starting channel value, eg: 001 - 512
for (int i = 0; i < 3; i++)
{
channel[i] = payload[i];
}
channel[3] = '\0';
// Determine first channel's value. Eg: 000 - 255
for (int i = 3; i < 6; i++)
{
val1[i - 3] = payload[i];
}
val1[3] = '\0';
// Determine second channel's value. Eg: 000 - 255
for (int i = 6; i < 9; i++)
{
val2[i - 6] = payload[i];
}
val2[3] = '\0';
// Determine third channel's value. Eg: 000 - 255
for (int i = 9; i < 12; i++)
{
val3[i - 9] = payload[i];
}
val3[3] = '\0';
// Apply the three channels
DmxSimple.write(atoi(channel), atoi(val1));
DmxSimple.write(atoi(channel) + 1, atoi(val2));
DmxSimple.write(atoi(channel) + 2, atoi(val3));
}
/**
* Determine if given timer has reached given interval
*
* @param unsigned long tmr The current timer
* @param int interval Length of time to run timer
* @return bool True when timer is complete
* @return bool False when timer is counting
*/
bool getTimer(unsigned long &tmr, int interval)
{
// Set initial value
if (tmr < 1)
{
tmr = millis();
}
// Determine difference of our timer against millis()
if (millis() - tmr >= interval)
{
// Complete. Reset timer
tmr = 0;
return true;
}
// Still needs to "count"
return false;
}