diff --git a/README.md b/README.md index 09f7129..c814db8 100644 --- a/README.md +++ b/README.md @@ -1 +1,117 @@ -# Contact +# CONTACT + +## What is CONTACT? +An open platform that brings together software and hardware to allow everyone to communicate up to eight mile radius. + +## Why we create CONTACT? +During and after an emergency, communication with communities is critical. + +Helping people at all levels to communicate empowers them to recognize important issues and find common grounds for action, and builds a sense of identity and participation in order to implement decisions. + +Well-conceived and effectively delivered emergency messages can help ensure public safety, protect property, facilitate response efforts, elicit cooperation, instill public confidence, and help families reunite. + +> Delivering effective emergency communications is an essential part of emergency management. We have learned through the experience of Hurricane Maria first that the people around you respond more quickly than the government. Second, although government assistance is available, coordination between people and agencies is needed, and communication is necessary for this. +[imagen unidades contact] + + +## How it's Work? +- Contact combines the use of an IoT device that includes a LoRa transmitter and an ESP32 microcontroller to provide communication access up to eight miles away. +![Contact's Reach](/unit/images/contact-reach.png) + +- It's the same as using the Internet in a Hotspot. It is not necessary to download additional applications, you only need a device with WiFi and a Web Browser. +![Main Screen](/unit/images/group-msg-00.png) +![Selecting Locations to communicate](/unit/images/sending-msg.png) + +- Contact code allows each unit to extend the distance by replicating any message up to two more units, creating a simple MESH by using a lifetime for each data packet that has been sent. +![Main Screen](/unit/images/simple-mesh.png) + +## Features +### Communication Reach +- It is possible to reach *up to 8 miles* away in a communication. Within cities with buildings or structures allows a communication of up to two miles, even within structures. But it is possible to double or triple the distance when using the units with a simple MESH. +![Main Screen](/unit/images/contact-reach.png) +![Main Screen](/unit/images/simple-mesh.png) +![Selecting Locations to communicate](/unit/images/msg-units-selection.png) + +- Is an alternative way of *taking communications trough hard places*. In most emergency incidents, mainly in natural disasters, geography or lack of access do not allow communication. +![Main Screen](/unit/images/simple-mesh.png) + +### Ease of Use +- To use contact it is not necessary to download any application. The unit works just like a hostpot, you use a WiFi connection and then you use the tools through any web browser. +![Main Screen](/unit/images/group-msg-00.png) +![Receiving Messages](/unit/images/group-msg-02.png) + +### Ease of Access +- The price is very affordable, the cost is $11.00 to produce a unit. But it can be less by producing more units in a bigger scale. +![Unit Inside](/unit/images/unit-inside.jpg) + +### Hardware Flexibility +- It is *Portable*, it's measure only 3.1 inches (8cm) x 2 inches (5cm). But we know that it can be smaller. +![Unit](/unit/images/contact-unit.jpg) +- It uses *rechargeable batteries* (3.7v 3.6aH, 18650) that can extend its use up to *30 continuous hours*. It *can be recharged and used with any usb micro "b" power source*, such as power banks for cell phones, small solar panels or the Eton Red Cross Charger. + + +### Scalability +- It uses a *frequency free of licenses* for Industry, Science and Medicine (900-Mhz frequencies, ISM band) in the United States. But it is possible to select **other LoRa license-free frequencies for other Countries** through a configuration screen. +![Contact Setup Screen](/unit/images/contact-setup.png) +- It can be *used as an open network platform to send any form data*. It has sending or receiving functions through specific RESTful technology. +![Data Form](/unit/images/form-data.png) +[Data form example] +```javascript + function sendData() { + var jsonData = jsonForm(document.getElementById('form')); + SendToServer(jsonData, 'GOVERNMENT'); // Your Contact Unit + document.getElementById('datos').innerHTML += + '
'  + jsonData + '

