Garage Door Tilt Sensor

I need to make a few battery operated garage door tilt sensors.

Are there any considerations on which sensor to choose? I think the new 9-axis sensor could do it, but may be bad on battery life due to idle power usage.

Or I suppose I could just do a contact switch, but I have had bad luck over time with every switch based tilt sensor I’ve made, that is why I was thinking about doing something fully digital. But I guess I need to think through this some more.

The RAK1904 won’t consume much current while active. Check this link. You can probably enable it’s interrupt functions which can wake up the MCU on WisBlock Core once threshold is detected.

That is what I was thinking, and then one can tell orientation (open/close) looking at the acceleration value on whichever axis is in the plane of gravity.

I ordered 2 RAK1904, so I’ll try it out in a few weeks when they get here.

1 Like

A number of modern garage door opener systems support connecting over Wifi. Check out the “MyQ” applications to trigger such things.

Using their app it allows you to determine the status of the garage door system , and allows you to integrate to Amazon drop off services, and remote control from the app.

I haven’t found a way to integrate it to GOogle Assistant or Google Home just yet - this portion of the product seems a bit “rickety”, but it’s pretty likely that a Chamberlain, or other garage door opener purchased in the last 15 years has a similar capability to connect to wifi – check it closely !!

1 Like

With regards to Google Assistant for voice command, my colleague created a tutorial on using WisBlock Core RAK11200 and IFTTT :+1:

That’s a good point.

That said, I explicitly do not want to control my garage doors remotely. I just want status indication.

I currently have a few Z-Wave tilt sensors, but as all of those are metal ball switches for the indication, and tend to get stuck or unreliable after many years (or extreme temperatures), I thought I would try a more solid state approach.

Well, the good news is that it is quite easy to do this with the RAK1904… If using timed polling.

Bad news is that I can’t get the interrupt handlers to work properly at all. Tried at least 50 different combinations/settings, and either I get no interrupts, or I constantly get interrupts even though nothing is changing.

I’m sure it is something I’m doing wrong, but it will have to wait for another day when I have more energy.

Never mind, I think I got it now. Seems like that happens every time I give up and post that I “can’t get it”. :slight_smile:

Needed to make the interrupt not latched on the LIS3DH.

And if running on battery, turning off the pullup resistor on the sensor is of critical importance (by approximately 140 uA)… Went from 177 uA average to 36 uA.

Putting it in low power mode doesn’t change the power usage much at the frequencies I’m using (maybe 6 uA), but turning off the resistor definitely does.

Should probably put that little tip in the datasheet. :slight_smile:

Thanks for the tip (once again!) @beegee . I knew about low power mode, but didn’t realize you could turn off the resistor (it is in the ST manual for the sensor, but it didn’t jump out at me).

Hi Jason,
Wondering if you managed to get this working and are able to share interrupt code. Like you I am so close but can’t seem to get it to work.
Thanks very much.
Steve

Sorry for the delay - not sure how I missed this post.

I did get it to work, and it has been in service successfully for a few months now. I am not a professional coder, so it is sloppy - but it does work. The code is copied below.

Note that the orientation of the sensor does matter. If you have yours mounted differently than mine you may need to monitor/trigger off a different axis than I do.

The way I did it is basically an “open” and “closed” with a deadband in the middle. To add the deadband so it didn’t flip-flip while the door was opening/closing (as there is a lot of vibration and shaking) I had to do it with 2 different interrupts and movement check routines.

No matter how I tuned it, I kept getting false open/closed events if trying to do it with a single interrupt.

Arduino Code
#include <SparkFunLIS3DH.h> 
#include <Wire.h>

// ******************************************************************************************************
// Uncomment the define below to get logging. Re-comment before putting in production to save battery.
// ******************************************************************************************************
//#define LOGSTUFF

#define SMART_SENSOR_PERIOD   (43200000)
#define SMART_SENSOR_BAND     (RAK_REGION_US915)
#define SMART_SENSOR_APPEUI   {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}

// ******************************************************************************************************
// Enter your device info here, or input without hard coding it using bluetooth, et al. 
// Note you would need to change bluetooth STOP logic in setup if using bluetooth.
// ******************************************************************************************************
#define SMART_SENSOR_DEVEUI   {0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF}
#define SMART_SENSOR_APPKEY   {0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF}

/** 3 Axis Sensor **/
LIS3DH Sensor( I2C_MODE, 0x18 );

/** Packet buffer for sending */
uint8_t collected_data[64] = { 0 };

/** App Variables */
unsigned long lastMillis = 0;
unsigned long currentMillis;
unsigned long deltaTimeReportMax = 43200000; //12 Hours
uint8_t lastReport = 99;
  
