Need help writing code for project after using AI bots didn't work

so helpful haha

like I said above, the main problem is figuring out why the arduino won't transmit signals from the sensors while the stepper motor is running. Sensors and motor have different power supply but share the same gnd.

I suppose there is a lot of information to sift through but that is where I am at if you can help great, if not then no need to add on to this post.

thanks for the input , I'll look into doing it that way. I have very little coding experience which is why I turned to the AI.

You would need help doing it my way then. I means using time based code with no delay() or blocking code at all.

Each sensor would be run independently, yet "at the same time, all the time". Each would maintain status data that a process status would read and decide what to do many times each millisecond. The way is taught here, it takes a while to learn but ... well ask around how long it took your helpers to learn non-blocking aka millis-code. And be sure, if you want to write auto,ation then it is a hill you really want to climb!

I am open to doing it any way that you think would work! Obviously I would need a lot of guidance to make it happen as all I can provide is instructions for how I want the stepper motor to operate based on inputs from the two ultrasonic sensors. Once I get the code working I'll 3d print a mount to attach the lead screw nut to the sliding door and a housing/ mount for the stepper motor.

I gave an explanation of the wiring already. Appreciate your constructive input in solving this :slight_smile:

Put your ear close to the ultrasonic sensors. If they are the common HC SR04, they aren't silent. Just tested with my Siberian: she can hear them (but didn't seem bothered by them).
As @Wawa and @GoForSmoke say, a different sensor such as a PIR sensor may be the ticket (and just easier to use, imo - they have two pots typically built in, one for distance, the other for duration: there's your three-second timer built in, easy peasy).
So here's a piece of advice from a mid-card helper in this forum (I'm not one of the gurus, but there are definitely gurus paying attention to this thread, including both who suggested different sensors) : forget ChatGPT. Ditch that code and start over, if only because quite frankly, the best helpers here don't respect ChatGPT and will therefore be less inclined to help you out.
I say to folks here often that I'm a hobbyist in Arduino with a decade under my belt and thousands of hours of coding on these things and I still feel like a total beginner next to, oh, at least six helpers in this very thread. You want this thing to work as intended? You've already lost my friend

and well, he's one of the best. Have a piece of humble pie and know that no one here at this point is going to write your code for you so it's trial and error with ChatGPT or roll your own and do it right.

Not the culture here, I'm afraid. At least hand draw a schematic and post a picture. Show everything, including part numbers, power supplies, not a Fritzing thing. Some helpers don't mind the Fritzing diagrams but others won't look at them. Know that you're dealing with some real old school souls here but they have the knowledge and experience far beyond ChatGPT if you meet them halfway.

This is so common, trying to do more than one thing without them interfering with one another. Can you please post code you have that shows each of the devices working independently? I think you said they did, so surely you have at least that?

It's worth the pain to learn. One of the core skills to getting things to run as intended in Arduino. I'd say learning the state machine with switch/case is another core basic competency to go beyond blinking an LED.

Why two? One for each side of the door, I guess? Please stop making us guess. Ditch everything ChatGpt told you and let's get on track, yeah?

2 Likes

Have a look at this. No parts required. It's an example adapted from something @cattledog posted in the forum that I tucked away and refer to from time to time when I have the timing blues. Non blocking, unlike delay(whatever); and should give you an idea how to hold open the cat door or make the sensor wait for a for sure cat and avoid false positives.


/* FINALLY! a cowpoke that gets it. Not the same toggle the light forever thang.
    credit whare it's due, cattledog
    https://forum.arduino.cc/t/countdown-timer-without-delay/680769
*/


const int ledPin =  LED_BUILTIN;      // the number of the LED pin
int ledState = LOW;                   // ledState used to set the LED
unsigned long previousMillis = 0;     // will store last time LED was updated
const long interval = 1000;           // interval at which to blink (milliseconds)
int timer = 5;                        // Set the timer to 5 seconds

void setup() {
  // set the digital pin as output:
  pinMode(ledPin, OUTPUT);
  Serial.begin(115200);
}

void loop() {
  unsigned long currentMillis = millis();
  if (currentMillis - previousMillis >= interval) {
    // save the last time you blinked the LED
    previousMillis = currentMillis;
    // if the LED is off turn it on and vice-versa:
    if (ledState == LOW) {
      ledState = HIGH;
    } 
    else {                  // take out the else for function thangs like motor relays that don't toggle
      ledState = LOW;
    }
    Serial.println(timer);
    timer--;//decrease timer count
    if (timer == -1)// less than 0
    {
      timer = 5; // rest to five for the demo, or don't and make it truly one-shot
    }
  }
  // set the LED with the ledState of the variable:
  digitalWrite(ledPin, ledState);
}

