esp32 wifi/ota failing when using HC-SR04 sensor

51 views Asked by At

I'm testing an esp32 to control a cat flap. Big cats can pass but it closes to keep a new kitten indoors. An antenna reads the cat's chip, and servos hold the flap shut for going out. But there's about a 1-foot space between the antenna and the flap where the kitten can sit and wait until the flap times out and opens again. Picture of antenna and flap So I put on an HC-SR04 distance sensor to monitor this vestibule area and reset the flap timeout until the kitten gives up and goes away. Everything was working fine until I added the HC-SR04, then I started having problems with OTA updates.

I tried using the NewPing library and the "standard" pulseIn method, and in both cases OTA updating was failing. I could see on my router that the esp32 was connected to wifi right up until the invitation was sent, then it would disappear and serial and MQTT output would stop. Pressing reboot did not help, the only way to get it working again was to unplug and plug back in again, or to update via USB. I discovered by trial and error that the program listed here with a 5000 us timeout for the pulseIn function and a sonar ping interval of 10000 ms gives me a successful OTA update, but a shorter sonar ping interval repeatedly causes problems. I'm happy that it's working, but I don't understand why and would like to be sure I've got a stable program. I'm uploading the entire code listing here just in case, but I've only had this issue when using the HC-SR04. Any thoughts (besides that I should try my hand at something other than coding, thanks)?

// Cat door RFID control

// Imports
#include <Arduino.h>
#include <WiFi.h>
#include <time.h>
#include <ESPmDNS.h>
#include <WiFiUdp.h>
#include <ArduinoOTA.h>
#include <esp_ota_ops.h>
#include <PubSubClient.h>
#include <ESP32Servo.h>

// GPIO pins:
#define power_pin 4
#define irq_pin 5
#define motion_pin 19
#define trigger_pin 33
#define echo_pin 35
#define max_distance 30

// Variables:
char message[20];
String id = "";
String cat_name = "";
String door_status = "";

bool contains_hex = false;
bool transmission = false;
bool motion_detected = false;
int pos;
int distance = 100;
int num_open_events = 0;
int num_close_events = 0;
int check = 0;

Servo servo1;
Servo servo2;
int servo_pos = 0;

String MittensID = "mittens_rfid";
String LeiaID = "leia_rfid";
String MarcoID = "marco_rfid";
String TestID = "test_rfid" ;
String time_string;
String output_string;
String detection_times[10]; //= {"00:00", "00:00", "00:00", "00:00", "00:00", "00:00", "00:00", "00:00", "00:00", "00:00"}
String detected_cats[10]; //= {"None", "None", "None", "None", "None", "None", "None", "None", "None", "None"}

// Timing
const char* ntp_server = "pool.ntp.org";
const int gmt_offset_sec = -3600;
const int daylight_offset_sec = -3600;
time_t curr_time;
struct tm time_info;
struct tm * local_time;
unsigned int last_sonar_check = millis();
unsigned int sonar_interval = 10000;
unsigned int pings = 0;
unsigned int start_ping = micros();
unsigned int ping_duration = 0;
unsigned int last_detection = millis();
unsigned int read_cycle = 50; // Time to wait since last detection before going from read state to process state
unsigned int last_new_id = millis();
unsigned int dwell_time = 10000; // Time to wait for cat to clear door or to come and go before processing after last new id
unsigned int hold_ms = millis();
unsigned int delay_interval = 5000;  // Keep door open for 5 s
unsigned int closed_ms = millis();
unsigned int close_interval = 90000; // Keep door closed so unauthorized cat doesn't just wait in vestibule for it to open again
unsigned int lock_period = 8; // hours
unsigned int lock_ms = millis();
unsigned int unlock_period = 16; // hours
unsigned int unlock_ms = millis();
unsigned int read_events = 0;
unsigned int failed_connections = 0;
String boot_date;

// WiFi/MQTT
const char* ssid = "wifi";
const char* password = "password";
const char* mqtt_server = "server";

// Machine states:
String state_name[] = {"standby", "read", "processing", "delay", "closed", "locked", "unlocked"};
enum {
  STANDBY_STATE = 0,
  READ_STATE = 1,
  PROCESSING_STATE = 2,
  DELAY_STATE = 3,
  LOCKED_STATE = 4,
  UNLOCKED_STATE = 5
};

int current_state = STANDBY_STATE;

// Mosquitto MQTT Broker
const char* mqtt_user = "user";
const char* mqtt_password = "password";

WiFiClient test1Client;
PubSubClient client(test1Client);
long lastMsg = 0;
char msg[50];