void recvCallback(SERVICE_LORA_RECEIVE_T * data)
{
  if (data->BufferSize > 0) {
    
    #ifdef LOGSTUFF
      Serial.println("Something received!");
    #endif 
    
    for (int i = 0; i < data->BufferSize; i++) {
      #ifdef LOGSTUFF
        Serial.printf("%x", data->Buffer[i]);
      #endif
    }
    #ifdef LOGSTUFF
      Serial.print("\r\n");
    #endif
  }
}

void joinCallback(int32_t status)
{
  #ifdef LOGSTUFF
    Serial.printf("Join status: %d\r\n", status);
  #endif
}

void sendCallback(int32_t status)
{
  if (status == 0) {
    #ifdef LOGSTUFF
      Serial.println("Successfully sent");
    #endif
  } else {
    #ifdef LOGSTUFF
      Serial.println("Sending failed");
    #endif
  }
}

void setup()
{
  #ifdef LOGSTUFF
    Serial.begin(115200, RAK_AT_MODE);
    delay(10000);
    Serial.println("------------------------------------------------------");
    Serial.println("RAKwireless RAK1904 RUI3");
    Serial.println("------------------------------------------------------");
    delay(5000);
  #endif

  //Stop BLE
  api.ble.uart.stop();

  // OTAA Device EUI MSB first
  uint8_t node_device_eui[8] = SMART_SENSOR_DEVEUI;
  // OTAA Application EUI MSB first
  uint8_t node_app_eui[8] = SMART_SENSOR_APPEUI;
  // OTAA Application Key MSB first
  uint8_t node_app_key[16] = SMART_SENSOR_APPKEY;

  if (!api.lorawan.appeui.set(node_app_eui, 8)) {
    #ifdef LOGSTUFF
      Serial.printf("LoRaWan Smart Sensor - set application EUI is incorrect! \r\n");
    #endif
    return;
  }
  if (!api.lorawan.appkey.set(node_app_key, 16)) {
    #ifdef LOGSTUFF
      Serial.printf("LoRaWan Smart Sensor - set application key is incorrect! \r\n");
    #endif
    return;
  }
  if (!api.lorawan.deui.set(node_device_eui, 8)) {
    #ifdef LOGSTUFF
      Serial.printf("LoRaWan Smart Sensor - set device EUI is incorrect! \r\n");
    #endif
    return;
  }

  if (!api.lorawan.band.set(SMART_SENSOR_BAND)) {
    #ifdef LOGSTUFF
      Serial.printf("LoRaWan Smart Sensor - set band is incorrect! \r\n");
    #endif
    return;
  }
  if (!api.lorawan.deviceClass.set(RAK_LORA_CLASS_A)) {
    #ifdef LOGSTUFF
      Serial.printf("LoRaWan Smart Sensor - set device class is incorrect! \r\n");
    #endif
    return;
  }
  if (!api.lorawan.njm.set(RAK_LORA_OTAA))	// Set the network join mode to OTAA
  {
    #ifdef LOGSTUFF
      Serial.printf("LoRaWan Smart Sensor - set network join mode is incorrect! \r\n");
    #endif
    return;
  }
  if (!api.lorawan.join())	// Join to Gateway
  {
    #ifdef LOGSTUFF
      Serial.printf("LoRaWan Smart Sensor - join fail! \r\n");
    #endif
    return;
  }

  #ifdef LOGSTUFF
    Serial.println("++++++++++++++++++++++++++");
    Serial.println("RUI3 Tilt Sensing");
    Serial.println("++++++++++++++++++++++++++");
  #endif

  // Configure Tilt Sensor
  Sensor.begin();
  
  Sensor.settings.accelSampleRate = 1;   //Hz.  Can be: 0,1,10,25,*50*,100,200,400,1600,5000 Hz
  Sensor.settings.accelRange = 2;        //Max G force readable.  Can be: 2, 4, 8, 16
  Sensor.settings.adcEnabled = 0;
  Sensor.settings.tempEnabled = 0;
  Sensor.settings.xAccelEnabled = 0;
  Sensor.settings.yAccelEnabled = 0;
  Sensor.settings.zAccelEnabled = 1;

  //LIS3DH_INT1_CFG   
  uint8_t dataToWrite = 0;
  dataToWrite |= 0x20;//Z high
  Sensor.writeRegister(LIS3DH_INT1_CFG, dataToWrite);
  delay(100);

  //LIS3DH_INT1_THS   
  dataToWrite = 0;
  dataToWrite |= 0x20; // 0x20 = 512mg @ 2G range
  Sensor.writeRegister(LIS3DH_INT1_THS, dataToWrite);
  delay(100);
  
  //LIS3DH_INT1_DURATION  
  dataToWrite = 0;
  dataToWrite |= 0x08;
  Sensor.writeRegister(LIS3DH_INT1_DURATION, dataToWrite);
  delay(100);

  //LIS3DH_INT2_CFG   
  dataToWrite = 0;
  dataToWrite |= 0x10;//Z low
  Sensor.writeRegister(0x34, dataToWrite);
  delay(100);

  //LIS3DH_INT2_THS   
  dataToWrite = 0;
  dataToWrite |= 0x10; // 0x10 = 1/8 range
  Sensor.writeRegister(0x36, dataToWrite);
  delay(100);
  
  //LIS3DH_INT2_DURATION  
  dataToWrite = 0;
  dataToWrite |= 0x08;
  Sensor.writeRegister(0x37, dataToWrite);
  delay(100);
 
  //LIS3DH_CTRL_REG0
  // Turn off pullup resistor to save power
  dataToWrite = 0;
  dataToWrite |= 0x90;
  Sensor.writeRegister(0x1E, dataToWrite);
  delay(100);
  
  //LIS3DH_CTRL_REG1
  // Low Power Mode
  Sensor.readRegister(&dataToWrite, LIS3DH_CTRL_REG1);
  dataToWrite |= 0x08; // Set Low Power Mode
  Sensor.writeRegister(LIS3DH_CTRL_REG1, dataToWrite);
  delay(100);

  //LIS3DH_CTRL_REG3
  //Choose source for pin 1
  dataToWrite = 0;
  dataToWrite |= 0x40; //AOI1 event (Generator 1 interrupt on pin 1)
  Sensor.writeRegister(LIS3DH_CTRL_REG3, dataToWrite);
  delay(100);
  
  //LIS3DH_CTRL_REG5
  Sensor.readRegister(&dataToWrite, LIS3DH_CTRL_REG5);
  dataToWrite = 0;
  dataToWrite &= 0x78; //Clear bits of interest
  Sensor.writeRegister(LIS3DH_CTRL_REG5, dataToWrite);
  delay(100);

  //LIS3DH_CTRL_REG6
  dataToWrite = 0;
  dataToWrite |= 0x20; // I2_IA2 -- works
  Sensor.writeRegister(LIS3DH_CTRL_REG6, dataToWrite);
  delay(100);

  // I do it a second time to make sure it took. Might not be necessary.
  //LIS3DH_CTRL_REG0
  // Turn off pullup resistor to save power
  dataToWrite = 0;
  dataToWrite |= 0x90;
  Sensor.writeRegister(0x1E, dataToWrite);
  delay(100);

  // Mini Base Slot D IO = WB_IO5
  pinMode(WB_IO5, INPUT);
  delay(100);
  attachInterrupt(WB_IO5, movement1, CHANGE);
  delay(100);

  pinMode(WB_IO6, INPUT);
  delay(100);
  attachInterrupt(WB_IO6, movement2, CHANGE);
  delay(100);
  
  /** Wait for Join success */
  while (api.lorawan.njs.get() == 0) {
    #ifdef LOGSTUFF
      Serial.print("Wait for LoRaWAN join...");
    #endif
    api.lorawan.join();
    delay(10000);
  }

  if (!api.lorawan.adr.set(true)) {
    #ifdef LOGSTUFF
      Serial.printf("LoRaWan Smart Sensor - set adaptive data rate is incorrect! \r\n");
    #endif
    return;
  }
  if (!api.lorawan.rety.set(1)) {
    #ifdef LOGSTUFF
      Serial.printf("LoRaWan Smart Sensor - set retry times is incorrect! \r\n");
    #endif
    return;
  }
  if (!api.lorawan.cfm.set(0)) {
    #ifdef LOGSTUFF
      Serial.printf("LoRaWan Smart Sensor - set confirm mode is incorrect! \r\n");
    #endif
    return;
  }

  /** Check LoRaWan Status*/
  #ifdef LOGSTUFF
    Serial.printf("Duty cycle is %s\r\n", api.lorawan.dcs.get()? "ON" : "OFF");	// Check Duty Cycle status
    Serial.printf("Packet is %s\r\n", api.lorawan.cfm.get()? "CONFIRMED" : "UNCONFIRMED");	// Check Confirm status
  #endif
  uint8_t assigned_dev_addr[4] = { 0 };
  api.lorawan.daddr.get(assigned_dev_addr, 4);
  #ifdef LOGSTUFF
    Serial.printf("Device Address is %02X%02X%02X%02X\r\n", assigned_dev_addr[0], assigned_dev_addr[1], assigned_dev_addr[2], assigned_dev_addr[3]);	// Check Device Address
    Serial.printf("Uplink period is %ums\r\n", SMART_SENSOR_PERIOD);
    Serial.println("");
  #endif
  api.lorawan.registerRecvCallback(recvCallback);
  api.lorawan.registerJoinCallback(joinCallback);
  api.lorawan.registerSendCallback(sendCallback);
}


