Click here to Skip to main content
15,883,848 members
Articles / Internet of Things / Arduino

Simulating and controlling GE Color Effects Lights with Arduino

Rate me:
Please Sign up or sign in to vote.
4.80/5 (13 votes)
27 Nov 2012CPOL13 min read 137K   1.4K   25  
Simulating and Controlling GE Color Effects Lights with Arduino
// Errors are 6.x

#include "global.h"
#include "GEColorEffectsBulb.h"

// Bit encoding
//
// A 1 is encoded as 001
// A 0 is encoded as 011

// Global prep area for preparing the data we are going to send to the bulbs
byte _prep[82];

// preset random brightnesses
static const byte _brightnesses[]
#if defined(PC) || defined(RASPBERRY)
#else
	PROGMEM
#endif
= {  0, 50, 100, 150, 200 };

// preset random colours
static const short _colours[]
#if defined(PC) || defined(RASPBERRY)
#else
	PROGMEM
#endif
//  red    green  blue   yellow orange violet white  magenta cyan
= { 0xF00, 0x0F0, 0x00F, 0xF80, 0xF20, 0x80F, 0xDDD, 0xF0F, 0x0FF };

// Get a random brightness
byte Bulb::RandomBrightness()
{
	// pick one the the available brightnesses
	#if defined(PC) || defined(RASPBERRY)
		return _brightnesses[rand() % 5];
	#else
		return pgm_read_byte_near(_brightnesses + random(5));
	#endif
}

// Get a random bulb
byte Bulb::RandomBulb(byte bulbs)
{
	#if defined(PC) || defined(RASPBERRY)
		return (byte)(rand() % bulbs);
	#else
		return (byte)random(bulbs);
	#endif
}

// Get a random colour
short Bulb::RandomColour()
{
	// pick one the the available colours
	#if defined(PC) || defined(RASPBERRY)
		// return the number of parameters for the provided command type
		return _colours[rand() % 9];
	#else
		return pgm_read_word_near(_colours + random(9));
	#endif
}

// Get the red component of the bulb colour
byte NonDisplayableBulb::Red(short colour)
{
	return (byte)((colour & 0xF00)>>8);
}

// Get the green component of the bulb colour
byte NonDisplayableBulb::Green(short colour)
{
	return (byte)((colour & 0xF0)>>4);
}

// Get the blue component of the bulb colour
byte NonDisplayableBulb::Blue(short colour)
{
	return (byte)(colour & 0x0F);
}

// create a bulb
void Bulb::Construct(byte string, byte address, short colour, byte brightness)
{
	#ifdef INCLUDE_PARAM_CHECK
	if ((colour & 0xF000) > 0)
	{
		#ifdef MINMEMORY
			_platform->WriteMessage(MSG_CRITICAL,"6.1\n");
		#else
			_platform->WriteMessage(MSG_CRITICAL, "Bulb::Construct Invalid colour 0x%X.\n", colour);
		#endif
		_platform->Error(6);
	}
	#endif

	// save the values
	_string = string;
	_address = address;
	_colour = colour;
	_brightness = brightness;

	// creating a bulb is a change so set the changed flag
	SetChanged();
}

// create a bulb
void NonDisplayableBulb::Construct(short colour, byte brightness)
{
	// save the values
	_colour = colour;
	_brightness = brightness;

	// dont need to set changed as these bulbs can't be displayed
}

// Get the bulb brightness
byte NonDisplayableBulb::Brightness()
{
	return _brightness;
}

// Set the bulb brightness
void NonDisplayableBulb::SetBrightness(byte brightness)
{
	byte b = brightness;

	// if greater than maximum make it the maximum
	if (brightness > 0xCC)
	{
		// 0xCC is the highest value allowed
		b = 0xCC;
	}

	if (b == _brightness)
	{
		return;
	}

	// save the new brightness
	_brightness = b;

	// it has been changed
	SetChanged();
}

// Get the bulb colour
short NonDisplayableBulb::Colour()
{
	// return the colour without the changed flag bit
	return (_colour & 0x0FFF);
}

// set the bulb colour
void NonDisplayableBulb::SetColour(short colour)
{
	#ifdef INCLUDE_PARAM_CHECK
	if ((colour & 0xF000) > 0)
	{
		#ifdef MINMEMORY
			_platform->WriteMessage(MSG_CRITICAL,"6.2\n");
		#else
			_platform->WriteMessage(MSG_CRITICAL, "NonDisplayableBulb::SetColour Invalid colour 0x%X.\n", colour);
		#endif
		_platform->Error(6);
	}
	#endif

	// if the colour has changed
	if (Colour() != colour)
	{
		// save the new colour
		_colour = colour;

		// flag the bulb as changed
		SetChanged();
	}
}