You seem to misunderstand the process rather completely. You write code that commands the motor to "perform as needed", thus it is absolutely essential to define "perform as needed" before the code can be written.

Likewise, you write code that sets up the sensors to perform "as needed", and then write code to read, interpret and act on the sensor results.

Naturally, you need to know beforehand how to set up the sensors, how to read and interpret the results, and how to act on those results, before you can start writing code.

You ignored post #4, showing how to do step #1 (continuously monitor a sensor). Thanks.

Your list of actions is sloppy. You have gaps and make assumptions to things happening.

Here is step #2. (if "either" sensor is triggered for three seconds, motor moves). In this, the sensors are pushbuttons with active LOW (but your motion sensors are active HIGH). Sensor activity for longer than three seconds calls a function (in this case, to light and LED... but can be used to move a motor):

byte sensorArray[] = {2, 3}; // two sensors
byte ledArray[] = {7, 8}; // two LEDs
unsigned long timeoutArray[] = {3000, 3000}; // two durations
unsigned long sensorTimer[2];

void setup() {
  Serial.begin(115200);
  for (byte pin = 0; pin < 2; pin++) {
    pinMode(ledArray[pin], OUTPUT); // LEDs for the buttons
  }
  for (byte pin = 0; pin < 2; pin++) {
    pinMode(sensorArray[pin], INPUT_PULLUP); // sensor pin >> N.O. button pins >> ground
  }
}

void loop() {
  for (byte pin = 0; pin < 2; pin++) { // reading both input pins
    if (!digitalRead(sensorArray[pin])) { // if button is held
      if (millis() - sensorTimer[pin] > timeoutArray[pin]) { // and timeout is reached
        movement(pin); // move the motor using [pin]
      }
    } else { // button was released
      digitalWrite(ledArray[pin], LOW); // LED off
      sensorTimer[pin] = millis(); // reset timer
    }
  }
}

void movement(byte pin) { // use this function for motor movement (open AND close)
  digitalWrite(ledArray[pin], HIGH); // LED on
}

Hardware file for wokwi.com:

diagram.json
{
  "version": 1,
  "author": "Anonymous maker",
  "editor": "wokwi",
  "parts": [
    { "type": "wokwi-arduino-nano", "id": "nano", "top": 0, "left": 0, "attrs": {} },
    {
      "type": "wokwi-pushbutton",
      "id": "btn1",
      "top": -61,
      "left": 144,
      "attrs": { "color": "green" }
    },
    {
      "type": "wokwi-pushbutton",
      "id": "btn2",
      "top": -109,
      "left": 144,
      "attrs": { "color": "blue" }
    },
    {
      "type": "wokwi-led",
      "id": "led1",
      "top": -106.8,
      "left": 11,
      "rotate": 270,
      "attrs": { "color": "blue", "flip": "1" }
    },
    {
      "type": "wokwi-led",
      "id": "led2",
      "top": -58.8,
      "left": 11,
      "rotate": 270,
      "attrs": { "color": "green", "flip": "1" }
    }
  ],
  "connections": [
    [ "nano:GND.2", "btn1:2.l", "black", [ "v0" ] ],
    [ "nano:2", "btn1:1.l", "green", [ "v0" ] ],
    [ "nano:GND.2", "btn2:2.l", "black", [ "v0" ] ],
    [ "nano:3", "btn2:1.l", "blue", [ "v0" ] ],
    [ "nano:GND.2", "led1:C", "black", [ "v0" ] ],
    [ "nano:GND.2", "led2:C", "black", [ "v0" ] ],
    [ "nano:8", "led1:A", "green", [ "v0" ] ],
    [ "nano:7", "led2:A", "green", [ "v0" ] ]
  ],
  "dependencies": {}
}

That doesn't cut it. Please post a schematic diagram (or, lacking that, a wiring diagram).

ChatGPT etc. doesn't understand anything as such. It's a language model and very bluntly said, it puts letters behind each other until a pattern emerges that compares favorably to data the model was trained on. AI has no concept of what you're doing, nor does it have the ability to verify if something works, makes sense, is correct etc.

The code that AI has produced for you is fatally flawed in several ways and not worth the effort of trying to 'fix'. Likewise, its 'explanation' is tautological, overly verbose and doesn't put the finger on the actually tricky bits of the project. Just dump the whole thing and start over.

