POST request to firebase functions with sim900 arduino - arduino

Im trying to send POST request with sim900 connected to arduino uno.
I send it to firebase functions but I dont get it in the function.
I will be happy to get solution for my problem,
or alternative way that i can store data from sensors to firebase with sim900 or any other celular network solution).
arduino code:
#include<SoftwareSerial.h>
SoftwareSerial client(7,8);
String reading="{ \"testID\" : 1, }";
void setup()
{
Serial.begin(9600);
client.begin(9600);
delay(500);
if(client.available())
{
Serial.println("Connected");
}
else
{
Serial.println("NotConnected");
}
//initSIM();
connectGPRS();
connectHTTP();
}
void loop()
{
}
void connectGPRS()
{
client.println("AT+SAPBR=3,1,\"Contype\",\"GPRS\"");
delay(1000);
ShowSerialData();
client.println("AT+SAPBR=3,1,\"APN\",\"www\"");//APN
delay(1000);
ShowSerialData();
client.println("AT+SAPBR=1,1");
delay(1000);
ShowSerialData();
client.println("AT+SAPBR=2,1");
delay(1000);
ShowSerialData();
}
void connectHTTP()
{
client.println("AT+HTTPINIT");
delay(1000);
ShowSerialData();
client.println("AT+HTTPPARA=\"CID\",1");
delay(1000);
ShowSerialData();
client.println("AT+HTTPPARA=\"URL\",\"www.us-central1-**************.cloudfunctions.net/helloWorld\"");//Public server IP address
delay(1000);
ShowSerialData();
client.println("AT+HTTPPARA=\"CONTENT\",\"application/json\"");
delay(1000);
ShowSerialData();
client.println("AT+HTTPDATA=" + String(reading.length()) + ",100000");
delay(1000);
ShowSerialData();
client.println(reading);
delay(1000);
ShowSerialData;
client.println("AT+HTTPACTION=1");
delay(1000);
ShowSerialData();
client.println("AT+HTTPREAD");
delay(1000);
ShowSerialData();
client.println("AT+HTTPTERM");
delay(1000);
ShowSerialData;
}
void ShowSerialData()
{
while(client.available()!=0)
{
Serial.write(client.read());
delay(100);
}
}
firebase function:
const functions = require('firebase-functions');
// Create and Deploy Your First Cloud Functions
// https://firebase.google.com/docs/functions/write-firebase-functions
exports.helloWorld = functions.https.onRequest((request, response) => {
response.send("Hello from Firebase!");
// Check for POST request
if (request.method !== "POST") {
console.log('Please send a POST request');
}
if (request.method !== "GET") {
console.log('Please send a GET request');
}
let data = request.body;
console.log(`Hello from Firebase! ${JSON.stringify(data)}`);
// console.log('testing: ' + data[0]);
// console.log('testing: ' + data.longitude);
// console.log('testing: ' + data['longitude']);
});
Thank you all!

