LoRaWAN Based Smart Helmet for Coal Mine Workers

by its_abhinav in Circuits > Arduino

2144 Views, 12 Favorites, 0 Comments

LoRaWAN Based Smart Helmet for Coal Mine Workers

cover_image.png

Mining, particularly in coal mines, presents numerous hazards to the lives of workers, making their safety a paramount concern. The Coal Mine Hazard Prevention and Safety Monitoring System leverages advanced technology to revolutionize safety in this high-risk environment. Our technology serves as a ray of hope for the devoted workers who brave these difficulties in the depths of these mines, where the very air can become toxic and the surroundings are harsh. Our strategy relies on the tactical placement of a variety of sensors to identify and foresee potential dangers. The MQ4 methane sensor, the MQ9 carbon monoxide sensor, and the MAX30102 sensor to continually monitor the heart rate of mine workers are important components. These sensors are the sentinels guarding the safety of our heroic miners, painstakingly incorporated into a unique helmet.


The innovation doesn't stop at the sensors as this project relies on the potent SX1278 LoRa (Long Range) module for efficient data transfer and communication. This link guarantees that up-to-the-minute information about the environment of the mine and the miners' health statistics are consistently transmitted to the surface, enabling quick responses to emergencies and improving safety procedures. Our integrated approach to mine safety and the effectiveness of our Coal Mine Hazard Prevention and Safety Monitoring System are both supported by the combination of these sensors and communication modules.

Supplies

supplies1.png
supplies2.png

Hardware Requirements:

  • MQ4 sensor x 1
  • MQ9 sensor x 1
  • MAX30102 pulse sensor x 1
  • SX1278 LoRa module x 2
  • Arduino Nano 33 IOT x 2
  • 3S Lithium-Ion 12v battery
  • LM7805 voltage regulator
  • Jumper Wires
  • Breadboard
  • Worker Helmet with headphone (Enclosure)
  • General PCB (Optional)
  • Soldering Iron (Optional)
  • Suitable antenna for LoRa (see Step 1)


Software Requirements:

Hardware Connections

210_project_master_circuit_diagram_bb.png
210_project_slave_circuit_diagram_bb.png

Talking about connections, at minimum there will exist two systems or nodes, one which will remain on the surface aka the master node and the other which will be present inside the helmet aka the slave node.


Master Connections:

The master node will only comprise of two main components, the LoRa module and the Arduino board*. The LoRa module works on the SPI communication protocol thus it has four pins for communication - MOSI, MISO, SCK and NSS alongside the 3.3v** and GND input pins.


Slave Connections:

The slave node consists of the following sensors - MQ4 and MQ9 for sensing concentration of Methane and Carbon Monoxide, respectively alongside a MAX30102 pulse oximeter and heart-rate sensor for calculating pulse rate of the worker. The circuit also includes a LoRa module for data transmission and a battery with LM7805 voltage regulator to power up the system.



Note: It is important to connect appropriate antennas to LoRa modules for optimum results. This article by Engr Fahad discusses about such antennas with the LoRa module in detail.

* Any Arduino board equipped with support for SPI communication can be used. For this project, we will be using Arduino Nano 33 IOT board.

** The SX1278 module is 3.3v only and can handle a maximum of 3.6v, so it should not be connected to 5v supply of any kind.

Testing the Connections

After the connections are complete on the breadboard, it is time to test the system with minimal code for testing the LoRa connection. Two Arduino codes are mentioned below with first one for the master node and next one for the slave node. SX1278 module works on half-duplex SPI communication thus even master can transmit messages while the slave receives the message which can be incorporated for making the whole system integrated and work in unison.


Master Test Code


/*
* Abhinav Sharma
* GitHub Repository Link: https://github.com/abhinav52-sh/SIT210-project
*
 * Master Code - Connections

  Module SX1278 // Arduino UNO/NANO    
    Vcc         ->   3.3V
    MISO        ->   D12
    MOSI        ->   D11     
    SLCK        ->   D13
    Nss         ->   D10 
    GND         ->   GND
 */

