Project in progress
Off-Grid Remote Monitoring: Batteries, Leak Detection, etc.

Off-Grid Remote Monitoring: Batteries, Leak Detection, etc. © GPL3+

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

  • 5 respects

Components and supplies

Abx00018 featured wi0j4nqatf
Arduino MKR GSM 1400
Low energy operation and sleep, built-in GSM for operation away from wifi. But choose the type of communication you need. My sketch needs the additional program space of the Zero/MKR series, but maybe if your project does fewer things, or you are a better programmer, you might be able to fit it into Uno compatible boards. I started with a Seeeduino GPRS, but I couldn't get it connected to
SIM card, data-only
I'm using a Google Fi SIM because it's easy to get a data-only card attached to an existing account and in that case you pay just for the additional data at $0.01/MB. Use whichever service works for your location and amount of data, and adjust the APN in the sketch accordingly.
Adafruit Lithium Ion Battery - 3.7v 2000mAh
The MKR GSM requires at least a 1500mAh battery to deal with the current spikes during GSM transmission
10167 01
DHT22 Temperature Sensor
Elecrow Water Sensor
This or the grove one, which are digital, or another one which is analog to tell you how high on the tiny sensor the water is. Adafruit has some eTape liquid level sensors too that offer more range of depth measurement. I may move to something like that because these water sensors are known not to be very durable or robust, and one of mine was noticeably tarnished after just a few days of testing.
Adafruit industries ada64 image 75px
Solderless Breadboard Half Size
Breadboard for testing, MKR proto shield for permanent installation
6-24V 12V/24V to 5V 3A CAR USB Charger Module DC Buck step down Converter
This steps down from the boat's 12V system to 5V, efficiently at low current (I chose based on this test: It outputs to USB, which is not necessary in this case and forces me to fit a short USB cable with its bulky connectors into the enclosure, or to remove the USB connection and wire directly, which seems like a better idea. Here's the chip manufacturer's page:
Adafruit industries ada592 image 75px
USB-A to Micro-USB Cable
Adafruit's super short one, though it is stiffer than most which can be difficult in the enclosure
3 Conductor 22AWG Stranded 100 Ft Shielded PVC
Waterproof Enclosure
I sized this for an earlier setup with a Uno sized board with two shields stacked. I will probably switch to a smaller enclosure
Cable gland for strain relief and water proofing
Five small holes for the data cables
Cable gland for strain relief and waterproofing
Three larger holes for the 12V cables for power supply, and the relays (when they are added)

Apps and online services

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.


Arudino sketch for remote monitoring with Thinger.ioArduino
// library name              // for...
#include "arduino_secrets.h" // private credentials
#include <MKRGSM.h>          // GSM communication, from Arduino
#include <ThingerMKRGSM.h>   //, from
#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

// credentials

// DHT config
#define DHTPIN 2  // digital pin sensor is connected to
#define DHTTYPE DHT22  // which DHT sensor? 11 or 22?

// 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.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.


  // set builtin led to output and turn it off
  digitalWrite(LED_BUILTIN, LOW);

  // 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

  // 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

  // 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: (adapted for the MKR's hardware Serial1 (at pins 13,14) which most Arduinos lack)
    if (Serial1.available()) {
    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);

      if (V_buffer.startsWith("Relay")) {
        Relay = V_buffer.substring(V_buffer.indexOf("\t")+1);

      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;
    }  // 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


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.
Mkr remote boat monitor fritzing bb nsnahvqhqb


Similar projects you might like

WaterPi: Houseplant Remote Watering and Monitoring System

Project tutorial by Demirhan Aydin

  • 136 respects

Indoor Air Quality Monitoring System

Project tutorial by Team East West University

  • 45 respects

Project KOOL: Temperature and Humidity Remote Monitoring

Project tutorial by vincent wong

  • 1 comment
  • 17 respects

Portable Warehouse/Control Shed/Greenhouse Monitoring

Project tutorial by Muhammad Afzal

  • 7 respects

WIZ750SR Based Remote Health Monitoring with Amazon Alexa

Project tutorial by Madhur Gupta

  • 6 respects

Smart Personal Money Vault Monitoring System Based on IoT

Project tutorial by Salah Uddin

  • 6 respects
Add projectSign up / Login