This is by no means a definitive solution but should work on most SIM modules but make sure you have up to date firmware for SSL connection.
This is only a snip of code from my project, you can only POST, GET and DELETE using these SIM modules built in HTTP functions, there's no PUT and PATCH so a bit limited for Firebase.
This is very basic code so you may have to tweak some timing here and there. It will work a lot faster if you put code to check the responses, most delays are not necessary.
Try this command first:
AT+HTTPSSL=1
If you get an error you MUST update the module's firmware before using Firebase.
Firebase will only work with HTTPS connection.
// Firebase HTTPS connection, POST example
#define MODEM_TX 16
#define MODEM_RX 17
// Set serial for debug console (to Serial Monitor, default speed 115200)
#define SerialMon Serial
// Set serial for AT commands (to SIM808 module)
#define client Serial1
void setup()
{
SerialMon.begin(115200);
// Set GSM module baud rate and UART pins
client.begin(115200, SERIAL_8N1, MODEM_RX, MODEM_TX);
delay(1000);
float tempIn = 21.5;
float humidity = 54.6;
float pressure = 1010.64;
char fbtimebuff[32] = "1:30 20/2/21";
char dataTEST[256];
char FirebaseUrl[300]; // dimension to suit required character space
const char FirebaseID[100] = "https://my-project-details.firebaseio.com"; // project ID
const char FirebaseAuth[100] = "YPXm66X-My-Big-Secrete-J9xsZsL"; // secret
strcpy(FirebaseUrl, FirebaseID); // Firebase account ID
strcat(FirebaseUrl, "/GPRS-Test/.json?auth="); // Parenet/child path + .json and auth
strcat(FirebaseUrl, FirebaseAuth);
sprintf(dataTEST, "{\"TempIn\" : \"%3.2f\",\"Humidity\" : \"%2.2f\",\"Pressure\" : \"%4.2f\",\"Time\" : \"%s\"}", tempIn, humidity, pressure, fbtimebuff);
// connect to GPRS network
SerialMon.printf("\n\nConnect to GPRS\n");
client.println("AT+CIPSHUT");
ShowSerialData();
delay(500);
client.println("AT+CGATT=1");
ShowSerialData();
delay(1000);
client.println("AT+SAPBR=3,1,\"Contype\",\"GPRS\"");
ShowSerialData();
delay(1000);
client.println("AT+SAPBR=1,1");
ShowSerialData();
delay(1000);
client.println("AT+SAPBR=2,1");
ShowSerialData();
delay(1000);
// post to Firebase
SerialMon.printf("post function to Firebase\n");
client.println("AT+HTTPINIT");
ShowSerialData();
delay(1000);
client.println("AT+HTTPSSL=1"); // set SSL for HTTPS
ShowSerialData();
delay(1000);
client.println("AT+HTTPPARA=\"CID\",1");
ShowSerialData();
delay(1000);
client.println("AT+HTTPPARA=\"URL\"," + String(FirebaseUrl));
ShowSerialData();
delay(1000); client.println("AT+HTTPPARA=\"CONTENT\",\"application/json\"");
ShowSerialData();
delay(1000);
int stlen = strlen(dataTEST);
SerialMon.printf("length: %d\n", stlen);
client.println("AT+HTTPDATA=" + String(stlen) + ",100000");
ShowSerialData();
delay(1000);
SerialMon.printf("set data\n");
client.println(dataTEST);
ShowSerialData();
delay(1000);
SerialMon.printf("POST data to Firebase\n");
client.println("AT+HTTPACTION=1");
ShowSerialData();
delay(6000);
client.println("AT+HTTPREAD");
ShowSerialData();
delay(1000);
SerialMon.printf("Dissconect\n");
client.println("AT+HTTPTERM");
ShowSerialData();
delay(1000);
// now disconnect from GPRS network
client.println("AT+CIPSHUT");
ShowSerialData();
delay(1000);
client.println("AT+SAPBR=0,1");
ShowSerialData();
delay(1000);
client.println("AT+CGATT=0");
ShowSerialData();
delay(1000);
}
void loop()
{
}

Related

Sending Sensor Data to Firebase using ESP32+Sim800L

I have a TTGO T-CALL ESP32+Sim800L board and I want to send accelerometer data to Firebase.
I am using the TinyGSM library which supports SSL/https connections for Sim800L.
I am currently sending dummy data to see if it works but it is giving me a failed flag.
Why is it not sending data to Firebase?
#define FIREBASE_HOST "https://XXXXXXXXXXXXXXXXXfirebaseio.com/"
#define FIREBASE_AUTH "XXXXXXXXXXXXXXXXXXXXhevKwQ3LALblGclqCk"
FirebaseData firebaseData;
void setup()
{
// Set console baud rate
Serial.begin(115200);
// Set-up modem reset, enable, power pins
pinMode(MODEM_PWKEY, OUTPUT);
pinMode(MODEM_RST, OUTPUT);
pinMode(MODEM_POWER_ON, OUTPUT);
digitalWrite(MODEM_PWKEY, LOW);
digitalWrite(MODEM_RST, HIGH);
digitalWrite(MODEM_POWER_ON, HIGH);
// Set GSM module baud rate and UART pins
SerialAT.begin(115200, SERIAL_8N1, MODEM_RX, MODEM_TX);
delay(3000);
SerialMon.println("Initializing modem...");
modem.init();
SerialMon.print("Connecting to APN: ");
SerialMon.print(apn);
if (!modem.gprsConnect(apn, gprsUser, gprsPass)) {
SerialMon.println(" fail");
}else {
SerialMon.println(" OK");
Firebase.begin(FIREBASE_HOST,FIREBASE_AUTH);
SerialMon.println(" Connection to Firebase Successful");
}
}
void loop()
{
String data = "5";
bool res = Firebase.pushString(firebaseData,"/ax", data);
Serial.println("Data sent");
Serial.println(res);
Serial.println(firebaseData.errorReason());
delay(1000);
}
This is the output:
I didn't find a lot of resources online, however, I did manage to do this and I made a GitHub repo for anyone who needs help with the same.
Basically as Firebase accepts only Https requests, it is not possible to formulate that on most microcontrollers and GSM modules.
To circumvent this problem, I created a php server to which I can send an HTTP POST request and the script can get the data from it and push it to Firebase with a php firebase library.