#include <SPI.h>
#include <LoRa.h>
 
int count = 0;

void setup() {
  Serial.begin(9600);
   
  while (!Serial);  
  Serial.println("LoRa Transmitter - Slave");
  
  if (!LoRa.begin(433E6)) { // or 915E6, the MHz speed of your module
    Serial.println("LoRa Initialization failed! Check Connections.");
    while (1);
  }
}
 
void loop() {
  Serial.print("Sending - ");
Serial.println(count);
String MyMessage = "Hey LoRa, this is Abhinav from SIT210 - ";
LoRa.beginPacket();  
LoRa.print(MyMessage);
LoRa.println(count);
LoRa.endPacket();
delay(200);
count++;
}


The LoRa library initializes the LoRa module while SPI library is required to communicate the module with the Arduino.


LoRa.begin(433E6);

The begin function initializes the sensor and 433 refers to the frequency the module works that is 433 MHz in Asia (varies based on region) .


LoRa.beginPacket(); 

The beginPacket() function is used to create a packet which will transmit the desired content.


 LoRa.print();

print() is used to send strings in the above created packet.


LoRa.endPacket();

endPacket(); tell the code that this is the end of the current packet and close the data transmission.


Slave Test Code


/* 
 *  Receiver Side Code
 * 
  Module SX1278 // Arduino UNO/NANO    
    Vcc         ->   3.3V
    MISO        ->   D12
    MOSI        ->   D11     
    SLCK        ->   D13
    Nss         ->   D10
    GND         ->   GND
 */


#include <SPI.h>
#include <LoRa.h>  
String inString = "";    // string to hold incoming charaters
String MyMessage = ""; // Holds the complete message


void setup() {
  Serial.begin(9600);
 delay(1000);
  while (!Serial);
  Serial.println("LoRa Receiver");
  if (!LoRa.begin(433E6)) { // or 915E6
    Serial.println("Starting LoRa failed!");
    while (1);
  }
}


void loop() {
 MyMessage = "";
  // try to parse packet
  int packetSize = LoRa.parsePacket();
  if (packetSize) { 
    // read packet    Lo
    while (LoRa.available())
    {
      int inChar = LoRa.read();
      inString += (char)inChar;
      MyMessage = inString;       
    }
    inString = "";     
    LoRa.packetRssi();    
  }
      
  
  Serial.println(MyMessage);    
}


The LoRa library initializes the LoRa module while SPI library is required to communicate the module with the Arduino.


LoRa.parsePacket();

parsePacket() function checks if any message is inbound, if yes it returns the size of that packet/message.


LoRa.read();

read() function is used to read the contents the message of the packet in bytes which is further collected in the MyMessage variable.


With proper connections and codes, the communication could be observed on the serial monitor successfully in real-time.

Setting Up Google Firebase

step3_image1.png

