Luke Dutton
Published © GPL3+

Fire Equipment Management

Firefighters must manage their equipment out in the field. Paper gets lost, and WIFI is rare. Simply transmit tracking info via cellular.

IntermediateFull instructions provided10 hours764

Things used in this project

Hardware components

Raspberry Pi Zero Wireless
Raspberry Pi Zero Wireless
×1
Hologram Nova
Hologram Nova
×1
NodeMCU ESP8266 Breakout Board
NodeMCU ESP8266 Breakout Board
×1
RGB Backlight LCD - 16x2
Adafruit RGB Backlight LCD - 16x2
×1
Photon
Particle Photon
×1
Base Shield V2
Seeed Base Shield V2
×1
Jumper wires (generic)
Jumper wires (generic)
×1
Adafruit Colorful Round Tactile Button Switch Assortment
×4
Resistor 10k ohm
Resistor 10k ohm
×7
Raspberry Pi wifi dongle
×1
Targus usb 3.0 hub
×1
Portable Power Bank
×2

Software apps and online services

SparkFun phant
phant is also a node.js module that was recently sunset. It can still be downloaded, but you'll probably need to do some code cleanup to get it to work. I'll have a github link in my code section that has the cleaned up version.
Hologram Data Router
Hologram Data Router
will serve as the cellular communication update link
Arduino IDE
Arduino IDE
Used to code on the ESP8266MOD
Intellij Idea - Community Edition
Java IDE I am using to create the code for my raspberry Pi
Particle Desktop IDE (Dev)
IDE I used to flash my particle photon microcontroller
Etcher
Used to flash Raspbian to the raspberry pi micro sd card

Story

Read more

Schematics

Particle Photon Inventory Controller

This is the device that allows users to select inventory items and quantity and alter the inventory total

Code

Wifi Access Point - ESP8266MOD

C/C++
This is the wireless access point that allows the particle photon to connect to the Raspberry Pi Zero W.
This code goes in the Arduino IDE and gets flashed onto the ESP8266.
Of course, you'll need to use your own SSID and Password.
/*
 * Copyright (c) 2015, Majenko Technologies
 * All rights reserved.
 * 
 * Redistribution and use in source and binary forms, with or without modification,
 * are permitted provided that the following conditions are met:
 * 
 * * Redistributions of source code must retain the above copyright notice, this
 *   list of conditions and the following disclaimer.
 * 
 * * Redistributions in binary form must reproduce the above copyright notice, this
 *   list of conditions and the following disclaimer in the documentation and/or
 *   other materials provided with the distribution.
 * 
 * * Neither the name of Majenko Technologies nor the names of its
 *   contributors may be used to endorse or promote products derived from
 *   this software without specific prior written permission.
 * 
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
 * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
 * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR
 * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
 * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
 * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
 * ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
 * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 */

/* Create a WiFi access point and provide a web server on it. */

#include <ESP8266WiFi.h>
#include <WiFiClient.h> 
#include <ESP8266WebServer.h>

/* Set these to your desired credentials. */
const char *ssid = "<ssid>";
const char *password = "<password>";

ESP8266WebServer server(80);

/* Just a little test message.  Go to http://192.168.4.1 in a web browser
 * connected to this access point to see it.
 */
void handleRoot() {
	server.send(200, "text/html", "<h1>You are connected</h1>");
}

void setup() {
	delay(1000);
	Serial.begin(115200);
	Serial.println();
	Serial.print("Configuring access point...");
	/* You can remove the password parameter if you want the AP to be open. */
	WiFi.softAP(ssid, password);

	IPAddress myIP = WiFi.softAPIP();
	Serial.print("AP IP address: ");
	Serial.println(myIP);
	server.on("/", handleRoot);
	server.begin();
	Serial.println("HTTP server started");
}

void loop() {
	server.handleClient();
}

Particle Photon Code