Arduino + SIM800A messages get cut-off

I am working with Arduino and SIM800A. My goal is to save the sms message received by SIM800A to a string variable to be used later. Yet to my surprise, the message always get cut off and I have no clue why.
I send to SIM:
This is a test message
The Serial Monitor only shows:
+CMT: "+XXXXXXXXXX","","21/02/20,01:52:40+28"
This is a tes
Below is the code
#include <SoftwareSerial.h>
// Configure software serial port
SoftwareSerial Sim(2, 3);
// Variable to store text message
char incomingMessage;
String textMessage;
void setup() {
pinMode(13, OUTPUT);
digitalWrite(13, HIGH);
// Initializing serial commmunication
Serial.begin(9600);
Sim.begin(9600);
delay(100);
while (!Sim.available()) {
Sim.println("AT");
delay(200);
Serial.println("Connecting...");
}
Serial.println("Connected!");
Sim.println("AT+CMGF=1"); //Set SMS to Text Mode
delay(200);
// Sim.println("AT+CMGL=\"ALL\"");
// delay(500);
Sim.println("AT+CNMI=1,2,0,0,0"); //Procedure to handle newly arrived messages(command name in text: new message indications to TE)
delay(1000);
Sim.read();
//Sim.println("AT+CMGL=\"REC UNREAD\""); // Read Unread Messages
}
void loop() {
if (Sim.available()>0) {
delay(200);
// Serial Buffer
while (Sim.available()>0) {
incomingMessage = Sim.read();
textMessage += incomingMessage;
}
delay(500);
Serial.println(textMessage);
textMessage = "";
}
}

Unable to use DS18B20 temperature sensor with a GSM shield on an Arduino UNO