// flag a bulb as having changed
void NonDisplayableBulb::SetChanged()
{
	// set the high bit in the colour
	_colour = _colour | 0x8000;
}

// flag a bulb as not having changed
void NonDisplayableBulb::ClearChanged()
{
	// clear the high bit in the colour
	_colour = _colour & 0x7FFF;
}

// check the bulb changed flag
bool NonDisplayableBulb::IsChanged()
{
	// check the high bit
	return ((_colour & 0x8000) > 0);
}

// converts a non zero value into an inverted bit ... this is done because the middle bit in an encoded bit is the inverse of the bit value
inline byte Bulb::GetBit(short i)
{
	return (i==0);
}

// converts a non zero value into an inverted bit ... the big difference here is the 1 value returned is positional within a byte and is passed in
inline byte Bulb::GetParallelBit(short bit, byte one)
{
	if(bit)
	{
		return 0;
	}
	else
	{
		return one;
	}
}

// populates the _prep buffer with the command string for 8 bulbs
void Bulb::PopulateMultiBulbBits(Bulb* bulbs[])
{
	// clear the buffer
	memset(_prep, 0x00, 82);

	// look at each string
	for (byte i = 0; i < MAXSTRINGS; i++)
	{
		// if the string has a bulb at this position and it has changed
		if (bulbs[i] != NULL && bulbs[i]->IsChanged())
		{
			#ifdef INCLUDE_MSG_SUPERDETAIL
				#ifdef MINMEMORY
					_platform->WriteMessage(MSG_SUPERDETAIL,"6.3\n");
				#else
					_platform->WriteMessage(MSG_SUPERDETAIL, "String %d Sending bulb %d brightness %d red %d green %d blue %d.\n", i, bulbs[i]->Address(), bulbs[i]->Brightness(), NonDisplayableBulb::Red(bulbs[i]->Colour()), NonDisplayableBulb::Green(bulbs[i]->Colour()), NonDisplayableBulb::Blue(bulbs[i]->Colour()));
				#endif
			#endif

			// create a one value which will be at the right position in the byte
			byte one = 0x80 >> i;

			// Start Bit
			_prep[3]  += one;

			// Address
			_prep[5]  += GetParallelBit(bulbs[i]->Address() & 0x20, one);
			_prep[6]  += one;

			_prep[8]  += GetParallelBit(bulbs[i]->Address() & 0x10, one);
			_prep[9]  += one;

			_prep[11] += GetParallelBit(bulbs[i]->Address() & 0x08, one);
			_prep[12] += one;

			_prep[14] += GetParallelBit(bulbs[i]->Address() & 0x04, one);
			_prep[15] += one;

			_prep[17] += GetParallelBit(bulbs[i]->Address() & 0x02, one);
			_prep[18] += one;

			_prep[20] += GetParallelBit(bulbs[i]->Address() & 0x01, one);
			_prep[21] += one;

			// Brightness
			_prep[23] += GetParallelBit(bulbs[i]->Brightness() & 0x80, one);
			_prep[24] += one;

			_prep[26] += GetParallelBit(bulbs[i]->Brightness() & 0x40, one);
			_prep[27] += one;

			_prep[29] += GetParallelBit(bulbs[i]->Brightness() & 0x20, one);
			_prep[30] += one;

			_prep[32] += GetParallelBit(bulbs[i]->Brightness() & 0x10, one);
			_prep[33] += one;

			_prep[35] += GetParallelBit(bulbs[i]->Brightness() & 0x08, one);
			_prep[36] += one;

			_prep[38] += GetParallelBit(bulbs[i]->Brightness() & 0x04, one);
			_prep[39] += one;

			_prep[41] += GetParallelBit(bulbs[i]->Brightness() & 0x02, one);
			_prep[42] += one;

			_prep[44] += GetParallelBit(bulbs[i]->Brightness() & 0x01, one);
			_prep[45] += one;

			// Blue
			_prep[47] += GetParallelBit(bulbs[i]->Colour() & 0x0008, one);
			_prep[48] += one;

			_prep[50] += GetParallelBit(bulbs[i]->Colour() & 0x0004, one);
			_prep[51] += one;

			_prep[53] += GetParallelBit(bulbs[i]->Colour() & 0x0002, one);
			_prep[54] += one;

			_prep[56] += GetParallelBit(bulbs[i]->Colour() & 0x0001, one);
			_prep[57] += one;

			// Green
			_prep[59] += GetParallelBit(bulbs[i]->Colour() & 0x0080, one);
			_prep[60] += one;

			_prep[62] += GetParallelBit(bulbs[i]->Colour() & 0x0040, one);
			_prep[63] += one;

			_prep[65] += GetParallelBit(bulbs[i]->Colour() & 0x0020, one);
			_prep[66] += one;

			_prep[68] += GetParallelBit(bulbs[i]->Colour() & 0x0010, one);
			_prep[69] += one;

			// Red
			_prep[71] += GetParallelBit(bulbs[i]->Colour() & 0x0800, one);
			_prep[72] += one;

			_prep[74] += GetParallelBit(bulbs[i]->Colour() & 0x0400, one);
			_prep[75] += one;

			_prep[77] += GetParallelBit(bulbs[i]->Colour() & 0x0200, one);
			_prep[78] += one;

			_prep[80] += GetParallelBit(bulbs[i]->Colour() & 0x0100, one);
			_prep[81] += one;
		}
	}
}

