Circuit Cycle

With new technologies taking over every area of our lives, our clothing should also be enhanced to aid us in better navigating our world. Circuit Cycle is a navigational jacket designed for the urban cyclist. It is about forward motion in both space and time. The jacket provides increased visibility and directional capabilities to guide her rider to their intended destination.

The jacket functions in response to an embedded GPS sensor and sensors for motion, direction and orientation. The coordinates for the destination are programmed into a computer code which is uploaded to the onboard device. LED light animation sequences indicate the commands for the direction. Wires travel along the jacket hidden inside the seams.

This jacket was a winner of the Gerber Technology Student Design competition in 2017. It was the culminating thesis project for my Technical Design degree at the Fashion Institute of Technology (FIT). I built the jacket, soldered all components, and studied coding and wearable technology for quite some time before beginning the project. The open source code was modified for this project with the assistance of very knowledgeable members of NYC Resistor . I am especially grateful to Miria Grunick for her hours spent troubleshooting late-night and her support. Photography by @Purky.

 

Check out Circuit Cycle on the Runway

@Gerbertech Annual Conference (2017)

 
 

Want a Closer Look?

Check out the video of the jacket in action below

 
 
 

Behind the Scenes

Soldering components with two (shaky) hands and a third hand

 
 

Circuit Cycle Source Code

// This code shows how to listen to the GPS module in an interrupt

// which allows the program to have more 'freedom' - just parse

// when a new NMEA sentence is available! Then access data when

// desired.

#include <Adafruit_GPS.h>

#include <SoftwareSerial.h>

#include <Wire.h> //#include <LSM303.h>

#include <Adafruit_NeoPixel.h>

//LSM303 compass;

#define LAT_LON_SIZE 311

const float lat_lon[LAT_LON_SIZE][2] PROGMEM = {

{40.767272, -73.993928},

{40.719115, -74.006666}, };

// Connect the GPS Power pin to 5V

// Connect the GPS Ground pin to ground

// If using software serial (sketch example default):

// Connect the GPS TX (transmit) pin to Digital 8

// Connect the GPS RX (receive) pin to Digital 7

// If using hardware serial:

// Connect the GPS TX (transmit) pin to Arduino RX1 (Digital 0)

// Connect the GPS RX (receive) pin to matching TX1 (Digital 1)

// If using hardware serial, comment

// out the above two lines and enable these two lines instead:

//Adafruit_GPS GPS(&Serial1); //HardwareSerial mySerial = Serial1;

// Set GPSECHO to 'false' to turn off echoing the GPS data to the Serial console

// Set to 'true' if you want to debug and listen to the raw GPS sentences

#define GPSECHO false

Adafruit_NeoPixel leftStrip = Adafruit_NeoPixel(5, 9, NEO_GRB + NEO_KHZ800);

Adafruit_NeoPixel rightStrip = Adafruit_NeoPixel(5, 6, NEO_GRB + NEO_KHZ800);

int pixelCount = 5;

int counter = 0;

int strip_delay = 10;

// this keeps track of whether we're using the interrupt

// off by default!

boolean usingInterrupt = false;

void setup()

{

// connect at 115200 so we can read the GPS fast enough and echo without dropping chars

// also spit it out

Serial.begin(9600);

delay(5000);

Serial.println("initialize");

//test to find the closest location from the list, just plug in a lat lon from the above Array

//Serial.print("Closest Test Loc: ");

//Serial.println(find_closest_location(40.726378, -74.005437));

//Wire.begin();

// 9600 NMEA is the default baud rate for Adafruit MTK GPS's- some use 4800

//GPS.begin(9600);

// Set the update rate

//GPS.sendCommand(PMTK_SET_NMEA_UPDATE_1HZ); // 1 Hz update rate

// For the parsing code to work nicely and have time to sort thru the data, and

// print it out we don't suggest using anything higher than 1 Hz

//compass.init();

//compass.enableDefault();

// Calibration values. Use the Calibrate example program to get the values for

// your compass.

//compass.m_min.x = -581;

compass.m_min.y = -731;

compass.m_min.z = -1097;

//compass.m_max.x = +615;

compass.m_max.y = +470;

compass.m_max.z = 505;

//delay(1000);

// Ask for firmware version

//Serial1.println(PMTK_Q_RELEASE);

leftStrip.begin();

leftStrip.show(); // Initialize all pixels to 'off'

rightStrip.begin();

rightStrip.show(); // Initialize all pixels to 'off'

}