void connect_wifi() {
  Serial.println("Connecting to Wi-Fi...");
  WiFi.begin(ssid, password);
  delay(500);
  if (WiFi.status() == WL_CONNECTED) {
      Serial.println("WiFi connected");
      Serial.println("IP address: ");
      Serial.println(WiFi.localIP());
  } else {
      Serial.println("WiFi not connected");
  }
}

void open_door() {
  num_open_events++;
  Serial.println(String("\nOpening - ") + String(num_open_events));
  client.publish("test1/status", String("Opening").c_str());
  client.publish("test1/status", String("Time: " + time_string).c_str());
  client.publish("test1/status", String("Open events: " + String(num_open_events)).c_str());
  if (door_status == "closed") {
    for (servo_pos = 120; servo_pos >= 0; servo_pos -=5) {
      servo1.write(servo_pos);
      servo2.write(180 - servo_pos);
      delay(100);
    }
  }
  door_status = "open";
}

void close_door() {
  num_close_events++;
  Serial.println(String("\nClosing - ") + String(num_close_events));
  client.publish("test1/status", String("Closing").c_str());
  client.publish("test1/status", String("Time: " + time_string).c_str());
  client.publish("test1/status", String("Close events: " + String(num_close_events)).c_str());
  if (door_status == "open") {
    for (servo_pos = 0; servo_pos <= 120; servo_pos +=1) {
      servo1.write(servo_pos);
      servo2.write(180 - servo_pos);
    }
  }
  closed_ms = millis();
  door_status = "closed";
}

void callback(char* topic, byte* message, unsigned int length) { // MQTT message handler
  Serial.print("Message arrived on topic: ");
  Serial.print(topic);
  Serial.print(". Message: ");
  String messageTemp;

  for (int i = 0; i < length; i++) {
    Serial.print((char)message[i]);
    messageTemp += (char)message[i];
  }

  if (String(topic) == "test1/open") {
    if (messageTemp == "true") {
      open_door();
    }
  } else if(String(topic) == "test1/close") {
    if (messageTemp == "true") {
      close_door();
    }
  } else if (String(topic) == "test1/lock") {
    lock_period = messageTemp.toInt(); // in hours
    close_door();
    current_state = LOCKED_STATE;
  } else if (String(topic) == "test1/unlock") {
    unlock_period = messageTemp.toInt(); // in hours
    close_door();
    current_state = UNLOCKED_STATE;
  } else if (String(topic) == "test1/read_cycle") {
      read_cycle = messageTemp.toInt();
      client.publish("test1/status", String("Set read cycle to " + String(read_cycle) + String(" ms")).c_str());
  } else if (String(topic) == "test1/dwell_time") {
      dwell_time = messageTemp.toInt() * 1000;
      client.publish("test1/status", String("Set dwell time to " + String(dwell_time/1000) + String(" seconds")).c_str());
  } else if (String(topic) == "test1/delay") {
      delay_interval = messageTemp.toInt() * 1000;
      client.publish("test1/status", String("Set enable delay to " + String(delay_interval/1000) + String(" seconds")).c_str());
  } else if (String(topic) == "test1/close_interval") {
      close_interval = messageTemp.toInt() * 1000;
      client.publish("test1/status", String("Set close interval to " + String(close_interval/1000) + String(" seconds")).c_str());
  } else if (String(topic) == "test1/settings") {
      output_string = "State: " + state_name[current_state];
      output_string = output_string + "\nDoor: " + door_status;
      output_string = output_string + "\nReads: " + String(read_events);
      output_string = output_string + "\nRead cycle: " + String(read_cycle) + " ms";
      output_string = output_string + "\nDwell time: " + String(dwell_time/1000) + " s";
      output_string = output_string + "\nEnable delay: " + String(delay_interval/1000) + " s";
      output_string = output_string + "\nClose interval: " + String(close_interval/1000) + " s";
      output_string = output_string + "\nBoot: " + boot_date;
      client.publish("test1/status", String(output_string).c_str());
  } else if (String(topic) == "test1/log") {
    if (!detected_cats[0].isEmpty()) {
      output_string = "Last 5 ops: ";
      for (int i = 0; i < 5; i++) {
        output_string = output_string + "\n" + String(detected_cats[i].substring(0,10)) + " " + String(detection_times[i].substring(11,19));
      }    
    }
    else {
      output_string = "No cats detected since last boot";
    }
    client.publish("test1/status", (String(output_string)).c_str());
  }
}

void irq_function() {
  current_state = STANDBY_STATE;
}