'; + } + + function jsonForm(form) { + // var objects = []; + var jsonString = "{data[{"; + if (typeof form == 'object' && form.nodeName.toLowerCase() == "form") { + var fields = form.getElementsByTagName("input"); + for(var i=0;i response.json()) + .then(json => console.log(json)) + .catch(function(error) { + console.log('Request failed', error) + }); + } +``` +- It **can be used as a Network or Internet Gateway** to send all received messages to another network, the Internet, [Contact API] or another platform such as Twilio. +![Gateway Confirmation Screen](/unit/images/gateway.png) +- *Beacon and Geolocation Integration*. It allowing it to be used as a Beacon to locate people in other rescue situations. By using a powerful open-technology microcontroller (ESP32) you can extend the capacity of the device while maintaining low cost and performance. +![Beacon Screen](/unit/images/beacon-option.png) +![Beacon On](/unit/images/beacon-active.png) + +## CALL FOR CODE + CONTACT +We believe that everyone has creative ideas. We, in response to **Call for Code**, develop the **Contact** capabilities around the powerful tools of **IBM Cloud** to demonstrate that it is a powerful platform that can grow. +- [Contact API] +- [Contact API - Github] +![Contac Api Architecture](/API/images/architecture.png) +The Contact API is **designed to help _you_ understand what your users are saying.** The API receives messages and analyzes them for sentiment and emotional tone. We store all the analysis data in a database and visualize it through charts, graphs and maps to help you make appropriate and informed decisions. [Click here][contact-home] to see it up and running. + +That is a reason why we provide a complete *Open Communication Platform* that allows others to control the hardware and the data directly; the people can create custom interfaces and even send the data to other platforms. +- [Unit Code on Github] +- [Unit Interfaces] + + +[inside]: https://github.com/jdastas/contact-platform/unit/images/unit-inside.jpg "Unit Inside" +[setup]: https://github.com/jdastas/contact-platform/unit/images/setup.png "Contact Setup Screen" +[gateway]: https://github.com/jdastas/contact-platform/unit/images/gateway.png "Internet Gateway" +[Data form example]: https://github.com/jdastas/contact-platform/unit/interfaces/form-data.html "Data Form Demo" +[Contact API]: https://contact-app.mybluemix.net/ "Contact API" +[Contact API - Github]: https://github.com/javierdastas/Contact/tree/master/API \ No newline at end of file diff --git a/unit/README.md b/unit/README.md new file mode 100644 index 0000000..efc8505 --- /dev/null +++ b/unit/README.md @@ -0,0 +1,72 @@ +# CONTACT-UNIT + +## What is CONTACT? +An open platform that brings together software and hardware to allow everyone to communicate up to eight mile radius. + +## Why we create CONTACT? +During and after an emergency, communication with communities is critical. + +Helping people at all levels to communicate empowers them to recognize important issues and find common grounds for action, and builds a sense of identity and participation in order to implement decisions. + +Well-conceived and effectively delivered emergency messages can help ensure public safety, protect property, facilitate response efforts, elicit cooperation, instill public confidence, and help families reunite. + +> Delivering effective emergency communications is an essential part of emergency management. We have learned through the experience of Hurricane Maria first that the people around you respond more quickly than the government. Second, although government assistance is available, coordination between people and agencies is needed, and communication is necessary for this. +[imagen unidades contact] + + +## How it's Work? +- Contact combines the use of an IoT device that includes a LoRa transmitter and an ESP32 microcontroller to provide communication access up to eight miles away. +[imagen de distancia] +[enlace a fotos] +- It's the same as using the Internet in a Hotspot. It is not necessary to download additional applications, you only need a device with WiFi and a Web Browser. +[una imagen] +[enlace a pantallas] +- Contact code allows each unit to extend the distance by replicating any message up to two more units, creating a simple MESH by using a lifetime for each data packet that has been sent. +[ejemplo del código] + + +## Features +### Communication Reach +- It is possible to reach *up to 8 miles* away in a communication. Within cities with buildings or structures allows a communication of up to two miles, even within structures. But it is possible to double or triple the distance when using the units with a simple MESH. +[imagen lista online communities] +- Is an alternative way of *taking communications trough hard places*. In most emergency incidents, mainly in natural disasters, geography or lack of access do not allow communication. + +### Ease of Access +- The price is very affordable, the cost is $11.00 to produce a unit. But it can be less by producing more units in a bigger scale. +[imagen por dentro] + +### Hardware Flexibility +- It is *Portable*, it's measure only 3.1 inches (8cm) x 2 inches (5cm). But we know that it can be smaller. +[imagenes o fotos] +- It uses *rechargeable batteries* (3.7v 3.6aH, 18650) that can extend its use up to *30 continuous hours*. It *can be recharged and used with any power source*, such as power banks for cell phones, small solar panels or the Eton Red Cross Charger. +[imagen conectado] + +### Scalability +- It uses a *frequency free of licenses* for Industry, Science and Medicine (900-Mhz frequencies, ISM band) in the United States. But it is possible to select **other LoRa license-free frequencies for other Countries** through a configuration screen. +[setup] +- It can be *used as an open network platform to send any form data*. It has sending or receiving functions through specific RESTful technology. +[imagen formulario] +[enlace código] +- It **can be used as a Network or Internet Gateway** to send all received messages to another network, the Internet, [Contact API]() or another platform such as Twilio. +[imagen] +- *Beacon and Geolocation Integration*. It allowing it to be used as a Beacon to locate people in other rescue situations. By using a powerful open-technology microcontroller (ESP32) you can extend the capacity of the device while maintaining low cost and performance. +[imagen] + +## CALL FOR CODE + CONTACT +We believe that everyone has creative ideas. We, in response to **Call for Code**, develop the **Contact** capabilities around the powerful tools of **IBM Cloud** to demonstrate that it is a powerful platform that can grow. +[enlace to dashboard] +[enlace to github Janiel] + + +That is a reason why we provide a complete *Open Communication Platform* that allows others to control the hardware and the data directly; the people can create custom interfaces and even send the data to other platforms. +[enlace to github Contact Unit] +[enlace a pantallas] + + + + +[setup]: https://github.com/jdastas/contact-platform/setup.png "Contact Setup Screen" + + +[setup]: https://github.com/jdastas/contact-platform/setup.png "Contact Setup Screen" +[gateway]: https://github.com/jdastas/contact-platform/gateway.png "Internet Gateway" \ No newline at end of file diff --git a/unit/device/device.ino b/unit/device/device.ino new file mode 100644 index 0000000..36583df --- /dev/null +++ b/unit/device/device.ino @@ -0,0 +1,1007 @@ +/******************************************************************************************* + CONTACT + A real time Open and Portable Communication Platform. + + Author: Javier A. Dastas + + Contact is an open platform that brings together software and + hardware to allow everyone to communicate. t use an IoT device + that includes a LoRa transmitter and an ESP32 microcontroller + to provide communication access and forms bridges to the + Internet if it necessary. It is possible to reach a radius of + up to eight miles between units or up to two miles away + through efficient structures. The range can be doubled or + tripled when the units are used as a simple MESH. It's an + alternative way of providing communications in disaster + situations or to communities in need. + + *******************************************************************************************/ + +#include +#include +#include "EEPROM.h" +//#include "SSD1306.h" // OLED - It's not necessary + + +#include +//#include // Avaible only on Some Units +//#include // + +#include +#include +#include + + +#include "Arduino.h" // Needed by ArduinoJSON Lib +#include + +#define SS 18 +#define RST 14 +#define DI0 26 + +// LoRa Frequency Bands +#define BAND_0 915E6 // USA Only - We use as Default + // till change on Setup Screen +#define BAND_1 920E6 // USA/Asia/Australia +#define BAND_2 865E6 // Europe/India +#define PABOOST true + +/********** GPS UNIT SETTINGS *************/ +#include + +HardwareSerial GPSSerial(2); + +#define RXD2 16 +#define TXD2 17 + +TinyGPSPlus gps; +String strGeo = ""; +/********** GPS *************/ + +const char* hostName = "CONTACT"; +String deviceName = "CONTACT"; +const char* ssid = "wifi-gateway"; +const char* password = ""; + +AsyncWebServer server(80); + +String MESH_MSG = ""; + +String message = ""; // char message[] + +boolean sendLoRaMsg = false; +boolean devNotification = false; +boolean sendLoRaMsgToOther = false; + +byte msgCount = 0; // count of outgoing messages +byte localAddress = 0xBB; // address of this device +byte destination = 0xFF; // destination to send to +long lastSendTime = 0; // last send time +int interval = 2000; // interval between sends + +int nTTL_MAX = 2; // Time-To-Live Package (MESH) + +#define MAX_TX_MESSAGES 15 +#define MAX_RX_MESSAGES 50 +#define MAX_UNITS 10 +#define DATA_SIZE 500 + +typedef struct MESSAGE_TYPE { + int packetId; + char TxID[12]; + char RxID[12]; + char data[DATA_SIZE]; + int packetsQty; + int packetCtr; + int packetType; // 1-Txt; 2-Sound; 3-Image; 4-Hand Shake; 5-Alert; 6-JSON + int ttl; + int packetQId; + byte sender; // 0 - Me; 1 - Him +}; +MESSAGE_TYPE rx_message_queue[MAX_RX_MESSAGES]; +MESSAGE_TYPE tx_message_queue[MAX_TX_MESSAGES]; + +typedef struct UNIT_TYPE { + char TxID[12]; + char lastDate[10]; + char lastTime[18]; +}; +UNIT_TYPE unit_queue[MAX_UNITS]; + +int rx_MSG_CTR_Q = 0; +int tx_MSG_CTR_Q = 0; +int UNITS_CTR_Q = 0; +int tx_MSG_ID = 0; + +int LAST_MSG_ID = 0; +char LAST_UNIT = 0; + +class FLASHvariables { +public: + char unitName[14]; + char unitDesc[30]; + + uint8_t unitFreq; // Unit Area Frequency + + double lat; + double lng; + + uint16_t isGateway; // 0 - No; 1 - Yes + + char ssid[20]; + char ssidPwd[20]; + + char smsServer[300]; + char dataServer[300]; + + char helpMessage[300]; + + uint16_t tstVar; // + + // METHODS + void save(); + void get(); + void initialize(); + +} CONTACTvars; + +void FLASHvariables::save() +{ + EEPROM.put(0, CONTACTvars); + EEPROM.commit(); +} +void FLASHvariables::get() +{ + EEPROM.begin(sizeof(CONTACTvars)); + EEPROM.get(0, CONTACTvars); +} +void FLASHvariables::initialize() +{ + // If data not found on flash RAM initialize all vars + strcpy(unitName, "CONTACT"); + strcpy(unitDesc, "Physical Location"); + + unitFreq = 0; + + lat = 0.00; lng = 0.00; + + isGateway = 0; // Default is SMS Unit + + strcpy(ssid, "not-defined"); + strcpy(ssidPwd, "not-defined"); + + strcpy(smsServer, "not-defined"); + strcpy(dataServer, "not-defined"); + + strcpy(helpMessage, "I need Help, immedialty!"); + + tstVar = 111; + + EEPROM.put(0, CONTACTvars); + EEPROM.commit(); +} + +void setup() +{ + pinMode(25,OUTPUT); //Send success, LED will bright 1 second + pinMode(16,OUTPUT); + digitalWrite(16, LOW); // set GPIO16 low to reset OLED + delay(50); + digitalWrite(16, HIGH); + + Serial.begin(115200); + GPSSerial.begin(9600, SERIAL_8N1, RXD2, TXD2); + + CONTACTvars.get(); + if (CONTACTvars.tstVar != 111) // all data is correct + { + Serial.println("No Data Exist on Flash RAM ... Initializing to defaults." + micros()); + CONTACTvars.initialize(); + } + else + Serial.println("Success retrieved persistent variables." + micros()); + + Serial.print("UNIT Name: "); + Serial.println(CONTACTvars.unitName); + + Serial.print("Device Type: "); + Serial.print(CONTACTvars.isGateway); + Serial.print(" - "); + Serial.println((CONTACTvars.isGateway==1?"Gateway":"Communication")); + + if (CONTACTvars.isGateway == 0) // Use Only for Communication + { + WiFi.mode(WIFI_AP); + + hostName = CONTACTvars.unitName; + + WiFi.softAP(hostName); // Use as OPEN HOT SPOT Communication Device + // WiFi.softAP(hostName, CONTACTvars.unitPwd); + delay(100); + Serial.println("Connected!"); + + Serial.println("Set softAPConfig"); + IPAddress Ip(1, 2, 3, 4); + IPAddress NMask(255, 255, 255, 0); + WiFi.softAPConfig(Ip, Ip, NMask); + + Serial.print("IP address: "); + Serial.println(WiFi.softAPIP()); + } + else // Use Unit as Internet Gateway + { + int nets = WiFi.scanNetworks(); + Serial.println("scan done"); + if (nets == 0) { + Serial.println("no networks found"); + } else { + WiFi.mode(WIFI_STA); + WiFi.disconnect(); + + ssid = CONTACTvars.ssid; + password = CONTACTvars.ssidPwd; + WiFi.begin(ssid, password); + Serial.print("WiFi Status: "); + Serial.println(WiFi.status()); + int conxTries = 0; + while ((WiFi.status() != WL_CONNECTED) and (conxTries++ < 40)) { + delay(500); + Serial.print("."); + } + Serial.println(""); + if ( WiFi.status() != WL_CONNECTED) { + Serial.println("Couldn't Get a WiFi connection"); + Serial.println("as Gateway."); + Serial.println("Please check the Settings Page."); + + CONTACTvars.isGateway = 0; // Set Unit to Default Again + CONTACTvars.save(); + + ESP.restart(); + } + else { + Serial.print("Connected to "); + Serial.print(ssid); + Serial.println(" Network.\n\n Ready to Send data as Gateway."); + + Serial.println("WiFi connected!"); + Serial.print(" IP address: "); + Serial.println(WiFi.localIP()); + Serial.print(" ESP Mac Address: "); + Serial.println(WiFi.macAddress()); + Serial.print(" Subnet Mask: "); + Serial.println(WiFi.subnetMask()); + Serial.print(" Gateway IP: "); + Serial.println(WiFi.gatewayIP()); + Serial.print(" DNS: "); + Serial.println(WiFi.dnsIP()); + + // Set up mDNS responder: + // - first argument is the domain name, in this example + // the fully-qualified domain name is "esp8266.local" + // - second argument is the IP address to advertise + // we send our IP address on the WiFi network + //if (MDNS.begin(CONTACTvars.unitName)) { + // Serial.println("MDNS Responder Started!"); + //} else { + // Serial.println("Error setting up MDNS responder!"); + //} + } + } + } + delay(100); + + // Not for all Devices, only based on Chrome + // char mdnsHostName [12+1]; + // mdnsHostName = CONTACTvars.unitName + // strcpy (mdnsHostName,"chat"); + + // if (!MDNS.begin(mdnsHostName)) { + // Serial.println("Error setting up MDNS responder!"); + // while(1){ + // delay(1000); + // } + // } + // Serial.println("MDNS Name Setted."); + + SPI.begin(5,19,27,18); + LoRa.setPins(SS,RST,DI0); + Serial.println("Starting LoRa."); + Serial.print(" Frequency used 0-USA Only, 1-USA/Asia/Australia, 2-Europe/Indida: "); + Serial.println(CONTACTvars.unitFreq); + double BAND = (CONTACTvars.unitFreq == 0? BAND_0 : (CONTACTvars.unitFreq == 1? BAND_1 : BAND_2)); + if (!LoRa.begin(BAND)) { + Serial.println("Starting LoRa failed!"); + while (1); + } + + // ********* Set Unit Web Pages and RESTs + + server.on("/", HTTP_GET, [](AsyncWebServerRequest *request){ + // Chat Page + request->send(200, "text/html", main_page()); + }); + + server.on("/deletechat", HTTP_GET, [](AsyncWebServerRequest *request){ + // Reset Chat Content + String jsonData = ""; + + int paramsNmbr = request->params(); + if (paramsNmbr > 0) { + AsyncWebParameter* p1 = request->getParam(0); + int nKey = String(p1->value()).toInt(); + String page = ""; + if (nKey = 20172009) + page = " Document
"; + else + page = " Document

