Off-grid Remote Monitoring: Batteries, Leak Detection, Etc.

Photo of Arduino

Made by Arduino / Sensors / IoT

About the project

Monitor a battery bank and environmental conditions; send notification if water is detected or batteries are unexpectedly low.

Project info

Items used in this project

Hardware components

ARDUINO MKR GSM 1400 ARDUINO MKR GSM 1400 x 1
Sim card Sim card x 1
Lithium Ion Battery - 3.7V 2000mAh Lithium Ion Battery - 3.7V 2000mAh x 1
DHT22 Temperature and Humidity Sensor DHT22 Temperature and Humidity Sensor x 1
Elecrow Water Sensor Elecrow Water Sensor x 1
Solderless Breadboard Half Size Solderless Breadboard Half Size x 1
USB OTG Cable- Fem. A to Micro B - 5in USB OTG Cable- Fem. A to Micro B - 5in x 1
3 Conductor 22AWG Stranded 100 Ft Shielded PVC 3 Conductor 22AWG Stranded 100 Ft Shielded PVC x 1
A02YYUW Waterproof Ultrasonic Sensor A02YYUW Waterproof Ultrasonic Sensor x 1

View all

Software apps and online services

Thinger.io Thinger.io

Story

ABOUT THIS PROJECT

I have a sailboat with an electric motor. As I've worked on it, I have returned to the boat a few times to find a dead battery bank ($$$$), or flooding (wood damage). Had I known about those situations earlier, I might have been able to reduce the damage.

There are commercial products for monitoring and control of off-grid systems, but DIY is more fun, forces learning, and can provide exactly the capabilities I want and nothing I don't need.

The main idea was to have notification of low batteries or water detected. I also wanted a way to check for recent readings to ensure that no notification didn't mean the sensor had stopped working. I plan to use more of the Arduino's I/O capabilities for expanded services: control ventilation based on indoor and outdoor moisture, and dispatch dump loads when there is extra solar and the batteries are full.

Testing the connection to the Victron battery monitor. The water sensors and DHT22 are dangling by their short testing wires.

CODE