C/C++
Use this code on the Particle Photon to attach to your access point
// This #include statement was automatically added by the Particle IDE.
#include <HttpClient.h>
// This #include statement was automatically added by the Particle IDE.
#include <SparkJson.h>
// This #include statement was automatically added by the Particle IDE.
#include <Grove_LCD_RGB_Backlight.h>
#include "Wire.h"
#include "application.h"
//prevents the particle photon from trying to contact the mothership, thereby blocking all user code.
SYSTEM_MODE(SEMI_AUTOMATIC);
//for the LCD
rgb_lcd lcd;
const int colorR = 255;
const int colorG = 0;
const int colorB = 0;
// Integers that contain which item in the array you're looking at
int itemSelected=0;
int quantitySelected = 0;
char intString[90];
char finalPath[200];
//Loop controls for decision making
bool start = false;
bool itemWasSelected = false;
bool quantityWasSelected = false;
bool confirmationCheck = false;
// Size of the library holding your items
int itemLibrarysz = 3;
// For the buttonPressed counting
int buttonPressed = 0;
// For the http request section
HttpClient http;
http_request_t request;
http_response_t response;
http_header_t headers[] = {
   { "Accept" , "*/*"},
 { NULL, NULL } // NOTE: Always terminate headers will NULL
};
//May eventually turn this into an object, or use set index [?][2] to the existing quantity in the json
String Items[3][3] = {  
  {"1", "Hose", "0"} ,   /*  initializers for row indexed by 0 */
  {"2", "Extinguisher", "0"} ,   /*  initializers for row indexed by 1 */
  {"3", "Helmet", "0"}   /*  initializers for row indexed by 2 */
};
void setup() {
       // set up the LCD's number of columns and rows:
   lcd.begin(16, 2);
   lcd.setRGB(colorR, colorG, colorB);
   lcd.clear();
   lcd.setCursor(0,0);
   lcd.print("Starting");
   delay(900);
   //WiFi.disconnect();
   WiFi.setCredentials("[RaspberryPiSSIDHere]", "[SSID_Password]", WPA2);
   WiFi.connect();
   lcd.clear();
   while(!WiFi.ready()){
       lcd.setCursor(0, 0);
       lcd.print("WiFi Not Ready");
       delay(300);
       lcd.clear();
   }
   lcd.clear();
   // Network address of the Raspberry Pi
request.ip= IPAddress(10,0,0,1);
   request.port = 8080;
   delay(1000);
   //Set up the listening pins for button input
   // For the reading of the Start/Select
   pinMode(D3, INPUT);
   // For the reading of the Up Button
   pinMode(D4, INPUT);
   // For the reading of the Down Button
   pinMode(D5, INPUT);
   // For the reading of the Cancel Button
   pinMode(D6, INPUT);
   // output each array element's value                      
  for ( int i = 0; i < 3; i++ ){
     for ( int j = 1; j < 2; j++ ) {
       String item = Items[i][j];
       lcd.setCursor(0, 0);
       lcd.print(item);// had errors in the past with converting body to string
       delay(1000);
       // Clear the screen.
       lcd.clear();
     }
  }
}
// pushes the inventory changes for the selected item
// Will need to look something like void postPhantPushRequest(Integer ItemID, String Description, Integer Quantity)
void postPhantPushRequest() {
   // TODO 
   // Use in final version
   String inputPath = "/input/[YourPhantStreamPublicKey]?private_key=[YourPhantStreamPrivateKey]";
   String itemIDString = "&ItemID=" + Items[itemSelected][0]; 
   String descriptionString = "&Description=" + Items[itemSelected][1];
   String quantityString = "&Quantity=";
   sprintf(finalPath, inputPath + itemIDString 
                           + descriptionString + quantityString + "%d", quantitySelected);
   request.path = finalPath;
   http.get(request, response, headers);
   lcd.clear();
   lcd.setCursor(0, 1);
   lcd.print(response.status);// had errors in the past with converting body to string
   delay(800);
   lcd.clear();
   lcd.setCursor(0,0);
   lcd.print(response.body);
   delay(800);
}
//retrieves json from the phant server to populate the inventory options
//For future 
void postPhantPullRequest() {
   request.path = "/output/[YourOtherPhantOutputStream-UpdatedByRPi].json";
   http.get(request, response, headers);
}
void adQuantity(){
 quantitySelected = quantitySelected + 1;
}
void rmQuantity(){
 quantitySelected = quantitySelected - 1;
}
void loop() {
   // set cursor at the beginning
   lcd.setCursor(0,0);
   lcd.print("Push To Begin");
   // need to add in about a second of load time so that it doesn't re read the same button push
   if(digitalRead(D3) == HIGH)
   {
       start = true;
       lcd.clear();
       lcd.setCursor(0, 0);
       //delay so you don't get a second select read
       lcd.print("Loading.");
       delay(180);
       lcd.clear();
       lcd.setCursor(0, 0);
       lcd.print("Loading..");
       delay(180);
       lcd.clear();
       lcd.setCursor(0, 0);
       lcd.print("Loading...");
       delay(180);
       lcd.clear();
       lcd.setCursor(0, 0);
       lcd.print("Loading....");
       delay(180);
   }
 while(start){
     delay(100);
     lcd.clear();
     lcd.setCursor(0, 0);
     lcd.print("Item:");
     lcd.setCursor(0,1);
     lcd.print(Items[itemSelected][1]);
     // Up button was pressed
     if(digitalRead(D4) == HIGH){
         // itemSelected is a 0 based index and 
         // itemLibrarysz is a one based size variable
         if(itemSelected < (itemLibrarysz - 1)){
           itemSelected ++;
           delay(100);
         }
     }
     //Down Button was pressed
     else if (digitalRead(D5) == HIGH){
         // Don't let them reduce below zero
         if(itemSelected > 0){
           itemSelected --;
           delay(100);
         }
     }
     // Select was pressed
     else if (digitalRead(D3) == HIGH){
         // Enter the quantity loop here
         itemWasSelected = true;
         lcd.clear();
         lcd.setCursor(0, 0);
         //delay so you don't get a second select read
         lcd.print("Loading.");
         delay(180);
         lcd.clear();
         lcd.setCursor(0, 0);
         lcd.print("Loading..");
         delay(180);
         lcd.clear();
         lcd.setCursor(0, 0);
         lcd.print("Loading...");
         delay(180);
         while(itemWasSelected){
             delay(100);
           lcd.clear();
           lcd.setCursor(0,0);
           lcd.print("Item:" + Items[itemSelected][1]);
           lcd.setCursor(0,1);
           sprintf(intString, "Quantity:%d", quantitySelected);
           lcd.print(intString);
               if(digitalRead(D4) == HIGH){
                   // itemSelected is a 0 based index and 
                   // itemLibrarysz is a one based size variable
                   if(quantitySelected < 99)
                       adQuantity();
                       delay(100);
               }
                 // Down Button was pressed
               else if (digitalRead(D5) == HIGH){
                   // Don't let them reduce below zero
                   if(quantitySelected > 0)
                       rmQuantity();
                       delay(100);
               }
               // Select button was pressed
               // Enter confirmation loop
               else if (digitalRead(D3) == HIGH){
                   lcd.clear();
                     lcd.setCursor(0, 0);
                     //delay so you don't get a second select read
                     lcd.print("Loading.");
                     delay(180);
                     lcd.clear();
                     lcd.setCursor(0, 0);
                     lcd.print("Loading..");
                     delay(180);
                     lcd.clear();
                     lcd.setCursor(0, 0);
                     lcd.print("Loading...");
                     delay(180);
                   quantityWasSelected = true;
                   lcd.clear();
                   lcd.setCursor(0,0);
                   sprintf(intString, "I:" + Items[itemSelected][1] + " Qty:%d", quantitySelected);
                   lcd.print(intString);
                   lcd.setCursor(0,1);
                   lcd.print("Yes Or No?");
                   while(quantityWasSelected){
                       delay(100);
                       if (digitalRead(D3) == HIGH){
                           sprintf(intString, "ItemID:" + Items[itemSelected][0] + "Item:" 
                           + Items[itemSelected][1] + " Quantity:%d", quantitySelected);
                           delay(1000);
                           lcd.clear();
                           lcd.setCursor(0,0);
                           delay(300);
                           lcd.clear();
                           postPhantPushRequest();
                           quantityWasSelected = false;
                           itemWasSelected = false;
                           quantitySelected = 0;
                           itemSelected = 0;
                           start = false;
                           delay(2000);
                           lcd.clear();
                       }
                       else if (digitalRead(D6) == HIGH)
                       {
                           // Reset the quantity because you are leaving the quantity loop
                           quantityWasSelected = false;
                           lcd.clear();
                       }
                   }
               }
               else if (digitalRead(D6) == HIGH)
               {
                   // Reset the quantity because you are leaving the quantity loop
                   itemWasSelected = false;
                   quantitySelected = 0;
                   lcd.clear();
               }
         }
     }
     // Cancel button was pressed
     else if (digitalRead(D6) == HIGH)
     {
         // Reset all the items because you are leaving the loop
       start = false;
       itemSelected = 0;
       quantitySelected =0;
       lcd.clear();
     }
 }
 delay(100);
}
/********
 END FILE
********/