// -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
void movement1()
// -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
{
  uint8_t dataRead;
  currentMillis = millis();
  
  #ifdef LOGSTUFF
    Serial.println("\r\n");
    Serial.println("---------------------------------------------------");
    Serial.println("Movement 1 report");
    Serial.println("---------------------------------------------------");
    Serial.printf("Current millis: ");
    Serial.println(currentMillis);
    Serial.printf("Last millis: ");
    Serial.println(lastMillis);
    Serial.printf("Last Report: ");
    Serial.println(lastReport);
  #endif

  if (lastReport == 1) {
    #ifdef LOGSTUFF
      Serial.println("lastReport is one. Exiting.");
    #endif
    
    return; 
  }

  #ifdef LOGSTUFF
    Serial.println("lastReport is not one. Continuing.");
  #endif
  
  Sensor.readRegister(&dataRead, LIS3DH_INT1_SRC);
  #ifdef LOGSTUFF
    Serial.print("Data read: ");
    Serial.println(dataRead);
  #endif

  #ifdef LOGSTUFF
    Serial.print("dataRead & 0x20 :");
    Serial.println(dataRead & 0x20);
    Serial.print("dataRead & 0x10 :");
    Serial.println(dataRead & 0x10);    
  #endif
  
  if (dataRead & 0x20) {
     // Continue on with the loop
  } else{
    #ifdef LOGSTUFF
      Serial.println("Not right status. Exiting.");
    #endif
    
    return;
  }

  lastMillis = currentMillis;
  lastReport = 1;
  
  uint16_t batt = (uint16_t)(api.system.bat.get() * 1000);
  #ifdef LOGSTUFF
    Serial.printf("battery %.2f\r\n", (float)batt);
    Serial.printf("Battery Level: %f\r\n", api.system.bat.get());
  #endif

  /** Cayenne Low Power Payload */  
  uint8_t data_len = 0;
  collected_data[data_len++] = 0x01;	//Data Channel: 1		//1
  collected_data[data_len++] = 0x00;	//Type: Digital Input	//2
  collected_data[data_len++] = 1;								//3
  collected_data[data_len++] = 0x02;	//Data Channel: 2		//4
  collected_data[data_len++] = 0x02;	//Type: Analog Input	//5
  collected_data[data_len++] = (uint8_t)(batt >> 8);			//6
  collected_data[data_len++] = (uint8_t)batt;					//7

  /** Send the data package */
  if (api.lorawan.send(data_len, (uint8_t *) & collected_data, 2, false, 1)) {
    #ifdef LOGSTUFF
      Serial.println("Sending is requested");
    #endif
  } else {
    #ifdef LOGSTUFF
      Serial.println("Sending failed");
    #endif
  }  

  //api.system.sleep.all(deltaTimeReportMax);  
  
}

