The project ended up including 5 separate pieces of code, working together to create the interconnected system. There are two server-side files in Python and three pieces of ESP32 code in C++ - for the lamp, responder, and the map demo.
This is the piece of code that ties everything together. It's configured to accept 7 different types of requests, either updating or returning data from a central database for the 31 lamps. The database has a row for each lamp, and looks something like this:
ID | Status | Timestamp |
---|---|---|
0 | IDLE | 18:56 |
4 | FIRE | 21:34 |
... | ... | ... |
30 | IDLE | 18:56 |
Here's an outline of the types of requests it can receive, and what it returns/updates with them:
type == CREATE
Upon receiving this command, the database will be deleted if it exists, and recreated from scratch. This is useful so we don't have to try/except catch in every other function, depending on the database status.
lamp.ino
with a floatIf the status posted from a lamp is a float rather than a string, the code sends a request to the smart lamp API to set the brightness to the number posted.
lamp.ino
Here, the ID of the lamp that is requesting is sent, and the function simply returns the current status of the lamp ID that sent the request.
responder.ino
This code allows the responder ESP32 module to be able to accept requets for help, and updates entries in the database to reflect that.
lamp.ino
with stringIf the status is a string, the code simply updates the entry in the database for it to reflect the current status. Additionally, if the status posted was FIRE, IDLE, or ACCEPTED, it sends a request to the smart lamp API to update the color accordingly.
ID_to_arrayNEW.ino
This is the code to control the laser-cut demonstration board with 31 LEDs, so it needs to return each ID that needs to be lit up. The easiest way to parse this in C++ seemed to be just separate IDs by spaces rather than return a string of a Python list. To do so, the function calls the function that would do a GET from responder.ino
and processes the returned path into IDs with spaces between, then returns that to the ESP32.
responder.ino
This is the trickiest bit of code. First, it queries the database to find out if there are currently any emergencies. If it doesn't find any, it simply returns that there are "No emergencies currently." If there is an emergency, it checks to see if the path has already been calculated to the emergency and saved into the path
table in the database. If so, it simply returns that path. If not, it sends a request to pathfinder.py
, asking for the shortest path between the fire station and the location of the fire. Finally, it stores the calculated path in the path
table of the database so that future GET requests don't have to wait for the entire pathfinder code to run, and can instead immediately return the already-calculated path for next time.
The gist of this code is just to control the lamp via inputs of motion detection, voice recognition, and buttons, and then display the relevant information on the screen.
The ultrasound sensor is put to use here. If the sensor reports back a ultrasound response of a low enough time, indicating the distance of an object that the sound bounces off of is within a certain threshold, the isDetected function returns back a 1. Otherwise, it returns a 0. So really, this isn’t motion detection, it is distance detection. Nonetheless, if you go in front of it, it senses that you’re there, and the microphone turns on.
The voice detection is done by a microphone attached to the system. Once you start speaking, the sound vibrates a piece of the microphone, which reports back raw audio readings. The readings are stored, then sent to the Google Speech API to be processed, which is currently parametrized to return words in English. The current keyphrase for the demo is “fire fire fire”, which then engages the emergency post.
When motion isn't detected for 10 seconds, lamp.ino
sends a request to the server code to set the lamp brightness to 0.1. If it then detects motion again, it sends a request to set the brightness to 0.5 (100% brightness was way too bright for something so close to our eyes)
The C++ code on the responder device doesn’t do much on its own. Rather, it relies heavily upon data and computation from the server side. After initializing, the ESP32 sends a GET request to emergencyRequest.py on the server. The server then returns either “No emergencies currently” or, if there is an emergency, a path from the responder’s location to the emergency in the form of a Python list of lamp IDs. The path calculated from pathfinder.py via the Google Distance Matrix API and a Dijkstra algorithm, along with connection information and time since it last synced with the server, is displayed in a UI on the OLED. This process is repeated every 5 seconds, displaying the relevant information so a firefighter would quickly see an emergency and be able to navigate directly to it. Additionally, upon a long press, this code has the ability to “accept” an emergency. This fires off a request to the server and updates the status in the database to “ACCEPTED,” allowing those nearby the emergency to immediately know when help is on the way.
Essentially, we construct a graph where every lamp post represents a node in the graph and the roads connecting lamps represent the edges of the graph. The edge weights of the graph are the amount of time in seconds it takes to get from one lamp to another based on current traffic conditions. Since these edge weights will always be positive and there can be cycles in the graph, the most efficient algorithm is Dijkstra which can run in O(VlogV+E) time if we use a Binary Heap (assuming that the number of edges is asymptotically equal to the number of nodes). The code is shown below:
We used the Google Distance Matrix API to create an adjacency matrix for all the edge weights. Since this graph must be directed (as traffic can be faster in one direction compared to another), the adjacency matrix is not necessarily symmetric. Basically, for every known road connection (indicated as a 1 in the initialization matrix), we perform a request to the Google Distance Matrix API and parse the JSON to determine what the current duration is. We then insert this value to the adjacency matrix in the corresponding location.
The purpose of this file is to be the code that runs on the miniature map and to be able to configure the LED path depending on the current most efficient path calculated by the dijkstra algorithm. In this file we assume that the LEDs are already connected to their respective pins but we also had to remember that we have 15 LEDs that were controlled with the Charlieplexer circuit. To start, we had to first initialize all of the regular pins to OUTPUT. This will ensure that we will set all of the LED values initially to LOW and enable us to control the pins later on in the program. As for the 5 Charlie-Plexer pins we have to set those to INPUT in order for them to initially have no defined polarity. After the intilizations, the program will then send a GET request every 5 seconds to update the map. When new information comes in, the data comes in as ID numbers. These ID numbers are then parsed and put one at a time into an already existing list of values called ID. One problem with the CharliePlexer is that out of all of the pins that it controls, only one can be turned on at a time. This means that in order to keep all of the pins looking like they’re continuously on we had to cycle through them. To facilitate this functionality we created a function called LedsIncrement which takes in one argument which is the index of the ID list. This value at this ID index is then mapped to an actual pin(the ID to pin map is shown below) and then activated. This function was put anywhere where there is a loop in order to make sure that no blocking statements would be able to delay the cycling of LEDs and make the LEDs look as though there were blinking. In addition an extra function needed to be written in order to be be able to turn on the CharliePlexer LEDs which involves resetting some of the 5 pins to INPUT, and setting the right pins to HIGH and the other ones to LOW to change the path of electricity.