Communication is everyone's right.

"; + request->send(200, "text/html", page); + } + }); + + server.onNotFound([](AsyncWebServerRequest *request){ + Serial.printf("NOT_FOUND: "); + /*if(request->method() == HTTP_GET) + Serial.printf("GET"); + else if(request->method() == HTTP_POST) + Serial.printf("POST"); + else if(request->method() == HTTP_DELETE) + Serial.printf("DELETE"); + else if(request->method() == HTTP_PUT) + Serial.printf("PUT"); + else if(request->method() == HTTP_PATCH) + Serial.printf("PATCH"); + else if(request->method() == HTTP_HEAD) + Serial.printf("HEAD"); + else if(request->method() == HTTP_OPTIONS) + Serial.printf("OPTIONS"); + else + Serial.printf("UNKNOWN"); + Serial.printf(" http://%s%s\n", request->host().c_str(), request->url().c_str()); */ + + request->send(404, "text/html", "

The content you are looking for was not found.

"); + + }); + + server.on("/getgeo", HTTP_GET, [](AsyncWebServerRequest *request) { + // Get Geolocation from the GPS - if Available on Unit + int paramsNmbr = request->params(); + if (paramsNmbr > 0) { + + String jsonData = "{\"data\":["; + jsonData += strGeo; + jsonData += "]}\n\n"; + + request->send(200, "text/json", jsonData); + + Serial.println(jsonData); + } + }); + + server.on("/getdev", HTTP_GET, [](AsyncWebServerRequest *request) { + // Get Available Units Online to Communicate + int paramsNmbr = request->params(); + if (paramsNmbr > 0) { + Serial.println("Dev List Request ..."); + AsyncWebParameter* p = request->getParam(0); + String jsonData = "{\"data\":["; + + for(int i = 0; i < UNITS_CTR_Q; i++) { + jsonData += ("{\"devunit\":\"" + ((String)unit_queue[i].TxID) + "\"}" ); + if (i < (UNITS_CTR_Q - 1)) { + jsonData += ","; + } + } + jsonData += "]}\n\n"; + + request->send(200, "text/json", jsonData); + + Serial.println(jsonData); + } + }); + + server.on("/get", HTTP_GET, [](AsyncWebServerRequest *request) { + // Get Received Messages from STACK + int paramsNmbr = request->params(); + if ((paramsNmbr > 0) && (rx_MSG_CTR_Q > 0)){ + AsyncWebParameter* p1 = request->getParam(0); + AsyncWebParameter* p2 = request->getParam(1); + + int nLastReaded = String(p1->value()).toInt(); + int bFirstTime = String(p2->value()).toInt(); + + Serial.print(String(bFirstTime)); + Serial.print(" - "); + Serial.print(String(nLastReaded)); + Serial.print(" - "); + Serial.print(rx_MSG_CTR_Q); + Serial.print(" - "); + Serial.println(nLastReaded == (rx_MSG_CTR_Q - 1)); + if (bFirstTime == 0) + if (nLastReaded == (rx_MSG_CTR_Q - 1)) + return; + Serial.println("Creating JSON Messages Data."); + String jsonData = "{\"data\":["; + String msgdata = ""; + int comma = (rx_MSG_CTR_Q - 1); + int nQueInit = 1; + int nStart = (bFirstTime == 1 ? nLastReaded : nLastReaded + 1); // nStart = 0 cambiado para no leer desde el inicio + + if (nLastReaded > rx_MSG_CTR_Q) + nQueInit = 2; + + for(int n = 1; n <= nQueInit; n++) { + if (nQueInit == 2) { + if (n == 1) + nStart = (nLastReaded + 1); + else + nStart = 0; + } + for(int i = nStart; i < (nStart > rx_MSG_CTR_Q ? MAX_RX_MESSAGES : rx_MSG_CTR_Q); i++) { + msgdata = rx_message_queue[i].data; + jsonData += ("{\"pid\":" + ((String)rx_message_queue[i].packetId) + ", "); + jsonData += ("\"message\":\"" + msgdata + "\", "); + jsonData += ("\"txid\":\"" + ((String)rx_message_queue[i].TxID) + "\", " ); + jsonData += ("\"pqty\":" + ((String)rx_message_queue[i].packetsQty) + ", " ); + jsonData += ("\"pctr\":" + ((String)rx_message_queue[i].packetCtr) + ", " ); + jsonData += ("\"ptype\":" + ((String)rx_message_queue[i].packetType) + ", " ); + jsonData += ("\"pqid\":" + ((String)rx_message_queue[i].packetQId) + ", " ); + jsonData += ("\"sender\":" + ((String)rx_message_queue[i].sender) + ", " ); + jsonData += ("\"ttl\":" + ((String)rx_message_queue[i].ttl) + "}" ); + if (i < comma) { + jsonData += ","; + } + } + if ((nQueInit == 2) && (n < 2)) { + jsonData += ","; + } + } + jsonData += "]}\n\n"; + + Serial.println(jsonData); + + String jsonHeader = "HTTP/1.0 200 OK\nContent-Type: application/json;charset=utf-8\nAccept: application/json\nAccess-Control-Allow-Credentials: true\nAccess-Control-Allow-Origin: *\nConnection: close\n\n"; + + request->send(200, "text/json", jsonData); + } // end if + }); + + server.on("/notify", HTTP_GET, [](AsyncWebServerRequest *request) { + // Send Handshake - Unit Broadcast or Availability Notification + int paramsNumbr = request->params(); + Serial.println("Unit Registration Request ..."); + if (paramsNumbr > 0) { + Serial.println("Creating Registration Message for LoRa ... "); + //AsyncWebParameter* p = request->getParam(0); + //strcpy(message, p->value().c_str()); + message = ""; //strcpy(message,""); + message = deviceName; // strcpy(message, deviceName.c_str()); + + Serial.println("Notify..."); + Serial.println(message); + + request->send(200, "text/json", "OK"); + + devNotification = true; + sendLoRaMsg = false; + } + }); + + server.on("/put", HTTP_GET, [](AsyncWebServerRequest *request) { + // Send Messages from Chat to the Stack. + // It's possible to send files or images fragmented on small pieces. + // Also an initial TIME-TO-LIVE Field is created for MESH the Package and + // send again, the default TTL MESH is One. + // The message will be sent up to twice before reaching the destination. + int paramsNumbr = request->params(); + if (paramsNumbr > 0) { + AsyncWebParameter* p = request->getParam(0); + message = String(p->value()); //strcpy(message, p->value().c_str()); + + AsyncWebParameter* u = request->getParam(1); + String rxUnit = String(u->value()); + + AsyncWebParameter* usr = request->getParam(2); + String usuario = String(usr->value()); + + String msg = ""; + int msgLen = 0; + int nInit = 0; + int nEnd = 0; + + msg = message; + msgLen = msg.length(); + + if (msgLen < 1) + return; + + Serial.println("WiFi Message Sended from Unit."); + Serial.print("Msg Original Size: "); + Serial.println(msgLen); + + int npackets = round((msgLen / DATA_SIZE) + 0.5); + + for(int i = 1; i <= npackets; i++) { + tx_MSG_ID++; + if (npackets > 1) { + nInit = ((DATA_SIZE - 1) * (i - 1)) + i; + nEnd = DATA_SIZE * i; + if (nEnd > msgLen) + nEnd = msgLen; + msg = String(message).substring( nInit, nEnd); + } + else + msg = message; + + + tx_message_queue[tx_MSG_CTR_Q].packetId = tx_MSG_ID; + deviceName.toCharArray(tx_message_queue[tx_MSG_CTR_Q].TxID, 12); + rxUnit.toCharArray(tx_message_queue[tx_MSG_CTR_Q].RxID, 12); + + //msgLen = (msg.length() + 1); + //Serial.print("Msg Size: ");Serial.println(msgLen); + + msg.toCharArray(tx_message_queue[tx_MSG_CTR_Q].data, msgLen + 1); + Serial.print("Msg on Queue: "); + Serial.print(tx_message_queue[tx_MSG_CTR_Q].data); + Serial.println("||"); + + tx_message_queue[tx_MSG_CTR_Q].packetsQty = npackets; + tx_message_queue[tx_MSG_CTR_Q].packetCtr = i; + tx_message_queue[tx_MSG_CTR_Q].packetType = 1; + //if (strcmp(user, "JSON") != 0) tx_message_queue[tx_MSG_CTR_Q].packetType = 6; + tx_message_queue[tx_MSG_CTR_Q].ttl = nTTL_MAX; + tx_message_queue[tx_MSG_CTR_Q].packetQId = tx_MSG_CTR_Q; + tx_message_queue[tx_MSG_CTR_Q].sender = 0; + + tx_MSG_CTR_Q++; + if (tx_MSG_CTR_Q == MAX_TX_MESSAGES) + tx_MSG_CTR_Q = 0; + } + + request->send(200, "text/json", "OK"); + devNotification = false; + sendLoRaMsg = true; + } + else + { + Serial.println("NOT Parameter Value Received."); + } + }); + + server.on("/setup", HTTP_GET, [](AsyncWebServerRequest *request){ + // Unit Settings Page + request->send(200, "text/html", settings_page()); + }); + + server.on("/putsetup", HTTP_GET, [](AsyncWebServerRequest *request){ + // Save Unit Settings to RAM (EEPROM). + int paramsNumbr = request->params(); + if (paramsNumbr > 0) { + Serial.println("* Dev Settings Saved."); + // dataServer=&smsServer=&ssidPwd=&ssid=&isGateway=0&unitDesc=LOCATION&unitName=CONTACT + AsyncWebParameter* ds = request->getParam(0); + AsyncWebParameter* ss = request->getParam(1); + AsyncWebParameter* spw = request->getParam(2); + AsyncWebParameter* sid = request->getParam(3); + AsyncWebParameter* ig = request->getParam(4); + AsyncWebParameter* uf = request->getParam(5); + AsyncWebParameter* ud = request->getParam(6); + AsyncWebParameter* un = request->getParam(7); + + strcpy(CONTACTvars.dataServer, ds->value().c_str()); + strcpy(CONTACTvars.smsServer, ss->value().c_str()); + + strcpy(CONTACTvars.ssid, sid->value().c_str()); + strcpy(CONTACTvars.ssidPwd, spw->value().c_str()); + + CONTACTvars.isGateway = String(ig->value()).toInt(); + Serial.print("Is Gateway? "); + Serial.println(CONTACTvars.isGateway); + + CONTACTvars.unitFreq = String(uf->value()).toInt(); + + strcpy(CONTACTvars.unitDesc, ud->value().c_str()); + strcpy(CONTACTvars.unitName, un->value().c_str()); + + CONTACTvars.save(); + + String jsonData = "{\"data\":[{\"status\":\"saved\"}]}\n\n"; + request->send(200, "text/json", jsonData); + //request->send(200, "text/html", settings_page()); + } + }); + + server.on("/getsetup", HTTP_GET, [](AsyncWebServerRequest *request) { + // Get the CONTACT vars Settings from RAM (EEPROM). + int paramsNmbr = request->params(); + if (paramsNmbr > 0) { + Serial.println("* Dev Settings Request ..."); + AsyncWebParameter* p = request->getParam(0); + String jsonData = "{\"data\":["; + + jsonData += ("{\"unitname\":\"" + String(CONTACTvars.unitName) + "\","); + jsonData += ("\"unitdesc\":\"" + String(CONTACTvars.unitDesc) + "\","); + jsonData += ("\"unitfreq\":\"" + String(CONTACTvars.unitFreq) + "\","); + jsonData += ("\"isgateway\":\"" + String(CONTACTvars.isGateway) + "\","); + jsonData += ("\"ssid\":\"" + String(CONTACTvars.ssid) + "\","); + jsonData += ("\"ssidpwd\":\"" + String(CONTACTvars.ssidPwd) + "\","); + jsonData += ("\"smsserver\":\"" + String(CONTACTvars.smsServer) + "\","); + jsonData += ("\"dataserver\":\"" + String(CONTACTvars.dataServer) + "\""); + jsonData += "}]}\n\n"; + Serial.println("**********"); Serial.println(jsonData); + request->send(200, "text/json", jsonData); + + Serial.println(jsonData); + } // end if + }); + + server.on("/restart", HTTP_GET, [](AsyncWebServerRequest *request) { + // Restar Unit to Get New Settings + int paramsNmbr = request->params(); + if (paramsNmbr > 0) { + Serial.println("* Dev Restart Request ..."); + ESP.restart(); + } + }); + + server.on("/putsomevars", HTTP_GET, [](AsyncWebServerRequest *request){ + // Save Chat Settings to RAM (EEPROM). + int paramsNumbr = request->params(); + if (paramsNumbr > 0) { + Serial.println("* Dev Settings on Chat."); + AsyncWebParameter* h = request->getParam(0); // help message + AsyncWebParameter* ud = request->getParam(1); + + strcpy(CONTACTvars.helpMessage, h->value().c_str()); + strcpy(CONTACTvars.unitDesc, ud->value().c_str()); + + CONTACTvars.save(); + + String jsonData = "{\"data\":[{\"status\":\"saved\"}]}\n\n"; + request->send(200, "text/json", jsonData); + } + }); + + server.on("/getsomevars", HTTP_GET, [](AsyncWebServerRequest *request) { + // Get the CONTACT vars Settings from RAM (EEPROM). + int paramsNmbr = request->params(); + if (paramsNmbr > 0) { + Serial.println("* Dev Some Settings Request ..."); + AsyncWebParameter* p = request->getParam(0); + String jsonData = "{\"data\":["; + jsonData += ("{\"location\":\"" + String(CONTACTvars.unitDesc) + "\","); + jsonData += ("\"helpmsg\":\"" + String(CONTACTvars.helpMessage) + "\""); + jsonData += "}]}\n\n"; + Serial.println("**********"); Serial.println(jsonData); + request->send(200, "text/json", jsonData); + + Serial.println(jsonData); + } // end if + }); + + server.on("/gateway", HTTP_GET, [](AsyncWebServerRequest *request){ + // Gateway Page + request->send(200, "text/html", gateway_page()); + }); + + + server.on("/delsmscache", HTTP_GET, [](AsyncWebServerRequest *request){ + // Gateway Page + request->send(200, "text/html", delete_sms_cache()); + }); + + server.on("/geo", HTTP_GET, [](AsyncWebServerRequest *request) { + int paramsNumbr = request->params(); + if (paramsNumbr > 0) { + AsyncWebParameter* p = request->getParam(0); + + message = String(p->value()); + + String msg = message; + int msgLen = msg.length(); + + if (msgLen < 1) + return; + + Serial.println("WiFi Geo Sended from Unit."); + + int npackets = round((msgLen / DATA_SIZE) + 0.5); + + tx_MSG_ID++; + msg = message; + + + tx_message_queue[tx_MSG_CTR_Q].packetId = tx_MSG_ID; + deviceName.toCharArray(tx_message_queue[tx_MSG_CTR_Q].TxID, 11); + + msg.toCharArray(tx_message_queue[tx_MSG_CTR_Q].data, msgLen + 1); + Serial.print("Msg on Queue (GEO): "); + Serial.println(tx_message_queue[tx_MSG_CTR_Q].data); + + tx_message_queue[tx_MSG_CTR_Q].packetsQty = 1; + tx_message_queue[tx_MSG_CTR_Q].packetCtr = 1; + tx_message_queue[tx_MSG_CTR_Q].packetType = 1; + tx_message_queue[tx_MSG_CTR_Q].ttl = 5; + tx_message_queue[tx_MSG_CTR_Q].packetQId = tx_MSG_CTR_Q; + tx_message_queue[tx_MSG_CTR_Q].sender = 0; + + tx_MSG_CTR_Q++; + if (tx_MSG_CTR_Q == MAX_TX_MESSAGES) + tx_MSG_CTR_Q = 0; + + request->send(200, "text/json", "OK GEO"); + devNotification = false; + sendLoRaMsg = true; + Serial.println("GEO WiFi setted."); + } + else + { + Serial.println("NOT Parameter Value Received."); + } + }); + + server.begin(); + Serial.println("Webserver Started"); + + deviceName = CONTACTvars.unitName; +} + +void loop(){ + + String jsonString = ""; + + strGeo = geoLocation(); + // Serial.println(strGeo); + + if (devNotification) { + Serial.println("Sending Hand Shake Notification ..."); + jsonString = "{\"pid\": 777, "; + jsonString += "\"txid\": \"" + deviceName + "\", "; + jsonString += "\"rxid\": \"ALL\", "; + jsonString += "\"msg\": \"" + deviceName + "\", "; + jsonString += "\"pqty\": 1, "; + jsonString += "\"pctr\": 1, "; + jsonString += "\"ptype\": 4, "; + jsonString += "\"ttl\": nTTL_MAX, "; + jsonString += "\"pqid\": 777}"; + + LoRa.beginPacket(); + LoRa.print(jsonString); + LoRa.endPacket(); + + Serial.println("Hand Shake Sended ..."); + devNotification = false; + } + if (sendLoRaMsg) { + Serial.println("Sending message ..."); + // Send Messages on Stack + for(int i = 0; i < tx_MSG_CTR_Q; i++) { + Serial.println(tx_message_queue[i].data); + jsonString = "{\"pid\": " + String(tx_message_queue[i].packetId) + ", "; + jsonString += "\"txid\": \"" + String(tx_message_queue[i].TxID) + "\", "; + jsonString += "\"rxid\": \"" + String(tx_message_queue[i].RxID) + "\", "; + jsonString += "\"msg\": \"" + String(tx_message_queue[i].data) + "\", "; + jsonString += "\"pqty\": " + String(tx_message_queue[i].packetsQty) + ", "; + jsonString += "\"pctr\": " + String(tx_message_queue[i].packetCtr) + ", "; + jsonString += "\"ptype\": 1, "; + jsonString += "\"ttl\": " + String(tx_message_queue[i].ttl) + ", "; + jsonString += "\"pqid\": " + String(tx_message_queue[i].packetQId) + "} "; + Serial.println(jsonString); + delay(10); + LoRa.beginPacket(); + Serial.print("Begin LoRa Packet"); + //LoRa.write(jsonString.length()); + Serial.print("Setting Size Packet"); + LoRa.print(jsonString); + Serial.print("Print LoRa Packet"); + LoRa.endPacket(); + Serial.print("End LoRa Packet"); + Serial.println(jsonString); + Serial.println("Message was sended."); + } + tx_MSG_CTR_Q = 0; + sendLoRaMsg = false; + } + + if (sendLoRaMsgToOther) { + // MESH Messaging + Serial.println("Sending message ..."); + jsonString = MESH_MSG; + Serial.println(jsonString); + delay(10); + LoRa.beginPacket(); + Serial.print("Begin LoRa Packet"); + //LoRa.write(jsonString.length()); + Serial.print("Setting Size Packet"); + LoRa.print(jsonString); + Serial.print("Print LoRa Packet"); + LoRa.endPacket(); + Serial.print("End LoRa Packet"); + Serial.println(jsonString); + Serial.println("Message was sended."); + + sendLoRaMsgToOther = false; + } + + onReceive(LoRa.parsePacket()); + +} + +void charcopy(char* src, char* dst, int len) { + memcpy(dst, src, sizeof(src[0])*len); +} + +String main_page() { + String page = (" Contact

User:

  •  
  •  
  •  

 ONLINE LOCATIONS

How do people call you?

Emergency Request Notification

This Notification Service reach all communities networks.

Cancel
Send
Settings
"); + return (page); +} + +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(); + } + /* Another LoRa Reader + while (LoRa.available()) { + incoming = LoRa.readString(); + } + */ + if (incoming.length() >= 40) // If possible message for our unit is processed. + { + String content = incoming; //.substring(1, incoming.length()); + Serial.println(content); + + // Allocate JsonBuffer + // Use arduinojson.org/assistant to compute the capacity. + const size_t capacity = JSON_OBJECT_SIZE(8) + 600; // = JSON_OBJECT_SIZE(8) + JSON_ARRAY_SIZE(2) + 60; + DynamicJsonBuffer jsonBuffer; //(capacity); + // Parse JSON object + JsonObject& root = jsonBuffer.parseObject(content); + if (!root.success()) { + Serial.println(F("Parsing failed!")); + return; + } + Serial.println("JSON Text on LoRa: "); + root.prettyPrintTo(Serial); + + if (strcmp(root["txid"].as(), hostName) == 0) { + Serial.println("Unidad que transmite recibiendo el mismo."); + Serial.println(root["txid"].as()); + return; + } + char* all = "ALL"; + if (CONTACTvars.isGateway == 1) // If Unit is Gateway Receive Any Message + root["rxid"] = "ALL"; + if ((strcmp(root["rxid"].as(), all) == 0) && ((root["ttl"].as() - 1) > 0) + && (strcmp(root["rxid"].as(), hostName) == 0)) + { // Mesh Network + Serial.print("** Msg to Mesh ..."); + Serial.println(Serial.println(root["rxid"].as())); + root["ttl"] = String(root["ttl"].as() - 1); + root.printTo(MESH_MSG); + sendLoRaMsgToOther = true; + return; + } + + if ((strcmp(root["rxid"].as(), hostName) != 0) && + (strcmp(root["rxid"].as(), all) != 0)) { + Serial.println("Ninguna"); + Serial.println(root["rxid"].as()); + return; + } + + if ((root["pid"].as() == 0) || (root["ptype"].as() == 0) || (String(root["msg"].as()).length() < 1) + || (String(root["txid"].as()).length() < 1)) { + Serial.println(F("Packet Data Error.")); + return; + } + if (root["ptype"].as() == 1) { + Serial.print("Text Message Received ..."); + + rx_message_queue[rx_MSG_CTR_Q].packetId = root["pid"].as(); + strcpy( rx_message_queue[rx_MSG_CTR_Q].data, root["msg"].as()); //,sizeof(root["message"].as())); //strcpy( ..., p->value().c_str()); + strcpy( rx_message_queue[rx_MSG_CTR_Q].TxID, root["txid"].as()); + strcpy( rx_message_queue[rx_MSG_CTR_Q].RxID, root["rxid"].as()); + rx_message_queue[rx_MSG_CTR_Q].packetsQty = root["pqty"].as(); + rx_message_queue[rx_MSG_CTR_Q].packetCtr = root["pctr"].as(); + rx_message_queue[rx_MSG_CTR_Q].packetType = root["ptype"].as(); + rx_message_queue[rx_MSG_CTR_Q].ttl = (root["ttl"].as() - 1); + rx_message_queue[rx_MSG_CTR_Q].packetQId = rx_MSG_CTR_Q; + Serial.println("All MSG Content Readed."); + rx_MSG_CTR_Q++; + if (rx_MSG_CTR_Q == MAX_RX_MESSAGES) + rx_MSG_CTR_Q = 0; + + } + if (root["ptype"].as() == 4) { + bool unitExist = false; + int i = 0; + Serial.println("Hand Shake Received ..."); + //Serial.println("Compare ..."); + for(i = 0; i < UNITS_CTR_Q; i++) { + + //Serial.println(unit_queue[i].TxID); + //Serial.println(root["msg"].as()); + //Serial.println(strcmp(unit_queue[i].TxID, root["msg"])); + // TO-DO: Verificar si la Unidad es un Nombre Valido + + if (strcmp(unit_queue[i].TxID, root["msg"]) == 0) { + unitExist = true; + break; + } + } + if (!unitExist) { + strcpy( unit_queue[UNITS_CTR_Q].TxID , root["msg"].as() ); + strcpy( unit_queue[UNITS_CTR_Q].lastDate , "01/01/2018" ); // Date is used from Device + strcpy( unit_queue[UNITS_CTR_Q].lastTime , "01:00:00 am" ); + + Serial.println(unit_queue[UNITS_CTR_Q].TxID); + Serial.println("New Unit Registered on Queue"); + UNITS_CTR_Q++; + } + else + Serial.println("Unit Reported Exist on Queue"); + } + // Extract values + + Serial.println("LoRa Data Readed!"); + } // if (incoming.length() >= 40) - If possible message for our unit is processed. +} + +void sendMessage() //(String outgoing) { +{ + LoRa.beginPacket(); // start packet + // LoRa.write(destination); // 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.print(message); + LoRa.endPacket(); // finish packet and send it + msgCount++; // increment message ID +} + +String geoLocation() { + while (GPSSerial.available()) { + gps.encode(GPSSerial.read()); + } + + return ("{\"lat\":\"" + String(gps.location.lat(),5) + "\"," + + "\"log\":\"" + String(gps.location.lng(),5) + "\"," + + "\"sat\":\"" + String(gps.satellites.value()) + "\"," + + "\"date\":\"" + + (gps.date.day() < 10 ? "0" : "") + String(gps.date.day()) + "/" + + (gps.date.month() < 10 ? "0" : "") + String(gps.date.month()) + "/" + + String(gps.date.year()) + "\"," + + "\"time\":\"" + (gps.time.hour() < 10 ? "0" : "") + String(gps.time.hour()) + + ":" + (gps.time.minute() < 10 ? "0" : "") + String(gps.time.minute()) + "\"}"); +} + +String settings_page() { + return (" Device Setup

CONTACT

DEVICE SETTINGS

 
"); +} + +String gateway_page() { + return (" Push Messages to Server

CONTACT

MESSAGES SENDED FROM ONLINE LOCATIONS

LOCATION
MESSAGE

 Messages received from Online Locations Units.

"); +} + +String delete_sms_cache() { + return (" Delete SMS Cache

Delete All SMS Cache from Browser

"); +} + + +String otherPage() { + return ("

The content you are looking for was not found.

"); +} + +// BENYWHY diff --git a/unit/images/beacon-active.png b/unit/images/beacon-active.png new file mode 100644 index 0000000..95834b0 Binary files /dev/null and b/unit/images/beacon-active.png differ diff --git a/unit/images/beacon-option.png b/unit/images/beacon-option.png new file mode 100644 index 0000000..404676d Binary files /dev/null and b/unit/images/beacon-option.png differ diff --git a/unit/images/contact-reach.png b/unit/images/contact-reach.png new file mode 100644 index 0000000..c6ae610 Binary files /dev/null and b/unit/images/contact-reach.png differ diff --git a/unit/images/data-form.png b/unit/images/data-form.png new file mode 100644 index 0000000..c5e0cba Binary files /dev/null and b/unit/images/data-form.png differ diff --git a/unit/images/gateway.png b/unit/images/gateway.png new file mode 100644 index 0000000..03825d0 Binary files /dev/null and b/unit/images/gateway.png differ diff --git a/unit/images/main-interface.png b/unit/images/main-interface.png new file mode 100644 index 0000000..0092dda Binary files /dev/null and b/unit/images/main-interface.png differ diff --git a/unit/images/msg-between-units.png b/unit/images/msg-between-units.png new file mode 100644 index 0000000..d6ca956 Binary files /dev/null and b/unit/images/msg-between-units.png differ diff --git a/unit/images/msg-group.png b/unit/images/msg-group.png new file mode 100644 index 0000000..2385d6d Binary files /dev/null and b/unit/images/msg-group.png differ diff --git a/unit/images/msg-units-selection.png b/unit/images/msg-units-selection.png new file mode 100644 index 0000000..b8497ef Binary files /dev/null and b/unit/images/msg-units-selection.png differ diff --git a/unit/images/sending-msg.png b/unit/images/sending-msg.png new file mode 100644 index 0000000..70cdde1 Binary files /dev/null and b/unit/images/sending-msg.png differ diff --git a/unit/images/setup.png b/unit/images/setup.png new file mode 100644 index 0000000..7e80399 Binary files /dev/null and b/unit/images/setup.png differ diff --git a/unit/images/simple-mesh.png b/unit/images/simple-mesh.png new file mode 100644 index 0000000..74ba501 Binary files /dev/null and b/unit/images/simple-mesh.png differ diff --git a/unit/images/units.png b/unit/images/units.png new file mode 100644 index 0000000..2ee5b54 Binary files /dev/null and b/unit/images/units.png differ diff --git a/unit/interfaces/gateway/delete-sms.html b/unit/interfaces/gateway/delete-sms.html new file mode 100644 index 0000000..8ca994e --- /dev/null +++ b/unit/interfaces/gateway/delete-sms.html @@ -0,0 +1,15 @@ + + + + + + + Delete SMS Cache + + +

Delete All SMS Cache from Browser

+ + + \ No newline at end of file diff --git a/unit/interfaces/gateway/pushsms.html b/unit/interfaces/gateway/pushsms.html new file mode 100644 index 0000000..72e918a --- /dev/null +++ b/unit/interfaces/gateway/pushsms.html @@ -0,0 +1,149 @@ + + + + + + + Push Messages to Server + + + +
+
+

CONTACT

+

MESSAGES SENDED FROM ONLINE LOCATIONS

+
+
+
+
LOCATION
MESSAGE
+ +
+
+
+

 Messages received from Online Locations Units.

+
+
+ + + + \ No newline at end of file diff --git a/unit/interfaces/main/main-screen.html b/unit/interfaces/main/main-screen.html new file mode 100644 index 0000000..9a98d17 --- /dev/null +++ b/unit/interfaces/main/main-screen.html @@ -0,0 +1,1388 @@ + + + + + + + + Contact + + + +
+
+
+
    +
+
+

User:

+

+
+
+
+
    +
  •  
  • +
  •  
  • +
  •  
  • +
+
+
+
+
+
+
+
+
+

 ONLINE LOCATIONS

+
+
+
+ +
+
+
+
+
+
+
+
+
+
+
+

How do people call you?

+ + +
+
+
+ +
+
+

Emergency Request Notification

+

This Notification Service reach all communities networks.

+
+
+
+ + +
+
+
+
+
+
+ +
+
+
+ +
+
+
Cancel
+
Send
+
+
+
+ +
+
+
+
+ Settings +
+
+ + + + + +
+
+ + + +
+
+
+
+ + + + \ No newline at end of file diff --git a/unit/interfaces/other-data-example/form-data.html b/unit/interfaces/other-data-example/form-data.html new file mode 100644 index 0000000..a539091 --- /dev/null +++ b/unit/interfaces/other-data-example/form-data.html @@ -0,0 +1,135 @@ + + + + + + + Goverment Data + + + +
+
+

GOVERNMENT DATA FORM - DEMO

+
+

Municipality Basic Data

+ +
+ +
+

Supplies:

+ +
+ +
+
+ +
+
+
+
+
+ + + + \ No newline at end of file