// -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
void movement2()
// -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
{
  uint8_t dataRead;
  currentMillis = millis();
  
  #ifdef LOGSTUFF
    Serial.println("\r\n");
    Serial.println("---------------------------------------------------");
    Serial.println("Movement 2 report");
    Serial.println("---------------------------------------------------");
    Serial.printf("Current millis: ");
    Serial.println(currentMillis);
    Serial.printf("Last millis: ");
    Serial.println(lastMillis);
    Serial.printf("Last Report: ");
    Serial.println(lastReport);
  #endif

  if (lastReport == 0) {
    #ifdef LOGSTUFF
      Serial.println("lastReport is zero. Exiting.");
    #endif
    
    return; 
  }

  #ifdef LOGSTUFF
    Serial.println("lastReport is not zero. Continuing.");
  #endif

  Sensor.readRegister(&dataRead, 0x35); // Read INT2_SRC
  #ifdef LOGSTUFF
    Serial.print("Data read: ");
    Serial.println(dataRead);
  #endif

  #ifdef LOGSTUFF
    Serial.print("dataRead & 0x20 :");
    Serial.println(dataRead & 0x20);
    Serial.print("dataRead & 0x10 :");
    Serial.println(dataRead & 0x10);    
  #endif
  
  if (dataRead & 0x10) {
     // Continue on with the loop
  } else {
    #ifdef LOGSTUFF
      Serial.println("Not right status. Exiting.");
    #endif
    
    return;
  }
    
  lastMillis = currentMillis;
  lastReport = 0;
  
  uint16_t batt = (uint16_t)(api.system.bat.get() * 1000);
  #ifdef LOGSTUFF
    Serial.printf("battery %.2f\r\n", (float)batt);
    Serial.printf("Battery Level: %f\r\n", api.system.bat.get());
  #endif

  /** Cayenne Low Power Payload */  
  uint8_t data_len = 0;
  collected_data[data_len++] = 0x01;  //Data Channel: 1              //1
  collected_data[data_len++] = 0x00;  //Type: Digital Input          //2
  collected_data[data_len++] = 0;                                    //3
  collected_data[data_len++] = 0x02;  //Data Channel: 2              //4
  collected_data[data_len++] = 0x02;  //Type: Analog Input           //5
  collected_data[data_len++] = (uint8_t)(batt >> 8);                 //6
  collected_data[data_len++] = (uint8_t)batt;                        //7

  /** Send the data package */
  if (api.lorawan.send(data_len, (uint8_t *) & collected_data, 2, false, 1)) {
    #ifdef LOGSTUFF
      Serial.println("Sending is requested");
    #endif
  } else {
    #ifdef LOGSTUFF
      Serial.println("Sending failed");
    #endif
  }  

  //api.system.sleep.all(deltaTimeReportMax);  
  
}

