Earthquake early warning systems are vital because they detect ground motion at the start of an earthquake, sending alerts that provide people with valuable seconds to prepare. These systems can save lives by issuing warnings, allowing individuals and automated systems to take protective actions before the most destructive waves hit. Similarly, emergency shutdown systems are crucial as they offer a safety measure during emergencies, helping to prevent catastrophic economic, environmental, and operational impacts.
This project aims to integrate seismic activity detection with automated emergency response protocols. It involves setting up a sensor to detect early signs of an earthquake. Upon detection, the system will send instant notifications to a dedicated mobile app via a cellular network. Users will receive timely alerts to take necessary precautions. The app can also send shutdown commands to deactivate water pumps, preventing potential hazards such as flooding due to seismic activity.
In an ideal scenario, an automated system will make the decision and safely shut down the machinery. However, we have designed a system that grants us the flexibility to manually control the process, giving us the ability to make decisions based on the situation at hand. This approach ensures that in the event of a false alarm, the production unit will not be needlessly halted, preventing any unnecessary disruption.
Hardware SetupWe will use a SeeedStudio Wio Terminal for sampling sensor readings, controlling actuators, and hosting a cellular network device.
The Blues Notecard will provide LTE cellular connectivity to the Wio Terminal using a Notecarrier Pi.
The Notecarrier Pi is designed for a Raspberry Pi, but the Wio terminal has a 40-pin connector compatible with the Notecarrier Pi.
The M5Stack Watering Unit is used as a water pump. Since the water pump draws a high current, it is directly powered by a power bank using an M5Stack USB TypeC2Grove unit.
For real-time monitoring of seismic activities, we will utilize the Grove - D7S Vibration Sensor which is based on the advanced D7S module developed by Omron Corporation. It is a high-precision earthquake sensor that utilizes a 3-axis acceleration sensor and a unique SI value calculation algorithm.
The SI value (Spectral Intensity) is equivalent to the average value of the integrated velocity response spectrum. It is an index that measures the destructive force of seismic motion and is closely related to damage to structures. The SI value is strongly correlated with the seismic intensity scale of Japan.
The Blues Notecard is placed into the M.2 slot on the Notecarrier Pi. A wooden board is used to secure the sensor and the Notecarrier Pi in place using screws. This setup will ensure the calibration of the D7S module and allow for movement to create a tremor effect later on. Also, a Molex antenna is connected to the U.FL connector of the MAIN socket on the Notecard and glued to the wooden board. The D7S Vibration Sensor is connected to the Wio Terminal through an I2C connection. The Watering Unit is connected to the Wio Terminal via a USB TypeC2Grove Unit, which splits the power and signal.
The Wio Terminal is mounted on top of the Blues Notecarrier Pi using the 40-pin headers. The Wio Terminal and the Watering Unit are powered using a power bank.
We need to set up Notehub, which is a cloud service that receives data from the Notecard and allows us to manage the device, and route that data to our own or 3rd party cloud apps and services. We can create a free account at https://notehub.io/sign-up and after successful login, we can create a new project.
We should copy the ProductUID which is used by Notehub to associate the Notecard to the project created.
Notecard ConfigurationFirst, connect the Notecarrier Pi to a computer via a USB cable. We will be using the in-browser REPL to configure the Notecard which can be accessed at https://dev.blues.io/notecard-playground. Click on the USB Notecard button and pair the device as shown in the images below.
We would be able to see the REPL after a successful pairing.
To associate the Notecard with the project in Notehub, we need to provide ProductUID for the project. Write the request (given below) in the text field above and click on the paper plane button.
> {"req":"hub.set", "product":"com.xxx.xxxxx:earthquake_notification"}
To synchronize the Notecard with the Notehub, send the request below.
> {"req":"hub.sync"}
After the synchronization process is finished, the response to the subsequent request will provide the UNIX Epoch time of the latest sync and the duration in seconds since the last completed synchronization.
> {"req":"hub.sync.status"}
{
"time": 1688462830,
"completed": 7
}
We will find the connected device on the Devices page and copy the device ID from the "Best ID" column.
Additionally, we should copy the "Project UID" from the Settings page as we will need to use them later in the mobile app settings.
For this project, we wanted to use an mobile app to get alerts notifications and send a command to shutdown the water pump. We will use the Pushcut app for iOS, which allows us to create actionable notifications using a webhook and trigger an action, such as a web request, using the Apple Shortcuts, a preinstalled automation app. The Pushcut app can be installed using the App store.
First, we will create a shortcut and add an action.
We will add a Get Contents of URL action that can be searched by typing "web request" keyword. We will set up the action by configuring the following settings.
URL: https://api.notefile.net/v1/projects/app:<Project UID>/devices/dev:<device ID>/signal
Method: POST
Headers: X-SESSION-TOKEN <session token>
Request body: { "body": { "motor": "off" } }
The Session Tokens can be obtained by executing the following command.
$ curl -X POST -L 'https://api.notefile.net/auth/login' -d '{"username":"<notehub_user", "password": "password"}'
In the Pushcut app, we will create a new notification.
To the newly created notification, we will add an action and select the Get Component of URL shortcut.
After saving the configuration, we can copy the autogenerated Webhook URL.
We would require to create a route that would forward the Notes to Pushcut. We can create a route by clicking the Create Route link at the top right on the Notehub Route page and select the General HTTP/HTTPS Request/Response route type.
Now paste the Webhook URL that we copied from the Pushcut app into the URL field.
In order to ensure that the correct Notecard outbound file data is sent to Pushcut, we need to specify the desired filters in the Filters section. We will select the "data.qo" file. This will guarantee that the data sent is always the intended data.
A high-level data flow diagram is shown below.
Please follow the instructions here to download and install the Arduino IDE. After installation, open the Arduino IDE and install the board package for the Wio Terminal by going to Tools > Board > Boards Manager. Search the board package as shown below and install it.
After the board package installation is completed, choose the Seeeduino Wio Terminal from Tools > Board > Seeed SAMD Boards menu and select the serial port of the connected board from Tools > Port menu. We need to install the Blues Wireless Notecard library using the Library Manager (Tool > Manage Libraries...) as shown below.
Similarly, we can find and install the JPEGDec and RTC_SAMD51 libraries.
FirmwareWe can utilize the following Arduino sketch to send/receive Notes via Notehub, read the sensor, and control the water pump. Create a new sketch with the following code and compile/upload the firmware to the connected Wio Terminal.
#include <Notecard.h>
#include <TFT_eSPI.h>
#include <D7S.h>
#include <JPEGDecoder.h>
#include <RTC_SAMD51.h>
#include <DateTime.h>
#include "Free_Fonts.h"
#include "blues_logo.h"
#define ATTN_INPUT_PIN BCM6
#define BUZZER_TIMEOUT_MS 5000
#define PUMP_PIN D1
#define usbSerial Serial
#define minimum(a,b) (((a) < (b)) ? (a) : (b))
#define myProductID "com.xxx.xxxxx:earthquake_notification"
TFT_eSPI tft;
RTC_SAMD51 rtc;
//old earthquake data
float oldSI = 0;
float oldPGA = 0;
//flag variables to handle collapse/shutoff only one time during an earthquake
bool shutoffHandled = false;
bool collapseHandled = false;
char datetime_str[20];
static bool attnInterruptOccurred;
Notecard notecard;
void attnISR()
{
attnInterruptOccurred = true;
}
void refreshDateTime(uint32_t flag)
{
tft.fillRect(0, 0, 319, 30, TFT_BLACK);
DateTime now = rtc.now();
sprintf(datetime_str, "%d/%02d/%02d %02d:%02d",
now.year(), now.month(), now.day(), now.hour(), now.minute());
usbSerial.println(datetime_str);
tft.setTextColor(TFT_WHITE, TFT_BLACK);
tft.setCursor(40, 20);
tft.setFreeFont(FM12);
tft.println(datetime_str);
}
//function to handle collapse event
void handleCollapse()
{
//put here the code to handle the collapse event
usbSerial.println("-------------------- COLLAPSE! --------------------");
}
void setupNotecard()
{
notecard.setDebugOutputStream(usbSerial);
notecard.begin();
J *req = notecard.newRequest("hub.set");
if (myProductID[0]) {
JAddStringToObject(req, "product", myProductID);
}
JAddStringToObject(req, "mode", "continuous");
JAddBoolToObject(req, "sync", true);
notecard.sendRequestWithRetry(req, 5); // 5 seconds
}
void attnArm()
{
if (J *req = notecard.newRequest("card.attn")) {
JAddStringToObject(req, "mode", "arm,signal");
notecard.sendRequest(req);
}
}
bool hasNoteHubSyncCompleted()
{
bool completed = false;
if (J *req = notecard.newRequest("hub.sync.status")) {
J *rsp = notecard.requestAndResponse(req);
if (JGetInt(rsp, "completed") > 0) {
completed = true;
}
notecard.deleteResponse(rsp);
}
return completed;
}
JTIME getNotecardTime()
{
J *req = notecard.newRequest("card.time");
JAddBoolToObject(req, "sync", true);
J *rsp = notecard.requestAndResponse(req);
notecard.logDebug(JConvertToJSONString(rsp));
JTIME ts = JGetInt(rsp, "time") + JGetInt(rsp, "minutes") * 60;
notecard.deleteResponse(rsp);
return ts;
}
void setupD7S()
{
D7S.begin();
while (!D7S.isReady()) {
usbSerial.print(".");
delay(500);
}
D7S.setAxis(SWITCH_AT_INSTALLATION);
usbSerial.println("Initializing the D7S, keep it steady during the initialization");
delay(2000);
usbSerial.print("Initializing...");
D7S.initialize();
while (!D7S.isReady()) {
usbSerial.print(".");
delay(500);
}
usbSerial.println("INITIALIZED!");
//check if there there was a collapse (if this is the first time the D7S is put in place the installation data may be wrong)
if (D7S.isInCollapse()) {
handleCollapse();
}
//reset the events shutoff/collapse memorized into the D7S
D7S.resetEvents();
usbSerial.println("\nListening for earthquakes!");
}
void setupRTC(JTIME ts)
{
rtc.begin();
rtc.adjust(DateTime(ts));
rtc.enableAlarm(0, rtc.MATCH_SS); // every minute
rtc.attachInterrupt(refreshDateTime);
}
void sendNote(float currentPGA, float currentSI)
{
J *req = notecard.newRequest("note.add");
if (req != NULL) {
JAddBoolToObject(req, "sync", true);
J *body = JAddObjectToObject(req, "body");
if (body != NULL) {
JAddStringToObject(body, "event", "earthquake");
JAddStringToObject(body, "deployment_id", "EQD0001");
J *data = JAddObjectToObject(body, "data");
if (data != NULL) {
JAddNumberToObject(data, "PGA", currentPGA);
JAddNumberToObject(data, "SI", currentSI);
}
}
notecard.sendRequest(req);
}
}
void buzz()
{
analogWrite(WIO_BUZZER, 225);
delay(1000);
analogWrite(WIO_BUZZER, 0);
delay(1000);
}
void setupTFT()
{
tft.begin();
tft.setRotation(3);
tft.fillScreen(TFT_BLACK);
tft.setFreeFont(FSS12);
tft.setCursor(0, 30);
}
void splashScreen()
{
tft.fillScreen(TFT_BLACK);
refreshDateTime(true);
tft.setTextColor(TFT_YELLOW);
tft.setFreeFont(FSS18);
tft.setCursor(70, 70);
tft.println("Earthquake");
tft.setCursor(50, 110);
tft.println("Alarm System");
tft.setFreeFont(FSS12);
tft.setCursor(65, 155);
tft.setTextColor(TFT_GREEN);
tft.println("powered by");
drawArrayJpeg(blues_logo, sizeof(blues_logo), 50, 160);
}
void setup()
{
usbSerial.begin(115200);
pinMode(WIO_BUZZER, OUTPUT);
pinMode(PUMP_PIN, OUTPUT);
pinMode(WIO_KEY_B, INPUT_PULLUP);
pinMode(WIO_KEY_C, INPUT_PULLUP);
setupTFT();
tft.print("Setting up D7S... ");
setupD7S();
tft.println("OK");
tft.print("Setting up Notecard... ");
setupNotecard();
tft.println("OK");
tft.print("Syncing Notehub... ");
while (! hasNoteHubSyncCompleted()) {
delay(5000);
}
tft.println("OK");
tft.print("Setting RTC time... ");
JTIME ts = getNotecardTime();
setupRTC(ts);
tft.println("OK");
pinMode(ATTN_INPUT_PIN, INPUT);
attachInterrupt(digitalPinToInterrupt(ATTN_INPUT_PIN), attnISR, RISING);
tft.print("Arming ATTN... ");
attnArm();
tft.println("OK");
splashScreen();
}
unsigned long int last_note_ms = millis();
void loop()
{
//checking if there is an earthquake occuring right now
if (D7S.isEarthquakeOccuring()) {
//check if the shutoff event has been handled and if the shutoff condition is met
//the call of D7S.isInShutoff() is executed after to prevent useless I2C call
if (!collapseHandled && D7S.isInCollapse()) {
handleCollapse();
collapseHandled = true;
}
//print information about the current earthquake
float currentSI = D7S.getInstantaneusSI();
float currentPGA = D7S.getInstantaneusPGA();
if (currentSI > oldSI || currentPGA > oldPGA) {
//getting instantaneus SI
usbSerial.print("\tInstantaneus SI: ");
usbSerial.print(currentSI, 4);
usbSerial.println(" [m/s]");
//getting instantaneus PGA
usbSerial.print("\tInstantaneus PGA (Peak Ground Acceleration): ");
usbSerial.print(currentPGA, 4);
usbSerial.println(" [m/s^2]\n");
// send Note
tft.fillScreen(TFT_BLACK);
refreshDateTime(true);
tft.setTextColor(TFT_RED);
tft.setFreeFont(FSS12);
tft.setCursor(0, 60);
tft.println("Earthquake Detected!\n");
tft.setTextColor(TFT_WHITE);
tft.print("Spectral Intensity: ");
tft.print(currentSI, 2);
tft.println(" m/s");
tft.print("Peak Ground Acc: ");
tft.print(currentPGA, 2);
tft.println(" m/s^2");
tft.setTextColor(TFT_WHITE);
tft.print("\nSending Note... ");
sendNote(currentPGA, currentSI);
tft.println("OK");
last_note_ms = millis();
while (millis() - last_note_ms < BUZZER_TIMEOUT_MS) {
buzz();
}
splashScreen();
//save the current data
oldSI = currentSI;
oldPGA = currentPGA;
}
} else {
//reset the old earthquake data
oldPGA = 0;
oldSI = 0;
//reset the flag of the handled events
shutoffHandled = false;
collapseHandled = false;
//reset D7S events
D7S.resetEvents();
}
if (attnInterruptOccurred) {
attnInterruptOccurred = false;
if (J *req = notecard.newRequest("hub.signal")) {
J *rsp = notecard.requestAndResponse(req);
if (rsp != NULL) {
J *body = JGetObject(rsp, "body");
if (body != NULL) {
char *command = JGetString(body, "motor");
usbSerial.println(command);
tft.fillScreen(TFT_BLACK);
refreshDateTime(true);
tft.setTextColor(TFT_YELLOW);
tft.setFreeFont(FSS12);
tft.setCursor(0, 70);
tft.println("Command received!\n");
tft.println(JConvertToJSONString(body));
// stop the pump
digitalWrite(PUMP_PIN, LOW);
delay(2000);
splashScreen();
}
}
notecard.deleteResponse(rsp);
}
attnArm(); // rearm
}
// manunally start/stop the pump
if (digitalRead(WIO_KEY_B) == LOW) {
digitalWrite(PUMP_PIN, LOW);
}
else if (digitalRead(WIO_KEY_C) == LOW) {
digitalWrite(PUMP_PIN, HIGH);
}
delay(10);
}
void drawArrayJpeg(const uint8_t arrayname[], uint32_t array_size, int xpos, int ypos)
{
int x = xpos;
int y = ypos;
JpegDec.decodeArray(arrayname, array_size);
renderJPEG(x, y);
}
void renderJPEG(int xpos, int ypos) {
// retrieve information about the image
uint16_t *pImg;
uint16_t mcu_w = JpegDec.MCUWidth;
uint16_t mcu_h = JpegDec.MCUHeight;
uint32_t max_x = JpegDec.width;
uint32_t max_y = JpegDec.height;
// Jpeg images are draw as a set of image block (tiles) called Minimum Coding Units (MCUs)
// Typically these MCUs are 16x16 pixel blocks
// Determine the width and height of the right and bottom edge image blocks
uint32_t min_w = minimum(mcu_w, max_x % mcu_w);
uint32_t min_h = minimum(mcu_h, max_y % mcu_h);
// save the current image block size
uint32_t win_w = mcu_w;
uint32_t win_h = mcu_h;
// save the coordinate of the right and bottom edges to assist image cropping
// to the screen size
max_x += xpos;
max_y += ypos;
// read each MCU block until there are no more
while (JpegDec.read()) {
// save a pointer to the image block
pImg = JpegDec.pImage ;
// calculate where the image block should be drawn on the screen
int mcu_x = JpegDec.MCUx * mcu_w + xpos; // Calculate coordinates of top left corner of current MCU
int mcu_y = JpegDec.MCUy * mcu_h + ypos;
// check if the image block size needs to be changed for the right edge
if (mcu_x + mcu_w <= max_x) win_w = mcu_w;
else win_w = min_w;
// check if the image block size needs to be changed for the bottom edge
if (mcu_y + mcu_h <= max_y) win_h = mcu_h;
else win_h = min_h;
// copy pixels into a contiguous block
if (win_w != mcu_w)
{
uint16_t *cImg;
int p = 0;
cImg = pImg + win_w;
for (int h = 1; h < win_h; h++)
{
p += mcu_w;
for (int w = 0; w < win_w; w++)
{
*cImg = *(pImg + w + p);
cImg++;
}
}
}
// calculate how many pixels must be drawn
uint32_t mcu_pixels = win_w * win_h;
tft.startWrite();
// draw image MCU block only if it will fit on the screen
if (( mcu_x + win_w ) <= tft.width() && ( mcu_y + win_h ) <= tft.height())
{
// Now set a MCU bounding window on the TFT to push pixels into (x, y, x + width - 1, y + height - 1)
tft.setAddrWindow(mcu_x, mcu_y, win_w, win_h);
// Write all MCU pixels to the TFT window
while (mcu_pixels--) {
// Push each pixel to the TFT MCU area
tft.pushColor(*pImg++);
}
}
else if ( (mcu_y + win_h) >= tft.height()) JpegDec.abort(); // Image has run off bottom of screen so abort decoding
tft.endWrite();
}
}
After a successful upload, we can view the startup messages and the landing page as displayed below.
First, the D7S module's calibration process is started. We should not move the board during the calibration. After calibration, it configures and synchronizes the Notehub and set the time on the onboard RTC.
Live DemoFor the demonstration, we attempted to simulate an industrial environment to display the functionality of a water pump with running liquid. While a real industrial setting is significantly more complex, this setup serves the intended purpose.
ConclusionThis project represents a significant leap forward in disaster response technology. It seamlessly integrates a cellular network with a mobile application to swiftly and reliably alert individuals of seismic events. The remote water pump shutdown feature via the app is crucial in preventing infrastructure damage and mitigating post-earthquake water-related risks. This project conclusively demonstrates the effectiveness of utilizing a cost-effective and reliable cellular network in emergency management systems.
Comments