Every single day, New York City EMS Dispatch sends out units to respond to thousands of emergency calls. This data is available to the general public; most do not even view it.
This project will take this same raw data and display it using hardware. The Particle Photon will query the NYC OpenData Portal for Critical EMS calls from exactly this date last year. The total number of these calls will be displayed on an OLED screen. The motor attached to the particle photon will rotate or stop moving based upon whether today's call volume exceeds yesterday's. The RGB LED attached to the particle photon will pulse in a heartbeat pattern but will increase its pulse rate if today's call volume exceeds yesterday's.
Build ProcessStep 1: Wire the HardwareConnect your pins as follows (look at the image for guidance):
RED_PIN: D2
GREEN_PIN: D3
BLUE_PIN: D4
MOTOR_IN1: D5
MOTOR_IN2: D6
MOTOR_ENA: D7
OLED SDA: D0
OLED SCL: D1
Wire a transistor between each LED channel and the LED. This step is key because the 3W RGB LED draws more current than the Photon pins supply. If we allow this to happen, the photon will short circuit or burn out.
I recommend soldering your wires together on a protoboard to make the project more permanent when assembling parts so things don't randomly just fall off or get unplugged. If you are doing this, make a layout of your wires so you don't mess it up and cause an even bigger hassle.
This project uses the NYC EMS Incident Dispatch Data dataset from data.cityofnewyork.us.
We are working with New York City EMS incident dispatch data located on www.data.cityofnewyork.us. Each entry within this dataset represents every single EMS dispatch over many years. Your first step in filtering is to select the date of your EMS calls. We will need to use the date of today of last year, as hospital data is very private and gets updated yearly. Therefore, you will use the "between" operator to create a filter around two timestamps that define a specific 24-hour period. After you have confirmed your date filter, create your severity filter. The final_severity_level_code is numeric and defines the level of urgency assigned to each call. The three highest priority levels are codes 1, 2, and 3. Use the In('1', '2', '3') function to filter for all three. When adding multiple filters, always do them individually and run each one through your browser prior to moving on. If you add both filters and receive an error message, you may not be able to determine what filter was causing it. First test your date filter; when it has returned some results, add your severity filter and test again.
Step 3: Set Up the Particle WebhookOpen the console for Particle at console.particle.io.
Click on Integrations > New Integration > Web Hook.
Enter the following code into the Settings section:
{"name": "getEMS","event": "getEMS","url": "https://data.cityofnewyork.us/api/v3/views/76xm-jjuj/query.json?app_token=YOUR_TOKEN&query=select count(case(incident_datetime between '{{{todayStart}}}' and '{{{todayEnd}}}' AND final_severity_level_code IN('1', '2', '3'), 1)) as today, count(case(incident_datetime between '{{{yesterdayStart}}}' and '{{{yesterdayEnd}}}' AND final_severity_level_code IN('1', '2', '3'), 1)) as yesterday where incident_datetime between '{{{yesterdayStart}}}' and '{{{todayEnd}}}'))","requestType": "GET","noDefaults": true,"rejectUnauthorized": true,"responseTemplate": "{{0.today}}, {{0.yesterday}}","unchunked": false}
Replace 'YOUR_TOKEN' with your NYC Open Data app token. You can get one free at https://data.cityofnewyork.us/
The response template pulls only those two values from the returned JSON and returns a comma-separated value to the Photon (like 1733, 1578).
Pressing the test button after won't work no matter what you do. The Test button fires the webhook with no data, so the date variables have nothing to fill in and the query fails. Flash your firmware and let the Photon trigger it naturally.
The firmware calculates the date range for one year ago today. It takes that data and publishes it to the webhook, which will in turn send data back that the code uses to determine how it runs.
#include <Adafruit_SSD1306.h>
#include "Particle.h"
SYSTEM_MODE(AUTOMATIC);
const int RED_PIN = D2;
const int GREEN_PIN = D3;
const int BLUE_PIN = D4;
const int MOTOR_IN1 = D5;
const int MOTOR_IN2 = D6;
const int MOTOR_ENA = D7;
int todaySaves = 0;
int yesterdaySaves = 0;
#define OLED_RESET -1
Adafruit_SSD1306 display(OLED_RESET);
void emsHandler(const char *event, const char *data);
void heartbeat(int restTime);
void setup() {
Serial.begin(9600);
// LED pins off at start
pinMode(RED_PIN, OUTPUT);
pinMode(GREEN_PIN, OUTPUT);
pinMode(BLUE_PIN, OUTPUT);
digitalWrite(RED_PIN, LOW);
digitalWrite(GREEN_PIN, LOW);
digitalWrite(BLUE_PIN, LOW);
// Motor off at start
pinMode(MOTOR_IN1, OUTPUT);
pinMode(MOTOR_IN2, OUTPUT);
pinMode(MOTOR_ENA, OUTPUT);
digitalWrite(MOTOR_ENA, LOW);
// Start OLED
display.begin(SSD1306_SWITCHCAPVCC, 0x3C);
display.clearDisplay();
display.display();
while (Particle.disconnected());
Serial.println("Connected to Particle Cloud");
// Run emsHandler when webhook response arrives
Particle.subscribe("hook-response/getEMS", emsHandler);
// Eastern Time (NYC)
Time.zone(-4);
// Calculate the date one year ago and the day before
time_t lastYear = Time.now() - (365 * 86400);
time_t yesterdayLastYear = lastYear - 86400;
// Format dates for the API
String todayStart = Time.format(lastYear, "%Y-%m-%d") + "T00:00:00";
String todayEnd = Time.format(lastYear, "%Y-%m-%d") + "T23:59:59";
String yesterdayStart = Time.format(yesterdayLastYear, "%Y-%m-%d") + "T00:00:00";
String yesterdayEnd = Time.format(yesterdayLastYear, "%Y-%m-%d") + "T23:59:59";
// Build JSON to fill in webhook URL variables
String data = "{";
data += "\"todayStart\":\"" + todayStart + "\",";
data += "\"todayEnd\":\"" + todayEnd + "\",";
data += "\"yesterdayStart\":\"" + yesterdayStart + "\",";
data += "\"yesterdayEnd\":\"" + yesterdayEnd + "\"";
data += "}";
Serial.println("Publishing: " + data);
delay(1000);
Particle.publish("getEMS", data, PRIVATE);
}
void loop() {
// Spin motor if today beats yesterday
if (todaySaves > yesterdaySaves) {
digitalWrite(MOTOR_ENA, HIGH);
digitalWrite(MOTOR_IN1, HIGH);
digitalWrite(MOTOR_IN2, LOW);
} else {
digitalWrite(MOTOR_ENA, LOW);
}
// Faster heartbeat if today beats yesterday
int beatDelay = (todaySaves > yesterdaySaves) ? 400 : 900;
heartbeat(beatDelay);
}
void heartbeat(int restTime) {
// Two quick red pulses then a pause
digitalWrite(RED_PIN, HIGH);
delay(120);
digitalWrite(RED_PIN, LOW);
delay(80);
digitalWrite(RED_PIN, HIGH);
delay(120);
digitalWrite(RED_PIN, LOW);
delay(restTime);
}
void emsHandler(const char *event, const char *data) {
// Copy response into char array for splitting
char buf[64];
strncpy(buf, data, sizeof(buf));
// Split comma-separated response
String todayStr = strtok(buf, ",");
String yesterdayStr = strtok(NULL, ",");
todaySaves = todayStr.toInt();
yesterdaySaves = yesterdayStr.toInt();
Serial.print("Today: "); Serial.println(todaySaves);
Serial.print("Yesterday: "); Serial.println(yesterdaySaves);
// Show today's count on OLED
display.clearDisplay();
display.setTextColor(WHITE);
display.setTextSize(2);
display.setCursor(0, 10);
display.println(todaySaves);
display.display();
}Step 5: AssemblyThe real building/sculpting part of this project was done by my partner, and I can't really speak for the work they did, but I can say to make sure you test each component during assembly to see what needs to be changed or adjusted.
These are some errors that I made during this build that you should learn from.
- Test every query in the browser before setting up the webhook
- Add filters one at a time. When something breaks, you need to know exactly what caused it.
- Make an outline/diagram of your wiring either physically on a breadboard or online on Fritzing so you don't mess up your wiring and don't spend a lot of time fixing easily avoidable mistakes.
The device pulls real data from NYC's open data portal every time it powers on. The OLED shows the count. The motor turns on if the lives saved today (of last year) are greater than yesterday's (of last year) and off if not. The LED pulses faster or slower depending on the same comparison.
















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