void reconnect() {
  Serial.print("Trying MQTT connection...");
  if (client.connect("test1_client", mqtt_user, mqtt_password)) {  // Try to connect
    Serial.println("MQTT connected");
    client.subscribe("test1/read_cycle");
    client.subscribe("test1/open");
    client.subscribe("test1/close");
    client.subscribe("test1/lock");
    client.subscribe("test1/unlock");
    client.subscribe("test1/delay");
    client.subscribe("test1/close_interval");
    client.subscribe("test1/standby");
    client.subscribe("test1/dwell_time");
    client.subscribe("test1/settings");
    client.subscribe("test1/log");
  } else {
    Serial.print("failed, rc=");
    Serial.print(client.state());
    failed_connections = failed_connections + 1;
  }
}

unsigned long get_time() {
  time_t now;
  struct tm time_info;
  if (!getLocalTime(&time_info)) {
    // failed to get time
    return(0);
  }
  time(&now);
  return now;
}

bool check_motion() {
  if (digitalRead(motion_pin)==HIGH) {
    return true;
  } else {
    return false;
  }
}

void(* resetFunc) (void)=0;

void setup() {

  Serial.begin(9600);
  Serial2.begin(9600);
  pinMode(power_pin, OUTPUT);
  digitalWrite(power_pin, HIGH);
  pinMode(irq_pin, INPUT);
  pinMode (motion_pin, INPUT);
  attachInterrupt(irq_pin, irq_function, CHANGE);

  connect_wifi();
  reconnect(); // (mqtt)
  configTime(0, 0, ntp_server);
  getLocalTime(&time_info);
  Serial.println(&time_info, "%A, %B %d %Y %H:%M:%S zone %Z %z ");
  setenv("TZ", "CET-1CEST,M3.5.0,M10.5.0/3",1);
  tzset();
  getLocalTime(&time_info);
  Serial.println(&time_info, "%A, %B %d %Y %H:%M:%S zone %Z %z ");
  curr_time = time(NULL);
  local_time = localtime(&curr_time);
  Serial.println(local_time);
  Serial.println(asctime(local_time));
  time_string = String(asctime(local_time));
  boot_date = time_string;
  client.setServer(mqtt_server, 1883);
  client.setCallback(callback);

  ArduinoOTA
    .onStart([]() {
      String type;
      if (ArduinoOTA.getCommand() == U_FLASH)
        type = "sketch";
      else // U_SPIFFS
        type = "filesystem";
      Serial.println("Start updating " + type);
    })
    .onEnd([]() {
      Serial.println("\nEnd");
    })
    .onProgress([](unsigned int progress, unsigned int total) {
      Serial.printf("Progress: %u%%\r", (progress / (total / 100)));
    })
    .onError([](ota_error_t error) {
      Serial.printf("Error[%u]: ", error);
      if (error == OTA_AUTH_ERROR) Serial.println("Auth Failed");
      else if (error == OTA_BEGIN_ERROR) Serial.println("Begin Failed");
      else if (error == OTA_CONNECT_ERROR) Serial.println("Connect Failed");
      else if (error == OTA_RECEIVE_ERROR) Serial.println("Receive Failed");
      else if (error == OTA_END_ERROR) Serial.println("End Failed");
    });

  ArduinoOTA.begin();

  delay(5);
  Serial.println("Starting");
  Serial.println("Started");
  last_detection = millis();

  servo1.attach(12);
  servo2.attach(14);
  servo1.write(90);
  servo2.write(90);

  pinMode(trigger_pin, OUTPUT);
  pinMode(echo_pin, INPUT);

  delay(5000);
  door_status = "closed";
  open_door();
  client.publish("test1/status", String("Feeder reboot: " + String(asctime(local_time))).c_str());

}