The best way to attack a project like this is divide & conquer. You said that you have tested each component individually. That's a great first step. Now it's a matter of adding parts to your code, one at a time, modifying the code until each bit works as intended, and then adding the next part.

For the switching logic of your program, I'd look into the concept of a finite state machine. There's a brief theoretical explanation and a bit of an example here: State Machines and Arduino Implementation – Norwegian Creations You'll find that there are plenty of other Arduino-focused examples, but they mostly suffer from IMO unnecessarily complex (at least when you're starting out) code structure. The example I linked you to uses a simple switch-case structure that's easy to understand and adopt for your own project.

By using a state machine logic, you'll be better able to identify the contingencies your project will run into; there are combinations of conditions that you may not have realized up til now and that will (not 'may', but will) throw you a loop if you continue with the kind of non-structure that AI has yielded.

Good luck with your project; it's a fun one and a great way to ease into microcontroller work.

2 Likes

The other helpers here know how make Arduino do more than one thing at a time too. Some are less task-code oriented but the pieces they want are the same.
Let me be real clear here: I do mainly software and not so great at hardware though what you want motor-wise does not seem tricky or beyond me. It would be good to have the oversight of the others during this.
I am also going to want you understanding the code and not just asserting that you do while you don't. If I ask questions and get crap answers, I'll slow down to let you catch up. I do have a life and will be busy at times, I'll be moving in a week or so, there will be days I won't be here. I will have homework for you to help you with understanding the code, all with simple code in tutorials that explain every bit.

I can start with sketches that do one thing only and convert them to work together as separate tasks that run in the same void loop() without stepping on each other.

Do you have a working sketch that you understand for the ultrasonic sensor only that does sense your cat?

Please note that your hearing range and sensitivity are not the cat's! Have you detected the cat with the sensor and does it ignore it? Cats can hear frequencies over 60KHz and the Ping operates at maybe 40KHz which may lead to a twist in this!

Cats do not see near-IR nor sense electric fields.
Your cat may be interested in or avoid the noise or ignore it... so please relate before building anything. In any case, if the Ping sensors are used, we need a working cat-detect sketch!

We also need a motor sketch that runs the motor for maybe 1 second, stops and reverses for 1 second. No button needed to complicate it, just run, stop, reverse.

Both sketches CAN use delay(). I'll convert the delays using a cut&dry technique. You will learn by running and reading the code and asking questions based on the code... and by learning a tutorial and messing with it or an example demo. Expect to hit that for a short period on a day here and there, many times so what you do has time to sink in. This isn't a quiz to pop and forget, it's long term to keep doing and build on. If you get tired, stop and come back later when your eyes are fresh.

Intro to the crucial part blinks 2 leds. Simple on purpose!

What Nick tells you is more than the code how part, it's the why!
Take your time. Run the code and read it more than once.
When timing code puzzles you, this tutorial probably has what you need to consider.

I'll give you led and button code to complement that, watching the button while blinking the led are two things at once. My button is a jumper with one end in GND and the other in the pin hole or not, but you can use a button if you have one.

The Ping sensors have a minimum range of 3cm and I'm wondering how you will position those? Maybe above the cat door with the floor distance = no cat. Or to one side reflecting off a wall by default?
Because at the door looking out is likely to not see the cat going through the door. Ping sensors are also pretty directional. What kind of spread do you get?

2cm - 400cm

Using multiple SR04 (two inside, two outside?)...

Files from WOKWI SR04x4:

sketch.ino
byte ledPin[]  = {16, 17, 14, 15}; // LEDs to indicate SR04

long duration, distance; // HC-SR04 measurements
byte trigPin[] = {2, 4, 6, 8};
byte echoPin[] = {3, 5, 7, 9};
byte counter = 0; // sensor counter

unsigned long timer, interval = 500; // timers

void setup() {
  Serial.begin (115200);
  for (int i = 0; i < 4; i++)  {
    pinMode(trigPin[i], OUTPUT);
    pinMode(echoPin[i], INPUT);
    pinMode(ledPin[i], OUTPUT);
  }
}

void loop() {
  if (millis() - timer > interval) { // program is running longer than "interval"
    timer = millis(); // store this time
    if (counter > 3) {
      counter = 0; // check counter for overflow
      Serial.println(); // format the Serial Monitor output
    }
    myPing(counter); // call the myPing() function
    counter++; // increment function counter
  }
}