I'm trying every solution but no way....
I have the official Antenova GSM shield and the sensor DS18B20.
If I connect only the GSM shield without the sensor, I get -127 from the sensor and the shield is able to do the HTTP post to my server successfully. If the sensor is connected, it returns the right temperature, but the client.connect(server, port) never returns. I set the sensor pin to 12 instead of 2 to avoid problems, but seem like they are in conflict. I'm already using the external power supply.
// libraries
#include <GSM.h>
#include <OneWire.h>
#include <DallasTemperature.h>
// PIN Number
#define PINNUMBER "1218"
// APN data
#define GPRS_APN "web.omnitel.it" // replace your GPRS APN
#define GPRS_LOGIN "" // replace with your GPRS login
#define GPRS_PASSWORD "" // replace with your GPRS password
// Data wire is plugged into pin 2 on the Arduino
#define ONE_WIRE_BUS 12
// Setup a oneWire instance to communicate with any OneWire devices
// (not just Maxim/Dallas temperature ICs)
OneWire oneWire(ONE_WIRE_BUS);
// Pass our oneWire reference to Dallas Temperature.
DallasTemperature sensors(&oneWire);
// initialize the library instance
GSMClient client;
GPRS gprs;
GSM gsmAccess;
// URL, path & port (for example: arduino.cc)
char server[] = "mancioboxblog.altervista.org";
char path[] = "/add.php";
int port = 80; // port 80 is the default for HTTP
// check the connection status
int con = 0;
// string to save the temp
String data = "";
// temp random
long temp;
void setup() {
//file version
Serial.println("Version: 1.6");
// initialize serial communications and wait for port to open:
Serial.begin(9600);
/* only for old USB usually not required
while (!Serial) {
; // wait for serial port to connect. Needed for native USB port only
}*/
Serial.println("Starting Arduino web client.");
// connection state
boolean notConnected = true;
// After starting the modem with GSM.begin()
// attach the shield to the GPRS network with the APN, login and password
while (notConnected) {
Serial.println("I'm trying to connect");
if ((gsmAccess.begin(PINNUMBER) == GSM_READY) &
(gprs.attachGPRS(GPRS_APN, GPRS_LOGIN, GPRS_PASSWORD) == GPRS_READY)) {
notConnected = false;
} else {
Serial.println("Not connected");
delay(1000);
}
}
Serial.println("I'm connected");
// this operation is faster than connect to sim card
// Start up the temperature sensor library
Serial.println("initialize sensors");
sensors.begin();
// wait the the gsm shield is initialized
delay(1000);
}
void loop() {
/* GET THE TEMPERATURE */
// call sensors.requestTemperatures() to issue a global temperature
// request to all devices on the bus
Serial.print(" Requesting temperatures...");
sensors.requestTemperatures(); // Send the command to get temperatures
Serial.println("DONE");
Serial.print("Temperature for Device 1 is: ");
temp = sensors.getTempCByIndex(0);
Serial.print(temp); // Why "byIndex"?
// You can have more than one IC on the same bus.
// 0 refers to the first IC on the wire
//only for test
//temp = random(33);
data = "temp=" + (String)temp;
Serial.println("temp stored = " + data);
delay(2000);
con = client.connect(server, port);
// wait connection engaged
delay(2000);
// if you get a connection, report back via serial:
if (con == 1) {
Serial.println("connected");
// Make a HTTP request:
client.print("POST ");
client.print(path);
client.println(" HTTP/1.1");
client.print("Host: ");
client.println(server);
client.println("Content-Type: application/x-www-form-urlencoded; charset=UTF-8");
client.println("Connection: close");
client.print("Content-Length: ");
client.println(data.length());
client.println("");
client.println(data);
client.println("");
//what I'm sending
Serial.print("POST ");
Serial.print(path);
Serial.println(" HTTP/1.1");
Serial.print("Host: ");
Serial.println(server);
Serial.println("Content-Type: application/x-www-form-urlencoded; charset=UTF-8");
Serial.println("Connection: close");
Serial.print("Content-Length: ");
Serial.println(data.length());
Serial.println("");
Serial.println(data);
Serial.println("");
// if you didn't get a connection to the server:
} else if(con == -1){
Serial.println("connection failed: TIMED_OUT -1");
} else if(con == -2){
Serial.println("connection failed: INVALID_SERVER -2");
} else if(con == -3){
Serial.println("connection failed: TRUNCATED -3");
} else if(con == -4){
Serial.println("connection failed: INVALID_RESPONSE -4");
}
if(client.connected()){
client.stop(); // DISCONNECT FROM THE SERVER
Serial.println("enter in the if and stop the client");
}else{
Serial.println("the client is not connected");
}
//keep over 2 second otherwise is not able to close and reopen connection
delay(2000); // WAIT MINUTES BEFORE SENDING AGAIN
Serial.println("code repeat");
}
Is there a problem with my code? Maybe I shouldn't read the sensor before the client connects?

SIM900 gprs web with arduino