The next step is to set up the Realtime database from Google Firebase. We are using the database to stream our data from the master system in real-time which can be accessed by the web interface. Firstly, go to console.firebase.google.com and sign in with your email id and then create a new project and name it anything of your choice (my project's name: SIT210-MineSafe-Innovators).


After creating the project, navigate to Realtime Database under the Build drop down menu in the left navigation bar. Ensure that the rules are set as 'true' for both read and write* for testing purposes. Now, note down a few details from the firebase project. First is to store the database URL from the Realtime Database page, then move to Project Settings > Service Accounts > Database secrets and store the secret. These two data fields will be useful in integrating firebase with the Master (Surface) Arduino System.


Then, store the credentials for integrating web interface with firebase. For this, navigate to Project Settings > General and click on the html icon for creating new web app credentials. After naming and creating the web app credentials, copy the javascript code under npm radio button which stores multiple fields such as apiKey, authDomain, etc. This is it for setting up the Firebase.


* Be sure to change the rules after the testing is complete to prevent any possible data theft.

Interfacing Helmet Unit With All Sensors

step4_image1.jpg
step4_image2.jpg

We have set up the LoRaWAN communication and the database to store the data in. Now, we will start with interfacing the sensors with the helmet unit which will then be sent to Surface via LoRa. To start we will connect all three sensors - MQ4, MQ9 and MAX30102 with the Arduino Board based on the following circuit diagram and make sure all the connections are taught (Step 1).


Now, for the programming part, I used this MAX3010x library and used one its examples to build upon my code. Next, for the gas sensors, I just simply read the analog values of the respective gas concentrations and used a formula to convert* that value to ppm (parts per million). Then, I combined all the sensor and LoRa code to work in unison which is provided below.


* To know more about the analog to ppm conversion, you can visit this article by Tessie.



Slave Unit Final Code

// Slave Node


/*
 *
 * GitHub Link: https://github.com/abhinav52-sh/SIT210-project
 *
  Module SX1278 // Arduino UNO/NANO    
    Vcc         ->   3.3V
    MISO        ->   D12
    MOSI        ->   D11    
    SLCK        ->   D13
    Nss         ->   D10
    GND         ->   GND
    -----------------------------------


    Module MAX30102 // Arduino UNO/NANO    
    Vcc         ->   5V
    SDA         ->   A4
    SCL         ->   A5      
    GND         ->   GND
    -----------------------------------


    Module MAX30102 // Arduino UNO/NANO    
    Vcc         ->   5V
    AO          ->   A0  
    GND         ->   GND
    -----------------------------------


    Module MAX30102 // Arduino UNO/NANO    
    Vcc         ->   5V
    AO          ->   A1  
    GND         ->   GND
 */


#include <SPI.h>  // include libraries
#include <LoRa.h>
#include <Wire.h>
#include "MAX30105.h"
#include "heartRate.h"


#define MQ4 A0
#define MQ9 A1


String outgoing;  // outgoing message


byte msgCount = 0;         // count of outgoing messages
byte localAddress = 0xEE;  // address of this device
byte destination1 = 0xBB;  // destination to send to
byte destination2 = 0xFF;  // destination to send to
long lastSendTime = 0;     // last send time
int interval = 500;        // interval between sends


MAX30105 particleSensor;
const byte RATE_SIZE = 4;  // averaging.
byte rates[RATE_SIZE];     //Array of heart rates
byte rateSpot = 0;
long lastBeat = 0;  // Time at which the last beat occurred


float beatsPerMinute;
int beatAvg;


int mq4_data = 0;
int mq9_data = 0;


const int R_0 = 945;  // for methane


void setup() {


  Serial.begin(9600);  // initialize serial

  delay(2000);
  Serial.println("LoRa Duplex");


  if (!LoRa.begin(433E6)) {  // initialize ratio at 915 MHz
    Serial.println("LoRa init failed. Check your connections.");
    while (true)
      ;  // if failed, do nothing
  }


  Serial.println("LoRa init succeeded.");


  if (!particleSensor.begin(Wire, I2C_SPEED_FAST))  //Use default I2C port, 400kHz speed
  {
    Serial.println("MAX30105 was not found. Please check wiring/power. ");
    while (1)
      ;
  }


  // initializing max30102 sensor
  particleSensor.setup();
  particleSensor.setPulseAmplitudeRed(0x0A);  //Turn Red LED to low to indicate sensor is running
  particleSensor.setPulseAmplitudeGreen(0);   //Turn off Green LED
}


void loop() {
  // if (millis() - lastSendTime > interval) {
  long irValue = particleSensor.getIR();
  if (checkForBeat(irValue) == true) {
    //We sensed a beat!
    long delta = millis() - lastBeat;
    lastBeat = millis();


    beatsPerMinute = 60 / (delta / 1000.0);


    if (beatsPerMinute < 255 && beatsPerMinute > 20) {
      rates[rateSpot++] = (byte)beatsPerMinute;  //Store this reading in the array
      rateSpot %= RATE_SIZE;                     //Wrap variable


      //Take average of readings
      beatAvg = 0;
      for (byte x = 0; x < RATE_SIZE; x++)
        beatAvg += rates[x];
      beatAvg /= RATE_SIZE;
    }
  }


  if (millis() - lastSendTime > interval) {
    lastSendTime = millis();
    mq4_data = getMethanePPM();
    mq9_data = getMonoxidePPM();


    String message = String(mq4_data) + "," + String(mq9_data) + "," + String(beatAvg);
    Serial.println(message);
    sendMessage(message, destination1);
  }
}


// function to transmit the message with all data fields from different sensors to the master(surface)
void sendMessage(String outgoing, byte destinationAddr) {
  LoRa.beginPacket();             // start packet
  LoRa.write(destinationAddr);    // add destination address
  LoRa.write(localAddress);       // add sender address
  LoRa.write(msgCount);           // add message ID
  LoRa.write(outgoing.length());  // add payload length
  LoRa.print(outgoing);           // add payload
  LoRa.endPacket();               // finish packet and send it
  Serial.print("Sending ID: ");
  Serial.println(msgCount);
  msgCount++;  // increment message ID
}


// function to receive any incoming data for future functionality
void onReceive(int packetSize) {
  if (packetSize == 0) return;  // if there's no packet, return


  // read packet header bytes:
  int recipient = LoRa.read();        // recipient address
  byte sender = LoRa.read();          // sender address
  byte incomingMsgId = LoRa.read();   // incoming msg ID
  byte incomingLength = LoRa.read();  // incoming msg length


  String incoming = "";


  while (LoRa.available()) {
    incoming += (char)LoRa.read();
  }


  if (incomingLength != incoming.length()) {  // check length for error
    Serial.println("error: message length does not match length");


    return;  // skip rest of function
  }


  // if the recipient isn't this device or broadcast,
  if (recipient != localAddress && recipient != 0xFF) {
    Serial.println("This message is not for me.");


    return;  // skip rest of function
  }


  // if message is for this device, or broadcast, print details:
  Serial.println("Received from: 0x" + String(sender, HEX));
  Serial.println("Sent to: 0x" + String(recipient, HEX));
  Serial.println("Message ID: " + String(incomingMsgId));
  Serial.println("Message length: " + String(incomingLength));
  Serial.println("Message: " + incoming);
  Serial.println();
}


// function to calculate methane's ppm conc.
float getMethanePPM() {
  float a0 = analogRead(MQ4);
  float v_out = a0 * 5 / 1023;
  float R_S = (5 - v_out) * 1000;
  float PPM = pow(R_S / R_0, -2.95) * 1000;
  return PPM;
}


// function to calculate carbon monoxide's ppm conc.
float getMonoxidePPM() {
  int rs = analogRead(A0);
  float ro = 20000.0;
  float rs_to_ro_ratio = rs / ro;
  float ppm = 10.0 * pow(rs_to_ro_ratio, 1.0 / 3.2);
  return ppm;
}


The above code collects the data from all sensors (MQ4, MQ9, MAX30102) and transmits it via the LoRa module (done earlier).

Downloads

Interfacing Master (Surface) Unit With Firebase

step5_image1.jpg

In our previous work, we developed a program to receive data via LoRa communication. Now, we are taking this a step further by enhancing our code to transmit the received data to the Realtime Database in Firebase, as previously set up in Step 3. It is essential to ensure that all the connections are configured according to the specifications outlined in Step 3.


To incorporate Firebase functionality into our Arduino code, we have employed the Firebase Arduino based on the WiFiNINA library. This choice is especially relevant since we are utilizing Arduino's Nano 33 IoT board equipped with u-blox's NINA chipset for Wi-Fi capabilities.


Building upon our previously created LoRa receiving code, we have introduced a new function, 'void publishData()'. This function's primary purpose is to facilitate the transmission of the received data to Firebase's Realtime Database. Before utilizing this function, we ensure that our Arduino board is connected to Wi-Fi. This connection is established by configuring your mobile device as a hotspot and providing the necessary credentials acquired in Step 3.


By implementing these modifications, our system becomes capable of not only receiving data through LoRa communication but also efficiently transmitting that data to Firebase's Realtime Database, thereby enabling real-time data updates and enhancing the functionality of our project.


Master Unit Final Code

// Master Node


/*
 *
 * GitHub Link: https://github.com/abhinav52-sh/SIT210-project
 *
  Module SX1278 // Arduino UNO/NANO    
    Vcc         ->   3.3V
    MISO        ->   D12
    MOSI        ->   D11    
    SLCK        ->   D13
    Nss         ->   D10
    GND         ->   GND
 */

#include <SPI.h>  // include libraries
#include <LoRa.h>


#include <Firebase_Arduino_WiFiNINA.h>


const char ssid[] = "***";
const char pass[] = "***";
const String DATABASE_URL = "***";
const String DATABASE_SECRET = "***";


FirebaseData firebaseData;


String outgoing;  // outgoing message


byte msgCount = 0;         // count of outgoing messages
byte localAddress = 0xBB;  // address of this device
byte destination1 = 0xEE;  // destination to send to
byte destination2 = 0xFF;  // destination to send to

void setup() {


  Serial.begin(9600);  // initialize serial for testing
  delay(2000);
  Serial.println("LoRa Duplex");


  if (!LoRa.begin(433E6)) {
    Serial.println("LoRa init failed. Check your connections.");
    while (true)
      ;  // if failed, do nothing
  }


  Serial.println("LoRa init succeeded.");


  establishConnection();
}


void loop() {


  // parse for a packet, and call onReceive with the result:
  onReceive(LoRa.parsePacket());
  delay(10);
}


// function for transmitting message for future expansion
void sendMessage(String outgoing, byte destinationAddr) {
  LoRa.beginPacket();             // start packet
  LoRa.write(destinationAddr);    // add destination address
  LoRa.write(localAddress);       // add sender address
  LoRa.write(msgCount);           // add message ID
  LoRa.write(outgoing.length());  // add payload length
  LoRa.print(outgoing);           // add payload
  LoRa.endPacket();               // finish packet and send it
  msgCount++;                     // increment message ID
}

// function to receive the incoming LoRa packet after performing few verifications
void onReceive(int packetSize) {
  if (packetSize == 0) return;  // if there's no packet, return


  // read packet header bytes:
  int recipient = LoRa.read();        // recipient address
  byte sender = LoRa.read();          // sender address
  byte incomingMsgId = LoRa.read();   // incoming msg ID
  byte incomingLength = LoRa.read();  // incoming msg length


  String incoming = "";


  while (LoRa.available()) {
    incoming += (char)LoRa.read();
  }


  if (incomingLength != incoming.length()) {  // check length for error
    Serial.println("error: message length does not match length");


    return;  // skip rest of function
  }


  // if the recipient isn't this device or broadcast,
  if (recipient != 0xBB) {
    Serial.println("This message is not for me.");
    return;  // skip rest of function
  }


  // if message is for this device, or broadcast, print details:
  Serial.println("Received from: 0x" + String(sender, HEX));
  Serial.println("Sent to: 0x" + String(recipient, HEX));
  Serial.println("Message ID: " + String(incomingMsgId));
  Serial.println("Message length: " + String(incomingLength));
  Serial.println("Message: " + incoming);
  Serial.println();


  if (sender == 0xEE || sender == 0xFF || sender == 0xDD) {

    int break1 = incoming.indexOf(',');
    int break2 = incoming.indexOf(',', break1 + 1);
    int break3 = incoming.indexOf(',', break2 + 1);


    int mq4_data = incoming.substring(0, break1).toInt();
    int mq9_data = incoming.substring(break1 + 1, break2).toInt();
    int heartRate = incoming.substring(break2 + 1, break3).toInt();


    publishData(sender, mq4_data, mq9_data, heartRate);
  }
}


void publishData(int sender, int mq4_data, int mq9_data, int heartRate) {
  String senderAddressHex = String(sender, HEX);
  String firebasePath1 = "/userdata/" + senderAddressHex + "/mq4";
  String firebasePath2 = "/userdata/" + senderAddressHex + "/mq9";
  String firebasePath3 = "/userdata/" + senderAddressHex + "/hrate";

  Firebase.setInt(firebaseData, firebasePath1, mq4_data);
  Firebase.setInt(firebaseData, firebasePath2, mq9_data);
  Firebase.setInt(firebaseData, firebasePath3, heartRate);


  if (firebaseData.dataAvailable()) {
    Serial.println("Data sent to Firebase successfully.");
  } else {
    Serial.println("Failed to send data to Firebase.");
    Serial.println(firebaseData.errorReason());
  }
}


// establishing connection to firebase via wifi using thier credentials
void establishConnection() {
  Serial.println("Connecting to Wi-Fi");
  int status = WL_IDLE_STATUS;

// connecting to Wi-Fi
  while (status != WL_CONNECTED) {
    status = WiFi.begin(ssid, pass);
    Serial.print(".");
    delay(100);
  }

// intitialing firebase with required credentials
  Firebase.begin(DATABASE_URL, DATABASE_SECRET, ssid, pass);
  Firebase.reconnectWiFi(true);


  Serial.println("Connected to wifi & firebase");
}


The code uses 'setInt()' function to upload integer data on Realtime database on the desired path.


Once the code is complete you can switch on both the master and slave units and verify the change in data on the Realtime database page (like in below screenshot). To ensure excellent results, designing a PCB for better structural integrity and user safety is recommended.

Downloads

Developing a React Web App (GUI)

step6_image1.png
step6_image2.png
step6_image3.png

After setting up the firebase, the next step is to create a user interface in the form of a web application using React. Ensure that NodeJS is installed on your system. To start right away with the website, download the website code from GitHub repository and extract the contents into a new folder in an accessible location on the computer. Then, open a terminal window in that folder location and type 'npm install' to install the required dependencies for the react app.


Once the command is executed successfully, a new 'node_modules' folder will be generated. Next, you'll want to navigate to the 'src > Components' folder and locate the 'config.js' file. In this file, you should replace the default credentials with your own, as outlined in Step 3. This step ensures that your web app is securely configured.

Additionally, open the 'Login.jsx' file. Here, you'll set up and remember the login credentials required to access the web application. These credentials will be essential for gaining access to the system.


With these initial setup steps completed, you're now ready to launch your web app. Simply execute the 'npm start' command to initiate the website, and it will open in your browser. Upon the initial loading of the page, you'll encounter a login screen, which serves as a security measure to prevent unauthorized access.

Enter the credentials you've established and press 'Enter'. This action will grant you access to the main user interface, featuring various visual elements and a card-based layout. In this interface, you'll eventually see a selection of helmets displayed as cards. However, it's important to note that these helmets are not yet linked to Firebase, so at this stage, the website won't display any helmet cards.


After all the above steps are complete, the system is ready to be tested with everything working in order and for better access to the web app it can be hosted on using a personal or free domain using Firebase which can be done by following this article by mihir0699.

I have hosted a sample website for the web app this link which can be accessed using below test credentials.

Username: visitor, Password: 0123


This article is a part of the assignment submitted to Deakin University, School of IT, Unit SIT210 - Embedded Systems Development by Abhinav Sharma (2210994752).