A short post about getting help with the hoodie code
In over my head
So. Writing the code for a hoodie that reads and responds to the weather forecast was perhaps overly ambitious for someone who has been programming for less than a year and also barely passed high school math. After a few hours on the struggle bus this past week, I felt the need to call in a specialist. Conveniently, my spouse (aka Hunky Assistant) is a very talented computer engineer. In about five hours, he wrote a program that would have taken me weeks to figure out. Is this cheating?
How it works
The hoodie’s LEDs are connected to an Adafruit Flora outfitted with a CC3000 WiFi module. We set up a simple php webpage that grabs 3-hour forecast data from the Open Weather API. Over WiFi, the Flora connects to the web page and snags the info for the predicted volume of precipitation (how much it’s supposed to rain), the temperature, and the wind speed, plugging them into the variables controlling LED color, brightness, and blinkiness.
The Code
//first version of SpringHoodie code #include <Adafruit_NeoPixel.h> #include <Adafruit_CC3000.h> #include <ccspi.h> #include <SPI.h> // ========= CC3000 Parameters ========= // These are the interrupt and control pins #define ADAFRUIT_CC3000_IRQ 2 // MUST be an interrupt pin! // These can be any two pins #define ADAFRUIT_CC3000_VBAT 9 #define ADAFRUIT_CC3000_CS 10 // Use hardware SPI for the remaining pins // On an UNO, SCK = 13, MISO = 12, and MOSI = 11 Adafruit_CC3000 cc3000 = Adafruit_CC3000(ADAFRUIT_CC3000_CS, ADAFRUIT_CC3000_IRQ, ADAFRUIT_CC3000_VBAT, SPI_CLOCK_DIVIDER); // you can change this clock speed #define WLAN_SSID "oo3klld" // cannot be longer than 32 characters! #define WLAN_PASS "blahlwken3kkfd32d" // Security can be WLAN_SEC_UNSEC, WLAN_SEC_WEP, WLAN_SEC_WPA or WLAN_SEC_WPA2 #define WLAN_SECURITY WLAN_SEC_WPA2 #define IDLE_TIMEOUT_MS 10000 // Amount of time to wait (in milliseconds) with no data // received before closing the connection. If you know the server // you're accessing is quick to respond, you can reduce this value. // What page to grab! #define WEBSITE "mbah.org" #define WEBPAGE "/weather.php" // =========== NEOPIXEL parameters =========== #define INSTALLED_PIXELS 14 #define PIN 6 //The long Neopixel Strip is called "strip" Adafruit_NeoPixel strip = Adafruit_NeoPixel(INSTALLED_PIXELS, PIN, NEO_GRB + NEO_KHZ800); // =========== Program vars ============= bool g_initialized = false; // Temp (degrees C) const uint8_t g_temp_min = 15; const uint8_t g_temp_max = 30; uint8_t g_temp = g_temp_min; // Wind (km/h) const uint8_t g_wind_min = 0; const uint8_t g_wind_max = 60; uint8_t g_wind = g_wind_min; // Rain (tenths of mm in last 3 hours) const uint8_t g_rain_min = 0; const uint8_t g_rain_max = 25; uint8_t g_rain = g_rain_min; // def gohaina rain const uint8_t colors[3][3] = { {0, 0, 50}, // orange {0, 0, 200}, // greenish {0, 0, 250}, // yellow }; // //// somewhat gohaina rain //const uint8_t colors[3][3] = { // {250, 150, 0}, // orange // {0, 0, 200}, // blue // {250, 20, 0}, // yellow //}; // //// not gohaina rain //const uint8_t colors[3][3] = { // {250, 150, 0}, // orange // {200, 200, 20}, // greenish // {250, 20, 0}, // yellow //}; #define COLOR_POINTS 5 #define COLOR_STATES 5 struct color_state { uint8_t rain_amount; uint8_t colors[COLOR_POINTS][3]; }; const uint8_t temp_lut[] = { 0, /* 0 */ 1, /* 1 */ 1, /* 2 */ 2, /* 3 */ 2, /* 4 */ 2, /* 5 */ 3, /* 6 */ 3, /* 7 */ 4, /* 8 */ 5, /* 9 */ 6, /* 10 */ 7, /* 11 */ 8, /* 12 */ 10, /* 13 */ 11, /* 14 */ 13, /* 15 */ }; const struct color_state cs[5] = { { 0, { { 250, 150, 0 }, { 80, 0, 10 }, { 250, 20, 0 }, { 150, 0, 0 }, { 250, 20, 0 }, } }, { 5, { { 250, 150, 0 }, { 200, 200, 20 }, { 250, 20, 0 }, { 0, 0, 50 }, { 0, 0, 200 }, } }, { 10, { { 250, 150, 0 }, { 0, 0, 250 }, { 250, 20, 0 }, { 0, 0, 50 }, { 0, 0, 200 }, } }, { 15, { { 20, 0, 100 }, { 0, 0, 250 }, { 250, 20, 0 }, { 0, 0, 50 }, { 0, 0, 200 }, } }, { 20, { { 0, 0, 100 }, { 0, 0, 250 }, { 20, 0, 100 }, { 0, 0, 50 }, { 0, 0, 200 }, } }, }; int t=0; #define INTERPOINT_DISTANCE 1000 //#define N_POINTS 3 //void get_color_on_domain(int i, uint8_t *r, uint8_t *g, uint8_t *b) //{ // int point0 = i / INTERPOINT_DISTANCE; // int point1 = (point0 + 1) % N_POINTS; // int interpoint = i % INTERPOINT_DISTANCE; // // *r = ((int32_t)colors[point1][0] - (int32_t)colors[point0][0]) * interpoint / INTERPOINT_DISTANCE + colors[point0][0]; // *g = ((int32_t)colors[point1][1] - (int32_t)colors[point0][1]) * interpoint / INTERPOINT_DISTANCE + colors[point0][1]; // *b = ((int32_t)colors[point1][2] - (int32_t)colors[point0][2]) * interpoint / INTERPOINT_DISTANCE + colors[point0][2]; //} void get_color_on_domain2(const struct color_state *ccs, int i, uint8_t *col) { int point0 = i / INTERPOINT_DISTANCE; int point1 = (point0 + 1) % COLOR_POINTS; int interpoint = i % INTERPOINT_DISTANCE; uint8_t j; for (j = 0; j < 3; j++) { col[j] = ((int32_t)ccs->colors[point1][j] - (int32_t)ccs->colors[point0][j]) * interpoint / INTERPOINT_DISTANCE + ccs->colors[point0][j]; } } bool displayConnectionDetails(void) { uint32_t ipAddress, netmask, gateway, dhcpserv, dnsserv; if(!cc3000.getIPAddress(&ipAddress, &netmask, &gateway, &dhcpserv, &dnsserv)) { Serial.println(F("Unable to retrieve the IP Address!\r\n")); return false; } else { Serial.print(F("\nIP Addr: ")); cc3000.printIPdotsRev(ipAddress); //Serial.print(F("\nNetmask: ")); cc3000.printIPdotsRev(netmask); //Serial.print(F("\nGateway: ")); cc3000.printIPdotsRev(gateway); //Serial.print(F("\nDHCPsrv: ")); cc3000.printIPdotsRev(dhcpserv); //Serial.print(F("\nDNSserv: ")); cc3000.printIPdotsRev(dnsserv); Serial.println(); return true; } } uint32_t ip; void setup_cc3000(void) { //Serial.println(F("Hello, CC3000!\n")); //Serial.print("Free RAM: "); Serial.println(getFreeRam(), DEC); /* Initialise the module */ //Serial.println(F("\nInitializing cc3000...")); if (!cc3000.begin()) { //Serial.println(F("Couldn't begin()! Check your wiring?")); while(1); } // Optional SSID scan // listSSIDResults(); //Serial.print(F("\nAttempting to connect to ")); Serial.println(WLAN_SSID); if (!cc3000.connectToAP(WLAN_SSID, WLAN_PASS, WLAN_SECURITY)) { //Serial.println(F("Failed!")); while(1); } //Serial.println(F("Connected!")); /* Wait for DHCP to complete */ //Serial.println(F("Request DHCP")); while (!cc3000.checkDHCP()) { delay(100); // ToDo: Insert a DHCP timeout! } /* Display the IP address DNS, Gateway, etc. */ // while (! displayConnectionDetails()) { // delay(1000); // } ip = 919684817; // 54.209.70.209 /* Try connecting to the website. Note: HTTP/1.1 protocol is used to keep the server from closing the connection before all data is read. */ Adafruit_CC3000_Client www = cc3000.connectTCP(ip, 80); if (www.connected()) { www.fastrprint(F("GET " WEBPAGE " HTTP/1.1\r\n" "Host: " WEBSITE "\r\n" "\r\n")); //www.fastrprint(WEBPAGE); //www.fastrprint(F(" HTTP/1.1\r\n")); //www.fastrprint(F("Host: ")); www.fastrprint(WEBSITE); www.fastrprint(F("\r\n")); //www.fastrprint(F("\r\n")); www.println(); } else { Serial.println(F("Connection failed")); return; } //Serial.println(F("-------------------------------------")); /* Read data until either the connection is closed, or the idle timeout is reached. */ unsigned long lastRead = millis(); updateFromHttpReply(www); www.close(); //Serial.println(F("-------------------------------------")); /* You need to make sure to clean up after yourself or the CC3000 can freak out */ /* the next time your try to connect ... */ //Serial.println(F("\n\nDisconnecting")); cc3000.disconnect(); } void updateFromHttpReply(Adafruit_CC3000_Client &www) { int consecutive_newlines = 0; bool in_payload = false; char payload[50]; size_t payload_len = 0; unsigned long lastRead = millis(); while (www.connected() && (millis() - lastRead < IDLE_TIMEOUT_MS)) { while (www.available()) { char c = www.read(); //Serial.print(consecutive_newlines); if (c == '\n' || c == '\r') { consecutive_newlines++; if (consecutive_newlines == 4) { in_payload = true; continue; } } else { consecutive_newlines = 0; } if (in_payload) { payload[payload_len++] = c; if (payload_len == sizeof(payload)) { return; } } //Serial.print(c); lastRead = millis(); } } payload[payload_len] = '\0'; //Serial.print("Got payload "); //Serial.println(payload); updateFromString(payload); printStatus(); } void setup() { Serial.begin(9600); //while(!Serial); //Serial.println("Ready:"); setup_cc3000(); strip.begin(); strip.show(); // Initializes strip to 'off' } void printStatus(void) { Serial.print("Temp: "); Serial.println(g_temp); Serial.print("Wind: "); Serial.println(g_wind); Serial.print("Rain: "); Serial.println(g_rain); } void updateFromString(char *s) { const char *temp_str = strtok(s, " "); const char *wind_str = strtok(NULL, " "); const char *rain_str = strtok(NULL, "\n"); g_temp = atoi(temp_str); if (g_temp < g_temp_min) { g_temp = g_temp_min; } else if (g_temp > g_temp_max) { g_temp = g_temp_max; } g_wind = atoi(wind_str); if (g_wind < g_wind_min) { g_wind = g_wind_min; } else if (g_wind > g_wind_max) { g_wind = g_wind_max; } g_rain = atoi(rain_str); if (g_rain < g_rain_min) { g_rain = g_rain_min; } else if (g_rain > g_rain_max) { g_rain = g_rain_max; } } void updateViaSerial(void) { if (!Serial.available()) { return; } String s = Serial.readStringUntil('\n'); Serial.println("Got "+ s + "\n"); updateFromString((char *) s.c_str()); printStatus(); } void loop() { uint8_t r, g, b; int i; updateViaSerial(); // Rain: Determine the n main colors const struct color_state *ccs; for (i = 0; i < COLOR_STATES; i++) { if (g_rain >= cs[i].rain_amount) { ccs = &cs[i]; } } // Temp: Determine the brightness uint8_t logtemp = temp_lut[g_temp - 15] + 15; uint8_t brightness = (uint32_t) (logtemp - g_temp_min) * 255 / (g_temp_max - g_temp_min); strip.setBrightness(brightness); uint8_t color[3]; // Fill colors[i] with interpolated color for (i = 0; i < 20; i++) { get_color_on_domain2(ccs, (t + i * COLOR_POINTS * 1000 / 20) % (COLOR_POINTS * 1000), color); strip.setPixelColor(i, color[0], color[1], color[2]); } // Wind: Determine the speed of the animation uint32_t animation_speed = (uint32_t) (g_wind - g_wind_min) * 70 / (g_wind_max - g_wind_min) + 5; // for (i = 0; i < 20; i++) { // get_color_on_domain((t + i * 3000 / 20) % 3000, &r, &g, &b); // strip.setPixelColor(i, r, g, b); // } strip.show(); t = t + animation_speed; if (t >= INTERPOINT_DISTANCE * COLOR_POINTS) { t = 0; } }
I’m thinking of adding another mode for the hoodie lights, operated by a momentary button. Or adding more LEDs. I was initially thinking of using a bunch of sequins, but I’m not sure the effect will be worth it. Another thing to keep in mind is that I’m running out of program space.