This code enables remote water quality monitoring by recording GPS coordinates, battery and solar charge levels, water temperature and stage, and transmitting the data to Cayenne. It was developed with the help of online resources, including code examples from Random Nerd Tutorials and Xinyuan-LilyGO. More information on the research and parts used can be found at https://github.com/shifrine/Eli-Shifrin/blob/main/Version_3.0
/*
This code is for a remote water quality monitoring station. It records GPS, battery level, solar charge, water temperature and stage, then sends it to Cayenne
written with the thanks the examples provided online:
-Random Nerd Tutorials. (n.d.). LilyGO T-Call SIM7000G ESP32 with GPS Tracker [Blog post]. Retrieved from https://randomnerdtutorials.com/lilygo-t-sim7000g-esp32-lte-gprs-gps/
-Xinyuan-LilyGO. (n.d.). Arduino_GPSTest.ino [Code]. Retrieved from https://github.com/Xinyuan-LilyGO/LilyGO-T-SIM7000G/blob/master/examples/Arduino_GPSTest/Arduino_GPSTest.ino
-Xinyuan-LilyGO. (n.d.). Arduino_Cayenne [Code]. Retrieved from https://github.com/Xinyuan-LilyGO/LilyGO-T-SIM7000G/tree/master/examples/Arduino_Cayenne
Full research and parts avalible: https://sites.google.com/gouldacademy.org/rqms-elishifrin/home
*/
//starts gsm library defines.
#define TINY_GSM_DEBUG Serial
#define CAYENNE_PRINT Serial
#define TINY_GSM_MODEM_SIM7000
#define USE_GSM //comment out to use Wifi.
#define TINY_GSM_TEST_GPRS true //provides more detail in serial port on LTE connection.
//Determines the above if to include the rest.
#ifdef USE_GSM
#include <CayenneMQTTGSM.h>
#else
#include <CayenneMQTTESP32.h>
#endif
#include <Arduino.h>
#include <Wire.h>
#include <TinyGsmClient.h>. //GPS library
#include <OneWire.h> //onewire library
#include <DallasTemperature.h> //library for temp sensor
//defines
#define ONE_WIRE_BUS 22 //Defines the pin that the temperature sensor is connected to.
//Channels being used for Cayenne conection.
#define BATTERY_VIRTUAL_CHANNEL 1
#define SOLAR_VIRTUAL_CHANNEL 2
#define STAGE_VIRTUAL_CHANNEL 3
#define WATER_TEMP_VIRTUAL_CHANNEL 4
#define LONGITUDE_VIRTUAL_CHANNEL 5
#define LATITUDE_VIRTUAL_CHANNEL 6
#define YEARZ_VIRTUAL_CHANNEL 7
#define MONTHZ_VIRTUAL_CHANNEL 8
#define DAYZ_VIRTUAL_CHANNEL 9
#define HOURZ_VIRTUAL_CHANNEL 10
#define MINZ_VIRTUAL_CHANNEL 11
#define SECZ_VIRTUAL_CHANNEL 12
#define PIN_TX 27
#define PIN_RX 26
#define UART_BAUD 115200
#define PWR_PIN 4
#define BAT_ADC 35
#define SOLAR_ADC 36
#define LED_PIN 12
#define STAGE_PIN 0 //Defines the pin that the ultrasonic sensor is connected to.
//Deep sleep start and period.
#define uS_TO_S_FACTOR 1000000ULL // Conversion factor for micro seconds to seconds
#define TIME_TO_SLEEP 900 // Time ESP32 will go to sleep (in seconds)
// Constructors
OneWire oneWire(ONE_WIRE_BUS);
DallasTemperature dallasOnewire(&oneWire);
HardwareSerial gsmSerial(1);
TinyGsm modem(gsmSerial);
DeviceAddress sensorWater = { 0x28, 0x3C, 0x7C, 0x49, 0xF6, 0xAC, 0x3C, 0x53 }; //Not needed, can return to auto configuration
//Global Varibles
int bootCount = 0;
bool send = false;
float latz = 0;
float lonz = 0;
float speedz = 0;
float altz = 0;
int vsatz = 0;
int usatz = 0;
float accuracyz = 0;
int yearz = 0;
int monthz = 0;
int dayz = 0;
int hourz = 0;
int minz = 0;
int secz = 0;
//Creates and stores and number with either decimals or integeres.
float temperature;
long stage, cm; //Data is taken in as stage, then converted to cm and displayed as sucht
#ifdef USE_GSM
// GSM connection info.
char apn[] = ""; // Access point name. Leave empty if it is not needed.
char gprsLogin[] = ""; // GPRS username. Leave empty if it is not needed.
char gprsPassword[] = ""; // GPRS password. Leave empty if it is not needed.
char pin[] = ""; // SIM pin number. Leave empty if it is not needed.
#else
// WiFi network info.
char ssid[] = "";
char wifiPassword[] = "";
#endif
// Cayenne authentication info. This should be obtained from the Cayenne Dashboard.
char username[] = "";
char password[] = "";
char clientID[] = "";
//Inital wakeup from deepsleep
void print_wakeup_reason() {
esp_sleep_wakeup_cause_t wakeup_reason;
wakeup_reason = esp_sleep_get_wakeup_cause();
switch (wakeup_reason) {
case ESP_SLEEP_WAKEUP_EXT0: Serial.println("Wakeup caused by external signal using RTC_IO"); break;
case ESP_SLEEP_WAKEUP_EXT1: Serial.println("Wakeup caused by external signal using RTC_CNTL"); break;
case ESP_SLEEP_WAKEUP_TIMER: Serial.println("Wakeup caused by timer"); break;
case ESP_SLEEP_WAKEUP_TOUCHPAD: Serial.println("Wakeup caused by touchpad"); break;
case ESP_SLEEP_WAKEUP_ULP: Serial.println("Wakeup caused by ULP program"); break;
default: Serial.printf("Wakeup was not caused by deep sleep: %d\n", wakeup_reason); break;
}
}
void setup() {
//create serials and UART Monitor/port.
Serial.begin(115200); //Serial Monitor baud rate
gsmSerial.begin(UART_BAUD, SERIAL_8N1, PIN_RX, PIN_TX); //Serialport for GSM
// NEED FOR GSM TO POWER ON
pinMode(PWR_PIN, OUTPUT);
//Launch SIM7000
digitalWrite(PWR_PIN, HIGH);
delay(300);
digitalWrite(PWR_PIN, LOW);
//Sets up STAGE_PIN 4
pinMode(STAGE_PIN, INPUT);
//Wait for the SIM7000 communication to be normal, and will quit when receiving any byte
int i = 6; //How many times send AT runs
delay(200);
while (i) {
Serial.println("Send AT");
gsmSerial.println("AT");
if (gsmSerial.available()) {
String r = gsmSerial.readString();
Serial.println(r);
break;
}
delay(1000);
i--;
}
#ifdef USE_GSM
Serial.print("GSM connect");
Cayenne.begin(username, password, clientID, gsmSerial, apn, gprsLogin, gprsPassword, pin);
#else
Serial.print("Wifi connect");
Cayenne.begin(username, password, clientID, ssid, wifiPassword);
#endif
dallasOnewire.begin();
//Boot and print in serial.
++bootCount; //Increment boot number and print it every reboot
Serial.println("Boot number: " + String(bootCount));
print_wakeup_reason();
}
void loop() {
readTemp();
read_stage();
gps();
print_All();
Cayenne.loop();
sendData();
print_All();
delay(1000);
if (send) {
start_Deepsleep();
}
}
void readTemp() {
dallasOnewire.requestTemperatures(); //going out over onewire to send data over bus 32
temperature = dallasOnewire.getTempCByIndex(0); //taking data and putting in Temp C
if (temperature != DEVICE_DISCONNECTED_C) // Check if reading was successful
{
} else {
temperature = 999; //If disconnected, sends value 999
}
}
void read_stage()
{
stage = pulseIn(STAGE_PIN, HIGH);
cm = stage / 10;// converts the range to cm
}
void sendData()
{
Cayenne.virtualWrite(WATER_TEMP_VIRTUAL_CHANNEL, temperature, "temp", "c");
Cayenne.virtualWrite(STAGE_VIRTUAL_CHANNEL, cm, "stage", "cm"); //
float batmv = readBattery(BAT_ADC);
Cayenne.virtualWrite(BATTERY_VIRTUAL_CHANNEL, batmv, TYPE_VOLTAGE, UNIT_MILLIVOLTS);
float solmv = readBattery(SOLAR_ADC);
Cayenne.virtualWrite(SOLAR_VIRTUAL_CHANNEL, solmv, TYPE_VOLTAGE, UNIT_MILLIVOLTS);
Cayenne.virtualWrite(LONGITUDE_VIRTUAL_CHANNEL, lonz, "longitude");
Cayenne.virtualWrite(LATITUDE_VIRTUAL_CHANNEL, latz, "latitude");
Cayenne.virtualWrite(YEARZ_VIRTUAL_CHANNEL, yearz, "year");
Cayenne.virtualWrite(MONTHZ_VIRTUAL_CHANNEL, monthz, "month");
Cayenne.virtualWrite(DAYZ_VIRTUAL_CHANNEL, dayz, "day");
Cayenne.virtualWrite(HOURZ_VIRTUAL_CHANNEL, hourz, "hour");
Cayenne.virtualWrite(MINZ_VIRTUAL_CHANNEL, minz, "minute");
Cayenne.virtualWrite(SECZ_VIRTUAL_CHANNEL, secz, "second");
send = true;
delay(3000);
}
float readBattery(uint8_t pin) {
int vref = 1100;
uint16_t volt = analogRead(pin);
float battery_voltage = ((float)volt / 4095.0) * 2.0 * 3.3 * (vref);
return battery_voltage;
}
void print_All() {
Serial.println("Water Temp C: " + String(temperature));
Serial.println("stage m: " + String(cm));
}
//deep_sleep starts
void start_Deepsleep() {
esp_sleep_enable_timer_wakeup(TIME_TO_SLEEP * uS_TO_S_FACTOR);
Serial.println("Setup ESP32 to sleep for every " + String(TIME_TO_SLEEP) + " Seconds");
Serial.println("Going to sleep now");
Serial.flush();
esp_deep_sleep_start();
Serial.println("This will never be printed");
}
CAYENNE_DISCONNECTED() {
start_Deepsleep();
}
//Here starts GPS power on, off enable and disable.
void enableGPS(void) {
//Set SIM7000G GPIO4 LOW ,turn on GPS power
// CMD:AT+SGPIO=0,4,1,1
// Only in version 20200415 is there a function to control GPS power
modem.sendAT("+SGPIO=0,4,1,1");
Serial.println("modem.sendAT");
if (modem.waitResponse(10000L) != 1)
{
Serial.println("ifpart");
DBG(" SGPIO=0,4,1,1 false ");
}
Serial.println("modem.waitResponse");
modem.enableGPS();
Serial.println("modem.enableGPS");
}
void disableGPS(void) {
modem.sendAT("+SGPIO=0,4,1,0");
if (modem.waitResponse(10000L) != 1) {
DBG(" SGPIO=0,4,1,0 false ");
}
modem.disableGPS();
}
void modemPowerOn() {
pinMode(PWR_PIN, OUTPUT);
digitalWrite(PWR_PIN, HIGH); //When powered HIGH=off
delay(1000); //Datasheet Ton mintues = 1S
digitalWrite(PWR_PIN, LOW);
}
void modemPowerOff() {
pinMode(PWR_PIN, OUTPUT);
digitalWrite(PWR_PIN, LOW);
delay(1500); //Datasheet Ton mintues = 1.2S
digitalWrite(PWR_PIN, HIGH);
}
void modemRestart() {
modemPowerOff();
delay(1000);
modemPowerOn();
}
void gps() {
if (!modem.testAT()) {
Serial.println("Failed to restart modem, attempting to continue without restarting");
modemRestart();
return;
}
Serial.println("Start positioning . Make sure to locate outdoors.");
Serial.println("The blue indicator light flashes to indicate positioning.");
enableGPS();
unsigned long previousMillis = 0;
const long interval = 60000; //How long in MILLISECONDS it will attempt to connect
while (1) {
if (modem.getGPS(&latz, &lonz, &speedz, &altz, &vsatz, &usatz, &accuracyz,
&yearz, &monthz, &dayz, &hourz, &minz, &secz)) {
Serial.println("Latitude: " + String(latz, 8) + "\tLongitude: " + String(lonz, 8));
Serial.println("Speed: " + String(speedz) + "\tAltitude: " + String(altz));
Serial.println("Visible Satellites: " + String(vsatz) + "\tUsed Satellites: " + String(usatz));
Serial.println("Accuracy: " + String(accuracyz));
Serial.println("Year: " + String(yearz) + "\tMonth: " + String(monthz) + "\tDay: " + String(dayz));
Serial.println("Hour: " + String(hourz) + "\tMinute: " + String(minz) + "\tSecond: " + String(secz));
break;
}
digitalWrite(LED_PIN, !digitalRead(LED_PIN));
delay(2000);
}
disableGPS();
}