void myPing(int SR04) {
  // print/LED
  digitalWrite(ledPin[SR04], HIGH);
  Serial.print("D");
  Serial.print(SR04);
  Serial.print(": ");

  // ping
  digitalWrite(trigPin[SR04], LOW);
  delayMicroseconds(2);
  digitalWrite(trigPin[SR04], HIGH);
  delayMicroseconds(10);
  digitalWrite(trigPin[SR04], LOW);
  Serial.print((pulseIn(echoPin[SR04], HIGH) / 2) / 29.1 );

  // print/LED
  Serial.print(" ");
  digitalWrite(ledPin[SR04], LOW);
}
diagram.json
{
  "version": 1,
  "author": "Anonymous maker",
  "editor": "wokwi",
  "parts": [
    {
      "type": "wokwi-arduino-nano",
      "id": "nano",
      "top": -12.4,
      "left": 164.5,
      "rotate": 270,
      "attrs": {}
    },
    { "type": "wokwi-hc-sr04", "id": "ultrasonic1", "top": -161.7, "left": 63.1, "attrs": {} },
    {
      "type": "wokwi-hc-sr04",
      "id": "ultrasonic2",
      "top": -161.7,
      "left": -138.5,
      "attrs": { "distance": "312" }
    },
    {
      "type": "wokwi-hc-sr04",
      "id": "ultrasonic3",
      "top": -315.3,
      "left": -138.5,
      "attrs": { "distance": "100" }
    },
    {
      "type": "wokwi-hc-sr04",
      "id": "ultrasonic4",
      "top": -315.3,
      "left": 63.1,
      "attrs": { "distance": "227" }
    },
    { "type": "wokwi-vcc", "id": "vcc1", "top": -335.24, "left": 48, "attrs": {} },
    { "type": "wokwi-gnd", "id": "gnd1", "top": 57.6, "left": 28.2, "attrs": {} },
    {
      "type": "wokwi-led",
      "id": "led1",
      "top": -157.2,
      "left": -178.2,
      "attrs": { "color": "red", "flip": "1" }
    },
    {
      "type": "wokwi-led",
      "id": "led2",
      "top": -301.2,
      "left": -178.2,
      "attrs": { "color": "red", "flip": "1" }
    },
    {
      "type": "wokwi-led",
      "id": "led3",
      "top": -147.6,
      "left": 234.2,
      "attrs": { "color": "red" }
    },
    {
      "type": "wokwi-led",
      "id": "led4",
      "top": -291.6,
      "left": 234.2,
      "attrs": { "color": "red" }
    }
  ],
  "connections": [
    [ "nano:4", "ultrasonic4:TRIG", "green", [ "h-172.8", "v-211.2", "h96.4" ] ],
    [ "nano:5", "ultrasonic4:ECHO", "green", [ "h-172.8", "v-211.2", "h106.4" ] ],
    [ "nano:2", "ultrasonic3:TRIG", "green", [ "h-172.8", "v-192", "h-105.2" ] ],
    [ "nano:3", "ultrasonic3:ECHO", "green", [ "h-172.8", "v-192", "h-95.2" ] ],
    [ "nano:6", "ultrasonic2:TRIG", "green", [ "h-172.8", "v-76.8", "h-105.6" ] ],
    [ "nano:7", "ultrasonic2:ECHO", "green", [ "h-172.8", "v-76.8", "h-96" ] ],
    [ "nano:8", "ultrasonic1:TRIG", "green", [ "h-172.8", "v-96", "h86.4" ] ],
    [ "nano:9", "ultrasonic1:ECHO", "green", [ "h-172.8", "v-96", "h96" ] ],
    [ "vcc1:VCC", "ultrasonic3:VCC", "red", [ "v124.8", "h-124.8" ] ],
    [ "vcc1:VCC", "ultrasonic2:VCC", "red", [ "v278.4", "h-48" ] ],
    [ "vcc1:VCC", "ultrasonic1:VCC", "red", [ "v278.4", "h124.8" ] ],
    [ "gnd1:GND", "ultrasonic2:GND", "black", [ "v-115.2", "h-75.6" ] ],
    [ "gnd1:GND", "ultrasonic1:GND", "black", [ "v-115.2", "h126" ] ],
    [ "gnd1:GND", "ultrasonic3:GND", "black", [ "v-268.8", "h-75.6" ] ],
    [ "gnd1:GND", "ultrasonic4:GND", "black", [ "v-268.8", "h126" ] ],
    [ "nano:A2", "led2:A", "green", [ "h9.6", "v-211.2", "h-442" ] ],
    [ "nano:A3", "led4:A", "green", [ "h9.6", "v-201.6", "h-28.8" ] ],
    [ "gnd1:GND", "led4:C", "black", [ "v-268.8", "h192" ] ],
    [ "gnd1:GND", "led2:C", "black", [ "v-268.8", "h-182.4" ] ],
    [ "nano:A1", "led3:A", "green", [ "h9.6", "v-124.8", "h-38.4" ] ],
    [ "nano:A0", "led1:A", "green", [ "h9.6", "v-134.4", "h-432" ] ],
    [ "gnd1:GND", "led3:C", "black", [ "v-268.8", "h201.6", "v124.8", "h9.6" ] ],
    [ "gnd1:GND", "led1:C", "black", [ "v-268.8", "h-182.4", "v105.6" ] ]
  ],
  "dependencies": {}
}