Sparkfun Phant code

Plain text
After instaling phant on your pi via npm, see https://www.npmjs.com/package/phant, you can run the database by simply typing 'Phant'.

If you want to run phant without it taking up a window, or automatically when your Pi starts, use 'Phant &'

You then telnet to the phant from your computer or from the pi. From the pi it would look like
`Telnet 127.0.0.1 8080`

From there you can type `help` to get the available options.
I created the phant streams the following way.
The output gives you a Public Key, a Private Key, and a Delete Key.

The different http requests below show how to add items and get the output
Raspberry pi Phant


phant> create
Enter a title> Inventory - photon pushes inventory changes to this stream
	Enter a description> The inventory information and updates sent to the Rpi
	Enter fields (comma separated)> ItemID,Description,Quantity
	Enter tags (comma separated)> inventory, sqlite, photon

	PUBLIC KEY: [publicKey]
	PRIVATE KEY:  [privateKey]
	DELETE KEY:  [deletekey]

	INPUT
		http://[piAddress]:8080/input/[PUBLIC_KEY]?private_key=[PRIVATE_KE]Y&FIELD1=VALUE1&=FIELD2=VALUE2

		curl -X GET 'http://192.168.1.107:8080/input/[publicKey]?private_key=[privateKey]&ItemID=1&Description=Hose&Quantity=4'

	OUTPUT
		http://[piAddress]:8080/output/[PUBLIC_KEY].FORMAT

	DELETE
		http://[piAddress]:8080/streams/[PUBLIC_KEY]/delete/[DELETE_KEY]