// populates the _prep buffer with the command string for 1 bulb
void Bulb::PopulateOneBulbBits()
{
	// clear the buffer
	memset(_prep, 0x00, 82);

	// if the bulb hasnt changed then dont send anything!
	if (!IsChanged())
	{
		return;
	}

	#ifdef INCLUDE_MSG_SUPERDETAIL
		#ifdef MINMEMORY
			_platform->WriteMessage(MSG_SUPERDETAIL,"6.4\n");
		#else
			_platform->WriteMessage(MSG_SUPERDETAIL, "String %d Sending bulb %d brightness %d red %d green %d blue %d.\n", (short)_string, (short)_address, (short)_brightness, ((Colour() & 0xF00) >> 8), ((Colour() & 0xF0)) >> 4, Colour() & 0x0F);
		#endif
	#endif

	// Start Bit
	_prep[3] = 1;

	// Address
	_prep[5] = GetBit(_address & 0x20);
	_prep[6] = 1;

	_prep[8] = GetBit(_address & 0x10);
	_prep[9] = 1;

	_prep[11] = GetBit(_address & 0x08);
	_prep[12] = 1;

	_prep[14] = GetBit(_address & 0x04);
	_prep[15] = 1;

	_prep[17] = GetBit(_address & 0x02);
	_prep[18] = 1;

	_prep[20] = GetBit(_address & 0x01);
	_prep[21] = 1;

	// Brightness
	_prep[23] = GetBit(_brightness & 0x80);
	_prep[24] = 1;

	_prep[26] = GetBit(_brightness & 0x40);
	_prep[27] = 1;

	_prep[29] = GetBit(_brightness & 0x20);
	_prep[30] = 1;

	_prep[32] = GetBit(_brightness & 0x10);
	_prep[33] = 1;

	_prep[35] = GetBit(_brightness & 0x08);
	_prep[36] = 1;

	_prep[38] = GetBit(_brightness & 0x04);
	_prep[39] = 1;

	_prep[41] = GetBit(_brightness & 0x02);
	_prep[42] = 1;

	_prep[44] = GetBit(_brightness & 0x01);
	_prep[45] = 1;

	// Blue
	_prep[47] = GetBit(_colour & 0x0008);
	_prep[48] = 1;

	_prep[50] = GetBit(_colour & 0x0004);
	_prep[51] = 1;

	_prep[53] = GetBit(_colour & 0x0002);
	_prep[54] = 1;

	_prep[56] = GetBit(_colour & 0x0001);
	_prep[57] = 1;

	// Green
	_prep[59] = GetBit(_colour & 0x0080);
	_prep[60] = 1;

	_prep[62] = GetBit(_colour & 0x0040);
	_prep[63] = 1;

	_prep[65] = GetBit(_colour & 0x0020);
	_prep[66] = 1;

	_prep[68] = GetBit(_colour & 0x0010);
	_prep[69] = 1;

	// Red
	_prep[71] = GetBit(_colour & 0x0800);
	_prep[72] = 1;

	_prep[74] = GetBit(_colour & 0x0400);
	_prep[75] = 1;

	_prep[77] = GetBit(_colour & 0x0200);
	_prep[78] = 1;

	_prep[80] = GetBit(_colour & 0x0100);
	_prep[81] = 1;
}

