Home Automation blog

Skip to Content

Controllable downlights part two

Posted by Chris

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

RGB downlightDMX slavesDMX housing

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;
}

View similar posts categorised as: Arduino Node RED HomeKit