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.

  • 6,290 views
  • 3 comments
  • 27 respects

Components and supplies

Abx00018 featured wi0j4nqatf
Arduino MKR GSM 1400
It has low energy operation and sleep, and built-in GSM for operation away from wifi. Choose the type of communication you need - they do make other Arduino MKRs with other communication types. 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 Thinger.io.
×1
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.
×1
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. UPDATE: I later read on forums that the battery needs to be capable for 2.5A peak supply and I don't think this one is. I think it's 1C, so can output 1*capacity = 2A.
×1
10167 01
DHT22 Temperature Sensor
×1
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 am now focusing on ultrasonic proximity sensors for the main bilge depth sensor 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. But I will probably keep them for places that rarely get wet or shouldn't get wet.
×1
Adafruit industries ada64 image 75px
Solderless Breadboard Half Size
Breadboard for testing, MKR proto shield for permanent installation
×1
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: https://blog.yavilevich.com/2017/03/efficient-dc-12v-to-5v-conversion-for-low-power-electronics-evaluation-of-six-modules/). 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: https://www.monolithicpower.com/en/mp2315.html
×1
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
×1
3 Conductor 22AWG Stranded 100 Ft Shielded PVC
×1
Waterproof Enclosure
I sized this for an earlier setup with a Uno-sized board with two shields stacked. I may switch to a smaller enclosure.
×1
Cable gland for strain relief and water proofing
Five small holes for the data cables
×1
Cable gland for strain relief and waterproofing
Three larger holes for the 12V cables for power supply, and the relays (when they are added)
×1
JSN-SR04T
Waterproof ultrasonic proximity sensor with 2.5 m of cable to the non-waterproof breakout board. The sensor itself is about 2 cm in diameter and has tabs that would hold it in place in a socket or bracket, but I haven't been able to find any specific recommendation of how to attach it.
×1

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.

Code

2 Arudino sketch for remote monitoring with Thinger.io: has ultrasonic sensor, no BMVArduino
I have this running on an Arduino MKR GSM 1400 with a data-only SIM card. You need to adapt the communication specifics for your SIM. And you need a thinger.io account or to replace the relevant code with appropriate bits for your IoT server.
// library name              // for...
#include <Arduino.h>         // these first 3 lines were added to stop the complier from complaining about min and max macros that are used somewhere in the included files
#undef max                   // I can probably remove them at some point when libraries are updated. I had not explicitly called Arduino.h before. [Still needed May 2020]
#undef min

#include <MKRGSM.h>          // GSM communication, from Arduino (Library Manager)
#include <ThingerMKRGSM.h>   // thinger.io, from thinger.io (Library Manager)
#include <DHT.h>             // temperature sensor, from Adafruit (Library Manager)
#include <ArduinoLowPower.h> // sleep for SAMD MCUs, from Arduino (Library Manager)
//#include <WDTZero.h>         // WatchDog functionality for Arduino Zero, MKRZero and MKR1000 only, allow MINUTES of watchdog time https://github.com/javos65/WDTZero/tree/master/WDTZero

#define sleep_minutes 5
// have I tried deepSleep?

// Only print to the serial monitor when debug is on
#define DEBUG 0
#ifdef DEBUG
  #define DEBUG_PRINTLN(x)  Serial.println(x)
  #define DEBUG_PRINT(x)  Serial.print(x)
#else
  #define DEBUG_PRINTLN(x)
#endif

// Thinger.io credentials
#define USERNAME "xxxxxxxx"
#define DEVICE_ID "yyyyyyyyy"
#define DEVICE_CREDENTIAL "zzzzzzzzz"
#define GPRS_APN "h2g2"  // Get onto Google Fi network
ThingerMKRGSM thing(USERNAME, DEVICE_ID, DEVICE_CREDENTIAL);

// Temp/RH initiliazation
#define DHTPIN 6  // digital pin sensor is connected to
#define DHTTYPE DHT22  // which DHT sensor? 11 or 22?
DHT dht(DHTPIN, DHTTYPE);

// Water sensors digital wet or dry type
#define highBilge 3   // digital pin the sensor is connected to
#define lowBilge 4    // digital pin the sensor is connected to
#define engineRoom 5  // digital pin the sensor is connected to

