Although I have lived in Chicago my entire life, I had a limited view of what the city was really like growing up. Once I entered high-school however, I was exposed to the bigger picture of Chicago. Taking the bus and train around the city became a part of my daily life, and although it allowed me to explore beautiful places I had never been to, it also ultimately shed light on the more flawed parts of the city.
This project takes the amount of 311 requests closed in a day and compares it to the amount of 311 requests created in a day to measure the competence of Chicago. This ratio determines:
1. The amount of lights that turn on in the main building of my project (LED panel).
2. The rotation of a stepper motor that controls the linear movement of a rail with clouds attached to it, revealing light in the city of Chicago.
WEBHOOKSThese dynamic webhooks gave me the amount of created and closed cases each day by getting the amount of entries in the "created_date" and "closed_date" columns.
#include "Particle.h"
#include "Stepper.h"
#include "neopixel.h"
#include <ArduinoJson.h>
SYSTEM_MODE(AUTOMATIC);
SYSTEM_THREAD(ENABLED);
const int STEPS_PER_REV = 2048;
Stepper motor2(STEPS_PER_REV, D4, D6, D5, D7);
#define LED_COUNT 64
#define LED_PIN SPI
Adafruit_NeoPixel panel(LED_COUNT, LED_PIN, WS2812B);
JsonDocument doc;
int openedCount = -1;
int closedCount = -1;
bool webhooksFired = false;
int currentSteps = 0;
// randomized order for the leds
int pixelOrder[LED_COUNT];
void allLedsOff() {
for(int i = 0; i < LED_COUNT; i++){
panel.setPixelColor(i, 0, 0, 0);
}
}
void setLEDs(int numLit) {
allLedsOff();
for(int i = 0; i < numLit; i++){
panel.setPixelColor(pixelOrder[i], panel.Color(120, 120, 10));
}
panel.show();
Serial.println("LEDs lit: " + String(numLit) + " / " + String(LED_COUNT));
}
void moveMotor(int targetSteps) {
int delta = targetSteps - currentSteps;
if(delta == 0){
Serial.println("motor already there");
return;
}
Serial.println("moving " + String(delta) + " steps");
motor2.step(-delta); // had to add this because it was initially spinning the wrong way
currentSteps = targetSteps;
Serial.println("motor done");
}
void updateInstallation() {
Serial.println("opened: " + String(openedCount));
Serial.println("closed: " + String(closedCount));
if(openedCount <= 0){
setLEDs(0);
moveMotor(0);
return;
}
float ratio = (float)closedCount / (float)openedCount;
if(ratio > 1.0) ratio = 1.0;
if(ratio < 0.0) ratio = 0.0;
int ledsToLight = (int)(ratio * LED_COUNT);
int targetSteps = (int)(ratio * STEPS_PER_REV);
Serial.println("ratio: " + String(ratio));
Serial.println("leds: " + String(ledsToLight) + " / 64");
Serial.println("steps: " + String(targetSteps) + " / 2048");
setLEDs(ledsToLight);
moveMotor(targetSteps);
}
void gotOpened(const char *event, const char *data) {
Serial.println(data);
deserializeJson(doc, data);
const char* countStr = doc[0]["createdcount"];
if(countStr){
openedCount = atoi(countStr);
} else {
openedCount = doc[0]["createdcount"].as<int>();
}
Serial.println("opened count: " + String(openedCount));
if(closedCount >= 0) updateInstallation();
}
void gotClosed(const char *event, const char *data) {
Serial.println(data);
deserializeJson(doc, data);
const char* countStr = doc[0]["countclosed"];
if(countStr){
closedCount = atoi(countStr);
} else {
closedCount = doc[0]["countclosed"].as<int>();
}
Serial.println("closed count: " + String(closedCount));
if(openedCount >= 0) updateInstallation();
}
String getDateString(int offsetDays) {
time_t t = Time.now() + (offsetDays * 86400);
int y = Time.year(t);
int m = Time.month(t);
int d = Time.day(t);
String date = String(y) + "-";
if(m < 10) date += "0";
date += String(m) + "-";
if(d < 10) date += "0";
date += String(d);
return date;
}
void setup() {
Serial.begin(9600);
motor2.setSpeed(10);
panel.begin();
panel.setBrightness(10);
allLedsOff();
panel.show();
// randomize the pixel order, called a Fisher-Yates shuffle (i had to find this online)
// using analogRead(A0) as the seed so its different every time it runs
// but ultimately, this makes it so not only does the LED panel
// reflect the ratio of closed : open cases, but it does so in a shuffled
// order
for(int i = 0; i < LED_COUNT; i++) pixelOrder[i] = i;
randomSeed(analogRead(A0));
for(int i = LED_COUNT - 1; i > 0; i--){
int j = random(0, i + 1);
int tmp = pixelOrder[i];
pixelOrder[i] = pixelOrder[j];
pixelOrder[j] = tmp;
}
Serial.println("leds randomized");
// spins back to start on every time it runs
motor2.step(STEPS_PER_REV);
currentSteps = 0;
Serial.println("reset");
Particle.subscribe("hook-response/getOpened", gotOpened);
Particle.subscribe("hook-response/getClosed", gotClosed);
}
void loop() {
if(!webhooksFired && Particle.connected()){
webhooksFired = true;
String startDate = getDateString(-1);
String endDate = getDateString(0);
Serial.println("getting data from " + startDate + " to " + endDate);
String payload = "{\"startDate\":\"" + startDate + "\",\"endDate\":\"" + endDate + "\"}";
Particle.publish("getOpened", payload);
delay(2000);
Particle.publish("getClosed", payload);
}ASSEMBLYFor this project, I was partnered up with my friend Nathan from the Sculpture class, meaning he took the lead on the physical assembly of the visual aspect of this project. He took measurements and then put the box together, created buildings and painted the setting, and made street lamps for some finishing touches.
I glued cotton balls to cardboard squares and then glued those squares to the insides of the box. For the clouds in front of the building, I rolled up cardboard, cut open small inserts in the sides of the box, and then glued the rolled cardboard to these inserts. I then did the same gluing technique to attach the clouds, except for the actual movable cloud. The movable cloud was glued to a piece of cardboard that I taped to the very edge of the 3d-printed linear actuator. I was very proud of this mechanism because there was a lot of trial-and-error in trying to figure out how to set up this entire movement in the air.
There were a few important takeaways I took from this project:
- Communication is very important. Had there been large issues with the project when we were nearing the deadline, we likely wouldn't have been able to fix them in time due to how little we talked throughout the middle of the project.
- You should have an idea of how you will assemble your project when you are forming the idea of the project in the first place. For me, I spent hours and hours trying to figure out the best way to set up the stepper motor system in my project.
When I ran the code for this video, the ratio of closed cases to created cases was extremely close to 1 (.97). Therefore, almost all of the lights were on and the clouds moved out almost all the way.











_3u05Tpwasz.png?auto=compress%2Cformat&w=40&h=40&fit=fillmax&bg=fff&dpr=2)
Comments