Skip to content

Instantly share code, notes, and snippets.

@jstockdale
Last active November 8, 2025 17:50
Show Gist options
  • Select an option

  • Save jstockdale/b84ea838266bfe3279a60e7150566673 to your computer and use it in GitHub Desktop.

Select an option

Save jstockdale/b84ea838266bfe3279a60e7150566673 to your computer and use it in GitHub Desktop.

Revisions

  1. jstockdale revised this gist Jul 16, 2024. 1 changed file with 25 additions and 6 deletions.
    31 changes: 25 additions & 6 deletions xiao-esp32-s3-t-beam-watchdog.ino
    Original file line number Diff line number Diff line change
    @@ -18,8 +18,10 @@
    */

    #define LED_PIN LED_BUILTIN // built in LED solid briefly on wakeup/boot, flashes dim while sensing, flashes bright if it detects t-beam is off or unresponsive
    #define SENSE_PIN A9 // one method we use to determine state is via an analog voltage threshold on GPIO0
    #define VOLTAGE_DIVIDER_5V_PIN A1 // we read an analog voltage divider 10k between 5V and A1, 10k between GND and A1 to determine if VBUS has power
    #define MOSFET_GATE_PIN D2 // should normally be low but we pull high to ground out the solar usb connection to simulate reconnecting
    #define POWER_PIN D8 // connect floating leg of t-beam power button to D8; used to power on the t-beam normally
    #define SENSE_PIN A9 // one method we use to determine state is via an analog voltage threshold on GPIO0
    #define RESET_PIN D10 // connect RST on t-beam to D10; used to hard boot if the t-beam has power but no uart activity

    #define UART_RX_PIN D7 // connect TX on t-beam to D7; we read the tx line to see if the t-beam is alive or "stuck"
    @@ -32,6 +34,7 @@
    #define WATCH_LOOPS 150

    int sensorValue = 0; // variable to store the analog value from SENSE_PIN
    int voltageValue = 0; // variable to store the analog value from VOLTAGE_DIVIDER_5V_PIN

    bool detectBool = false;
    int detectCount = 0;
    @@ -84,8 +87,13 @@ void setup() {
    // we will set them to OUTPUT only when powering or resetting the device
    pinMode(POWER_PIN, INPUT);
    pinMode(RESET_PIN, INPUT);
    // GPIO0 (SENSE_PIN) sense pin is always INPUT
    // GPIO0 (SENSE_PIN) and VOLTAGE_DIVIDER_5V_PIN sense pins are always INPUT
    pinMode(SENSE_PIN, INPUT);
    pinMode(VOLTAGE_DIVIDER_5V_PIN, INPUT);
    // MOSFET_GATE_PIN is always OUTPUT
    pinMode(MOSFET_GATE_PIN, OUTPUT);
    // set MOSFET_GATE_PIN to HIGH to ground out solar panel
    digitalWrite(MOSFET_GATE_PIN, HIGH);
    // built-in led is always OUTPUT
    pinMode(LED_PIN, OUTPUT);

    @@ -94,7 +102,10 @@ void setup() {
    // setup UART connected to t-beam
    Serial1.begin(UART_BAUD, SERIAL_8N1, UART_RX_PIN, UART_TX_PIN);

    delay(250); // wait a bit to make sure serial is initialized
    delay(1000); // wait a bit to make sure serial is initialized

    // Say hello
    Serial.println("Hello world! Watchdog initialized.");

    // start listening for t-beam on Serial1
    Serial1.onReceive(onReceiveFunction, false);
    @@ -113,6 +124,9 @@ void setup() {
    */
    esp_sleep_enable_timer_wakeup(TIME_TO_SLEEP * uS_TO_S_FACTOR);
    Serial.println("Setup ESP32 to sleep for every " + String(TIME_TO_SLEEP) + " Seconds");

    Serial.println("Reconnecting solar panel; setting MOSFET_GATE_PIN to LOW.");
    digitalWrite(MOSFET_GATE_PIN, LOW);
    }

    // loop until we think we should go to sleep
    @@ -121,16 +135,21 @@ void loop() {
    digitalWrite(LED_PIN, LOW);
    // read GPIO0 / SENSE_PIN
    sensorValue = analogRead(SENSE_PIN);
    Serial.println("State: " + String(sensorValue) + ", " + String(uart_received_bytes));
    voltageValue = analogRead(VOLTAGE_DIVIDER_5V_PIN);
    Serial.println("State: GPIO0 analog " + String(sensorValue) + ", UART RX " + String(uart_received_bytes) + " bytes, 5V divider analog " + String(voltageValue));
    digitalWrite(LED_PIN, HIGH);
    if(sensorValue > 2000 && uart_received_bytes > 0) {
    // if GPIO0 is above 2000 when reading the ADC and the UART has received data
    if(sensorValue > 2000 && uart_received_bytes > 0 || voltageValue < 2000) {
    // if sensorValue is above 2000 when reading the ADC and the UART has received data
    // we think the t-beam is turned on and most likely functioning properly
    // turn off the led, count the loop, and zero the detect count
    // if voltageValue is below 2000 we don't have (enough) power, so ... we can't
    // do anything until VBUS power comes back on. either from a usb charger or
    // the solar panel getting sufficient light.
    digitalWrite(LED_PIN, HIGH);
    detectCount = 0;
    ++okCount;
    } else {
    // if voltageValue is 2000 or more, we likely have solar or usb power
    // if sensorValue is 2000 or less, the device is probably off
    // if sensorvalue is 4095 and uart_received_bytes == 0, the device is probably "stuck"
    digitalWrite(LED_PIN, LOW);
  2. jstockdale revised this gist Jun 25, 2024. 1 changed file with 1 addition and 1 deletion.
    2 changes: 1 addition & 1 deletion xiao-esp32-s3-t-beam-watchdog.ino
    Original file line number Diff line number Diff line change
    @@ -25,7 +25,7 @@
    #define UART_RX_PIN D7 // connect TX on t-beam to D7; we read the tx line to see if the t-beam is alive or "stuck"
    #define UART_TX_PIN D6 // this can be a no connect; not currently used

    #define UART_BAUD 38400 // Any baudrate from 300 to 115200
    #define UART_BAUD 115200 // Any baudrate from 300 to 115200

    #define MIN_LOOPS 150
    #define MAX_LOOPS 600
  3. jstockdale revised this gist Jun 24, 2024. 1 changed file with 6 additions and 5 deletions.
    11 changes: 6 additions & 5 deletions xiao-esp32-s3-t-beam-watchdog.ino
    Original file line number Diff line number Diff line change
    @@ -27,9 +27,9 @@

    #define UART_BAUD 38400 // Any baudrate from 300 to 115200

    #define MIN_LOOPS 100
    #define MIN_LOOPS 150
    #define MAX_LOOPS 600
    #define WATCH_LOOPS 100
    #define WATCH_LOOPS 150

    int sensorValue = 0; // variable to store the analog value from SENSE_PIN

    @@ -71,11 +71,12 @@ void onReceiveFunction(void) {
    // This is a callback function that will be activated on UART RX events
    size_t available = Serial1.available();
    uart_received_bytes = uart_received_bytes + available;
    Serial.printf("onReceive Callback:: There are %d bytes available: ", available);
    //Serial.printf("onReceive Callback:: There are %d bytes available: ", available);
    while (available--) {
    Serial.print((char)Serial1.read());
    //Serial.print((char)Serial1.read());
    Serial1.read();
    }
    Serial.println();
    //Serial.println();
    }

    void setup() {
  4. jstockdale revised this gist Jun 23, 2024. 1 changed file with 10 additions and 15 deletions.
    25 changes: 10 additions & 15 deletions xiao-esp32-s3-t-beam-watchdog.ino
    Original file line number Diff line number Diff line change
    @@ -87,12 +87,17 @@ void setup() {
    pinMode(SENSE_PIN, INPUT);
    // built-in led is always OUTPUT
    pinMode(LED_PIN, OUTPUT);

    Serial.begin(115200); // setup serial

    // setup main serial for program output
    Serial.begin(115200);
    // setup UART connected to t-beam
    Serial1.begin(UART_BAUD, SERIAL_8N1, UART_RX_PIN, UART_TX_PIN);

    delay(250); // wait a bit to let serial startup
    delay(250); // wait a bit to make sure serial is initialized

    // start listening for t-beam on Serial1
    Serial1.onReceive(onReceiveFunction, false);
    Serial.println("Listening for t-beam serial tx on: " + UART_RX_PIN);

    //Increment wake number and print it every wake cycle
    ++bootCount;
    @@ -113,18 +118,7 @@ void setup() {
    void loop() {
    // turn on the built-in LED
    digitalWrite(LED_PIN, LOW);
    // bitbang read the uartTxPin for uartTxReadCycles to hopefully see a HIGH (1)
    // for(int i = 0; i < uartTxReadCycles; i++) {
    // uartTxValue = digitalRead(uartTxPin);
    // if(uartTxValue == HIGH) {
    // // we only care that the UART went high while we were looking
    // // UART is high between transmissions if the device is on and responding
    // // UART floats around 0V and reads LOW when the t-beam is powered but "stuck" (requires hard boot)
    // // since we think the device is okay, we won't reset after pressing power
    // resetAfterPower = false;
    // break;
    // }
    // }
    // read GPIO0 / SENSE_PIN
    sensorValue = analogRead(SENSE_PIN);
    Serial.println("State: " + String(sensorValue) + ", " + String(uart_received_bytes));
    digitalWrite(LED_PIN, HIGH);
    @@ -199,6 +193,7 @@ void loop() {
    if(okCount > WATCH_LOOPS || (detectBool == false && loopCount > MIN_LOOPS) || loopCount > targetLoops) {
    Serial.println("Going to sleep now");
    Serial.flush();
    Serial1.onReceive(NULL);
    esp_deep_sleep_start();
    }

  5. jstockdale revised this gist Jun 23, 2024. 1 changed file with 1 addition and 1 deletion.
    2 changes: 1 addition & 1 deletion xiao-esp32-s3-t-beam-watchdog.ino
    Original file line number Diff line number Diff line change
    @@ -2,7 +2,7 @@
    Basic deep sleep sketch based on examples provided with xiao-esp32-s3
    *** PRE-ALPHA ***
    version rc-0; updated to use Serial1 UNTESTED
    2024-06-23 - version rc-0; updated to use Serial1
    Copyright 2024 @jstockdale/Off by One, Inc.
  6. jstockdale revised this gist Jun 23, 2024. 1 changed file with 1 addition and 1 deletion.
    2 changes: 1 addition & 1 deletion xiao-esp32-s3-t-beam-watchdog.ino
    Original file line number Diff line number Diff line change
    @@ -145,7 +145,7 @@ void loop() {
    ++detectCount;
    detectBool = true;
    Serial.println(detectCount);
    // we add one to targetLoops (up to a max of maxLoops) if the device is off
    // we add one to targetLoops (up to a max of MAX_LOOPS) if the device is off
    // this behaves well since the UART tx detection isn't perfect but is a weird hack
    // TODO(jstockdale): fix the way the various loop limits interact
    if(targetLoops < MAX_LOOPS) {
  7. jstockdale revised this gist Jun 23, 2024. 1 changed file with 2 additions and 2 deletions.
    4 changes: 2 additions & 2 deletions xiao-esp32-s3-t-beam-watchdog.ino
    Original file line number Diff line number Diff line change
    @@ -22,8 +22,8 @@
    #define POWER_PIN D8 // connect floating leg of t-beam power button to D8; used to power on the t-beam normally
    #define RESET_PIN D10 // connect RST on t-beam to D10; used to hard boot if the t-beam has power but no uart activity

    #define UART_TX_PIN D7 // connect TX on t-beam to D7; we read the tx line to see if the t-beam is alive or "stuck"
    #define UART_RX_PIN D6 // this can be a no connect; not currently used
    #define UART_RX_PIN D7 // connect TX on t-beam to D7; we read the tx line to see if the t-beam is alive or "stuck"
    #define UART_TX_PIN D6 // this can be a no connect; not currently used

    #define UART_BAUD 38400 // Any baudrate from 300 to 115200

  8. jstockdale revised this gist Jun 23, 2024. 1 changed file with 1 addition and 1 deletion.
    2 changes: 1 addition & 1 deletion xiao-esp32-s3-t-beam-watchdog.ino
    Original file line number Diff line number Diff line change
    @@ -25,7 +25,7 @@
    #define UART_TX_PIN D7 // connect TX on t-beam to D7; we read the tx line to see if the t-beam is alive or "stuck"
    #define UART_RX_PIN D6 // this can be a no connect; not currently used

    #define UART_BAUD 38400 // Any baudrate from 300 to 115200
    #define UART_BAUD 38400 // Any baudrate from 300 to 115200

    #define MIN_LOOPS 100
    #define MAX_LOOPS 600
  9. jstockdale revised this gist Jun 23, 2024. 1 changed file with 96 additions and 68 deletions.
    164 changes: 96 additions & 68 deletions xiao-esp32-s3-t-beam-watchdog.ino
    Original file line number Diff line number Diff line change
    @@ -2,6 +2,7 @@
    Basic deep sleep sketch based on examples provided with xiao-esp32-s3
    *** PRE-ALPHA ***
    version rc-0; updated to use Serial1 UNTESTED
    Copyright 2024 @jstockdale/Off by One, Inc.
    @@ -10,30 +11,34 @@
    1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer.
    2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution.
    THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS “AS IS” AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
    by @jstockdale
    */

    int sensorPin = A9; // one method we use to determine state is via an analog voltage threshold on GPIO0
    int powerPin = D8; // connect floating leg of t-beam power button to D8; used to power on the t-beam normally
    int resetPin = D10; // connect RST on t-beam to D10; used to hard boot if the t-beam has power but no uart activity
    int uartTxPin = D7; // connect TX on t-beam to D7; we bitbang read the tx line to see if the t-beam is alive
    int ledPin = LED_BUILTIN; // built in LED solid briefly on wakeup/boot, flashes dim while sensing, flashes bright if it detects t-beam is off or unresponsive
    #define LED_PIN LED_BUILTIN // built in LED solid briefly on wakeup/boot, flashes dim while sensing, flashes bright if it detects t-beam is off or unresponsive
    #define SENSE_PIN A9 // one method we use to determine state is via an analog voltage threshold on GPIO0
    #define POWER_PIN D8 // connect floating leg of t-beam power button to D8; used to power on the t-beam normally
    #define RESET_PIN D10 // connect RST on t-beam to D10; used to hard boot if the t-beam has power but no uart activity

    #define UART_TX_PIN D7 // connect TX on t-beam to D7; we read the tx line to see if the t-beam is alive or "stuck"
    #define UART_RX_PIN D6 // this can be a no connect; not currently used

    #define UART_BAUD 38400 // Any baudrate from 300 to 115200

    #define MIN_LOOPS 100
    #define MAX_LOOPS 600
    #define WATCH_LOOPS 100

    int sensorValue = 0; // variable to store the analog value from SENSE_PIN

    int sensorValue = 0; // variable to store the analog value from sensorPin
    int uartTxValue = 0; // variable to store the state of the tx uart pin (digital read)
    int uartTxReadCycles = 10; // how many times should we bitbang read the UART tx pin to decide if the device is on (it's ok if this is very occasionally wrong because of the data on the tx pin)
    bool resetAfterPower = true; // if we don't see any activity on the UART, after pressing power we will reset/hard boot the device by pulling RST to ground using resetPin
    bool detectBool = false;
    int detectCount = 0;
    int okCount = 0;
    int loopCount = 0; // used to track our total number of elapsed loops during boot and each wake cycle
    int minLoops = 100; // minimum number of sense-read loops that will be performed (approx .1s per loop) if device is on
    int maxLoops = 600; // maximum number of sense-read loops to perform before sleeping (approx .1s per loop; hard limit)
    int targetLoops = minLoops; // current target number of loops; we add watchLoops to the current loop count each time we reset the device to extend the runtime of the watchdog
    int watchLoops = 100; // number of sense-read loops to perform before powering or rebooting device

    volatile int loopCount = 0; // used to track our total number of elapsed loops during boot and each wake cycle
    volatile int targetLoops = MIN_LOOPS; // current target number of loops; we add WATCH_LOOPS to the current loop count each time we reset the device to extend the runtime of the watchdog

    #define uS_TO_S_FACTOR 1000000ULL /* Conversion factor for micro seconds to seconds */
    #define TIME_TO_SLEEP 600 /* Time ESP32 will go to sleep (in seconds) */
    @@ -59,18 +64,34 @@ void print_wakeup_reason() {
    }
    }

    // volatile declaration will avoid any compiler optimization when reading variable values
    volatile size_t uart_received_bytes = 0;

    void onReceiveFunction(void) {
    // This is a callback function that will be activated on UART RX events
    size_t available = Serial1.available();
    uart_received_bytes = uart_received_bytes + available;
    Serial.printf("onReceive Callback:: There are %d bytes available: ", available);
    while (available--) {
    Serial.print((char)Serial1.read());
    }
    Serial.println();
    }

    void setup() {
    // set up powerPin and resetPin as INPUT so that they are high impedence
    // set up POWER_PIN and RESET_PIN as INPUT so that they are high impedence
    // we will set them to OUTPUT only when powering or resetting the device
    pinMode(powerPin, INPUT);
    pinMode(resetPin, INPUT);
    // UART and GPIO0 (sensorPin) sense pin are always INPUT
    pinMode(uartTxPin, INPUT);
    pinMode(sensorPin, INPUT);
    pinMode(POWER_PIN, INPUT);
    pinMode(RESET_PIN, INPUT);
    // GPIO0 (SENSE_PIN) sense pin is always INPUT
    pinMode(SENSE_PIN, INPUT);
    // built-in led is always OUTPUT
    pinMode(ledPin, OUTPUT);
    pinMode(LED_PIN, OUTPUT);

    Serial.begin(115200); // setup serial

    Serial1.begin(UART_BAUD, SERIAL_8N1, UART_RX_PIN, UART_TX_PIN);

    delay(250); // wait a bit to let serial startup

    //Increment wake number and print it every wake cycle
    @@ -91,89 +112,96 @@ void setup() {
    // loop until we think we should go to sleep
    void loop() {
    // turn on the built-in LED
    digitalWrite(ledPin, LOW);
    digitalWrite(LED_PIN, LOW);
    // bitbang read the uartTxPin for uartTxReadCycles to hopefully see a HIGH (1)
    for(int i = 0; i < uartTxReadCycles; i++) {
    uartTxValue = digitalRead(uartTxPin);
    if(uartTxValue == HIGH) {
    // we only care that the UART went high while we were looking
    // UART is high between transmissions if the device is on and responding
    // UART floats around 0V and reads LOW when the t-beam is powered but "stuck" (requires hard boot)
    // since we think the device is okay, we won't reset after pressing power
    resetAfterPower = false;
    break;
    }
    }
    sensorValue = analogRead(sensorPin);
    Serial.println("State: " + String(sensorValue) + ", " + String(uartTxValue));
    digitalWrite(ledPin, HIGH);
    if(sensorValue > 2000 && uartTxValue == 1) {
    // if GPIO0 is above 2000 when reading the ADC and the UART recently went HIGH
    // for(int i = 0; i < uartTxReadCycles; i++) {
    // uartTxValue = digitalRead(uartTxPin);
    // if(uartTxValue == HIGH) {
    // // we only care that the UART went high while we were looking
    // // UART is high between transmissions if the device is on and responding
    // // UART floats around 0V and reads LOW when the t-beam is powered but "stuck" (requires hard boot)
    // // since we think the device is okay, we won't reset after pressing power
    // resetAfterPower = false;
    // break;
    // }
    // }
    sensorValue = analogRead(SENSE_PIN);
    Serial.println("State: " + String(sensorValue) + ", " + String(uart_received_bytes));
    digitalWrite(LED_PIN, HIGH);
    if(sensorValue > 2000 && uart_received_bytes > 0) {
    // if GPIO0 is above 2000 when reading the ADC and the UART has received data
    // we think the t-beam is turned on and most likely functioning properly
    // turn off the led, count the loop, and zero the detect count
    digitalWrite(ledPin, HIGH);
    digitalWrite(LED_PIN, HIGH);
    detectCount = 0;
    ++okCount;
    } else {
    // if sensorValue is 2000 or less, the device is probably off
    // if sensorvalue is 4095 and uartTxValue is 0, the device is probably "stuck"
    digitalWrite(ledPin, LOW);
    // if sensorvalue is 4095 and uart_received_bytes == 0, the device is probably "stuck"
    digitalWrite(LED_PIN, LOW);
    delay(5);
    digitalWrite(ledPin, HIGH);
    digitalWrite(LED_PIN, HIGH);
    okCount = 0;
    ++detectCount;
    detectBool = true;
    Serial.println(detectCount);
    // we add one to targetLoops (up to a max of maxLoops) if the device is off
    // this behaves well since the UART tx detection isn't perfect but is a weird hack
    // TODO(jstockdale): fix the way the various loop limits interact
    if(targetLoops < maxLoops) {
    if(targetLoops < MAX_LOOPS) {
    ++targetLoops;
    Serial.println("++targetLoops");
    }
    }
    if(detectCount >= watchLoops) {
    // we have observed the t-beam to be off for watchLoops consecutive sense-loops
    // we will power on the device by pulling the powerPin to ground for 500ms

    if(detectCount >= WATCH_LOOPS) {
    // we have observed the t-beam to be off for WATCH_LOOPS consecutive sense-loops
    // we will power on the device by pulling the POWER_PIN to ground for 500ms
    Serial.println("Powering ESP32-S3 ON");
    digitalWrite(powerPin, HIGH);
    pinMode(powerPin, OUTPUT);
    digitalWrite(powerPin, LOW);
    digitalWrite(ledPin, LOW);
    digitalWrite(POWER_PIN, HIGH);
    pinMode(POWER_PIN, OUTPUT);
    digitalWrite(POWER_PIN, LOW);
    digitalWrite(LED_PIN, LOW);
    delay(500);
    digitalWrite(powerPin, HIGH);
    digitalWrite(ledPin, HIGH);
    pinMode(powerPin, INPUT);
    digitalWrite(POWER_PIN, HIGH);
    digitalWrite(LED_PIN, HIGH);
    pinMode(POWER_PIN, INPUT);
    delay(100);
    if(resetAfterPower) {
    // since we didn't see the tx pin go high on the t-beam, we think it is "stuck"
    // we're also hard booting / resetting the device by pulling resetPin to ground for 500ms

    if(uart_received_bytes == 0) {
    // since we didn't see any serial data tx from the t-beam, we think it is "stuck"
    // hard boot / reset the device by pulling RESET_PIN to ground for 500ms
    Serial.println("Resetting ESP32-S3 by pulling RST to GND");
    digitalWrite(resetPin, HIGH);
    pinMode(resetPin, OUTPUT);
    digitalWrite(resetPin, LOW);
    digitalWrite(RESET_PIN, HIGH);
    pinMode(RESET_PIN, OUTPUT);
    digitalWrite(RESET_PIN, LOW);
    delay(500);
    digitalWrite(resetPin, HIGH);
    pinMode(resetPin, INPUT);
    digitalWrite(RESET_PIN, HIGH);
    pinMode(RESET_PIN, INPUT);
    delay(100);
    }

    // zero counters
    okCount = 0;
    detectCount = 0;
    if (targetLoops < maxLoops) {
    // increase targetLoops by WATCH_LOOPS so we can see if we're successful or try until MAX_LOOPS
    if (targetLoops < MAX_LOOPS) {
    Serial.println("Current loop count: " + String(loopCount));
    Serial.println("Updating target loops from: " + String(targetLoops));
    targetLoops = (loopCount + 1 + watchLoops > maxLoops) ? maxLoops : loopCount + 1 + watchLoops;
    targetLoops = (loopCount + 1 + WATCH_LOOPS > MAX_LOOPS) ? MAX_LOOPS : loopCount + 1 + WATCH_LOOPS;
    Serial.println("Updated target loops to: " + String(targetLoops));
    }
    }
    // deep sleep to save power if we've seen watchLoops consecutive ok loops
    // or performed minLoops sense-loops where the t-beam is always-on
    if(okCount > watchLoops || (detectBool == false && loopCount > minLoops) || loopCount > targetLoops) {

    // deep sleep to save power if we've seen WATCH_LOOPS consecutive ok loops
    // or performed MIN_LOOPS sense-loops where the t-beam is always-on
    // TODO(jstockdale): improve this logic
    if(okCount > WATCH_LOOPS || (detectBool == false && loopCount > MIN_LOOPS) || loopCount > targetLoops) {
    Serial.println("Going to sleep now");
    Serial.flush();
    esp_deep_sleep_start();
    }

    ++loopCount;
    delay(100);
    }
    }
  10. jstockdale created this gist Jun 23, 2024.
    179 changes: 179 additions & 0 deletions xiao-esp32-s3-t-beam-watchdog.ino
    Original file line number Diff line number Diff line change
    @@ -0,0 +1,179 @@
    /*
    Basic deep sleep sketch based on examples provided with xiao-esp32-s3
    *** PRE-ALPHA ***
    Copyright 2024 @jstockdale/Off by One, Inc.
    Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met:
    1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer.
    2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution.
    THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS “AS IS” AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
    by @jstockdale
    */

    int sensorPin = A9; // one method we use to determine state is via an analog voltage threshold on GPIO0
    int powerPin = D8; // connect floating leg of t-beam power button to D8; used to power on the t-beam normally
    int resetPin = D10; // connect RST on t-beam to D10; used to hard boot if the t-beam has power but no uart activity
    int uartTxPin = D7; // connect TX on t-beam to D7; we bitbang read the tx line to see if the t-beam is alive
    int ledPin = LED_BUILTIN; // built in LED solid briefly on wakeup/boot, flashes dim while sensing, flashes bright if it detects t-beam is off or unresponsive

    int sensorValue = 0; // variable to store the analog value from sensorPin
    int uartTxValue = 0; // variable to store the state of the tx uart pin (digital read)
    int uartTxReadCycles = 10; // how many times should we bitbang read the UART tx pin to decide if the device is on (it's ok if this is very occasionally wrong because of the data on the tx pin)
    bool resetAfterPower = true; // if we don't see any activity on the UART, after pressing power we will reset/hard boot the device by pulling RST to ground using resetPin
    bool detectBool = false;
    int detectCount = 0;
    int okCount = 0;
    int loopCount = 0; // used to track our total number of elapsed loops during boot and each wake cycle
    int minLoops = 100; // minimum number of sense-read loops that will be performed (approx .1s per loop) if device is on
    int maxLoops = 600; // maximum number of sense-read loops to perform before sleeping (approx .1s per loop; hard limit)
    int targetLoops = minLoops; // current target number of loops; we add watchLoops to the current loop count each time we reset the device to extend the runtime of the watchdog
    int watchLoops = 100; // number of sense-read loops to perform before powering or rebooting device

    #define uS_TO_S_FACTOR 1000000ULL /* Conversion factor for micro seconds to seconds */
    #define TIME_TO_SLEEP 600 /* Time ESP32 will go to sleep (in seconds) */

    RTC_DATA_ATTR int bootCount = 0;

    /*
    Method to print the reason by which ESP32
    has been awaken from sleep
    */
    void print_wakeup_reason() {
    esp_sleep_wakeup_cause_t wakeup_reason;

    wakeup_reason = esp_sleep_get_wakeup_cause();

    switch (wakeup_reason) {
    case ESP_SLEEP_WAKEUP_EXT0: Serial.println("Wakeup caused by external signal using RTC_IO"); break;
    case ESP_SLEEP_WAKEUP_EXT1: Serial.println("Wakeup caused by external signal using RTC_CNTL"); break;
    case ESP_SLEEP_WAKEUP_TIMER: Serial.println("Wakeup caused by timer"); break;
    case ESP_SLEEP_WAKEUP_TOUCHPAD: Serial.println("Wakeup caused by touchpad"); break;
    case ESP_SLEEP_WAKEUP_ULP: Serial.println("Wakeup caused by ULP program"); break;
    default: Serial.printf("Wakeup was not caused by deep sleep: %d\n", wakeup_reason); break;
    }
    }

    void setup() {
    // set up powerPin and resetPin as INPUT so that they are high impedence
    // we will set them to OUTPUT only when powering or resetting the device
    pinMode(powerPin, INPUT);
    pinMode(resetPin, INPUT);
    // UART and GPIO0 (sensorPin) sense pin are always INPUT
    pinMode(uartTxPin, INPUT);
    pinMode(sensorPin, INPUT);
    // built-in led is always OUTPUT
    pinMode(ledPin, OUTPUT);

    Serial.begin(115200); // setup serial
    delay(250); // wait a bit to let serial startup

    //Increment wake number and print it every wake cycle
    ++bootCount;
    Serial.println("Sleep cycle count: " + String(bootCount));

    //Print the wakeup reason for ESP32
    print_wakeup_reason();

    /*
    here we configure the wake up source
    we set our ESP32 to wake up every TIME_TO_SLEEP seconds
    */
    esp_sleep_enable_timer_wakeup(TIME_TO_SLEEP * uS_TO_S_FACTOR);
    Serial.println("Setup ESP32 to sleep for every " + String(TIME_TO_SLEEP) + " Seconds");
    }

    // loop until we think we should go to sleep
    void loop() {
    // turn on the built-in LED
    digitalWrite(ledPin, LOW);
    // bitbang read the uartTxPin for uartTxReadCycles to hopefully see a HIGH (1)
    for(int i = 0; i < uartTxReadCycles; i++) {
    uartTxValue = digitalRead(uartTxPin);
    if(uartTxValue == HIGH) {
    // we only care that the UART went high while we were looking
    // UART is high between transmissions if the device is on and responding
    // UART floats around 0V and reads LOW when the t-beam is powered but "stuck" (requires hard boot)
    // since we think the device is okay, we won't reset after pressing power
    resetAfterPower = false;
    break;
    }
    }
    sensorValue = analogRead(sensorPin);
    Serial.println("State: " + String(sensorValue) + ", " + String(uartTxValue));
    digitalWrite(ledPin, HIGH);
    if(sensorValue > 2000 && uartTxValue == 1) {
    // if GPIO0 is above 2000 when reading the ADC and the UART recently went HIGH
    // we think the t-beam is turned on and most likely functioning properly
    // turn off the led, count the loop, and zero the detect count
    digitalWrite(ledPin, HIGH);
    detectCount = 0;
    ++okCount;
    } else {
    // if sensorValue is 2000 or less, the device is probably off
    // if sensorvalue is 4095 and uartTxValue is 0, the device is probably "stuck"
    digitalWrite(ledPin, LOW);
    delay(5);
    digitalWrite(ledPin, HIGH);
    okCount = 0;
    ++detectCount;
    detectBool = true;
    Serial.println(detectCount);
    // we add one to targetLoops (up to a max of maxLoops) if the device is off
    // this behaves well since the UART tx detection isn't perfect but is a weird hack
    // TODO(jstockdale): fix the way the various loop limits interact
    if(targetLoops < maxLoops) {
    ++targetLoops;
    Serial.println("++targetLoops");
    }
    }
    if(detectCount >= watchLoops) {
    // we have observed the t-beam to be off for watchLoops consecutive sense-loops
    // we will power on the device by pulling the powerPin to ground for 500ms
    Serial.println("Powering ESP32-S3 ON");
    digitalWrite(powerPin, HIGH);
    pinMode(powerPin, OUTPUT);
    digitalWrite(powerPin, LOW);
    digitalWrite(ledPin, LOW);
    delay(500);
    digitalWrite(powerPin, HIGH);
    digitalWrite(ledPin, HIGH);
    pinMode(powerPin, INPUT);
    delay(100);
    if(resetAfterPower) {
    // since we didn't see the tx pin go high on the t-beam, we think it is "stuck"
    // we're also hard booting / resetting the device by pulling resetPin to ground for 500ms
    Serial.println("Resetting ESP32-S3 by pulling RST to GND");
    digitalWrite(resetPin, HIGH);
    pinMode(resetPin, OUTPUT);
    digitalWrite(resetPin, LOW);
    delay(500);
    digitalWrite(resetPin, HIGH);
    pinMode(resetPin, INPUT);
    delay(100);
    }
    // zero counters
    okCount = 0;
    detectCount = 0;
    if (targetLoops < maxLoops) {
    Serial.println("Current loop count: " + String(loopCount));
    Serial.println("Updating target loops from: " + String(targetLoops));
    targetLoops = (loopCount + 1 + watchLoops > maxLoops) ? maxLoops : loopCount + 1 + watchLoops;
    Serial.println("Updated target loops to: " + String(targetLoops));
    }
    }
    // deep sleep to save power if we've seen watchLoops consecutive ok loops
    // or performed minLoops sense-loops where the t-beam is always-on
    if(okCount > watchLoops || (detectBool == false && loopCount > minLoops) || loopCount > targetLoops) {
    Serial.println("Going to sleep now");
    Serial.flush();
    esp_deep_sleep_start();
    }
    ++loopCount;
    delay(100);
    }