Three-seconds sensor (button) activation of motor (LED) from previous post.

Files for WOKWI threeSeconds...

sketch.ino
byte sensorArray[] = {2, 3}; // two sensors
byte ledArray[] = {7, 8}; // two LEDs
unsigned long timeoutArray[] = {3000, 3000}; // two durations
unsigned long sensorTimer[2];

void setup() {
  Serial.begin(115200);
  for (byte pin = 0; pin < 2; pin++) {
    pinMode(ledArray[pin], OUTPUT); // LEDs for the buttons
  }
  for (byte pin = 0; pin < 2; pin++) {
    pinMode(sensorArray[pin], INPUT_PULLUP); // sensor pin >> N.O. button pins >> ground
  }
}

void loop() {
  for (byte pin = 0; pin < 2; pin++) { // reading both input pins
    if (!digitalRead(sensorArray[pin])) { // if button is held
      if (millis() - sensorTimer[pin] > timeoutArray[pin]) { // and timeout is reached
        movement(pin); // move the motor using [pin]
      }
    } else { // button was released
      digitalWrite(ledArray[pin], LOW); // LED off
      sensorTimer[pin] = millis(); // reset timer
    }
  }
}

void movement(byte pin) { // use this function for motor movement (open AND close)
  digitalWrite(ledArray[pin], HIGH); // LED on
}
diagram.json
{
  "version": 1,
  "author": "Anonymous maker",
  "editor": "wokwi",
  "parts": [
    { "type": "wokwi-arduino-nano", "id": "nano", "top": 0, "left": 0, "attrs": {} },
    {
      "type": "wokwi-pushbutton",
      "id": "btn1",
      "top": -61,
      "left": 144,
      "attrs": { "color": "green" }
    },
    {
      "type": "wokwi-pushbutton",
      "id": "btn2",
      "top": -109,
      "left": 144,
      "attrs": { "color": "blue" }
    },
    {
      "type": "wokwi-led",
      "id": "led1",
      "top": -106.8,
      "left": 11,
      "rotate": 270,
      "attrs": { "color": "blue", "flip": "1" }
    },
    {
      "type": "wokwi-led",
      "id": "led2",
      "top": -58.8,
      "left": 11,
      "rotate": 270,
      "attrs": { "color": "green", "flip": "1" }
    },
    {
      "type": "wokwi-text",
      "id": "legendservo1",
      "top": -86.4,
      "left": 182.4,
      "rotate": 90,
      "attrs": { "text": "____HOLD____\n(three seconds)" }
    },
    {
      "type": "wokwi-text",
      "id": "legendservo2",
      "top": -76.8,
      "left": -48,
      "attrs": { "text": "(start motors)" }
    }
  ],
  "connections": [
    [ "nano:GND.2", "btn1:2.l", "black", [ "v0" ] ],
    [ "nano:2", "btn1:1.l", "green", [ "v0" ] ],
    [ "nano:GND.2", "btn2:2.l", "black", [ "v0" ] ],
    [ "nano:3", "btn2:1.l", "blue", [ "v0" ] ],
    [ "nano:GND.2", "led1:C", "black", [ "v0" ] ],
    [ "nano:GND.2", "led2:C", "black", [ "v0" ] ],
    [ "nano:8", "led1:A", "green", [ "v0" ] ],
    [ "nano:7", "led2:A", "green", [ "v0" ] ]
  ],
  "dependencies": {}
}

Then make a motor run on millis(), something like this...

Files for WOKWI stepperNonBlocking...

sketch.ino
// Stepper motor, no delay(), no library, non-blocking

byte dirPin   = 2;
byte stepPin  = 3;
byte limitPin = 4;
byte startPin = 5;

bool stepNow   = 0;       // flag to indicate time to step
bool stepState = LOW;     // used to start a stepper pulse
bool currentButtonState;  // current button state (pressed/not pressed)
bool lastButtonRead;      // previous button reading