I have a problem with this simple code.
#include <SoftwareSerial.h>
SoftwareSerial SIM900(7, 8); // configure software serial port
void setup() {
pinMode(9, OUTPUT);
digitalWrite(9, HIGH);
SIM900.begin(19200);
Serial.begin(19200);
Serial.println("power up" );
delay(15000);
}
void loop()
{
Serial.println("SubmitHttpRequest - started" );
SubmitHttpRequest();
//callSomeone();
Serial.println("SubmitHttpRequest - finished" );
delay(10000);
}
void SubmitHttpRequest()
{
SIM900.println("AT+CSQ"); // Signal quality check
delay(100);
ShowSerialData();
SIM900.println("AT+CSQ"); // Signal quality check
delay(100);
ShowSerialData();
SIM900.println("AT+CGATT?"); //Attach or Detach from GPRS Support
delay(100);
ShowSerialData();
SIM900.println("AT+SAPBR=3,1,\"CONTYPE\",\"GPRS\"");
delay(1000);
ShowSerialData();
SIM900.println("AT+SAPBR=3,1,\"APN\",\"gprs-service.com\"");
delay(1000);
ShowSerialData();
SIM900.println("AT+SAPBR=1,1");
delay(1000);
ShowSerialData();
SIM900.println("AT+HTTPINIT"); //init the HTTP request
delay(1000);
ShowSerialData();
SIM900.print("AT+HTTPPARA=\"URL\",\"http://xxxxxx.com/master.php");
SIM900.println("\"");
delay(1000);
ShowSerialData();
SIM900.println("AT+HTTPACTION=0");//submit the request
delay(10000);
ShowSerialData();
SIM900.println("AT+HTTPREAD");// read the data from the website you access
delay(500);
ShowSerialData();
SIM900.println("");
delay(100);
SIM900.println("AT+HTTPTERM");// read the data from the website you access
delay(300);
ShowSerialData();
}
void getContent()
{
String content = "";
while(SIM900.available() != 0)
{
content = content + String(char(SIM900.read()));
Serial.write(char(SIM900.read()));
delay(500);
Serial.flush();
}
}
void ShowSerialData()
{
while(SIM900.available()!=0)
Serial.write(char (SIM900.read()));
}
Receipt few characters, the output is this:
power up
SubmitHttpRequest - started
AT+CSQ
+CSQ: 25,0
OK
AT+CSQ
+CSQ: 25,0
OK
AT+CGATT?
+CGATT: 1
OK
AT+SAPBR=3,1,"CONTYPE","GPRS"
OK
AT+SAPBR=3,1,"APN","gprs-service.com"
OK
AT+SAPBR=1,1
OK
AT+HTTPINIT
OK
AT+HTTPPARA="URL","http://xxxxxx.com/master.php"
OK
AT+HTTPACTION=0
OK
+HTTPACTION:0,200,66
AT+HTTPREAD
+HTTPREAD:66
Numero de accesos: 77 | Identifica
AT+HTTPTERM
OK
SubmitHttpRequest - finished
==> "Numero de accesos: 77 | Identifica"
But, realy, the output should be:
Numero de accesos: 77 | Identificador: 4l02pnl828o3kctg4strc87ed1
Exactly like: HTTPREAD missing characters with Arduino and SIM900
Any ideas or help?
Thanks!
Numero de accesos: 77 | Identifica is 34 charcters. This means that sim900 gives back 66 characters which is the correct length.
Try to increase the delay before ShowSerialData or wait until you have 66 bytes in serial buffer.

How to read an SMS from Arduino and get LED to switch on or off

