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