unsigned long previousMillis = 0;   // store last step
unsigned long interval = 2;         // (milliseconds) half-interval for stepping
unsigned long debounceTimeout = 50; // debounceTimeout
unsigned long timer = 0;            // measures button press time

void setup() {
  Serial.begin(9600);
  pinMode(dirPin, OUTPUT);
  pinMode(stepPin, OUTPUT);
  pinMode(limitPin, INPUT_PULLUP);
  pinMode(startPin, INPUT_PULLUP);
  digitalWrite(dirPin, HIGH); // 1 = CW
  welcome();
  while (digitalRead(startPin)); // wait for START button press
  Serial.print("*** PRESS LIMIT ***");
}

void loop() {
  checkElapsedTime();
  stepMotor();
  readSwitch();
}

void readSwitch() {
  bool currentButtonRead = digitalRead(limitPin);  // read button pin
  if (currentButtonRead != lastButtonRead) {        // if button pin reading changes...
    timer = millis();                               // ...start a timer
    lastButtonRead = currentButtonRead;             // ... and store current state
  }

  if ((millis() - timer) > debounceTimeout) {                   // if button read change was longer than debounceTimeout
    if (currentButtonState == HIGH && lastButtonRead == LOW) {  // ... and State NOT pressed while Button PRESSED

      // change stepper direction and light the LED
      digitalWrite(dirPin, !digitalRead(dirPin));
      digitalWrite(LED_BUILTIN, !digitalRead(LED_BUILTIN));
      if (digitalRead(dirPin))
        Serial.print(" CW");
      else
        Serial.print(" CCW");
    }
    currentButtonState = currentButtonRead;  // change the state
  }
}

void checkElapsedTime() { // for the stepper motor
  unsigned long currentMillis = millis(); // store the time
  if (currentMillis - previousMillis >= interval) { // compare elapsed time to interval
    previousMillis = currentMillis; // store last step time
    stepNow = 1; // flag to indicate it is time to step the motor
  }
}

void stepMotor() {
  if (stepNow) {
    stepNow = 0; // clear step flag

    // two methods to step the stepper...
    squarewave(); // HIGH for interval, LOW for interval
    // singlepulse(); // HIGH/LOW pulse then wait for next interval
  }
}

void squarewave() { // toggle the state
  stepState = !stepState;
  delay(10); // let the motor catch arrive at the next step
  digitalWrite(stepPin, stepState); // step the motor
}

void singlepulse() { // minimum requirement to step a stepper
  digitalWrite(stepPin, HIGH);
  delay(10); // let the motor arrive at the next step
  digitalWrite(stepPin, LOW);
}

void welcome() {
  Serial.println("Step and change direction using event timing.");
  Serial.println("- non-blocking\n- no library\n- square wave or single pulse\n ** PRESS START **");
}
diagram.json
{
  "version": 1,
  "author": "Anonymous maker",
  "editor": "wokwi",
  "parts": [
    { "type": "wokwi-arduino-nano", "id": "nano", "top": -24, "left": -38.9, "attrs": {} },
    {
      "type": "wokwi-stepper-motor",
      "id": "stepper1",
      "top": -230.41,
      "left": 89.82,
      "attrs": { "size": "8", "arrow": "yellow" }
    },
    { "type": "wokwi-a4988", "id": "drv1", "top": -148.8, "left": 14.4, "attrs": {} },
    { "type": "wokwi-vcc", "id": "vcc1", "top": -200.84, "left": 67.2, "attrs": {} },
    {
      "type": "wokwi-pushbutton",
      "id": "btn1",
      "top": -70.6,
      "left": 96,
      "attrs": { "color": "yellow" }
    },
    {
      "type": "wokwi-text",
      "id": "legendservo1",
      "top": -96,
      "left": 105.6,
      "attrs": { "text": "LIMIT" }
    },
    {
      "type": "wokwi-text",
      "id": "legendservo2",
      "top": -96,
      "left": -57.6,
      "attrs": { "text": "START" }
    },
    {
      "type": "wokwi-pushbutton",
      "id": "btn3",
      "top": -70.6,
      "left": -67.2,
      "attrs": { "color": "green" }
    }
  ],
  "connections": [
    [ "drv1:GND.2", "drv1:GND.1", "black", [ "h19.35", "v57.6" ] ],
    [ "nano:GND.2", "drv1:GND.1", "black", [ "v0" ] ],
    [ "drv1:STEP", "nano:3", "green", [ "h-19.2", "v19.2", "h67.2" ] ],
    [ "drv1:RESET", "drv1:SLEEP", "green", [ "h-9.6", "v9.6" ] ],
    [ "drv1:GND.2", "drv1:ENABLE", "black", [ "h19.35", "v-20.85", "h-76.8", "v11.33" ] ],
    [ "nano:2", "drv1:DIR", "green", [ "v-28.8", "h-67.2", "v-28.8" ] ],
    [ "vcc1:VCC", "drv1:VMOT", "red", [ "v0" ] ],
    [ "drv1:VMOT", "drv1:VDD", "red", [ "h9.75", "v57.6" ] ],
    [ "drv1:2B", "stepper1:A+", "green", [ "h0" ] ],
    [ "drv1:2A", "stepper1:A-", "green", [ "h0" ] ],
    [ "drv1:1A", "stepper1:B-", "green", [ "h0" ] ],
    [ "drv1:1B", "stepper1:B+", "green", [ "h0" ] ],
    [ "drv1:ENABLE", "drv1:MS1", "green", [ "h-9.6", "v9.6" ] ],
    [ "drv1:MS1", "drv1:MS2", "green", [ "h-9.6", "v9.6" ] ],
    [ "drv1:MS2", "drv1:MS3", "green", [ "h-9.6", "v9.6" ] ],
    [ "nano:GND.2", "btn1:2.l", "black", [ "v0" ] ],
    [ "nano:4", "btn1:1.l", "green", [ "v0" ] ],
    [ "nano:5", "btn3:1.r", "green", [ "v0" ] ],
    [ "nano:GND.2", "btn3:2.r", "black", [ "v0" ] ]
  ],
  "dependencies": {}
}