#include <SoftwareSerial.h>
char inchar; //Will hold the incoming character from the serial port.
SoftwareSerial cell(2,3); //Create a 'fake' serial port. Pin 2 is the Rx pin, pin 3 is the Tx pin.
int led1 = A2;
void setup()
{
// Prepare the digital output pins
pinMode(led1, OUTPUT);
digitalWrite(led1, HIGH);
//Initialize GSM module serial port for communication.
cell.begin(19200);
delay(30000); // Give time for GSM module to register on network, etc.
cell.println("AT+CMGF=1"); // Set SMS mode to text
delay(200);
cell.println("AT+CNMI=3,3,0,0"); // Set module to send SMS data to serial out upon receipt
delay(200);
}
void loop()
{
//If a character comes in from the cellular module...
if(cell.available() >0)
{
delay(10);
inchar=cell.read();
if (inchar=='a')
{
delay(10);
inchar=cell.read();
if (inchar=='0')
{
digitalWrite(led1, LOW);
}
else if (inchar=='1')
{
digitalWrite(led1, HIGH);
}
delay(10);
delay(10);
}
cell.println("AT+CMGD=1,4"); // Delete all SMS
}
}
This is the code for receiving SMSes from the cellular network. I am using the Arduino Gboard with SIM900. There is no error in the code, but the LED on the board doesn't switch on or off in response to an SMS.
Why?
Here's a fully functional code for sending a command thru SMS using Arduino and GSM, it will also reply the state of the light.
#include <SoftwareSerial.h>
SoftwareSerial GPRS(10, 11);
String textMessage;
String lampState;
const int relay = 12; //If you're using a relay to switch, if not reverse all HIGH and LOW on the code
void setup() {
pinMode(relay, OUTPUT);
digitalWrite(relay, HIGH); // The current state of the light is ON
Serial.begin(9600);
GPRS.begin(9600);
delay(5000);
Serial.print("GPRS ready...\r\n");
GPRS.print("AT+CMGF=1\r\n");
delay(1000);
GPRS.print("AT+CNMI=2,2,0,0,0\r\n");
delay(1000);
}
void loop(){
if(GPRS.available()>0){
textMessage = GPRS.readString();
Serial.print(textMessage);
delay(10);
}
if(textMessage.indexOf("ON")>=0){ //If you sent "ON" the lights will turn on
// Turn on relay and save current state
digitalWrite(relay, HIGH);
lampState = "ON";
Serial.println("Lamp set to ON\r\n");
textMessage = "";
GPRS.println("AT+CMGS=\"+631234567890\""); // RECEIVER: change the phone number here with international code
delay(500);
GPRS.print("Lamp was finally switched ON.\r");
GPRS.write( 0x1a );
delay(1000);
}
if(textMessage.indexOf("OFF")>=0){
// Turn off relay and save current state
digitalWrite(relay, LOW);
lampState = "OFF";
Serial.println("Lamp set to OFF\r\n");
textMessage = "";
GPRS.println("AT+CMGS=\"+631234567890\""); /// RECEIVER: change the phone number here with international code
delay(500);
GPRS.print("Lamp was finally switched OFF.\r");
GPRS.write( 0x1a );
delay(1000);
}
if(textMessage.indexOf("STATUS")>=0){
String message = "Lamp is " + lampState;
GPRS.print("AT+CMGF=1");
delay(1000);
Serial.println("Lamp state resquest");
textMessage = "";
GPRS.println("AT+CMGS=\"+631234567890\""); // RECEIVER: change the phone number here with international code
delay(500);
GPRS.print("Lamp is currently ");
GPRS.println(lampState ? "ON" : "OFF"); // This is to show if the light is currently switched on or off
GPRS.write( 0x1a );
delay(1000);
}
}
Change
AT+CNMI=3,3,0,0
to:
AT+CNMI=2,2,0,0,0
The simplest way is the best way.
// if You use SoftwareSerial lib, declare object for GSM
SoftwareSerial gsm(8,9); // TX, RX
void setup(){
// initialise UART and GSM communication between Arduino and modem
Serial.begin(115200);
gsm.begin(115200);
// wait 5-10sec. for modem whitch must connect to the network
delay(5000);
// configure modem - remember! modem didn't remeber Your's configuration!
gsm.print("at+cmgf=1\r"); // use full functionality (calls, sms, gprs) - see app note
gsm.print("at+clip=1\r"); // enable presentation number
gsm.print("at+cscs=\"GSM\"\r"); // configure sms as standard text messages
gsm.print("at+cnmi=1,2,0,0,0\r"); // show received sms and store in sim (probobly, I don't compre this settings with app note but it's working :)
}
void loop(){
String response = gsmAnswer();
if(response.indexOf("+CMT:") > 0 ) { // SMS arrived
// Now You can parse Your Message, if You wont controll only LED, just write
if(response.indexOf("LED ON") > 0) {
digitalWrite(LED_PIN, HIGH); // enable it
}else if(response.indexOf("LED OFF") > 0) {
digitalWrite(LED_PIN, LOW); // turn off
}
delay(1000);
}
}
String gsmAnswer(){
String answer;
while(!gsm.available());
while(gsm.available()){
delay(5);
if(Serial.available() > 0){
char s = (char)gsm.read();
answer += s;
}
}
return answer;
}
One think more, incomming sms has the following format:
+CMT: "+48xxxxxxxxx","","17/07/07,21:57:04+08"
Test of arrived messages
You should first know exactly what the response is before attempting to parse it.
Try something simple like the following code (note: untested!) to get a feeling of what you should look for:
void loop() {
if(cell.available() > 0) {
char ch = cell.read();
Serial.print(ch);
}
}
My guess is you'll see more than just a '0' or a '1' :)
void loop() {
while(cell.available() > 0) {inchar = cell.read(); readString+=c;delay(1);} ///can be a delay up to (10) so you can get a clear reading
Serial.print(readString); /// Declare a string " String readString; "
for (i=0; i<200; i++){ /// Serch for the txt you sent up to (200) times it depends how long your ""readString" is
if(readString.substring(i,i+4)=="RING"){ //// I am looking for the word RING sent from my phone
digitalWrite(13,HIGH);
break;
}
}
}
this will help you find the specific word ir your reading (text message)

Resources