// -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
void loopTimerReport()
// -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
{
  uint8_t dataRead;

  currentMillis = millis();
  uint8_t tiltStatus = 99;
  
  #ifdef LOGSTUFF
    Serial.println("\r\n");
    Serial.println("---------------------------------------------------");
    Serial.println("loopTimerReport report");
    Serial.println("---------------------------------------------------");
    Serial.printf("Current millis: ");
    Serial.println(currentMillis);
    Serial.printf("Last millis: ");
    Serial.println(lastMillis);
    Serial.printf("Last Report: ");
    Serial.println(lastReport);
  #endif

  Sensor.readRegister(&dataRead, LIS3DH_INT1_SRC);
  #ifdef LOGSTUFF
    Serial.print("Data read: ");
    Serial.println(dataRead);
  #endif

  #ifdef LOGSTUFF
    Serial.print("dataRead & 0x20 :");
    Serial.println(dataRead & 0x20);
    Serial.print("dataRead & 0x10 :");
    Serial.println(dataRead & 0x10);    
  #endif
  
  if(dataRead & 0x20) tiltStatus = 0x01;    // Serial.println("Z high");
  if(dataRead & 0x10) tiltStatus = 0x00;    // Serial.println("Z low");

  #ifdef LOGSTUFF
    Serial.print("New Tilt Status: ");
    Serial.println(tiltStatus);
  #endif

  lastMillis = currentMillis;
  lastReport = tiltStatus;
  
  uint16_t batt = (uint16_t)(api.system.bat.get() * 1000);
  #ifdef LOGSTUFF
    Serial.printf("battery %.2f\r\n", (float)batt);
    Serial.printf("Battery Level: %f\r\n", api.system.bat.get());
  #endif

  /** Cayenne Low Power Payload */  
  uint8_t data_len = 0;
  collected_data[data_len++] = 0x01;  //Data Channel: 1              //1
  collected_data[data_len++] = 0x00;  //Type: Digital Input          //2
  collected_data[data_len++] = tiltStatus;                           //3
  collected_data[data_len++] = 0x02;  //Data Channel: 2              //4
  collected_data[data_len++] = 0x02;  //Type: Analog Input           //5
  collected_data[data_len++] = (uint8_t)(batt >> 8);                 //6
  collected_data[data_len++] = (uint8_t)batt;                        //7

  /** Send the data package */
  if (api.lorawan.send(data_len, (uint8_t *) & collected_data, 2, false, 1)) {
    #ifdef LOGSTUFF
      Serial.println("Sending is requested");
    #endif
  } else {
    #ifdef LOGSTUFF
      Serial.println("Sending failed");
    #endif
  }  
}

void loop()
{
  // Perform sensor read
  loopTimerReport();
  
  #ifdef LOGSTUFF    
    Serial.println("Going to long sleep.");
  #endif
  
  api.system.sleep.all(deltaTimeReportMax);  

  #ifdef LOGSTUFF
    Serial.println("Timer Wakeup.\r\n");
  #endif
}
1 Like