void loop() {

  //Serial.println("WiFi:" + WiFi.localIP());
  if (WiFi.status() != WL_CONNECTED) {
    Serial.println("Rechecking Wifi");
    connect_wifi();
  }

  if (!client.connected()) {
    Serial.println("MQTT disconnected, reconnecting...");
    reconnect();
  }

  if (failed_connections > 100) {
    resetFunc();
  }

  ArduinoOTA.handle();

  client.loop();  //maintain connection and check for messages

  curr_time = time(NULL);
  local_time = localtime(&curr_time);
  time_string = String(asctime(local_time));

  if (millis() - last_sonar_check > sonar_interval) {
  
    // Unlock faulty HC-SR04 sensor
    //if (digitalRead(echo_pin) == HIGH) {
    //  Serial.println("Resetting echo pin");
    //  pinMode(echo_pin, OUTPUT);
    //  digitalWrite(echo_pin, LOW);
    //  delay(100);
    //  pinMode(echo_pin, INPUT);
    //}

    digitalWrite(trigger_pin, LOW);
    delayMicroseconds(2);
    digitalWrite(trigger_pin, HIGH);
    delayMicroseconds(10);
    digitalWrite(trigger_pin, LOW);
    start_ping = micros(); // assume echo pin is already reading high (pulse has started)
    ping_duration = 0;
    //while (micros() - start_ping < 2000) { 
    ping_duration = pulseIn(echo_pin, HIGH, 5000); // pin, pulse type, max microseconds to wait for pulse to start
    //}
    distance = ping_duration * 0.01724;
    Serial.println(String("Ping duration: " + String(ping_duration)).c_str());
    client.publish("test1/status", String("Ping duration: " + String(ping_duration)).c_str());
    pings +=1;
    client.publish("test1/status", String("Pings: " + String(pings)+ ", distance: " + String(distance)).c_str());
    Serial.println(String("Pings: " + String(pings)+ ", distance: " + String(distance)).c_str());
    last_sonar_check = millis();
    if (distance > 0 && distance < 15) { // Keep door closed if it's closed and Marco's still waiting for it to reopen
      closed_ms = millis();
      client.publish("test1/status", String("Extending door close time, distance: " + String(distance)).c_str());
      Serial.println(String("Extending door close time, distance: " + String(distance)).c_str());
    }
  }

// motion_detected = check_motion();
// if (motion_detected) {
//   Serial.println("Motion detected");
// }

  switch (current_state) {
    case STANDBY_STATE: {
        if (Serial2.available() > 0) {
          last_detection = millis();
          pos = 0;
          current_state = READ_STATE;
          read_events++;
          Serial.println("Reading...");
        } else if (door_status == "closed" && millis() - closed_ms > close_interval) {// Open door if close_interval has passed
          open_door();
        }
        break;
      }
    case READ_STATE: {
        // Read message
        if (Serial2.available() > 0 && pos < 20) {
          last_detection = millis();
          message[pos] = Serial2.read();
          pos++;
        }
        if (millis() - last_detection > read_cycle) {
          current_state = PROCESSING_STATE;
          last_detection = millis();
        } 
        if (pos > 20) {
        current_state = STANDBY_STATE;
        }
      }
      break;
    case PROCESSING_STATE: {
          //client.publish("test1/status", "Processing...");
          id = "";
          id = String(message);
          strcpy (message, "");
          id = id.substring(2,17);

          contains_hex = false;
          for (int i = 0; i < id.length(); i++) {
            if (!isdigit(id[i])) {
              contains_hex = true;
            }
          }
          if (contains_hex) {
            current_state = STANDBY_STATE; // Ignore random hex characters
            break;
          }

          if (id == MittensID) {
            cat_name = "Mittens";
          } else if (id == LeiaID) {
            cat_name = "Leia";
          } else if (id == MarcoID) {
            cat_name = "Marco";
          } else if (id == TestID) {
            cat_name = "Test";
          } else {
            cat_name = "ID " + id;
            current_state = STANDBY_STATE; // Ignore spurious cat IDs
          }

          if (id == MarcoID || id == TestID) { // Close for Marco, make sure timing is such that door stays closed (new close order without open) if he's the next cat detected.
            closed_ms = millis();
            close_door();
            current_state = STANDBY_STATE;
          } else if (id == MittensID || id == LeiaID) { // Open for Mittens or Leia
            open_door();
            hold_ms = millis();
            current_state = DELAY_STATE;
          }

          //if ((millis() - last_new_id > dwell_time) || (cat_name != detected_cats[0])) { //New cat detected or some time has passed
            last_new_id = millis();
            for (int i = 9; i > 0; i--) {
              detection_times[i] = detection_times[i-1];
              detected_cats[i] = detected_cats[i-1];
            }
            detection_times[0] = String(time_string);
            detected_cats[0] = String(cat_name);
            client.publish("test1/status", String(String(cat_name) + " detected at " + time_string.substring(11,19)).c_str());  
          //} 

      break;
      }
    case DELAY_STATE: { // Cat has been successfuly detected; wait for delay interval for cat to clear the opening
      if (millis() - hold_ms > delay_interval) {
        current_state = STANDBY_STATE;
      }
      break;
    }
    case LOCKED_STATE: { // Door remains closed until lock period has expired, then return to normal operation
      if (millis() - lock_ms > lock_period * 3600000) {
        open_door();
        current_state = STANDBY_STATE;
      }
    }
    case UNLOCKED_STATE: { // Door remains open until unlock period has expired, then return to normal operation
      if (millis() - unlock_ms > unlock_period * 3600000) {
        current_state = STANDBY_STATE;
      }
    }
  }
}

0

There are 0 answers