Here's a PIR version, a demo, of the sort of thing that will get the job done. Should be pretty self explanatory. I may revise it to add a second sensor or I may leave kitty outside to beg (meow) his privileged butt back inside.
Ooh, maybe a sound sensor...

/* Circuit: PIR module on pin 2, +5V and GND supplied by Arduino.
            LED just uses the built in LED. It's just intended to
            show the timing of the output device, which would have to
            be replaced by a motor of some kind to actually open and close
            a door.
            PIR sensor module set to LOW jumper mode (non retriggering)
            and time and sensitivity pots on the module left as they were
            for mine, out of the box.

            Set Serial monitor to 115200 baud, no line ending.

            More info on PIR sensor courtesy of Adafruit Industries:
            https://learn.adafruit.com/pir-passive-infrared-proximity-motion-sensor/testing-a-pir

 Timing/sensor explanation:
 
      This is intended for a motivated cat. That is, kitty is gonna have
      to work a little so the door doesn't just open if the cat is merely
      detected once. What if Mr. Whiskers decides to sleep in front of
      the kitty door? You want him to catch a cold from an open door?
      So this presumes Mr. Whiskers perhaps paws at something of circles
      or paces or looks back at his pet human indicating a desire to cross
      the threshold of the door. My two cats certainly do.
      Then, once the cat has been moving for three seconds, he is considered
      present and wanting the door to open. It will open (led will light up
      for this demo).
      If Mr Kitty decides to waffle about before crossing the threshold, he
      will continue to be detected and the door will stay open as long as he's
      purposefully wasting his day away as cats are known to do.
      It will stay open for another five seconds once he has passed through
      the door.

 by Hallowed31, July 03, 2024.  
 Countdown timer technique courtesy of cattledog from the Arduino Forum.   
*/

int ledPin = LED_BUILTIN;
int pirPin = 2;
int ledState;
int lastLedState;
int pirState;
int lastPirState;
int eventCounter = 0;
unsigned long stopwatchRuntime = 0;
unsigned long lastStopwatchCheck = 0; // keep track of time that no detection changed to detction
unsigned long stopwatchTimer = 0; // keep track of time at every new detection event
unsigned long holdDoorOpenTimer = 0;
const long interval = 1000; // how often to reprint door open timer on Serial monitor
int timer = 5;

void setup() {
  pinMode(ledPin, OUTPUT);
  pinMode(pirPin, INPUT);
  Serial.begin(115200);
  Serial.println("pirCatDoorTimerDemo");
  Serial.println();
  pirState = 0; // set initial states
  lastPirState = 0;
  ledState = 0;
  lastLedState = 0;
}

void loop() {
  stopwatchRuntime = millis(); // start free running timer that never actually stops
  pirState = digitalRead(pirPin);
  switch (pirState) {
    case 0:
      /* cat got door open by moving, time for device to obey cat */
      theCatsOutOftheBag();
      break;
    case 1:
      /* cat moving to open door, ie not just sleeping there */
      ledState = pirState;
      theCatsInTheBag();
      break;
  }
}