// Display bulbs on multiple strings in parallel
void Bulb::DisplayParallel(Bulb* bulbs[])
{
	bool changed = false; // on of the bulbs has changed

	// look at each string
	for (byte i = 0; i < MAXSTRINGS; i++)
	{
		// if the bulb exists && it has changed
		if (bulbs[i] != NULL && bulbs[i]->IsChanged())
		{
			// as long as one of them is changed I need to display it
			changed = true;
			break;
		}
	}

	// only if something has changed
	if (changed)
	{
		// prepare the buffer for display
		Bulb::PopulateMultiBulbBits(bulbs);

		// try to clear any work which might interrupt us
		#if defined(RASPBERRY) && defined(DOYIELD)
			sched_yield();
		#endif

		// grab the start time
		#ifdef RASPBERRY
			#ifdef USEDELAYUNTIL
				unsigned until = _platform->DelayUntil(0);
			#endif
		#endif

		#ifdef USEARDUINO
			noInterrupts();
		#endif

		// for each component of the message
		for(byte i = 0; i < 82; i++)
		{
			// write the message
			_platform->WriteParallel(_prep[i]);

			// wait so that the message bit is on the wire for 10us
			#if defined(RASPBERRY) && defined(USEDELAYUNTIL)
				until += 10; // hardcoded 10us pulse width
				_platform->DelayUntil(until);
			#else
				if (MICROSECONDSWAITPARALLEL != 0)
				{
					_platform->DelayMicroseconds(MICROSECONDSWAITPARALLEL);
				}
			#endif
		}

		#ifndef PC
			// leave all the lines low
			_platform->WriteParallel((byte)0);
		#endif

		#ifdef USEARDUINO
			interrupts();
		#endif

		#if defined(SENDTOEMULATOR) || defined(SENDTOEMULATORMSGS)
			// for each component of the message
			for(byte i = 0; i < 82; i++)
			{
				// send it to the emulator
				_platform->SendToEmulator(_prep[i]);
			}
		#endif
	}

	// for each light string
	for (byte i = 0; i < MAXSTRINGS; i++)
	{
		// if we have a bulb
		if (bulbs[i] != NULL)
		{
			// clear its changed state ... I do this even if it is not flagged as changed as it is cheaper than checking and clearing
			bulbs[i]->ClearChanged();
		}
	}

	#ifdef INCLUDE_MSG_SUPERDETAIL
		#ifdef MINMEMORY
			_platform->WriteMessage(MSG_SUPERDETAIL,"6.5\n");
		#else
			_platform->WriteMessage(MSG_SUPERDETAIL, "Bulbs Sent.\n");
		#endif
	#endif
}

// Display the bulb
void Bulb::Display()
{
	if (IsChanged())
	{
		// prepare the buffer for display
		PopulateOneBulbBits();

		// clear the changed bit on this bulb
		ClearChanged();

		// try to clear any work which might interrupt us
		#if defined(RASPBERRY) && defined(DOYIELD)
			sched_yield();
		#endif

		// grab the start time
		#if defined(RASPBERRY) && defined(USEDELAYUNTIL)
			unsigned until = _platform->DelayUntil(0);
		#endif

		#ifdef USEARDUINO
			noInterrupts();
		#endif

		// for each component of the message
		for(byte i = 0; i < 82; i++)
		{
			// write the message
			_platform->WritePin(_string, _prep[i]);

			// wait so that the message bit is on the wire for 10us
			#if defined(RASPBERRY) && defined(USEDELAYUNTIL)
				until += 10; // hardcoded 10us pulse width
				_platform->DelayUntil(until);
			#else
				if (USWAITSINGLE != 0)
				{
					_platform->DelayMicroseconds(USWAITSINGLE);
				}
			#endif
		}

		#ifndef PC
			// leave all the lines low
			_platform->WritePin(_string, 0);
		#endif

		#ifdef USEARDUINO
			interrupts();
		#endif

		#if defined(SENDTOEMULATOR) || defined(SENDTOEMULATORMSGS)
			// for each component of the message
			for(byte i = 0; i < 82; i++)
			{
				// send to emulator
				_platform->SendToEmulator(_string, _prep[i]);
			}
		#endif

		#ifdef INCLUDE_MSG_SUPERDETAIL
			#ifdef MINMEMORY
				_platform->WriteMessage(MSG_SUPERDETAIL,"6.6\n");
			#else
				_platform->WriteMessage(MSG_SUPERDETAIL, "Bulb Sent.\n");
			#endif
		#endif
	}
}

By viewing downloads associated with this article you agree to the Terms of Service and the article's licence.

If a file you wish to view isn't highlighted, and is a text file (not binary), please let us know and we'll add colourisation support for it.

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)


Written By
Australia Australia
This member has not yet provided a Biography. Assume it's interesting and varied, and probably something to do with programming.

Comments and Discussions