Off-grid Remote Monitoring: Batteries, Leak Detection, Etc.
About the project
Monitor a battery bank and environmental conditions; send notification if water is detected or batteries are unexpectedly low.
Project info
Difficulty: Moderate
Platforms: Arduino
Estimated time: 1 day
License: Creative Commons Attribution CC BY version 4.0 or later (CC BY 4+)
Items used in this project
Hardware components
View all
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
- // library name // for...
- #include "arduino_secrets.h" // private credentials
- #include <MKRGSM.h> // GSM communication, from Arduino
- #include <ThingerMKRGSM.h> // thinger.io, from thinger.io
- #include <DHT.h> // temperature sensor, from Adafruit
- #include <ArduinoLowPower.h> // sleep, from Arduino
- #include <SPI.h> // reading battery monitor over TTL/serial
- #define GPRS_APN "h2g2" // Get onto Google Fi network
- // Thinger.io credentials
- #define USERNAME "SECRET_THINGER_USERNAME"
- #define DEVICE_ID "SECRET_THINGER_DEVICE_ID"
- #define DEVICE_CREDENTIAL "SECRET_THINGER_DEVICE_CREDENTIAL"
- ThingerMKRGSM thing(USERNAME, DEVICE_ID, DEVICE_CREDENTIAL);
- // DHT config
- #define DHTPIN 2 // digital pin sensor is connected to
- #define DHTTYPE DHT22 // which DHT sensor? 11 or 22?
- DHT dht(DHTPIN, DHTTYPE);
- // Water sensors
- #define highBilge 5 // digital pin the sensor is connected to
- #define lowBilge 7 // digital pin the sensor is connected to
- #define engineRoom 8 // digital pin the sensor is connected to
- // Setting up Victron battery monitor variables
- char p_buffer[80];
- #define P(str) (strcpy_P(p_buffer, PSTR(str)), p_buffer)
- char c;
- String V_buffer;
- float Current;
- float Voltage;
- float SOC;
- float TTG;
- float CE;
- int Alarm_low_voltage;
- int Alarm_high_voltage;
- int Alarm_low_soc;
- String Alarm;
- String Relay;
- boolean endpointRateLimiter = 1;
- void setup() {
- Serial.begin(115200);
- Serial.println("Starting setup");
- // Victron Battery Monitor
- // DO NOT CONNECT POWER OR GROUND WIRES, the BMV is not isolated,
- // the official Victron cable is isolating for this reason
- // The Victron uses 3.3V TTL, so the MKR boards 3.3V circuit is perfect
- // initialize serial communication with the Victron BMV at 19200 bits per second (per jepefe):
- // Serial1 is a hardware serial port in pins 13 and 14 in the MKR series
- Serial1.begin(19200); // In jepefe's code, for Arduinos without hardware serial, this would be Victron.begin(19200)
- // and Victron was defined above by its pins and SoftwareSerial. Apparently it has been deprecated in favor of NewSoftSerial
- // this website has plain and direct language about this, which is rarely discussed in discussions I have seen about
- // downstream use of the serials. https://www.pjrc.com/teensy/td_libs_SoftwareSerial.html
- thing.set_apn(GPRS_APN);
- // set builtin led to output and turn it off
- pinMode(LED_BUILTIN, OUTPUT);
- digitalWrite(LED_BUILTIN, LOW);
- // Thinger.io resources
- thing["led"] << [](pson & in) {
- digitalWrite(LED_BUILTIN, in ? HIGH : LOW);
- };
- // pin control example over internet (i.e. turning on/off a light, a relay, etc)
- //thing["relay"] << digitalPin(7); //change this to the right pin when connecting the relay
- //Temperature and RH sensor setup
- dht.begin();
- // Wake on water detected
- LowPower.attachInterruptWakeup(highBilge, NULL, LOW);
- LowPower.attachInterruptWakeup(engineRoom, NULL, LOW);
- LowPower.attachInterruptWakeup(lowBilge, NULL, LOW);
- delay(15000); //give a chance to reprogram
- Serial.println("end of setup");
- }
- void loop() {
- digitalWrite(LED_BUILTIN, LOW); // turn LED back off when returning to loop
- thing.handle();
- Serial.println("thing.handle");
- // check for high water, call endpoint to send email if detected (call once)
- if ((digitalRead(highBilge) == LOW
- || digitalRead(lowBilge) == LOW
- || digitalRead(engineRoom) == LOW)
- && endpointRateLimiter) {
- digitalWrite(LED_BUILTIN, HIGH);
- pson data;
- data["water high bilge"] = digitalRead(highBilge);
- data["water low bilge"] = digitalRead(lowBilge);
- data["water engine room"] = digitalRead(engineRoom);
- thing.call_endpoint("WaterDetectedEmail", data);
- endpointRateLimiter = 0;
- }
- else digitalWrite(LED_BUILTIN, LOW);
- // these declarations are not needed for DHT or thinger, but I'm
- // using them to get integers and avoid using overly precise data
- int humidity = dht.readHumidity();
- //int celsius = dht.readTemperature();
- int fahrenheit = dht.readTemperature(true);
- // 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)
- if (Serial1.available()) {
- c = Serial1.read();
- Serial.println(c);
- if (V_buffer.length() <80) {
- V_buffer += c;
- }
- if (c == 'n') {
- if (V_buffer.startsWith("I")) {
- String temp_string = V_buffer.substring(V_buffer.indexOf("t")+1);
- double temp_int = temp_string.toInt();
- Current = (float) temp_int/1000;
- }
- if (V_buffer.startsWith("V")) {
- String temp_string = V_buffer.substring(V_buffer.indexOf("t")+1);
- int temp_int = temp_string.toInt();
- Voltage = (float) temp_int/1000;
- }
- if (V_buffer.startsWith("SOC")) {
- String temp_string = V_buffer.substring(V_buffer.indexOf("t")+1);
- int temp_int = temp_string.toInt();
- SOC = (float) temp_int/10;
- }
- if (V_buffer.startsWith("TTG")) {
- String temp_string = V_buffer.substring(V_buffer.indexOf("t")+1);
- double temp_int = temp_string.toInt();
- if (temp_int >0) {
- TTG = (float) temp_int/60;
- }
- else {
- TTG = 240;
- }
- }
- if (V_buffer.startsWith("CE")) {
- String temp_string = V_buffer.substring(V_buffer.indexOf("t")+1);
- double temp_int = temp_string.toInt();
- CE = (float) temp_int/1000;
- }
- if (V_buffer.startsWith("Alarm")) {
- Alarm = V_buffer.substring(V_buffer.indexOf("t")+1);
- Alarm.trim();
- }
- if (V_buffer.startsWith("Relay")) {
- Relay = V_buffer.substring(V_buffer.indexOf("t")+1);
- Relay.trim();
- }
- if (V_buffer.startsWith("AR")) {
- String temp_string = V_buffer.substring(V_buffer.indexOf("t")+1);
- int temp_int = temp_string.toInt();
- if (bitRead(temp_int,0)) {
- Alarm_low_voltage = 1;
- }
- else {
- Alarm_low_voltage = 0;
- }
- if (bitRead(temp_int,1)) {
- Alarm_high_voltage = 1;
- }
- else {
- Alarm_high_voltage = 0;
- }
- if (bitRead(temp_int,2)) {
- Alarm_low_soc = 1;
- }
- else {
- Alarm_low_soc = 0;
- }
- }
- Serial.println(Current);
- Serial.println(Voltage);
- Serial.println(SOC);
- V_buffer="";
- } // end if new line
- } // end if Serial1 (Victron) is available
- pson data;
- data["BMV:Current"] = Current;
- data["BMV:Voltage"] = Voltage;
- data["BMV:SoC"] = SOC;
- //data["BMV:TTG"] = TTG;
- //data["BMV:CE"] = CE;
- //data["BMV:int Alarm_low_voltage;
- //data["BMV:int Alarm_high_voltage;
- //data["BMV:int Alarm_low_soc;
- //data["BMV:Alarm"] = Alarm;
- //data["BMV:Relay"] = Relay;
- data["humidity"] = humidity;
- //data["celsius"] = celsius;
- data["fahrenheit"] = fahrenheit;
- data["water high bilge"] = digitalRead(highBilge);
- data["water low bilge"] = digitalRead(lowBilge);
- data["water engine room"] = digitalRead(engineRoom);
- thing.write_bucket("BoatMonitorDataBucket", data);
- Serial.println("wrote data to thinger bucket, now to sleep!");
- //sleep for this many minutes (minutes*milliseconds in a minute)
- LowPower.sleep(2 * 60000); // 2 for testing, 15 for summer, 30 for winter
- }
- /*
- void InterruptWake() // Interrupt routine, not currently called by the interrupts
- {
- digitalWrite(LED_BUILTIN, HIGH); //turn on the LED when woken. Can remove all the LED
- // parts of the RTC/Sleep system when the sketch is done to save that power, or no leave
- // it for confirming operation. But would I watch the LED for 20 minutes to see if it lights?
- //Keep Interrupt routein short, but this is an alarm. Either trigger it here or in Thinger
- }*/
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.
Leave your feedback...