// Bilge depth ultrasonic sensor
#include <NewPing.h>
#define TRIGGER_PIN  1  // Arduino pin tied to trigger pin on the ultrasonic sensor
#define ECHO_PIN     2  // Arduino pin tied to echo pin on the ultrasonic sensor
#define MAX_DISTANCE 150 // Maximum distance we want to ping for (in centimeters). Maximum sensor distance is rated at 400-500cm.
#define measurements_to_average 10
NewPing sonar(TRIGGER_PIN, ECHO_PIN, MAX_DISTANCE); // NewPing setup of pins and maximum distance.
    
// Bilge pump sensor sensor
//static const char BILGE_SWITCH = 1; // digital pin connected to bilge pump switch through voltage divider 

boolean endpointRateLimiter = 1;

//WDTZero WatchDoggy; // Define WDTZero as WDT

void setup() {
  Serial.begin(115200);
  DEBUG_PRINTLN("Starting setup");

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

  // setup pins for ultrasonic sensor: use internal pullup resistor for echo pin
  pinMode(TRIGGER_PIN,OUTPUT);
  pinMode(ECHO_PIN,INPUT_PULLUP);

  // Thinger.io resources 
  //thing["led"] << digitalPin(LED_BUILTIN);
  // 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 from sleep on water detected
        // add one for the depth from the prox sensor if/when it operates reliably
  LowPower.attachInterruptWakeup(highBilge, NULL, LOW);
  LowPower.attachInterruptWakeup(engineRoom, NULL, LOW);
  LowPower.attachInterruptWakeup(lowBilge, NULL, LOW);

  //pinMode(BILGE_SWITCH, INPUT_PULLUP);
 
  delay(10000);  //give a chance to reprogram

  // Set up a watchdog timer, long to allow communications with thinger, which take more than 16 seconds, but sometimes do hang and need a reset
  //WatchDoggy.setup(WDT_SOFTCYCLE16M); // initialize WDT-softcounter refesh cycle on 16 minutes interval
  
  DEBUG_PRINTLN("Setup Complete.");
}

void loop() {

  DEBUG_PRINTLN("Entering main loop, starting thinger.handle.");
  digitalWrite(LED_BUILTIN, LOW); // turn LED back off when returning to loop

  digitalWrite(LED_BUILTIN, HIGH); // LED comes on for thinger.handle 
  // Clear the watchdog timer
  //WatchDoggy.clear(); 
  thing.handle();         // LED is on 7-8 seconds, then off for ~15 seconds for the rest of the loop
  //WatchDoggy.clear();   // Clear the watchdog timer
  digitalWrite(LED_BUILTIN, LOW); // LED comes off when finished with thinger.handle
  DEBUG_PRINTLN("thinger.handle done.");


  // these declarations are not needed for DHT or thinger, but I'm
  // them to get integers and avoid using overly precise data
  byte humidity = dht.readHumidity();
  int8_t fahrenheit = dht.readTemperature(true);

  byte bilge_depth = get_bilge_depth();

  // check for high water, call endpoint to send email if detected (call once
  if ((digitalRead(highBilge) == LOW
      || digitalRead(lowBilge) == LOW
      || digitalRead(engineRoom) == LOW
      || bilge_depth > 20)
      && endpointRateLimiter) {
    digitalWrite(LED_BUILTIN, HIGH); //LED comes on when water detected
    DEBUG_PRINTLN("water sensor check positive!");
    pson data;
    data["water high bilge"] = digitalRead(highBilge);
    data["water low bilge"] = digitalRead(lowBilge);
    data["water engine room"] = digitalRead(engineRoom);
    data["bilge water depth"] = bilge_depth;
    thing.call_endpoint("WaterDetectedEmail", data);
    endpointRateLimiter = 0;
    }  
  else {digitalWrite(LED_BUILTIN, LOW);
  DEBUG_PRINTLN("water sensor check clear.");}

  // bilge pump run timer
 /* static unsigned long bilge_timer = millis();
  static unsigned long bilgeTimeHigh = 0;       //[seconds]
  // check bilge pump
  if (diintgitalRead(BILGE_SWITCH) == LOW)   // Bilge switch activated by high water
  {
    if (millis() - bilge_timer > 1000)  // Count every second water is high
    { bilge_timer = millis();
      bilgeTimeHigh++; }
  }
  else {bilge_timer = millis();}
 */
 
  pson data;
  //data["bilge pump time"] = bilgeTimeHigh;
  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);
  data["bilge water depth"] = bilge_depth;
 
  // Print the data to the Serial monitor when debugging
  DEBUG_PRINTLN("about to write data to thinger bucket");
  //DEBUG_PRINT("Current:");
  //DEBUG_PRINTLN(Current);
  //DEBUG_PRINT("Voltage:");
  //DEBUG_PRINTLN(Voltage);
  //DEBUG_PRINT("SOC:");
  //DEBUG_PRINTLN(SOC);
  DEBUG_PRINT("Humidity:");
  DEBUG_PRINTLN(humidity);
  DEBUG_PRINT("Temperature (F):");
  DEBUG_PRINTLN(fahrenheit);
  DEBUG_PRINT("Water in bilge (cm):");
  DEBUG_PRINTLN(bilge_depth);
  
  thing.write_bucket("BoatMonitorDataBucket", data);              //died here a couple times
  DEBUG_PRINTLN("Wrote data to thinger bucket, now to sleep!");

  // Sleep
  // Shut stuff off // test the power savings of these and uncomment or remove them
 // USBDevice.detach();        // Is this just communcations? What is power is coming in through USB?
 /* Serial.end();
  Serial1.end();
  digitalWrite(LED_BUILTIN, LOW);
  */
  thing.stop(); //this seems to prevent thinger.handle hanging. https://community.thinger.io/t/problem-with-gsm-900a/1874
  //sleep for this many minutes (minutes * milliseconds in a minute) 
  LowPower.sleep(sleep_minutes * 60000); 
  
  // Turn stuff back on
 // USBDevice.attach();         // remove this when finished testing to save a little power?
  /*Serial.begin(115200);
  Serial1.begin(19200);
  */
  DEBUG_PRINTLN("Woke up!");
}