uint32_t timer = millis();

/*void loop() // run over and over again

{

// read data from the GPS in the 'main loop'

char c = GPS.read();

// if you want to debug, this is a good time to do it!

//if (GPSECHO)

// if (c) Serial.print(c);

// if a sentence is received, we can check the checksum, parse it...

//if (GPS.newNMEAreceived()) {

// a tricky thing here is if we print the NMEA sentence, or data

// we end up not listening and catching other sentences!

// so be very wary if using OUTPUT_ALLDATA and trytng to print out data

//Serial.println(GPS.lastNMEA()); // this also sets the newNMEAreceived() flag to false

//if (!GPS.parse(GPS.lastNMEA())) // this also sets the newNMEAreceived() flag to false

// return; // we can fail to parse a sentence in which case we should just wait for another //}

// if millis() or timer wraps around, we'll just reset it

if (timer > millis()) timer = millis();

// every 300 milliseconds, update the heading/distance indicators

if (millis() - timer > 300) {

timer = millis();

// reset the timer

//Serial.print("\nTime: ");

//Serial.print(GPS.hour, DEC);

Serial.print(':');

//Serial.print(GPS.minute, DEC);

Serial.print(':');

//Serial.print(GPS.seconds, DEC);

Serial.print('.');

//Serial.println(GPS.milliseconds);

//Serial.print("Date: ");

//Serial.print(GPS.day, DEC);

Serial.print('/');

//Serial.print(GPS.month, DEC);

Serial.print("/20");

//Serial.println(GPS.year, DEC);

//Serial.print("Fix: ");

Serial.print((int)GPS.fix);

//Serial.print(" quality: ");

Serial.println((int)GPS.fixquality);

if (GPS.fix) {

Serial.print("GPS FIX");

//Serial.print("Location: ");

//Serial.print(GPS.latitude, 2);

Serial.print(GPS.lat);

//Serial.print(", ");

//Serial.print(GPS.longitude, 2);

Serial.println(GPS.lon);

float fLat = decimalDegrees(GPS.latitude, GPS.lat);

float fLon = decimalDegrees(GPS.longitude, GPS.lon);

int closest_loc = find_closest_location(fLat, fLon);

float targetLat = pgm_read_float(&lat_lon[closest_loc][0]);

float targetLon = pgm_read_float(&lat_lon[closest_loc][1]);

//Serial.print("Speed (knots): "); Serial.println(GPS.speed);

//Serial.print("Angle: "); Serial.println(GPS.angle);

//Serial.print("Altitude: "); Serial.println(GPS.altitude);

//Serial.print("Satellites: "); Serial.println((int)GPS.satellites);

//compass.read();

//int heading = compass.heading((LSM303::vector){0,-1,0});

Serial.print("Heading: ");

Serial.println(heading); if ((calc_bearing(fLat, fLon, targetLat, targetLon) - heading) > 0) {

headingDirection(calc_bearing(fLat, fLon, targetLat, targetLon)-heading);

}

else {

headingDirection(calc_bearing(fLat, fLon, targetLat, targetLon)-heading+360);

}

//headingDistance((double)calc_dist(fLat, fLon, targetLat, targetLon));

//Serial.print("Distance Remaining:");

Serial.println((double)calc_dist(fLat, fLon, targetLat, targetLon));

}

}

} */

void loop() {

Serial.println("N");

headingDirection(349);

delay(strip_delay);

Serial.println("NNE");

headingDirection(12);

delay(strip_delay);

Serial.println("NE ");

headingDirection(34);

delay(strip_delay);

Serial.println("ENE ");

headingDirection(77);

delay(strip_delay);

Serial.println("E ");

headingDirection(100);

delay(strip_delay);

Serial.println("ESE ");

headingDirection(103);

delay(strip_delay);

Serial.println("SE ");

headingDirection(130);

delay(strip_delay);

Serial.println("SSE ");

headingDirection(147);

delay(strip_delay);

Serial.println("S ");

headingDirection(170);

delay(strip_delay);

Serial.println("SSW ");

headingDirection(200);

delay(strip_delay);

Serial.println("SW ");

headingDirection(214);

delay(strip_delay);

Serial.println("WSW ");

headingDirection(238);

delay(strip_delay);

Serial.println("W ");

headingDirection(259);

delay(strip_delay);

Serial.println("WNW ");

headingDirection(282);

delay(strip_delay);

Serial.println("NW ");

headingDirection(304);

delay(strip_delay);

Serial.println("NWN ");

headingDirection(330);

delay(strip_delay);

}

