#include "EEPROM.h"
#include "EtherCard.h"
#include "NanodeMAC.h"
#define kRedPin 3
#define kGreenPin 5
#define kBluePin 6
#define kEnEtherPin 4 // ethernet only enabled if this pin is high
#define kPerlinOctaves 3
static const float kaPerlinOctaveAmplitude[kPerlinOctaves] =
{
0.75f, 0.4f, 0.1f,
};
#define pctToByte(_v) (((int)(_v) * 255) / 100)
byte gbEthEnabled = true;
byte Ethernet::buffer[500];
static uint8_t mymac[6] = { 0,0,0,0,0,0 };
NanodeMAC mac( mymac );
static uint8_t myip[4] = { 192,168,1,25 };
static BufferFiller bfill; // used as cursor while filling the buffer
#define kNoiseExtreme 100.0f
float gT = 0.0f;
#define kConfigEepromAddress 0
struct
{
byte nWritten;
int hsl[2][3];
float spd;
} gConfig;
void setup()
{
//Serial.begin( 57600 );
readConfig();
pinMode( kRedPin, OUTPUT );
pinMode( kGreenPin, OUTPUT );
pinMode( kBluePin, OUTPUT );
pinMode( kEnEtherPin, INPUT );
digitalWrite( kEnEtherPin, HIGH ); // turn on pullup resistors
hslWrite( gConfig.hsl[0] );
ether.begin( sizeof( Ethernet::buffer ), mymac );
ether.staticSetup( myip );
}
void loop()
{
gT += gConfig.spd;
if( gT <= 0.0f && gConfig.spd < 0 )
gConfig.spd = -gConfig.spd;
else if( gT >= kNoiseExtreme && gConfig.spd > 0 )
gConfig.spd = -gConfig.spd;
int lHsl[3];
float p = perlinNoise1D( gT );
//Serial.print( gT );
//Serial.print( ' ' );
//Serial.println( p );
int t = constrain( (int)(255.0f * p), 0, 255 );
lerpHsl( gConfig.hsl[0], gConfig.hsl[1], t, lHsl );
hslWrite( lHsl );
// handle any incoming http
tickServer();
delay( 14 );
}
//-------------------------------------------------------------------
// -- config --
//
void readConfig()
{
byte* lpConfig = (byte*)&gConfig;
for( int i = 0; i < sizeof( gConfig ); ++i )
{
lpConfig[i] = EEPROM.read( kConfigEepromAddress + i );
}
// if this is the first time we've ever booted, set up some sane defaults
if( gConfig.nWritten )
{
gConfig.spd = 0.01f;
gConfig.nWritten = 0;
}
}
void writeConfig()
{
byte* lpConfig = (byte*)&gConfig;
for( int i = 0; i < sizeof( gConfig ); ++i )
{
EEPROM.write( kConfigEepromAddress + i, lpConfig[i] );
}
}
//-------------------------------------------------------------------
// -- http --
//
void tickServer()
{
tickEthPower();
// if the eth chip is off, let's bail!
if( !gbEthEnabled )
{
return;
}
word len = ether.packetReceive();
word pos = ether.packetLoop(len);
// check if valid tcp data is received
if (pos)
{
bfill = ether.tcpOffset();
char* data = (char *) Ethernet::buffer + pos;
// we only support GET
if( ( strncmp( "GET /", data, 5 ) != 0 ) ||
!handleGet( data+5, bfill ) )
{
bfill.emit_p(PSTR(
"HTTP/1.0 401 Unauthorized\r\n"
"Content-Type: text/html\r\n"
"\r\n"
"
401 Unauthorized
"));
}
ether.httpServerReply( bfill.position() ); // send web page data
}
}
void tickEthPower()
{
// do we need to power up/down the eth chip?
byte bEthShouldBeOn = digitalRead( kEnEtherPin );
if( gbEthEnabled != bEthShouldBeOn )
{
if( !bEthShouldBeOn )
{
ether.powerDown();
}
else
{
ether.powerUp();
}
gbEthEnabled = bEthShouldBeOn;
}
}
// handle an HTTP GET request, dispatching to appropriate handler
byte handleGet( const char* lpRequest, BufferFiller& buf )
{
if( strncmp( "hsl?", lpRequest, 4 ) == 0 )
return handleHsl( lpRequest+4, buf );
if( strncmp( "speed?", lpRequest, 6 ) == 0 )
return handleSpeed( lpRequest+6, buf );
if( *lpRequest == ' ' )
{
buf.emit_p(PSTR(
"HTTP/1.0 200 OK\r\n"
"Content-Type: text/plain\r\n"
"\r\n"
"bingo bongo"));
return 1;
}
return 0;
}
// handle an HTTP GET /hsl?... request
byte handleHsl( const char* lpRequest, BufferFiller& buf )
{
byte i = getIntArg( lpRequest, "i", 0 ) & 0x1;
gConfig.hsl[i][0] = getIntArg( lpRequest, "h", 0 ) % 360;
gConfig.hsl[i][1] = pctToByte( getIntArg( lpRequest, "s", 100 ) ) & 0xff;
gConfig.hsl[i][2] = pctToByte( getIntArg( lpRequest, "l", 50 ) ) & 0xff;
writeConfig();
byte rgb[3];
hslToRgb( gConfig.hsl[i][0], gConfig.hsl[i][1], gConfig.hsl[i][2], rgb );
buf.emit_p(PSTR(
"HTTP/1.0 200 OK\r\n"
"Content-type: text/plain\r\n"
"\r\n"
"dunnit. rgb=$D $D $D\r\n"), (word)rgb[0], (word)rgb[1], (word)rgb[2]);
return 1;
}
// handle an HTTP GET /speed?... request
byte handleSpeed( const char* lpRequest, BufferFiller& buf )
{
gConfig.spd = 0.001f * getIntArg( lpRequest, "millis", 10 );
writeConfig();
buf.emit_p(PSTR(
"HTTP/1.0 200 OK\r\n"
"Content-type: text/plain\r\n"
"\r\n"
"dunnit.\r\n"));
return 1;
}
// find & read an int value from a url encoded string, or "value" if it's not there
int getIntArg(const char* data, const char* key, int value )
{
char temp[16];
if (ether.findKeyVal(data, temp, sizeof temp, key) > 0)
value = atoi(temp);
return value;
}
//-------------------------------------------------------------------
// -- lerp --
//
void lerpHsl( const int* a, const int* b, byte t, int* lpOutHsl )
{
lpOutHsl[0] = lerpHueFp8( a[0], b[0], t );
lpOutHsl[1] = lerpFp8( a[1], b[1], t );
lpOutHsl[2] = lerpFp8( a[2], b[2], t );
}
int lerpFp8( int a, int b, byte t )
{
long t0 = ( b - a ) * t;
return ( a + ( t0 >> 8 ) );
}
int lerpHueFp8( int a, int b, byte t )
{
// adjust inputs so we take the shortest route around the circle
int cwdist = b - a;
if( abs(cwdist) > 180 )
{
if( b > a ) a += 360;
else b += 360;
}
long t0 = ( b - a ) * t;
int l = a + (t0 >> 8);
return ( l % 360 );
}
float lerpFloat( float a, float b, float t )
{
return ( a + t * (b-a) );
}
//-------------------------------------------------------------------
// -- perlin noise --
//
// http://freespace.virgin.net/hugo.elias/models/m_perlin.htm
float perlinNoise1( long x )
{
x = ( x<<13 ) ^ x;
return ( 1.0f - ( (x * (x * x * 15731 + 789221L) + 1376312589L) & 0x7fffffff) * (1.0f / 1073741824.0f) );
}
float perlinSmoothedNoise1( long x )
{
return perlinNoise1(x)*0.5f
+ perlinNoise1(x-1)*0.25f
+ perlinNoise1(x+1)*0.25f;
}
float perlinLerpedNoise1( float x )
{
long xInteger = long(x);
float xFraction = x - xInteger;
float v1 = perlinSmoothedNoise1( xInteger );
float v2 = perlinSmoothedNoise1( xInteger + 1 );
return lerpFloat( v1, v2, xFraction );
}
float perlinNoise1D( float x )
{
float total = 0.0f;
for( int octave = 0; octave < kPerlinOctaves; octave++ )
{
int frequency = 1 << octave;
float amplitude = kaPerlinOctaveAmplitude[octave];
total += perlinLerpedNoise1( x * frequency ) * amplitude;
}
// map from [-1,1] to [0,1] and constrain
return constrain( (total + 1.0f) * 0.5f, 0.0f, 1.0f );
}
//-------------------------------------------------------------------
// -- hsl output --
//
void hslWrite( const int* hsl )
{
byte rgb[3];
hslToRgb( hsl[0], hsl[1], hsl[2], rgb );
analogWrite( kRedPin, rgb[0] );
analogWrite( kGreenPin, rgb[1] );
analogWrite( kBluePin, rgb[2] );
}
// from http://www.kasperkamperman.com/blog/arduino/arduino-programming-hsb-to-rgb/
const byte gamma_curve[] = {
0, 1, 1, 2, 2, 2, 2, 2, 2, 3, 3, 3, 3, 3, 3, 3,
3, 3, 3, 3, 3, 3, 3, 4, 4, 4, 4, 4, 4, 4, 4, 4,
4, 4, 4, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 6, 6, 6,
6, 6, 6, 6, 6, 7, 7, 7, 7, 7, 7, 7, 8, 8, 8, 8,
8, 8, 9, 9, 9, 9, 9, 9, 10, 10, 10, 10, 10, 11, 11, 11,
11, 11, 12, 12, 12, 12, 12, 13, 13, 13, 13, 14, 14, 14, 14, 15,
15, 15, 16, 16, 16, 16, 17, 17, 17, 18, 18, 18, 19, 19, 19, 20,
20, 20, 21, 21, 22, 22, 22, 23, 23, 24, 24, 25, 25, 25, 26, 26,
27, 27, 28, 28, 29, 29, 30, 30, 31, 32, 32, 33, 33, 34, 35, 35,
36, 36, 37, 38, 38, 39, 40, 40, 41, 42, 43, 43, 44, 45, 46, 47,
48, 48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 62,
63, 64, 65, 66, 68, 69, 70, 71, 73, 74, 75, 76, 78, 79, 81, 82,
83, 85, 86, 88, 90, 91, 93, 94, 96, 98, 99, 101, 103, 105, 107, 109,
110, 112, 114, 116, 118, 121, 123, 125, 127, 129, 132, 134, 136, 139, 141, 144,
146, 149, 151, 154, 157, 159, 162, 165, 168, 171, 174, 177, 180, 183, 186, 190,
193, 196, 200, 203, 207, 211, 214, 218, 222, 226, 230, 234, 238, 242, 248, 255,
};
// http://www.dipzo.com/wordpress/?p=50
void hslToRgb( int hue, byte sat, byte light, byte* lpOutRgb )
{
if( sat == 0 )
{
lpOutRgb[0] = lpOutRgb[1] = lpOutRgb[2] = light;
return;
}
float nhue = (float)hue * (1.0f / 360.0f);
float nsat = (float)sat * (1.0f / 255.0f);
float nlight = (float)light * (1.0f / 255.0f);
float m2;
if( light < 128 )
m2 = nlight * ( 1.0f + nsat );
else
m2 = ( nlight + nsat ) - ( nsat * nlight );
float m1 = ( 2.0f * nlight ) - m2;
lpOutRgb[0] = hueToChannel( m1, m2, nhue + (1.0f / 3.0f) );
lpOutRgb[1] = hueToChannel( m1, m2, nhue );
lpOutRgb[2] = hueToChannel( m1, m2, nhue - (1.0f / 3.0f) );
}
inline byte hueToChannel( float m1, float m2, float h )
{
return hueToChannelRaw( m1, m2, h );
}
byte hueToChannelRaw( float m1, float m2, float h )
{
float channel = hueToChannelInternal( m1, m2, h );
byte uchan = (byte)(255.0f * channel);
return uchan;
}
// this gives much better colour matching than Raw, but results in more jumpy fading
byte hueToChannelGamma( float m1, float m2, float h )
{
float channel = hueToChannelInternal( m1, m2, h );
byte uchan = (byte)(255.0f * channel);
return gamma_curve[uchan];
}
float hueToChannelInternal( float m1, float m2, float h )
{
if( h < 0.0f ) h += 1.0f;
if( h > 1.0f ) h -= 1.0f;
if( ( 6.0f * h ) < 1.0f ) return ( m1 + ( m2 - m1 ) * 6.0f * h );
if( ( 2.0f * h ) < 1.0f ) return m2;
if( ( 3.0f * h ) < 2.0f ) return ( m1 + ( m2 - m1 ) * ((2.0f/3.0f) - h) * 6.0f );
return m1;
}