phant> create
Enter a title> Inventory - Photon will pull json info from here
	Enter a description> The inventory information that the Photon will use
	Enter fields (comma separated)> ItemID,Description,Quantity
	Enter tags (comma separated)> inventory, sqlite, photon

	Stream created!
	PUBLIC KEY: [publicKey]
	PRIVATE KEY:  [privateKey]
	DELETE KEY:  [deleteKey]

	INPUT
		http://[piAddress]:8080/input/[PUBLIC_KEY]?private_key=[PRIVATE_KEY]&FIELD1=VALUE1&=FIELD2=VALUE2

		curl -X GET 'http://[piAddress]:8080/input/[publicKey]?private_key=[privateKey]&ItemID=1&Description=Hose&Quantity=4'

	OUTPUT
		http://[piAddress]:8080/output/PUBLIC_KEY.FORMAT

	DELETE
		http://[piAddress]:8080/streams/PUBLIC_KEY/delete/DELETE_KEY

SQLite DB setup

SQL
I used the following setup information once I downloaded and installed Sqlite3.
Make sure to save the sqlite database so that it can be used later by your application.
Begin;
CREATE TABLE INVENTORY (ItemID NUMERIC, Description TEXT, Quantity NUMERIC, TriggerLvl NUMERIC, Triggered NUMERIC);
Commit;

Begin;
INSERT INTO INVENTORY values(1,"Hose",4,2,0);
INSERT INTO INVENTORY values(2,"Extinguisher",3,1,0);
INSERT INTO INVENTORY values(3,"Helmets",8,7,0);
Commit;

Raspberry Pi - Java manager for phant/sqlite3/Nova/Inventory

Java program that will talk to phant/sqlite3/ and trigger Nova messages.

Raspberry Pi Zero Wireless AP setup shell script

You need to save this file on your pi as a .sh file and then modify it to an executable file, then run it. Huge Thank you to LewisCowles1986

Credits

Luke Dutton

Luke Dutton

7 projects • 3 followers
I currently have a BAS in Information Technology with a certificate in Software Application Development

Comments