int calc_bearing(float flat1, float flon1, float flat2, float flon2)

{

float calc;

float bear_calc;

float x = 69.1 * (flat2 - flat1);

float y = 69.1 * (flon2 - flon1) * cos(flat1/57.3); calc=atan2(y,x);

bear_calc= degrees(calc);

if(bear_calc<=1){

bear_calc=360+bear_calc;

}

return bear_calc;

}

void headingDirection(float heading)

{

Serial.print("heading ");

Serial.println(heading);

//Use this part of the code to determine which way you need to go.

//Remember: this is not the direction you are heading, it is the direction to the destination (north = forward).

if ((heading > 348.75)||(heading < 11.25)) {

Serial.println(" N");

//Serial.println("Forward");

GoForward(leftStrip.Color(16, 247, 206), strip_delay);

}

if ((heading >= 11.25)&&(heading < 33.75)) {

Serial.println("NNE");

//Serial.println("Go Right");

TurnRight(leftStrip.Color(16, 247, 206), strip_delay);

}

if ((heading >= 33.75)&&(heading < 56.25)) {

Serial.println(" NE");

//Serial.println("Go Right");

TurnRight(leftStrip.Color(16, 247, 206), strip_delay);

}

if ((heading >= 56.25)&&(heading < 78.75)) {

Serial.println("ENE");

//Serial.println("Go Right");

TurnRight(leftStrip.Color(16, 247, 206), strip_delay);

}

if ((heading >= 78.75)&&(heading < 101.25)) {

Serial.println(" E");

//Serial.println("Go Right");

TurnRight(leftStrip.Color(16, 247, 206), strip_delay);

}

if ((heading >= 101.25)&&(heading < 123.75)) {

Serial.println("ESE");

//Serial.println("Go Right");

TurnRight(leftStrip.Color(16, 247, 206), strip_delay);

}

if ((heading >= 123.75)&&(heading < 146.25)) {

Serial.println(" SE");

//Serial.println("Go Right");

TurnRight(leftStrip.Color(16, 247, 206), strip_delay);

}

if ((heading >= 146.25)&&(heading < 168.75)) {

Serial.println("SSE");

//Serial.println("Go Right");

TurnRight(leftStrip.Color(16, 247, 206), strip_delay);

}

if ((heading >= 168.75)&&(heading < 191.25)) {

Serial.println(" S");

//Serial.println("Turn Around");

TurnAround(leftStrip.Color(247, 16, 70), strip_delay);

}

if ((heading >= 191.25)&&(heading < 213.75)) {

Serial.println("SSW");

//Serial.println("Go Left");

TurnLeft(leftStrip.Color(16, 247, 206), strip_delay);

}

if ((heading >= 213.75)&&(heading < 236.25)) {

Serial.println(" SW");

//Serial.println("Go Left");

TurnLeft(leftStrip.Color(16, 247, 206), strip_delay);

}

if ((heading >= 236.25)&&(heading < 258.75)) {

Serial.println("WSW");

//Serial.println("Go Left");

TurnLeft(leftStrip.Color(16, 247, 206), strip_delay);

}

if ((heading >= 258.75)&&(heading < 281.25)) {

Serial.println(" W");

//Serial.println("Go Left");

TurnLeft(leftStrip.Color(16, 247, 206), strip_delay);

}

if ((heading >= 281.25)&&(heading < 303.75)) {

Serial.println("WNW");

//Serial.println("Go Left");

TurnLeft(leftStrip.Color(16, 247, 206), strip_delay);

}

if ((heading >= 303.75)&&(heading < 326.25)) {

Serial.println(" NW");

//Serial.println("Go Left");

TurnLeft(leftStrip.Color(16, 247, 206), strip_delay);

}

if ((heading >= 326.25)&&(heading < 348.75)) {

Serial.println("NWN");

//Serial.println("Go Left");

TurnLeft(leftStrip.Color(16, 247, 206), strip_delay);

}

}

unsigned long calc_dist(float flat1, float flon1, float flat2, float flon2)

{

float dist_calc=0;

float dist_calc2=0;

float diflat=0;

float diflon=0;

diflat=radians(flat2-flat1);

flat1=radians(flat1);

flat2=radians(flat2);

diflon=radians((flon2)-(flon1));

dist_calc = (sin(diflat/2.0)*sin(diflat/2.0));

dist_calc2= cos(flat1);

dist_calc2*=cos(flat2);

dist_calc2*=sin(diflon/2.0);

dist_calc2*=sin(diflon/2.0);

dist_calc +=dist_calc2;

dist_calc=(2*atan2(sqrt(dist_calc),sqrt(1.0-dist_calc)));

dist_calc*=6371000.0; //Converting to meters

return dist_calc;

}