// Ultrasonic proxmity sensor function
//int8_t 
byte get_bilge_depth() {
    // I considered a DHT provide the temp and RH in the bilge. While it wouldn't change quickly, it would change with lake temp over the summer 
    // and drastically when hauled out - but that would only cause a ~2cm difference in the result so I'll save the pins and complication
    float celsius = 18; // dht.readTemperature();
    float humidity = 75; //dht.readHumidity();
    float sound_speed = (331.4 + (0.606 * celsius) + (0.0124* humidity))/10000; // speed of sound in cm/ms adjusted for temperature and humidity
    float duration = sonar.ping_median(measurements_to_average, MAX_DISTANCE);  
    byte distance = (duration / 2) * sound_speed; 
    byte bilge_depth = 74 - distance; // Sensor is mounted 74 cm above the bilge floor
    return bilge_depth;
}


/*
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 routine short, but this is an alarm. Either trigger it here or in Thinger
}*/

/*
void dummy() {
  // This function will be called once on device wakeup
  boolean endpointRateLimiter = 1; // The copy of this at the top means one email per cold start, this one means one per wake
  // Remember to avoid calling delay() and long running functions since this functions executes in interrupt context
}
*/

/*
void myshutdown()
{
 could put something here that runs before the watchdog reset 
}*/
1 Arudino sketch for remote monitoring with Thinger.io: Victron BMV code, no ultrasonicArduino
This version has code for my old Victron BMV-600, which I no longer use. The newer code lacks this, and has code for an ultrasonic sensor for measuring bilge depth. In the new code, I also addedd thing.stop() which appears to have drastically reduced how often the device hangs.
// 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.
Mkr remote boat monitor fritzing bb nsnahvqhqb

Comments

Similar projects you might like

WaterPi: Houseplant Remote Watering and Monitoring System

Project tutorial by Demirhan Aydin

  • 38,721 views
  • 16 comments
  • 154 respects

Indoor Air Quality Monitoring System

Project tutorial by Team East West University

  • 39,035 views
  • 10 comments
  • 59 respects

Portable Environment Monitoring

Project tutorial by Muhammad Afzal

  • 2,915 views
  • 0 comments
  • 12 respects

Smart Personal Money Vault Monitoring System Based on IoT

Project tutorial by Salah Uddin

  • 2,372 views
  • 1 comment
  • 13 respects

Anomaly Detection & Temperature Monitoring Using Bolt IoT

Project tutorial by ashish anand

  • 3,865 views
  • 2 comments
  • 19 respects

Smart Garbage Monitoring System Using Arduino 101

Project tutorial by Technovation

  • 41,525 views
  • 15 comments
  • 60 respects
Add projectSign up / Login