Arudino sketch for remote monitoring with Thinger.io

  1. // library name // for...
  2. #include "arduino_secrets.h" // private credentials
  3. #include <MKRGSM.h> // GSM communication, from Arduino
  4. #include <ThingerMKRGSM.h> // thinger.io, from thinger.io
  5. #include <DHT.h> // temperature sensor, from Adafruit
  6. #include <ArduinoLowPower.h> // sleep, from Arduino
  7. #include <SPI.h> // reading battery monitor over TTL/serial
  8.  
  9. #define GPRS_APN "h2g2" // Get onto Google Fi network
  10.  
  11. // Thinger.io credentials
  12. #define USERNAME "SECRET_THINGER_USERNAME"
  13. #define DEVICE_ID "SECRET_THINGER_DEVICE_ID"
  14. #define DEVICE_CREDENTIAL "SECRET_THINGER_DEVICE_CREDENTIAL"
  15. ThingerMKRGSM thing(USERNAME, DEVICE_ID, DEVICE_CREDENTIAL);
  16.  
  17. // DHT config
  18. #define DHTPIN 2 // digital pin sensor is connected to
  19. #define DHTTYPE DHT22 // which DHT sensor? 11 or 22?
  20. DHT dht(DHTPIN, DHTTYPE);
  21.  
  22. // Water sensors
  23. #define highBilge 5 // digital pin the sensor is connected to
  24. #define lowBilge 7 // digital pin the sensor is connected to
  25. #define engineRoom 8 // digital pin the sensor is connected to
  26.  
  27. // Setting up Victron battery monitor variables
  28. char p_buffer[80];
  29. #define P(str) (strcpy_P(p_buffer, PSTR(str)), p_buffer)
  30.  
  31. char c;
  32. String V_buffer;
  33.  
  34. float Current;
  35. float Voltage;
  36. float SOC;
  37. float TTG;
  38. float CE;
  39. int Alarm_low_voltage;
  40. int Alarm_high_voltage;
  41. int Alarm_low_soc;
  42. String Alarm;
  43. String Relay;
  44.  
  45. boolean endpointRateLimiter = 1;
  46.  
  47. void setup() {
  48. Serial.begin(115200);
  49. Serial.println("Starting setup");
  50.  
  51. // Victron Battery Monitor
  52. // DO NOT CONNECT POWER OR GROUND WIRES, the BMV is not isolated,
  53. // the official Victron cable is isolating for this reason
  54. // The Victron uses 3.3V TTL, so the MKR boards 3.3V circuit is perfect
  55. // initialize serial communication with the Victron BMV at 19200 bits per second (per jepefe):
  56. // Serial1 is a hardware serial port in pins 13 and 14 in the MKR series
  57. Serial1.begin(19200); // In jepefe's code, for Arduinos without hardware serial, this would be Victron.begin(19200)
  58. // and Victron was defined above by its pins and SoftwareSerial. Apparently it has been deprecated in favor of NewSoftSerial
  59. // this website has plain and direct language about this, which is rarely discussed in discussions I have seen about
  60. // downstream use of the serials. https://www.pjrc.com/teensy/td_libs_SoftwareSerial.html
  61.  
  62. thing.set_apn(GPRS_APN);
  63.  
  64. // set builtin led to output and turn it off
  65. pinMode(LED_BUILTIN, OUTPUT);
  66. digitalWrite(LED_BUILTIN, LOW);
  67.  
  68. // Thinger.io resources
  69. thing["led"] << [](pson & in) {
  70. digitalWrite(LED_BUILTIN, in ? HIGH : LOW);
  71. };
  72.  
  73. // pin control example over internet (i.e. turning on/off a light, a relay, etc)
  74. //thing["relay"] << digitalPin(7); //change this to the right pin when connecting the relay
  75.  
  76. //Temperature and RH sensor setup
  77. dht.begin();
  78.  
  79. // Wake on water detected
  80. LowPower.attachInterruptWakeup(highBilge, NULL, LOW);
  81. LowPower.attachInterruptWakeup(engineRoom, NULL, LOW);
  82. LowPower.attachInterruptWakeup(lowBilge, NULL, LOW);
  83.  
  84. delay(15000); //give a chance to reprogram
  85. Serial.println("end of setup");
  86. }
  87.  
  88. void loop() {
  89.  
  90. digitalWrite(LED_BUILTIN, LOW); // turn LED back off when returning to loop
  91. thing.handle();
  92. Serial.println("thing.handle");
  93.  
  94. // check for high water, call endpoint to send email if detected (call once)
  95. if ((digitalRead(highBilge) == LOW
  96. || digitalRead(lowBilge) == LOW
  97. || digitalRead(engineRoom) == LOW)
  98. && endpointRateLimiter) {
  99. digitalWrite(LED_BUILTIN, HIGH);
  100. pson data;
  101. data["water high bilge"] = digitalRead(highBilge);
  102. data["water low bilge"] = digitalRead(lowBilge);
  103. data["water engine room"] = digitalRead(engineRoom);
  104. thing.call_endpoint("WaterDetectedEmail", data);
  105. endpointRateLimiter = 0;
  106. }
  107. else digitalWrite(LED_BUILTIN, LOW);
  108.  
  109. // these declarations are not needed for DHT or thinger, but I'm
  110. // using them to get integers and avoid using overly precise data
  111. int humidity = dht.readHumidity();
  112. //int celsius = dht.readTemperature();
  113. int fahrenheit = dht.readTemperature(true);
  114.  
  115. // Victron BMV code from: http://www.jw5zla.com/?p=7 (adapted for the MKR's hardware Serial1 (at pins 13,14) which most Arduinos lack)
  116. if (Serial1.available()) {
  117. c = Serial1.read();
  118.  
  119. Serial.println(c);
  120. if (V_buffer.length() <80) {
  121. V_buffer += c;
  122. }
  123.  
  124. if (c == 'n') {
  125.  
  126. if (V_buffer.startsWith("I")) {
  127. String temp_string = V_buffer.substring(V_buffer.indexOf("t")+1);
  128. double temp_int = temp_string.toInt();
  129. Current = (float) temp_int/1000;
  130. }
  131.  
  132. if (V_buffer.startsWith("V")) {
  133. String temp_string = V_buffer.substring(V_buffer.indexOf("t")+1);
  134. int temp_int = temp_string.toInt();
  135. Voltage = (float) temp_int/1000;
  136. }
  137.  
  138. if (V_buffer.startsWith("SOC")) {
  139. String temp_string = V_buffer.substring(V_buffer.indexOf("t")+1);
  140. int temp_int = temp_string.toInt();
  141. SOC = (float) temp_int/10;
  142. }
  143.  
  144. if (V_buffer.startsWith("TTG")) {
  145. String temp_string = V_buffer.substring(V_buffer.indexOf("t")+1);
  146. double temp_int = temp_string.toInt();
  147. if (temp_int >0) {
  148. TTG = (float) temp_int/60;
  149. }
  150. else {
  151. TTG = 240;
  152. }
  153. }
  154.  
  155. if (V_buffer.startsWith("CE")) {
  156. String temp_string = V_buffer.substring(V_buffer.indexOf("t")+1);
  157. double temp_int = temp_string.toInt();
  158. CE = (float) temp_int/1000;
  159. }
  160.  
  161. if (V_buffer.startsWith("Alarm")) {
  162. Alarm = V_buffer.substring(V_buffer.indexOf("t")+1);
  163. Alarm.trim();
  164. }
  165.  
  166. if (V_buffer.startsWith("Relay")) {
  167. Relay = V_buffer.substring(V_buffer.indexOf("t")+1);
  168. Relay.trim();
  169. }
  170.  
  171. if (V_buffer.startsWith("AR")) {
  172. String temp_string = V_buffer.substring(V_buffer.indexOf("t")+1);
  173. int temp_int = temp_string.toInt();
  174.  
  175. if (bitRead(temp_int,0)) {
  176. Alarm_low_voltage = 1;
  177. }
  178. else {
  179. Alarm_low_voltage = 0;
  180. }
  181.  
  182. if (bitRead(temp_int,1)) {
  183. Alarm_high_voltage = 1;
  184. }
  185. else {
  186. Alarm_high_voltage = 0;
  187. }
  188.  
  189. if (bitRead(temp_int,2)) {
  190. Alarm_low_soc = 1;
  191. }
  192. else {
  193. Alarm_low_soc = 0;
  194. }
  195. }
  196. Serial.println(Current);
  197. Serial.println(Voltage);
  198. Serial.println(SOC);
  199. V_buffer="";
  200. } // end if new line
  201. } // end if Serial1 (Victron) is available
  202. pson data;
  203. data["BMV:Current"] = Current;
  204. data["BMV:Voltage"] = Voltage;
  205. data["BMV:SoC"] = SOC;
  206. //data["BMV:TTG"] = TTG;
  207. //data["BMV:CE"] = CE;
  208. //data["BMV:int Alarm_low_voltage;
  209. //data["BMV:int Alarm_high_voltage;
  210. //data["BMV:int Alarm_low_soc;
  211. //data["BMV:Alarm"] = Alarm;
  212. //data["BMV:Relay"] = Relay;
  213.  
  214. data["humidity"] = humidity;
  215. //data["celsius"] = celsius;
  216. data["fahrenheit"] = fahrenheit;
  217. data["water high bilge"] = digitalRead(highBilge);
  218. data["water low bilge"] = digitalRead(lowBilge);
  219. data["water engine room"] = digitalRead(engineRoom);
  220. thing.write_bucket("BoatMonitorDataBucket", data);
  221. Serial.println("wrote data to thinger bucket, now to sleep!");
  222.  
  223. //sleep for this many minutes (minutes*milliseconds in a minute)
  224. LowPower.sleep(2 * 60000); // 2 for testing, 15 for summer, 30 for winter
  225. }
  226. /*
  227. void InterruptWake() // Interrupt routine, not currently called by the interrupts
  228. {
  229. digitalWrite(LED_BUILTIN, HIGH); //turn on the LED when woken. Can remove all the LED
  230. // parts of the RTC/Sleep system when the sketch is done to save that power, or no leave
  231. // it for confirming operation. But would I watch the LED for 20 minutes to see if it lights?
  232.  
  233. //Keep Interrupt routein short, but this is an alarm. Either trigger it here or in Thinger
  234. }*/

SCHEMATICS

Wiring schematic

This is my first fritzing sketch; recommendations are welcome. These are not the right water sensors (these are analog, but I show them as if they were digital like the ones I'm using are). I didn't find a good way to depict the connections to the battery monitor and charge controller. Also the battery is actually connected to the JST port on the Arduino, not wired directly to pins.

Schematics, diagrams and documents

Wiring schematic

This is my first fritzing sketch; recommendations are welcome. These are not the right water sensors (these are analog, but I show them as if they were digital like the ones I'm using are). I didn't find a good way to depict the connections to the battery monitor and charge controller. Also the battery is actually connected to the JST port on the Arduino, not wired directly to pins.

Code

Arduino sketch

Arduino sketch for remote monitoring with Thinger.io

Credits

Photo of Arduino

Arduino

Arduino is the world’s leading open-source hardware and software ecosystem. The Company offers a range of software tools, hardware platforms and documentation enabling almost anybody to be creative with technology.

   

Leave your feedback...