void theCatsInTheBag() {
  stopwatchTimer = stopwatchRuntime - lastStopwatchCheck;
  if (lastPirState == 0) {
    lastPirState = 1;
    eventCounter += 1;
  }
  catDetectING(); // print Serial debugging data for events and timer
  if (stopwatchTimer > 3000) { // if cat keeps moving over timeframe of three seconds...
    digitalWrite(ledPin, ledState); // do the thing (turn led on in this test)
    lastLedState = ledState; // and set state to act upon so as soon as cat through door, it holds
  }
}

void theCatsOutOftheBag() {
  lastPirState = pirState;
  lastStopwatchCheck = stopwatchRuntime;
  if (lastLedState == 1) {
    if (stopwatchRuntime - holdDoorOpenTimer >= interval) {
      holdDoorOpenTimer = stopwatchRuntime;
      catDetectED(); // print Serial debugging data for events, timer and door countdown
      timer--;//decrease timer count
      if (timer == -1)// less than 0
      {
        doorClosingMessage();
        timer = 5; // reset timer variable for next door opening event
        lastLedState = lastPirState;
      }
    }
    digitalWrite(ledPin, lastLedState);
  }
  else {
    lastLedState = 0;
    digitalWrite(ledPin, lastLedState);
  }
}

void catDetectED() {
  Serial.print("Event Counter: ");
  Serial.print(eventCounter);
  Serial.print("\tTime since detection: ");
  Serial.print(stopwatchTimer);
  Serial.print("\tDoor open timer: ");
  Serial.println(timer);
}
void catDetectING() {
  Serial.print("Event Counter: ");
  Serial.print(eventCounter);
  Serial.print("\tTime since detection: ");
  Serial.println(stopwatchTimer);
}
void doorClosingMessage() {
  Serial.println();
  Serial.println("Closing Door - watch your tail!");
  Serial.println();
}

Note, this is intended to sort of show how i go about doing these projects. One simple sensor first (PIR have a few good options built in that bypasses the need to actually code for certain things - in fact it bypasses the need for an Arduino altogether if you're good with transistors and relays), and also the simplest of outputs - the Arduino built in LED, to have a super reliable output circuit that rules out things like motor drivers or shoddy wiring when you're just trying to get the code logic right.

Oh, one more thing: This code works as I intended it to. If you decide to try it, great. If you find parts useful and adapt them to your project, great, but I recommend you do not modify what I posted. It's code that works, so make a copy of it, rename it something else, don't forget to change the name here Serial.println("pirCatDoorTimerDemo"); in void setup() to something else, and make changes to your copy.
That way it's easy to just roll back to what already worked if need be.
It's just a good habit in this hobby.

Oh, and since it's in fashion (although my irl version isn't the default PIR setup but this still works in Wokwi), here's a Wokwi.

I built a PIR 'cat sensor' for a drinking fountain a year or two ago. Worked a treat. It used an STM8S003 uC, but a controllerless implementation would have been feasible as well, as you state.
The only drawback I can think of when using a PIR instead of ultrasonic is that the latter can detect movement/distance. A PIR sensor is overall 'dumber' (technically, you could 'smarten it up' by making its sensitivity adjustable and then playing with that, but I didn't bother and stock modules don't offer this functionality either).

The device worked very well, so I can attest to a PIR sensor being an effective detection mechanism for cats. The only 'downside' is that it detected humans as well. The false positives didn't hurt in this application.

Oh cool.

I don't do wokwi, copy the tested working sketch in the IDE and post here in code tags please.
If the door is open, an IR led on one side and detector on the other, or two of those would prove if the cat is in the doorway and ensure there's a way to know to not shut the door on the cat.
IR leds and detectors are wayyyy cheaper than Ping modules!

To ping 6 inches and back takes less than a millisecond even in below freezing air, but to keep one Ping from falsing another only one chirps at a time. The cat won't move far in a few ms anyway, we still keep constant sensing beyond need!
Fur can absorb sound (a wool shirt can at least) so I figure a Ping above watches the floor and any change to that gets taken as cat, closer or no return. A fixed distance echo plane also cuts the wait time for return, timeout means cat detected. If not ping from above then ping from the side or both to cover the space on one side of the door though light sensing would be faster and cheaper. Redundancy is more sure but this is your choice!

Wow, the Nick Gammon link got 50+ clicks already!