tempDistance = calc_dist(current_lat, current_lon, target_lat, target_lon);

/*

Serial.print("current_lat: ");

Serial.println(current_lat, 6);

Serial.print("current_lon: ");

Serial.println(current_lon, 6);

Serial.print("target_lat: ");

Serial.println(target_lat, 6);

Serial.print("target_lon: ");

Serial.println(target_lon, 6);

Serial.print("tempDistance: ");

Serial.println(tempDistance);

Serial.print("Array Loc: ");

Serial.println(i);

*/

if ((minDistance > tempDistance) || (minDistance == -1)) {

minDistance = tempDistance;

closest = i;

}

}

return closest;

}

// Convert NMEA coordinate to decimal degrees

float decimalDegrees(float nmeaCoord, char dir) {

uint16_t wholeDegrees = 0.01*nmeaCoord;

int modifier = 1;

if (dir == 'W' || dir == 'S')

{

modifier = -1;

}

return (wholeDegrees + (nmeaCoord - 100.0*wholeDegrees)/60.0) * modifier;

}

void TurnLeft (uint32_t c, uint8_t wait) {

/*

int i = 0;

for (i = 0; i < pixelCount; i++) {

rightStrip.setPixelColor(i, 0);

leftStrip.setPixelColor(i, c);

}

leftStrip.show();

rightStrip.show();

*/ theaterChaseRainbow(wait, true, false);

//delay(wait);

}

void TurnRight (uint32_t c, uint8_t wait) {

theaterChaseRainbow(wait, false, true);

/*

int i = 0;

for (i = 0; i < pixelCount; i++) {

rightStrip.setPixelColor(i, c);

leftStrip.setPixelColor(i, 0);

}

leftStrip.show();

rightStrip.show();

delay(wait);

*/

}

void TurnAround (uint32_t c, uint8_t wait) {

int i, j;

for (int j = 0; j < 100; j++) { for (i = 0; i < pixelCount; i++) {

rightStrip.setPixelColor(i, 255, 255, 255);

leftStrip.setPixelColor(i, 255, 255, 255);

}

leftStrip.show();

rightStrip.show();

delay(10);

for (i = 0; i < pixelCount; i++) {

rightStrip.setPixelColor(i, 0);

leftStrip.setPixelColor(i, 0);

}

leftStrip.show(); rightStrip.show();

delay(50);

}

}

void GoForward (uint32_t c, uint8_t wait) { theaterChaseRainbow(wait, true, true);

/*

int i = 0;

for (i = 3; i < pixelCount; i++) {

rightStrip.setPixelColor(i, c);

leftStrip.setPixelColor(i, c);

}

for (i = 0; i < 3; i++) {

rightStrip.setPixelColor(i, 0);

leftStrip.setPixelColor(i, 0);

}

leftStrip.show();

rightStrip.show();

delay(wait);

*/

}

//Theatre-style crawling lights with rainbow effect void

theaterChaseRainbow(uint8_t wait, bool do_left, bool do_right) {

for (int j=0; j < 256; j++) {

// cycle all 256 colors in the wheel

for (uint16_t i=0; i < pixelCount; i++) {

if (do_left) { leftStrip.setPixelColor(i, Wheel( (i+j) % 255));

//turn every third pixel on

}

else { leftStrip.setPixelColor(i, 0);

//turn every third pixel on

}

if (do_right) { rightStrip.setPixelColor(i, Wheel( (i+j) % 255));

//turn every third pixel on

}

else { rightStrip.setPixelColor(i, 0);

//turn every third pixel on

}

}

leftStrip.show();

rightStrip.show();

delay(5);

}

}

// Input a value 0 to 255 to get a color value.

// The colours are a transition r - g - b - back to r. uint32_t Wheel(byte WheelPos) {

if(WheelPos < 85) { return leftStrip.Color(WheelPos * 3, 255 - WheelPos * 3, 0);

}

else if(WheelPos < 170) { WheelPos -= 85; return leftStrip.Color(255 - WheelPos * 3, 0, WheelPos * 3);

}

else { WheelPos -= 170;

return leftStrip.Color(0, WheelPos * 3, 255 - WheelPos * 3);

}

}