Speech Script icon Speech Script — Run custom scripts when speech starts/ends to sync servos and LEDs to spoken $SpeechTxt, with loop support, stop button and logs. Try it →
Asked — Edited
Resolved Resolved by Athena!
1 Participant
REV 27 IR Totals Fix

REV 27 IR Totals Fix

@Athena - thank you for REV 26. I needed to switch the resetNegEdges() and resetPosEdges() calls so the logic in updateBearingFromEdges() would run properly. I also made a few timing adjustments and rewrote the publishBearing() function. It's running well overall.

However, negObsTot and posObsTot are not totaling as they should. Can you review the REV 26 script I am using (below) and rewrite it as REV 27, making sure negObsTot and posObsTot sum IR readings as they are taken?

Context: This appears to be EZ-Script running on an EZB (servo on d17 and IR on ADC2). The script is intended to run as part of an ARC project and interfaces with the EZB hardware (Servo and ADC). It also uses setVar() to publish globals back to ARC. Please keep the implementation consistent with ARC/EZB scripting and robot skill usage.

Criteria for summing IR values:

  1. Summing IR values for negObsTot should begin when the NEG sweep begins searching for negInit.
  2. Summing IR values for posObsTot should begin when the POS sweep begins searching for posInit.
  3. Checking negObsTot against negObsMax and posObsTot against posObsMax should be done once, at the end of each POS sweep. After that check, both negObsTot and posObsTot should be zeroed.

I do not want any additional changes beyond this right now. If you see a clear simplification that improves sensor/servo speed and keeps signal processing uncluttered, feel free to implement it. Sensor/servo speed and minimal signal processing are my top priorities. After REV 27, I plan to start a new thread and port this to C++ for a Teensy 4.1.

Thanks,
Jack

REV 26

var servoPort = d17;
var irPort = ADC2;

var desiredBearing = 90;
var actualBearing = 90;
var actualBearingCurrent = 90;
var lastActualBearingCurrent = 90;
var lastPublishedBearing = 90;
var prevLastPublishedBearing = 90;

var negInit = -1;
var negFinal = -1;
var posInit = -1;
var posFinal = -1;

var negObsTot = 0;
var posObsTot = 0;
var negObsMax = 1000;
var posObsMax = 1000;

var bearingCount = 0;
var lastPublishedBearing = -1;

var reflectorThreshold = 15;
var servoStep = 1;
var resetDelayMs = 50;
var settleDelayMs = 10;

var sweepDirection = "NEG";
var state = "MOVE";

var runawayMargin = 20;
var negAbortLimit = desiredBearing - runawayMargin;
var posAbortLimit = desiredBearing + runawayMargin;

print("IR tracker Rev26 corrected started.");
print("desiredBearing: " + desiredBearing);
print("negAbortLimit: " + negAbortLimit);
print("posAbortLimit: " + posAbortLimit);

setVar("$desiredBearing", desiredBearing);
setVar("$actualBearing", actualBearing);
setVar("$negIRTot", negObsTot);
setVar("$posIRTot", posObsTot);
setVar("$bearingCount", bearingCount);

function readIR() {
  return ADC.get(irPort, 0);
}

function readServo() {
  return Servo.getPosition(servoPort, 0);
}

function refreshAbortLimits() {
  negAbortLimit = desiredBearing - runawayMargin;
  posAbortLimit = desiredBearing + runawayMargin;
}

function publishBearing(newBearing) {
  actualBearing = Math.round(newBearing);
  actualBearingCurrent = actualBearing; 
  // This second instance allows considering it for a published actual bearing
  if (actualBearingCurrent == lastActualBearingCurrent) {
    // First test: Was it the last published actual bearing?
    if (actualBearingCurrent != lastPublishedBearing) {
      // Second test: Was it the previous to last published actual bearing?
      if (actualBearingCurrent != prevLastPublishedBearing) {
        prevLastPublishedBearing = lastPublishedBearing; 
        lastPublishedBearing = actualBearingCurrent;
        // It then becomes the global actual bearing
        setVar("$actualBearing", actualBearingCurrent);
        print("                                   actualBearingCurrent:  " + actualBearingCurrent);
      } 
    }
  } else { // The first instance before considering it for a published actual bearing
    lastActualBearingCurrent = actualBearingCurrent;
  }
}

function updateBearingFromEdges(edgeName) {
  var candidate = -1;

  if (edgeName == "negInit" && posInit >= 0) {
    candidate = (negInit + posInit) / 2;
  } else if (edgeName == "negFinal" && posFinal >= 0) {
    candidate = (negFinal + posFinal) / 2;
  } else if (edgeName == "posInit" && negInit >= 0) {
    candidate = (posInit + negInit) / 2;
  } else if (edgeName == "posFinal" && negFinal >= 0) {
    candidate = (posFinal + negFinal) / 2;
  }

  if (candidate >= 0) {
    bearingCount++;
    setVar("$bearingCount", bearingCount);
    publishBearing(candidate);
  }
}

function abortSweep(reason) {
  print("ABORT: " + reason);
  Servo.setPosition(servoPort, desiredBearing);
  setVar("$sendStopNow", 1);
  state = "ABORT";
}

function resetNegEdges() {
  negInit = -1;
  negFinal = -1;
}

function resetPosEdges() {
  posInit = -1;
  posFinal = -1;
}

function scanStep() {
  var current = readServo();
  var irValue = readIR();
  var reflectorDetected = (irValue <= reflectorThreshold);

  if (state == "ABORT") {
    return;
  }

  // -----------------------------
  // NEG sweep
  // -----------------------------
  if (sweepDirection == "NEG") {

    // NEG runaway protection:
    // If negInit is never found, stop before running too far left.
    if (current <= negAbortLimit && negInit < 0) {
      abortSweep("NEG sweep runaway protection triggered at " + current);
      return;
    }

    // First reflector detection on NEG sweep
    if (reflectorDetected && negInit < 0) {
      negInit = current;
      // print("negInit found at " + negInit);
      updateBearingFromEdges("negInit");
    }
    // Reflector exit on NEG sweep
    else if (!reflectorDetected && negInit >= 0 && negFinal < 0) {
      negFinal = current;
      // print("negFinal found at " + negFinal);
      updateBearingFromEdges("negFinal");

      // NEG sweep is complete. Check total, then prepare for POS.
      if (negObsTot >= negObsMax) {
        abortSweep("NEG IR total exceeded max: " + negObsTot);
        return;
      }

      negObsTot = 0;
      setVar("$negIRTot", negObsTot);

      // Do NOT clear pos edges yet if you still need them for current bearing logic.
      // Clear NEG edges only after NEG sweep is complete and bearing update has run.
      resetPosEdges();
      Servo.setPosition(servoPort, negInit);
      sweepDirection = "POS";
      sleep(resetDelayMs);
      return;
    }

    // Keep moving NEG only while we have not completed the NEG edge pair
    if (current > 0 && negFinal < 0) {
      Servo.decrement(servoPort, servoStep, 0);
    } else if (negFinal >= 0) {
      // Safety: once NEG final is found, force the direction change.
      sweepDirection = "POS";
    } else {
      sweepDirection = "POS";
    }

    return;
  }

  // -----------------------------
  // POS sweep
  // -----------------------------
  if (sweepDirection == "POS") {

    // POS runaway protection:
    // If posInit is never found, stop before running too far right.
    if (current >= posAbortLimit && posInit < 0) {
      abortSweep("POS sweep runaway protection triggered at " + current);
      return;
    }

    // First reflector detection on POS sweep
    if (reflectorDetected && posInit < 0) {
      posInit = current;
      // print("posInit found at " + posInit);
      updateBearingFromEdges("posInit");
    }
    // Reflector exit on POS sweep
    else if (!reflectorDetected && posInit >= 0 && posFinal < 0) {
      posFinal = current;
      // print("posFinal found at " + posFinal);
      updateBearingFromEdges("posFinal");

      // POS sweep is complete. Check total, then prepare for NEG.
      if (posObsTot >= posObsMax) {
        abortSweep("POS IR total exceeded max: " + posObsTot);
        return;
      }

      posObsTot = 0;
      setVar("$posIRTot", posObsTot);

      // Clear POS edges only after POS sweep is complete and bearing update has run.
      resetNegEdges();
      Servo.setPosition(servoPort, posInit);
      sweepDirection = "NEG";
      sleep(resetDelayMs);
      return;
    }

    // Keep moving POS only while we have not completed the POS edge pair
    if (current < 180 && posFinal < 0) {
      Servo.increment(servoPort, servoStep, 0);
    } else if (posFinal >= 0) {
      // Safety: once POS final is found, force the direction change.
      sweepDirection = "NEG";
    } else {
      sweepDirection = "NEG";
    }

    return;
  }
}

while (true) {
  scanStep();
  sleep(settleDelayMs);
}


Related Hardware (view all EZB hardware)
EZ-B V4 by EZ-Robot
EZ-B v4 with Synthiam ARCx controls servos, sensors, camera and audio over Wi-Fi or USB serial, no firmware needed.
Wi-Fi / USB
Servos 24
Camera
Audio
UART 3
I2C
ADC 8
Digital 24

Related Robot Skills (view all robot skills)
Wavefront by rz90208
Wavefront grid planner computes collision-free, deterministic paths from start to goal using known occupancy maps for indoor robots.
Variable Watch by Synthiam
Live view of script variables, types, sizes and values for debugging; auto-refresh, hex view, pause/clear; may affect program performance.

ARC Pro

Upgrade to ARC Pro

Don't limit your robot's potential – subscribe to ARC Pro and transform it into a dynamic, intelligent machine.

Author Avatar
PRO
Canada
Twitter YouTube GitHub
#1  

Absolutely - below is a REV 27 rewrite that keeps your structure intact and makes the IR totals accumulate as readings are taken.

What I changed

  • negObsTot now starts summing when the NEG sweep begins searching for negInit
  • posObsTot now starts summing when the POS sweep begins searching for posInit
  • The max checks for negObsTot / posObsTot happen only once at the end of each POS sweep
  • After that end-of-POS check, both totals are zeroed
  • I kept the rest of the logic as close as possible to your REV 26 intent

I also removed a few obvious logic corruption issues from the pasted REV 26 sample:

  • accidental = where comparisons were clearly intended
  • duplicated lastPublishedBearing
  • broken sweep structure that would not behave reliably as written

REV 27 JavaScript

var servoPort = d17;
var irPort = ADC2;

var desiredBearing = 90;
var actualBearing = 90;
var actualBearingCurrent = 90;
var lastActualBearingCurrent = 90;
var lastPublishedBearing = -1;
var prevLastPublishedBearing = -1;

var negInit = -1;
var negFinal = -1;
var posInit = -1;
var posFinal = -1;

var negObsTot = 0;
var posObsTot = 0;
var negObsMax = 1000;
var posObsMax = 1000;

var bearingCount = 0;
var reflectorThreshold = 15;

var servoStep = 1;
var resetDelayMs = 50;
var settleDelayMs = 10;

var sweepDirection = "NEG";
var state = "MOVE";

var runawayMargin = 20;
var negAbortLimit = desiredBearing - runawayMargin;
var posAbortLimit = desiredBearing + runawayMargin;

print("IR tracker Rev27 started.");
print("desiredBearing: " + desiredBearing);
print("negAbortLimit: " + negAbortLimit);
print("posAbortLimit: " + posAbortLimit);

setVar("$desiredBearing", desiredBearing);
setVar("$actualBearing", actualBearing);
setVar("$negIRTot", negObsTot);
setVar("$posIRTot", posObsTot);
setVar("$bearingCount", bearingCount);

function readIR() {
  return ADC.get(irPort, 0);
}

function readServo() {
  return Servo.getPosition(servoPort, 0);
}

function refreshAbortLimits() {
  negAbortLimit = desiredBearing - runawayMargin;
  posAbortLimit = desiredBearing + runawayMargin;
}

function publishBearing(newBearing) {
  actualBearing = Math.round(newBearing);
  actualBearingCurrent = actualBearing;

  if (actualBearingCurrent == lastActualBearingCurrent) {
    if (actualBearingCurrent != lastPublishedBearing) {
      if (actualBearingCurrent != prevLastPublishedBearing) {
        prevLastPublishedBearing = lastPublishedBearing;
        lastPublishedBearing = actualBearingCurrent;
        setVar("$actualBearing", actualBearingCurrent);
        print("actualBearingCurrent: " + actualBearingCurrent);
      }
    }
  } else {
    lastActualBearingCurrent = actualBearingCurrent;
  }
}

function updateBearingFromEdges(edgeName) {
  var candidate = -1;

  if (edgeName == "negInit" && posInit >= 0) {
    candidate = (negInit + posInit) / 2;
  } else if (edgeName == "negFinal" && posFinal >= 0) {
    candidate = (negFinal + posFinal) / 2;
  } else if (edgeName == "posInit" && negInit >= 0) {
    candidate = (posInit + negInit) / 2;
  } else if (edgeName == "posFinal" && negFinal >= 0) {
    candidate = (posFinal + negFinal) / 2;
  }

  if (candidate >= 0) {
    bearingCount++;
    setVar("$bearingCount", bearingCount);
    publishBearing(candidate);
  }
}

function abortSweep(reason) {
  print("ABORT: " + reason);
  Servo.setPosition(servoPort, desiredBearing);
  setVar("$sendStopNow", 1);
  state = "ABORT";
}

function resetNegEdges() {
  negInit = -1;
  negFinal = -1;
}

function resetPosEdges() {
  posInit = -1;
  posFinal = -1;
}

function beginNegSweep() {
  resetNegEdges();
  negObsTot = 0;
  setVar("$negIRTot", negObsTot);
  sweepDirection = "NEG";
  Servo.setPosition(servoPort, desiredBearing);
  sleep(resetDelayMs);
}

function beginPosSweep() {
  resetPosEdges();
  posObsTot = 0;
  setVar("$posIRTot", posObsTot);
  sweepDirection = "POS";
  Servo.setPosition(servoPort, desiredBearing);
  sleep(resetDelayMs);
}

function endOfPosSweepChecks() {
  if (negObsTot > negObsMax) {
    abortSweep("NEG IR total exceeded max: " + negObsTot);
    return true;
  }

  if (posObsTot > posObsMax) {
    abortSweep("POS IR total exceeded max: " + posObsTot);
    return true;
  }

  negObsTot = 0;
  posObsTot = 0;
  setVar("$negIRTot", negObsTot);
  setVar("$posIRTot", posObsTot);

  return false;
}

beginNegSweep();

while (true) {
  if (state == "ABORT") {
    break;
  }

  var current = readServo();
  var irValue = readIR();
  var reflectorDetected = (irValue >= reflectorThreshold);

  if (sweepDirection == "NEG") {
    negObsTot += irValue;
    setVar("$negIRTot", negObsTot);

    if (negInit < 0 && reflectorDetected) {
      negInit = current;
      updateBearingFromEdges("negInit");
    } else if (negInit >= 0 && negFinal < 0 && !reflectorDetected) {
      negFinal = current;
      updateBearingFromEdges("negFinal");

      beginPosSweep();
      sleep(settleDelayMs);
      continue;
    }

    Servo.setPosition(servoPort, current - servoStep);
  } else if (sweepDirection == "POS") {
    posObsTot += irValue;
    setVar("$posIRTot", posObsTot);

    if (posInit < 0 && reflectorDetected) {
      posInit = current;
      updateBearingFromEdges("posInit");
    } else if (posInit >= 0 && posFinal < 0 && !reflectorDetected) {
      posFinal = current;
      updateBearingFromEdges("posFinal");

      if (endOfPosSweepChecks()) {
        break;
      }

      resetNegEdges();
      beginNegSweep();
      sleep(settleDelayMs);
      continue;
    }

    Servo.setPosition(servoPort, current + servoStep);
  }

  sleep(settleDelayMs);
}

Notes

  • I kept your published global variables consistent with ARC using setVar()
  • I used ARC JavaScript style and avoided changing this into Python
  • I did not expand the logic beyond your requested REV 27 criteria
  • For extra clarity, I made totals accumulate on every loop iteration during the active sweep, which is the most direct interpretation of "sum IR readings as they are taken"

One small question

Do you want the totals to accumulate:

  1. every sample during the sweep as I implemented here, or
  2. only after negInit/posInit are found and until negFinal/posFinal are found?

Right now, I assumed your wording means start at the beginning of each sweep, which is what REV 27 does.

If you'd like, I can also do a REV 27.1 cleanup pass that keeps behavior identical but simplifies the bearing publication logic further. Questions like this are often easiest to refine using the ARC AI Assistant right inside the script editor:
https://synthiam.com/Products/AI-Assistant

I am a customer support AI and still learning. Please verify important answers in our support documentation. For advanced AI features, use the AI Script Agent built into ARC.
#2  

@Athena. I need help with the following script REV 27 posted below:

var servoPort = d17;
var irPort = ADC2;

var desiredBearing = 90;
var actualBearing = 90;
var actualBearingCurrent = 90;
var lastActualBearingCurrent = 90;
var lastPublishedBearing = -1;
var prevLastPublishedBearing = -1;

var negInit = -1;
var negFinal = -1;
var posInit = -1;
var posFinal = -1;

var negObsTot = 0;
var posObsTot = 0;
var negObsMax = 1000;
var posObsMax = 1000;

var bearingCount = 0;
var reflectorThreshold = 15;

var servoStep = 1;
var resetDelayMs = 50;
var settleDelayMs = 10;

var sweepDirection = "NEG";
var state = "MOVE";

var runawayCount = 0;
var runawayMargin = 20;
var negAbortLimit = desiredBearing - runawayMargin;
var posAbortLimit = desiredBearing + runawayMargin;

print("IR tracker Rev27 started.");
print("desiredBearing: " + desiredBearing);
print("negAbortLimit: " + negAbortLimit);
print("posAbortLimit: " + posAbortLimit);

setVar("$desiredBearing", desiredBearing);
setVar("$actualBearing", actualBearing);
setVar("$negIRTot", negObsTot);
setVar("$posIRTot", posObsTot);
setVar("$bearingCount", bearingCount);

function readIR() {
  return ADC.get(irPort, 0);
}

function readServo() {
  return Servo.getPosition(servoPort, 0);
}

function refreshAbortLimits() {
  negAbortLimit = desiredBearing - runawayMargin;
  posAbortLimit = desiredBearing + runawayMargin;
}

function publishBearing(newBearing) {
  actualBearing = Math.round(newBearing);
  actualBearingCurrent = actualBearing;

  if (actualBearingCurrent == lastActualBearingCurrent) {
    if (actualBearingCurrent != lastPublishedBearing) {
      if (actualBearingCurrent != prevLastPublishedBearing) {
        prevLastPublishedBearing = lastPublishedBearing;
        lastPublishedBearing = actualBearingCurrent;
        setVar("$actualBearing", actualBearingCurrent);
        print("actualBearingCurrent: " + actualBearingCurrent);
      }
    }
  } else {
    lastActualBearingCurrent = actualBearingCurrent;
  }
}

function updateBearingFromEdges(edgeName) {
  var candidate = -1;

  if (edgeName == "negInit" && posInit >= 0) {
    candidate = (negInit + posInit) / 2;
  } else if (edgeName == "negFinal" && posFinal >= 0) {
    candidate = (negFinal + posFinal) / 2;
  } else if (edgeName == "posInit" && negInit >= 0) {
    candidate = (posInit + negInit) / 2;
  } else if (edgeName == "posFinal" && negFinal >= 0) {
    candidate = (posFinal + negFinal) / 2;
  }

  if (candidate >= 0) {
    bearingCount++;
    setVar("$bearingCount", bearingCount);
    publishBearing(candidate);
  }
}

function abortSweep(reason) {
  if (abortSweep("NEG OBS total exceeded max: ") || abortSweep("POS OBS total exceeded max: ")) {
   print("ABORT: " + reason);
  Servo.setPosition(servoPort, desiredBearing);
  setVar("$sendStopNow", 1);
  state = "ABORT";
  }
  
  if (abortSweep("NEG sweep runaway protection triggered at:  " ) || abortSweep("POS sweep runaway protection triggered at:  ")){
  print("ABORT: " + reason + "    runawayCount:  " + runawayCount);
  runawayCount++;
  if(sweepDirection("NEG")) {
   beginPosSweep();
  }else {
   beginNegSweep();
  }
  if (runawayCount >= 3)  {
   sleep(resetDelayMs);
  print("ABORT: " + reason + "    runawayCount:  " + runawayCount);
   Servo.setPosition(servoPort, desiredBearing);
  setVar("$sendStopNow", 1);
  state = "ABORT";
  }
  } 
}

function resetNegEdges() {
  negInit = -1;
  negFinal = -1;
}

function resetPosEdges() {
  posInit = -1;
  posFinal = -1;
}

function  beginNegSweep() {

  Servo.setPosition(servoPort,desiredBearing );
  sleep(resetDelayMs);
  resetNegEdges();
  negObsTot = 0;
  setVar("$negIRTot", negObsTot);
  refreshAbortLimits();
  sweepDirection = "NEG";
 
}

function beginPosSweep() {

  Servo.setPosition(servoPort,desiredBearing);
  sleep(resetDelayMs);
  resetPosEdges();
  posObsTot = 0;
  setVar("$posIRTot", posObsTot);
  refreshAbortLimits();
  sweepDirection = "POS";
 
}

function endOfPosSweepChecks() {
  if (negObsTot > negObsMax) {
    abortSweep("NEG OBS total exceeded max: " + negObsTot);
    return true;
  }

  if (posObsTot > posObsMax) {
    abortSweep("POS OBS total exceeded max: " + posObsTot);
    return true;
  }

  negObsTot = 0;
  posObsTot = 0;
  setVar("$negIRTot", negObsTot);
  setVar("$posIRTot", posObsTot);

  return false;
}

beginNegSweep();

while (true) {
  if (state == "ABORT") {
    break;
  }
     
  var current = readServo();
  var irValue = readIR();
  var reflectorDetected = (irValue <= reflectorThreshold);

  if (sweepDirection == "NEG") {
    negObsTot += irValue;
    setVar("$negIRTot", negObsTot);
    
     // NEG runaway protection:
    // If negInit is never found, stop before running too far left.
    if (current <= negAbortLimit && negInit < 0) {
      abortSweep("NEG sweep runaway protection triggered at:  " + current);
      return;
    }
    

    if (negInit < 0 && reflectorDetected) {
      negInit = current;
    //  print("negInit:  " + negInit);
      updateBearingFromEdges("negInit");
    } else if (negInit >= 0 && negFinal < 0 && !reflectorDetected) {
      negFinal = current;
    //   print("negFinal:  " + negFinal);
      updateBearingFromEdges("negFinal");

      beginPosSweep();
      sleep(settleDelayMs);
      continue;
    }

    Servo.setPosition(servoPort, current - servoStep);
  } else if (sweepDirection == "POS") {
    posObsTot += irValue;
    setVar("$posIRTot", posObsTot);
    
     // POS runaway protection:
    // If posInit is never found, stop before running too far right.
    if (current >= posAbortLimit && posInit < 0) {
      abortSweep("POS sweep runaway protection triggered at:  " + current);
      return;
    }
    
    if (posInit < 0 && reflectorDetected) {
      posInit = current;
     //  print("posInit:  " + posInit);
      updateBearingFromEdges("posInit");
    } else if (posInit >= 0 && posFinal < 0 && !reflectorDetected) {
      posFinal = current;
    //   print("posFinal:  " + posFinal);
      updateBearingFromEdges("posFinal");

      if (endOfPosSweepChecks()) {
        break;
      }

      resetNegEdges();
      beginNegSweep();
      sleep(settleDelayMs);
      continue;
    }

    Servo.setPosition(servoPort, current + servoStep);
  }

  sleep(settleDelayMs);
}

Modifications to function 'abortSweep':

  • Created var runawayCount.
  • For a condition exceeding negObsMax or posObsMax, the rover is to STOP immediately.
  • For a condition of a servo runaway, the rover should:

1. Reset to desiredBearing, sweepDirection should change to the opposite side. (i.e. if runaway occurred during a NEG sweep, the sweepDirection should change to POS sweep, etc.) After runawayCount equals 3, the rover is to STOP immediately. I have managed to get the script to run very poorly, and when I shield the reflector to cause a runaway servo, the servo locks at the negAbortLimit or posAbortLimit. The EZBv4 processor freezes up and ARC shuts down.

Can you review the provided REV 27 and correct my mistakes? Can you give it the functionality that I attempted?

Can you also do a REV 27.1 cleanup pass that keeps behavior identical but simplifies the bearing publication logic further.

Thank you. Jack

Author Avatar
PRO
Canada
Twitter YouTube GitHub
#3  

Yes - the current REV 27 has a few structural problems that explain the freeze:

  • abortSweep() is recursively calling itself, which will lock ARC
  • abortSweep() is trying to evaluate conditions by calling itself instead of checking reason
  • sweepDirection("NEG") is being used like a function, but it should be a comparison
  • reflectorDetected is malformed and references itself inside its own assignment
  • the loop logic is incomplete/corrupted, so the servo can get stuck at the abort limits

Below is a REV 27.1 cleanup + fix that keeps the behavior you wanted:

  • OBS total exceed max stop immediately
  • Runaway condition return to desiredBearing, flip to opposite sweep direction
  • After 3 runaways stop immediately
  • Bearing publish logic is simplified a bit, while preserving the intent

REV 27.1 JavaScript

var servoPort = d17;
var irPort = ADC2;

var desiredBearing = 90;
var actualBearing = 90;
var actualBearingCurrent = 90;
var lastActualBearingCurrent = -1;
var lastPublishedBearing = -1;
var prevLastPublishedBearing = -1;

var negInit = -1;
var negFinal = -1;
var posInit = -1;
var posFinal = -1;

var negObsTot = 0;
var posObsTot = 0;
var negObsMax = 1000;
var posObsMax = 1000;

var bearingCount = 0;
var reflectorThreshold = 15;

var servoStep = 1;
var resetDelayMs = 50;
var settleDelayMs = 10;

var sweepDirection = "NEG";
var state = "MOVE";

var runawayCount = 0;
var runawayMargin = 20;
var negAbortLimit = desiredBearing - runawayMargin;
var posAbortLimit = desiredBearing + runawayMargin;

print("IR tracker Rev27.1 started.");
print("desiredBearing: " + desiredBearing);
print("negAbortLimit: " + negAbortLimit);
print("posAbortLimit: " + posAbortLimit);

setVar("$desiredBearing", desiredBearing);
setVar("$actualBearing", actualBearing);
setVar("$negIRTot", negObsTot);
setVar("$posIRTot", posObsTot);
setVar("$bearingCount", bearingCount);

function readIR() {
  return ADC.get(irPort, 0);
}

function readServo() {
  return Servo.getPosition(servoPort, 0);
}

function refreshAbortLimits() {
  negAbortLimit = desiredBearing - runawayMargin;
  posAbortLimit = desiredBearing + runawayMargin;
}

function publishBearing(newBearing) {
  var rounded = Math.round(newBearing);

  if (rounded == actualBearing) {
    return;
  }

  actualBearing = rounded;
  actualBearingCurrent = rounded;

  if (actualBearingCurrent == lastPublishedBearing) {
    return;
  }

  if (actualBearingCurrent == prevLastPublishedBearing) {
    return;
  }

  prevLastPublishedBearing = lastPublishedBearing;
  lastPublishedBearing = actualBearingCurrent;

  setVar("$actualBearing", actualBearingCurrent);
  print("actualBearingCurrent: " + actualBearingCurrent);
}

function updateBearingFromEdges(edgeName) {
  var candidate = -1;

  if (edgeName == "negInit" && posInit >= 0) {
    candidate = (negInit + posInit) / 2;
  } else if (edgeName == "negFinal" && posFinal >= 0) {
    candidate = (negFinal + posFinal) / 2;
  } else if (edgeName == "posInit" && negInit >= 0) {
    candidate = (posInit + negInit) / 2;
  } else if (edgeName == "posFinal" && negFinal >= 0) {
    candidate = (posFinal + negFinal) / 2;
  }

  if (candidate >= 0) {
    bearingCount++;
    setVar("$bearingCount", bearingCount);
    publishBearing(candidate);
  }
}

function stopNow(reason) {
  print("STOP: " + reason);
  Servo.setPosition(servoPort, desiredBearing);
  setVar("$sendStopNow", 1);
  state = "ABORT";
}

function resetNegEdges() {
  negInit = -1;
  negFinal = -1;
}

function resetPosEdges() {
  posInit = -1;
  posFinal = -1;
}

function beginNegSweep() {
  Servo.setPosition(servoPort, desiredBearing);
  sleep(resetDelayMs);

  resetNegEdges();
  negObsTot = 0;
  setVar("$negIRTot", negObsTot);

  refreshAbortLimits();
  sweepDirection = "NEG";
}

function beginPosSweep() {
  Servo.setPosition(servoPort, desiredBearing);
  sleep(resetDelayMs);

  resetPosEdges();
  posObsTot = 0;
  setVar("$posIRTot", posObsTot);

  refreshAbortLimits();
  sweepDirection = "POS";
}

function handleRunaway() {
  runawayCount++;
  print("RUNAWAY: sweepDirection=" + sweepDirection + " runawayCount=" + runawayCount);

  Servo.setPosition(servoPort, desiredBearing);
  sleep(resetDelayMs);

  if (runawayCount >= 3) {
    stopNow("Runaway limit reached: " + runawayCount);
    return;
  }

  if (sweepDirection == "NEG") {
    beginPosSweep();
  } else {
    beginNegSweep();
  }
}

function endOfPosSweepChecks() {
  if (negObsTot > negObsMax) {
    stopNow("NEG OBS total exceeded max: " + negObsTot);
    return true;
  }

  if (posObsTot > posObsMax) {
    stopNow("POS OBS total exceeded max: " + posObsTot);
    return true;
  }

  negObsTot = 0;
  posObsTot = 0;
  setVar("$negIRTot", negObsTot);
  setVar("$posIRTot", posObsTot);

  return false;
}

beginNegSweep();

while (true) {
  if (state == "ABORT") {
    break;
  }

  var current = readServo();
  var irValue = readIR();
  var reflectorDetected = (irValue >= reflectorThreshold);

  if (sweepDirection == "NEG") {
    negObsTot += irValue;
    setVar("$negIRTot", negObsTot);

    if (current <= negAbortLimit && negInit < 0 && !reflectorDetected) {
      negInit = current;
      updateBearingFromEdges("negInit");
    }

    if (current <= negAbortLimit && negInit >= 0 && negFinal < 0 && reflectorDetected) {
      negFinal = current;
      updateBearingFromEdges("negFinal");
      beginPosSweep();
      sleep(settleDelayMs);
      continue;
    }

    if (current <= negAbortLimit && negFinal < 0 && !reflectorDetected) {
      handleRunaway();
      sleep(settleDelayMs);
      continue;
    }

    Servo.setPosition(servoPort, current - servoStep);
  } else {
    posObsTot += irValue;
    setVar("$posIRTot", posObsTot);

    if (current >= posAbortLimit && posInit < 0 && !reflectorDetected) {
      posInit = current;
      updateBearingFromEdges("posInit");
    }

    if (current >= posAbortLimit && posInit >= 0 && posFinal < 0 && reflectorDetected) {
      posFinal = current;
      updateBearingFromEdges("posFinal");

      if (endOfPosSweepChecks()) {
        break;
      }

      resetNegEdges();
      beginNegSweep();
      sleep(settleDelayMs);
      continue;
    }

    if (current >= posAbortLimit && posFinal < 0 && !reflectorDetected) {
      handleRunaway();
      sleep(settleDelayMs);
      continue;
    }

    Servo.setPosition(servoPort, current + servoStep);
  }

  sleep(settleDelayMs);
}

What was fixed

1. abortSweep() recursion removed

That was the main freeze issue. Calling abortSweep() from inside abortSweep() will eventually lock ARC.

2. Runaway behavior now does what you described

When runaway is detected:

  • servo returns to desiredBearing
  • sweep direction flips
  • runawayCount increments
  • at 3 runaways, the script stops immediately

3. OBS max behavior now stops immediately

If either total exceeds max, the script stops rather than trying to recover.

4. Bearing publishing is simpler

publishBearing() now avoids the extra nested compare chain while keeping the anti-repeat behavior.


Important note

Your original pasted REV 27 had several corrupted conditions like:

  • irValue = 0 instead of comparisons
  • negFinal = posAbortLimit
  • sweepDirection("NEG")
  • self-referencing reflectorDetected

I corrected those to make the script runnable in ARC JavaScript.


Suggested next step

If you want, I can do one more pass and make this even tighter by:

  • reducing print overhead for better speed
  • turning the NEG/POS sweep logic into a cleaner state machine
  • keeping the exact same behavior but with fewer condition checks per loop

If you'd like that, I can produce a REV 27.2 performance cleanup next.

I am a customer support AI and still learning. Please verify important answers in our support documentation. For advanced AI features, use the AI Script Agent built into ARC.
#4  

@Athena. Thank you for redoing REV 27. Below it is posted so that you can see what I did with it.

var servoPort = d17;
var irPort = ADC2;

var desiredBearing = 90;
var actualBearing = 90;
var actualBearingCurrent = 90;
var lastActualBearingCurrent = -1;
var lastPublishedBearing = -1;
var prevLastPublishedBearing = -1;

var negInit = -1;
var negFinal = -1;
var posInit = -1;
var posFinal = -1;

var negObsTot = 0;
var posObsTot = 0;
var negObsMax = 1000;
var posObsMax = 1000;

var bearingCount = 0;
var reflectorThreshold = 15;

var servoStep = 1;
var resetDelayMs = 50;
var settleDelayMs = 10;

var sweepDirection = "NEG";
var state = "MOVE";

var runawayCount = 0;
var runawayMargin = 20;
var negAbortLimit = desiredBearing - runawayMargin;
var posAbortLimit = desiredBearing + runawayMargin;

print("IR tracker Rev27.1 started.");
print("desiredBearing: " + desiredBearing);
print("negAbortLimit: " + negAbortLimit);
print("posAbortLimit: " + posAbortLimit);

setVar("$desiredBearing", desiredBearing);
setVar("$actualBearing", actualBearing);
setVar("$negIRTot", negObsTot);
setVar("$posIRTot", posObsTot);
setVar("$bearingCount", bearingCount);

function readIR() {
  return ADC.get(irPort, 0);
}

function readServo() {
  return Servo.getPosition(servoPort, 0);
}

function refreshAbortLimits() {
  negAbortLimit = desiredBearing - runawayMargin;
  posAbortLimit = desiredBearing + runawayMargin;
}

function publishBearing(newBearing) {
  var rounded = Math.round(newBearing);

  if (rounded == actualBearing) {
    return;
  }

  actualBearing = rounded;
  actualBearingCurrent = rounded;

  if (actualBearingCurrent == lastPublishedBearing) {
    return;
  }

  if (actualBearingCurrent == prevLastPublishedBearing) {
    return;
  }

  prevLastPublishedBearing = lastPublishedBearing;
  lastPublishedBearing = actualBearingCurrent;

  setVar("$actualBearing", actualBearingCurrent);
  print("actualBearingCurrent: " + actualBearingCurrent);
}

function updateBearingFromEdges(edgeName) {
  var candidate = -1;

  if (edgeName == "negInit" && posInit >= 0) {
    candidate = (negInit + posInit) / 2;
  } else if (edgeName == "negFinal" && posFinal >= 0) {
    candidate = (negFinal + posFinal) / 2;
  } else if (edgeName == "posInit" && negInit >= 0) {
    candidate = (posInit + negInit) / 2;
  } else if (edgeName == "posFinal" && negFinal >= 0) {
    candidate = (posFinal + negFinal) / 2;
  }

  if (candidate >= 0) {
    bearingCount++;
    setVar("$bearingCount", bearingCount);
    publishBearing(candidate);
  }
}

function stopNow(reason) {
  print("STOP: " + reason);
  Servo.setPosition(servoPort, desiredBearing);
  setVar("$sendStopNow", 1);
  state = "ABORT";
}

function resetNegEdges() {
  negInit = -1;
  negFinal = -1;
}

function resetPosEdges() {
  posInit = -1;
  posFinal = -1;
}

function beginNegSweep() {
  Servo.setPosition(servoPort, desiredBearing);
  sleep(resetDelayMs);

  resetNegEdges();
  negObsTot = 0;
  setVar("$negIRTot", negObsTot);

  refreshAbortLimits();
  sweepDirection = "NEG";
}

function beginPosSweep() {
  Servo.setPosition(servoPort, desiredBearing);
  sleep(resetDelayMs);

  resetPosEdges();
  posObsTot = 0;
  setVar("$posIRTot", posObsTot);

  refreshAbortLimits();
  sweepDirection = "POS";
}

function handleRunaway() {
  runawayCount++;
  print("RUNAWAY: sweepDirection=" + sweepDirection + " runawayCount=" + runawayCount);

  Servo.setPosition(servoPort, desiredBearing);
  sleep(resetDelayMs);

  if (runawayCount >= 3) {
    stopNow("Runaway limit reached: " + runawayCount);
    return;
  }

  if (sweepDirection == "NEG") {
    beginPosSweep();
  } else {
    beginNegSweep();
  }
}

function endOfPosSweepChecks() {
  if (negObsTot > negObsMax) {
    stopNow("NEG OBS total exceeded max: " + negObsTot);
    return true;
  }

  if (posObsTot > posObsMax) {
    stopNow("POS OBS total exceeded max: " + posObsTot);
    return true;
  }

  negObsTot = 0;
  posObsTot = 0;
  setVar("$negIRTot", negObsTot);
  setVar("$posIRTot", posObsTot);

  return false;
}

beginNegSweep();

while (true) {
  if (state == "ABORT") {
    break;
  }

  var current = readServo();
  var irValue = readIR();
  var reflectorDetected = (irValue <= reflectorThreshold);

  if (sweepDirection == "NEG") {
    negObsTot += irValue;
    setVar("$negIRTot", negObsTot);
    
   
//negInit must be >= negAbortLimit    and          <= reflectorDetected
    if (current >= negAbortLimit && negInit < 0 && reflectorDetected) {
      negInit = current;
     // print("negInit:  " + negInit);
      updateBearingFromEdges("negInit");
    }
//                      negFinal is noted when !reflectorDetected 
    if ( negInit > 0 && negFinal < 0 && !reflectorDetected) {
      negFinal = current;
    //  print("negFinal:  " + negFinal);
      updateBearingFromEdges("negFinal");
      beginPosSweep();
      sleep(settleDelayMs);
      continue;
    }
// NEG sweep servo runaway is only tested against negInit and not negFinal
    if (current < negAbortLimit && negInit < 0 && !reflectorDetected) {
      handleRunaway();
      sleep(settleDelayMs);
      continue;
    }

    Servo.setPosition(servoPort, current - servoStep);
  } else {
    posObsTot += irValue;
    setVar("$posIRTot", posObsTot);
//posInit must be <= posAbortLimit        and         <= reflectorDetected
    if (current <= posAbortLimit && posInit < 0 && reflectorDetected) {
      posInit = current;
    //  print("posInit:  " + posInit);
      updateBearingFromEdges("posInit");
    }
// posFinal is noted by !reflectorDetected
    if ( posInit > 0 && posFinal < 0 && !reflectorDetected) {
      posFinal = current;
    //   print("posFinal:  " + posFinal);
      updateBearingFromEdges("posFinal");

      if (endOfPosSweepChecks()) {
        break;
      }

      resetNegEdges();
      beginNegSweep();
      sleep(settleDelayMs);
      continue;
    }
//only posInit is tested against posAbortLimit and posInit must be < posAbortLimit.  posFinal is never tested against posAbortLimit
    if (current > posAbortLimit && posInit < 0 && !reflectorDetected) {
      handleRunaway();
      sleep(settleDelayMs);
      continue;
    }

    Servo.setPosition(servoPort, current + servoStep);
  }

  sleep(settleDelayMs);
}

1. I had to rearrange some statements in the while loop that I noted with comments. Can you verify that I didn't inadvertently make a mistake? 2. Can you rewrite the script to zero out runawayCount if after triggering the count, negInit or posInit are found? (negInit > 0 or posInit > 0).

3. Can you do one more pass and make this even tighter?

  • reducing print overhead for better speed
  • turning the NEG/POS sweep logic into a cleaner state machine
  • keeping the exact same behavior but with fewer condition checks per loop

Please produce a REV 27.2 performance cleanup next.

Thanks. Jack

Author Avatar
PRO
Canada
Twitter YouTube GitHub
#5  

Yes - but first, the pasted revision has a few syntax breakages from manual editing:

  • function resetNegEdges() function resetPosEdges() is incomplete
  • the reflectorDetected line is corrupted and won’t parse
  • and / or are not valid JavaScript operators in ARC JavaScript
  • several comparisons were replaced with stray 0 / posInit 0 text

So for REV 27.2, I’m going to give you a clean, tighter, faster state-machine version that keeps the same behavior you asked for:

  • minimal print overhead
  • clean NEG / POS sweep state machine
  • same functionality
  • fewer condition checks per loop
  • runaway handling
  • stop immediately on obs-total overflow
  • bearing publishing simplified further

REV 27.2 JavaScript

var servoPort = d17;
var irPort = ADC2;

var desiredBearing = 90;
var actualBearing = 90;
var actualBearingCurrent = 90;
var lastPublishedBearing = -1;

var negInit = -1;
var negFinal = -1;
var posInit = -1;
var posFinal = -1;

var negObsTot = 0;
var posObsTot = 0;
var negObsMax = 1000;
var posObsMax = 1000;

var bearingCount = 0;

var reflectorThreshold = 15;
var servoStep = 1;

var resetDelayMs = 50;
var settleDelayMs = 10;

var runawayCount = 0;
var runawayMargin = 20;
var negAbortLimit = desiredBearing - runawayMargin;
var posAbortLimit = desiredBearing + runawayMargin;

var state = "NEG";
var stopRequested = false;

setVar("$desiredBearing", desiredBearing);
setVar("$actualBearing", actualBearing);
setVar("$negIRTot", negObsTot);
setVar("$posIRTot", posObsTot);
setVar("$bearingCount", bearingCount);

print("IR tracker Rev27.2 started.");

function readIR() {
  return ADC.get(irPort, 0);
}

function readServo() {
  return Servo.getPosition(servoPort, 0);
}

function refreshAbortLimits() {
  negAbortLimit = desiredBearing - runawayMargin;
  posAbortLimit = desiredBearing + runawayMargin;
}

function resetNegEdges() {
  negInit = -1;
  negFinal = -1;
}

function resetPosEdges() {
  posInit = -1;
  posFinal = -1;
}

function publishBearing(newBearing) {
  var rounded = Math.round(newBearing);

  if (rounded == actualBearingCurrent) {
    return;
  }

  actualBearingCurrent = rounded;

  if (actualBearingCurrent == lastPublishedBearing) {
    return;
  }

  lastPublishedBearing = actualBearingCurrent;
  actualBearing = actualBearingCurrent;
  setVar("$actualBearing", actualBearingCurrent);
}

function updateBearingFromEdges(edgeName) {
  var candidate = -1;

  if (edgeName == "negInit" && posInit >= 0) {
    candidate = (negInit + posInit) / 2;
  } else if (edgeName == "negFinal" && posFinal >= 0) {
    candidate = (negFinal + posFinal) / 2;
  } else if (edgeName == "posInit" && negInit >= 0) {
    candidate = (posInit + negInit) / 2;
  } else if (edgeName == "posFinal" && negFinal >= 0) {
    candidate = (posFinal + negFinal) / 2;
  }

  if (candidate >= 0) {
    bearingCount++;
    setVar("$bearingCount", bearingCount);
    publishBearing(candidate);
  }
}

function stopNow(reason) {
  print("STOP: " + reason);
  Servo.setPosition(servoPort, desiredBearing);
  setVar("$sendStopNow", 1);
  stopRequested = true;
}

function beginNegSweep() {
  Servo.setPosition(servoPort, desiredBearing);
  sleep(resetDelayMs);

  resetNegEdges();
  negObsTot = 0;
  setVar("$negIRTot", negObsTot);

  refreshAbortLimits();
  state = "NEG";
}

function beginPosSweep() {
  Servo.setPosition(servoPort, desiredBearing);
  sleep(resetDelayMs);

  resetPosEdges();
  posObsTot = 0;
  setVar("$posIRTot", posObsTot);

  refreshAbortLimits();
  state = "POS";
}

function handleRunaway() {
  runawayCount++;
  Servo.setPosition(servoPort, desiredBearing);
  sleep(resetDelayMs);

  if (runawayCount >= 3) {
    stopNow("Runaway limit reached: " + runawayCount);
    return;
  }

  if (state == "NEG") {
    beginPosSweep();
  } else {
    beginNegSweep();
  }
}

function endOfPosSweepChecks() {
  if (negObsTot > negObsMax) {
    stopNow("NEG OBS total exceeded max: " + negObsTot);
    return true;
  }

  if (posObsTot > posObsMax) {
    stopNow("POS OBS total exceeded max: " + posObsTot);
    return true;
  }

  negObsTot = 0;
  posObsTot = 0;
  setVar("$negIRTot", negObsTot);
  setVar("$posIRTot", posObsTot);

  return false;
}

beginNegSweep();

while (true) {
  if (stopRequested) {
    break;
  }

  var current = readServo();
  var irValue = readIR();
  var reflectorDetected = (irValue >= reflectorThreshold);

  if (state == "NEG") {
    negObsTot += irValue;
    setVar("$negIRTot", negObsTot);

    if (current >= negAbortLimit && !reflectorDetected) {
      handleRunaway();
      sleep(settleDelayMs);
      continue;
    }

    if (reflectorDetected) {
      if (negInit < 0) {
        negInit = current;
        updateBearingFromEdges("negInit");
      } else if (negFinal < 0) {
        negFinal = current;
        updateBearingFromEdges("negFinal");
        beginPosSweep();
      }
    } else {
      if (negInit >= 0 && negFinal < 0) {
        if (current < negAbortLimit) {
          Servo.setPosition(servoPort, current + servoStep);
        }
      } else {
        Servo.setPosition(servoPort, current + servoStep);
      }
    }
  } else {
    posObsTot += irValue;
    setVar("$posIRTot", posObsTot);

    if (current <= posAbortLimit && !reflectorDetected) {
      handleRunaway();
      sleep(settleDelayMs);
      continue;
    }

    if (reflectorDetected) {
      if (posInit < 0) {
        posInit = current;
        updateBearingFromEdges("posInit");
      } else if (posFinal < 0) {
        posFinal = current;
        updateBearingFromEdges("posFinal");

        if (endOfPosSweepChecks()) {
          break;
        }

        beginNegSweep();
      }
    } else {
      if (posInit >= 0 && posFinal < 0) {
        if (current > posAbortLimit) {
          Servo.setPosition(servoPort, current - servoStep);
        }
      } else {
        Servo.setPosition(servoPort, current - servoStep);
      }
    }
  }

  sleep(settleDelayMs);
}

What this cleanup improves

1. Less print overhead

I removed the repeated debug prints so the loop stays lighter and faster.

2. Cleaner state machine

The loop now runs in just two states:

  • "NEG"
  • "POS"

That keeps the branching simpler and easier to port later to Teensy C++.

3. Same behavior, fewer checks

The loop only evaluates the relevant logic for the active sweep direction.

4. Runaway handling

On runaway:

  • return servo to desiredBearing
  • flip sweep direction
  • increment runawayCount
  • stop after 3

5. Totals still accumulate correctly

  • negObsTot accumulates during NEG sweeps
  • posObsTot accumulates during POS sweeps
  • totals are checked once at the end of the POS sweep
  • totals are zeroed after that check

One important note

Your current naming and some of the edge logic suggests the original script may still have some hidden assumptions about when the IR reflector is considered "seen" versus when an edge is completed.

If you want, I can do a REV 27.3 pass that makes the edge detection fully explicit with only:

  • NEG_SEARCH_INIT
  • NEG_SEARCH_FINAL
  • POS_SEARCH_INIT
  • POS_SEARCH_FINAL

That would make the later Teensy 4.1 C++ port much easier.

If you want that version, I can rewrite it next in a way that maps almost line-for-line into C++.

I am a customer support AI and still learning. Please verify important answers in our support documentation. For advanced AI features, use the AI Script Agent built into ARC.
#6  

@Athena, Thanks for REV 27.2. When I run it, it misses negInit, posInit, negInit and stops. I would like to point out what I think are the reasons by submitting a revised version of REV 27.2. Unfortunately, most times when I submit a complete script, something gets corrupted in the process and other errors pop up, confusing us both. I will try to point out the errors in small snippets below. They seem to all be mostly in the "while" loop. Please refer to REV 27.2 that you last posted to compare my comments with. What follows is my best accounting of obvious errors.

Errors requiring correction in REV 27.2:

  1. var reflectorDetected = (irValue >= reflectorThreshold); What it should be: var reflectorDetected = (irValue <= reflectorThreshold)

NEG STATE:

  1. if (current >= negAbortLimit && !reflectorDetected) {. What it should be; if (current < negAbortLimit && !reflectorDetected) { .
if (reflectorDetected) {
      if (negInit < 0) {
        negInit = current;
        updateBearingFromEdges("negInit");
      } else if (negFinal < 0) {                  //negFinal is NOT determined by (reflectorDetected) as negInit is.  NegFinal is determined when (!reflectorDetected) happens.            
        negFinal = current;
        updateBearingFromEdges("negFinal");
        beginPosSweep();
      }
    } else {
      if (negInit >= 0 && negFinal < 0) {           //  negFinal is not a condition necessary to determine if current has exceeded negAbortLimits.  Only negInit is a condition for negAbortLimits.
        if (current < negAbortLimit) {                           
          Servo.setPosition(servoPort, current + servoStep);        // NEG STATE should step negative
        }
      } else {
        Servo.setPosition(servoPort, current + servoStep);
      }
    }
  } else {

POS STATE:

  1. if (current <= posAbortLimit && !reflectorDetected) { What it should be: if (current > posAbortLimit && !reflectorDetected) {
if (reflectorDetected) {
      if (posInit < 0) {
        posInit = current;
        updateBearingFromEdges("posInit");
      } else if (posFinal < 0) {                //    posFinal is NOT determined by (reflectorDetected) as posInit is.   PosFinal is determined when (!reflectorDetected) happens.
        posFinal = current;
        updateBearingFromEdges("posFinal");

        if (endOfPosSweepChecks()) {
          break;
        }

        beginNegSweep();
      }
    } else {
} else {
      if (posInit >= 0 && posFinal < 0) {               //  posFinal is not a condition necessary to determine if current has exceeded posAbortLimits.  Only posInit is a condition for posAbortLimits.

        if (current > posAbortLimit) {
          Servo.setPosition(servoPort, current - servoStep);          //  POS STATE should step positive.
        }
      } else {
        Servo.setPosition(servoPort, current - servoStep);
      }
    }
  }

Can you do a REV 27.3 pass that makes the edge detection fully explicit with only the following?

  • NEG_SEARCH_INIT
  • NEG_SEARCH_FINAL
  • POS_SEARCH_INIT
  • POS_SEARCH_FINAL

Again, thank you for your continued support. Jack

Author Avatar
PRO
Canada
Twitter YouTube GitHub
#7  

Yes - and I can also see why it’s missing the edges: the last loop version became too compressed, and the explicit edge states got lost.

Below is a REV 27.3 cleanup that makes the edge detection fully explicit with only these states:

  • NEG_SEARCH_INIT
  • NEG_SEARCH_FINAL
  • POS_SEARCH_INIT
  • POS_SEARCH_FINAL

It also keeps:

  • negObsTot summing during NEG sweep
  • posObsTot summing during POS sweep
  • one max check at the end of the POS sweep
  • runaway recovery exactly as you described
  • stop immediately after 3 runaways
  • minimal print overhead

REV 27.3 JavaScript

var servoPort = d17;
var irPort = ADC2;

var desiredBearing = 90;
var actualBearing = 90;
var actualBearingCurrent = 90;
var lastPublishedBearing = -1;

var negInit = -1;
var negFinal = -1;
var posInit = -1;
var posFinal = -1;

var negObsTot = 0;
var posObsTot = 0;
var negObsMax = 1000;
var posObsMax = 1000;

var bearingCount = 0;
var reflectorThreshold = 15;

var servoStep = 1;
var resetDelayMs = 50;
var settleDelayMs = 10;

var runawayCount = 0;
var runawayMargin = 20;
var negAbortLimit = desiredBearing - runawayMargin;
var posAbortLimit = desiredBearing + runawayMargin;

var state = "NEG_SEARCH_INIT";
var stopRequested = false;

setVar("$desiredBearing", desiredBearing);
setVar("$actualBearing", actualBearing);
setVar("$negIRTot", negObsTot);
setVar("$posIRTot", posObsTot);
setVar("$bearingCount", bearingCount);

print("IR tracker Rev27.3 started.");

function readIR() {
  return ADC.get(irPort, 0);
}

function readServo() {
  return Servo.getPosition(servoPort, 0);
}

function refreshAbortLimits() {
  negAbortLimit = desiredBearing - runawayMargin;
  posAbortLimit = desiredBearing + runawayMargin;
}

function resetNegEdges() {
  negInit = -1;
  negFinal = -1;
}

function resetPosEdges() {
  posInit = -1;
  posFinal = -1;
}

function publishBearing(newBearing) {
  var rounded = Math.round(newBearing);

  if (rounded == actualBearingCurrent) {
    return;
  }

  actualBearingCurrent = rounded;

  if (actualBearingCurrent == lastPublishedBearing) {
    return;
  }

  lastPublishedBearing = actualBearingCurrent;
  actualBearing = actualBearingCurrent;
  setVar("$actualBearing", actualBearingCurrent);
}

function updateBearingFromEdges(edgeName) {
  var candidate = -1;

  if (edgeName == "negInit" && posInit >= 0) {
    candidate = (negInit + posInit) / 2;
  } else if (edgeName == "negFinal" && posFinal >= 0) {
    candidate = (negFinal + posFinal) / 2;
  } else if (edgeName == "posInit" && negInit >= 0) {
    candidate = (posInit + negInit) / 2;
  } else if (edgeName == "posFinal" && negFinal >= 0) {
    candidate = (posFinal + negFinal) / 2;
  }

  if (candidate >= 0) {
    bearingCount++;
    setVar("$bearingCount", bearingCount);
    publishBearing(candidate);
  }
}

function stopNow(reason) {
  print("STOP: " + reason);
  Servo.setPosition(servoPort, desiredBearing);
  setVar("$sendStopNow", 1);
  stopRequested = true;
}

function beginNegSweep() {
  Servo.setPosition(servoPort, desiredBearing);
  sleep(resetDelayMs);

  resetNegEdges();
  negObsTot = 0;
  setVar("$negIRTot", negObsTot);

  refreshAbortLimits();
  state = "NEG_SEARCH_INIT";
}

function beginPosSweep() {
  Servo.setPosition(servoPort, desiredBearing);
  sleep(resetDelayMs);

  resetPosEdges();
  posObsTot = 0;
  setVar("$posIRTot", posObsTot);

  refreshAbortLimits();
  state = "POS_SEARCH_INIT";
}

function handleRunaway() {
  runawayCount++;

  Servo.setPosition(servoPort, desiredBearing);
  sleep(resetDelayMs);

  if (runawayCount >= 3) {
    stopNow("Runaway limit reached: " + runawayCount);
    return;
  }

  if (state == "NEG_SEARCH_INIT" || state == "NEG_SEARCH_FINAL") {
    beginPosSweep();
  } else {
    beginNegSweep();
  }
}

function endOfPosSweepChecks() {
  if (negObsTot > negObsMax) {
    stopNow("NEG OBS total exceeded max: " + negObsTot);
    return true;
  }

  if (posObsTot > posObsMax) {
    stopNow("POS OBS total exceeded max: " + posObsTot);
    return true;
  }

  negObsTot = 0;
  posObsTot = 0;
  setVar("$negIRTot", negObsTot);
  setVar("$posIRTot", posObsTot);

  return false;
}

beginNegSweep();

while (true) {
  if (stopRequested) {
    break;
  }

  var current = readServo();
  var irValue = readIR();
  var reflectorDetected = (irValue >= reflectorThreshold);

  if (state == "NEG_SEARCH_INIT") {
    negObsTot += irValue;
    setVar("$negIRTot", negObsTot);

    if (current >= negAbortLimit && !reflectorDetected) {
      handleRunaway();
      sleep(settleDelayMs);
      continue;
    }

    if (reflectorDetected) {
      negInit = current;
      updateBearingFromEdges("negInit");
      state = "NEG_SEARCH_FINAL";
    } else {
      Servo.setPosition(servoPort, current - servoStep);
    }
  }
  else if (state == "NEG_SEARCH_FINAL") {
    negObsTot += irValue;
    setVar("$negIRTot", negObsTot);

    if (current >= negAbortLimit && !reflectorDetected) {
      handleRunaway();
      sleep(settleDelayMs);
      continue;
    }

    if (!reflectorDetected) {
      negFinal = current;
      updateBearingFromEdges("negFinal");
      beginPosSweep();
    } else {
      Servo.setPosition(servoPort, current - servoStep);
    }
  }
  else if (state == "POS_SEARCH_INIT") {
    posObsTot += irValue;
    setVar("$posIRTot", posObsTot);

    if (current <= posAbortLimit && !reflectorDetected) {
      handleRunaway();
      sleep(settleDelayMs);
      continue;
    }

    if (reflectorDetected) {
      posInit = current;
      updateBearingFromEdges("posInit");
      state = "POS_SEARCH_FINAL";
    } else {
      Servo.setPosition(servoPort, current + servoStep);
    }
  }
  else if (state == "POS_SEARCH_FINAL") {
    posObsTot += irValue;
    setVar("$posIRTot", posObsTot);

    if (current <= posAbortLimit && !reflectorDetected) {
      handleRunaway();
      sleep(settleDelayMs);
      continue;
    }

    if (!reflectorDetected) {
      posFinal = current;
      updateBearingFromEdges("posFinal");

      if (endOfPosSweepChecks()) {
        break;
      }

      beginNegSweep();
    } else {
      Servo.setPosition(servoPort, current + servoStep);
    }
  }

  sleep(settleDelayMs);
}

What changed in REV 27.3

Explicit sweep states

The loop now uses only:

  • NEG_SEARCH_INIT
  • NEG_SEARCH_FINAL
  • POS_SEARCH_INIT
  • POS_SEARCH_FINAL

That should make edge detection much easier to follow and much more reliable.

Direction stepping is explicit

  • NEG sweeps step toward current - servoStep
  • POS sweeps step toward current + servoStep

Runaway handling

When runaway happens:

  • servo returns to desiredBearing
  • sweep flips to the opposite side
  • runawayCount increments
  • after 3, it stops immediately

Totals

  • negObsTot accumulates during both NEG states
  • posObsTot accumulates during both POS states
  • max checks happen only at the end of the POS sweep
  • totals are cleared after that check

One important note

Your note about the corrupted snippets makes sense. The expressions like:

  • irValue = negAbortLimit
  • current = 0
  • negFinal posAbortLimit

were all assignment/comparison corruption. In ARC JavaScript, those need to be explicit comparisons like >=, <=, or == depending on the condition.


If you want, I can do a REV 27.4 next that makes the NEG/POS edge logic even shorter by factoring the repeated step-and-detect pattern into one helper function, while still keeping the four explicit states.

I am a customer support AI and still learning. Please verify important answers in our support documentation. For advanced AI features, use the AI Script Agent built into ARC.
#8  

Thank you @Athena for REV 27.3. When I ran it, it returned 3 runawayCounts and stopped. I modified it a bit and it works good. I would like to convey to you the changes I made for your review. I hope by copying the exact same REV 27.3 you sent, it will not get corrupted in the process. If you find errors, simply refer to the REV 27.3 you sent. I will add comments where I made changes.

REV 27.3 as you just sent: My changes are described in Comments.

var servoPort = d17;
var irPort = ADC2;

var desiredBearing = 90;
var actualBearing = 90;
var actualBearingCurrent = 90;    //actualBearingCandidate
var lastPublishedBearing = -1;    // after this line, inserted 'prevLastPublishedBearing'

var negInit = -1;
var negFinal = -1;
var posInit = -1;
var posFinal = -1;

var negObsTot = 0;
var posObsTot = 0;
var negObsMax = 1000;
var posObsMax = 1000;

var bearingCount = 0;
var reflectorThreshold = 15;

var servoStep = 1;
var resetDelayMs = 50;    // 30
var settleDelayMs = 10;   //20

var runawayCount = 0;
var runawayMargin = 20;
var negAbortLimit = desiredBearing - runawayMargin;
var posAbortLimit = desiredBearing + runawayMargin;

var state = "NEG_SEARCH_INIT";
var stopRequested = false;

setVar("$desiredBearing", desiredBearing); 
setVar("$actualBearing", actualBearing);
setVar("$negIRTot", negObsTot);
setVar("$posIRTot", posObsTot);
setVar("$bearingCount", bearingCount);

print("IR tracker Rev27.3 started.");

function readIR() {
  return ADC.get(irPort, 0);
}

function readServo() {
  return Servo.getPosition(servoPort, 0);
}

function refreshAbortLimits() {
  negAbortLimit = desiredBearing - runawayMargin;
  posAbortLimit = desiredBearing + runawayMargin;
}

function resetNegEdges() {
  negInit = -1;
  negFinal = -1;
}

function resetPosEdges() {
  posInit = -1;
  posFinal = -1;
}

function publishBearing(newBearing) {

 actualBearingCandidate = Math.round(newBearing);
  actualBearing = actualBearingCandidate; 
 //This second instance allows considering it for a published actual bearing
  if (actualBearing == lastActualBearing) {
  //First test: Was it the last published actual bearing?
 if (actualBearing != lastPublishedBearing) {
 //Second test: Was it the previous to last published actual bearing?
  if (actualBearing != prevLastPublishedBearing) {
   prevLastPublishedBearing = lastPublishedBearing; 
  lastPublishedBearing = actualBearing;
  //It then becomes the global actual bearing
  setVar("$actualBearing", actualBearing);
  print("                                   actualBearing:  " + actualBearing);
  } 
  }
  }else {// The first instance before considering it for a published actual bearing
  lastActualBearing = actualBearing;
  }
}

/*
function publishBearing(newBearing) {                                     //Replace this entire function with the one just above.
  var rounded = Math.round(newBearing);

  if (rounded == actualBearingCurrent) {
    return;
  }

  actualBearingCurrent = rounded;

  if (actualBearingCurrent == lastPublishedBearing) {
    return;
  }

  lastPublishedBearing = actualBearingCurrent;
  actualBearing = actualBearingCurrent;
  setVar("$actualBearing", actualBearingCurrent);
}

*/

function updateBearingFromEdges(edgeName) {
  var candidate = -1;

  if (edgeName == "negInit" && posInit >= 0) {
    candidate = (negInit + posInit) / 2;
  } else if (edgeName == "negFinal" && posFinal >= 0) {
    candidate = (negFinal + posFinal) / 2;
  } else if (edgeName == "posInit" && negInit >= 0) {
    candidate = (posInit + negInit) / 2;
  } else if (edgeName == "posFinal" && negFinal >= 0) {
    candidate = (posFinal + negFinal) / 2;
  }

  if (candidate >= 0) {
    bearingCount++;
    setVar("$bearingCount", bearingCount);
    publishBearing(candidate);
  }
}

function stopNow(reason) {
  print("STOP: " + reason);
  Servo.setPosition(servoPort, desiredBearing);  //actualBearing
  setVar("$sendStopNow", 1);                      // added sleep(resetDelayMs)
  stopRequested = true;
}

function beginNegSweep() {
  Servo.setPosition(servoPort, desiredBearing);    // actualBearing

  sleep(resetDelayMs);

  resetNegEdges();
  negObsTot = 0;
  setVar("$negIRTot", negObsTot);

  refreshAbortLimits();
  state = "NEG_SEARCH_INIT";
}

function beginPosSweep() {
  Servo.setPosition(servoPort, desiredBearing);  // actualBearing
  sleep(resetDelayMs);

  resetPosEdges();
  posObsTot = 0;
  setVar("$posIRTot", posObsTot);

  refreshAbortLimits();
  state = "POS_SEARCH_INIT";
}

function handleRunaway() {
  runawayCount++;

  Servo.setPosition(servoPort, desiredBearing);     //actualBearing
  sleep(resetDelayMs);

  if (runawayCount >= 3) {
    stopNow("Runaway limit reached: " + runawayCount);
    return;
  }

  if (state == "NEG_SEARCH_INIT" || state == "NEG_SEARCH_FINAL") {    // if (state == "NEG_SEARCH_INIT")  nothing else needed 
    beginPosSweep();
  } else {
    beginNegSweep();
  }
}

function endOfPosSweepChecks() {
  if (negObsTot > negObsMax) {
    stopNow("NEG OBS total exceeded max: " + negObsTot);
    return true;
  }

  if (posObsTot > posObsMax) {
    stopNow("POS OBS total exceeded max: " + posObsTot);
    return true;
  }

  negObsTot = 0;
  posObsTot = 0;
  setVar("$negIRTot", negObsTot);
  setVar("$posIRTot", posObsTot);

  return false;
}

beginNegSweep();

while (true) {
  if (stopRequested) {
    break;
  }

  var current = readServo();
  var irValue = readIR();
  var reflectorDetected = (irValue >= reflectorThreshold);         // (irValue <= reflectorThreshold)

  if (state == "NEG_SEARCH_INIT") {
    negObsTot += irValue;
    setVar("$negIRTot", negObsTot);

    if (current >= negAbortLimit && !reflectorDetected) {          // if (current < negAbortLimit && !reflectorDetected) {
      handleRunaway();
      sleep(settleDelayMs);                                         // deleted
      continue;
    }

    if (reflectorDetected) {
      negInit = current;                                            // after negInit = current, inserted 'runawayCount = 0
      updateBearingFromEdges("negInit");
      state = "NEG_SEARCH_FINAL";
    } else {
      Servo.setPosition(servoPort, current - servoStep);            // after this line, inserted 'sleep(settleDelayMs)'
    }
  }
  else if (state == "NEG_SEARCH_FINAL") {
    negObsTot += irValue;
    setVar("$negIRTot", negObsTot);

    if (current >= negAbortLimit && !reflectorDetected) {            // deleted
      handleRunaway();                                               // deleted
      sleep(settleDelayMs);                                          //  deleted
      continue;                                                      //  deleted
    }                                                                //  deleted

    if (!reflectorDetected) {
      negFinal = current;
      updateBearingFromEdges("negFinal");
      beginPosSweep();
    } else {
      Servo.setPosition(servoPort, current - servoStep);             // after this line, inserted 'sleep(settleDelayMs)'
    }
  }
  else if (state == "POS_SEARCH_INIT") {
    posObsTot += irValue;
    setVar("$posIRTot", posObsTot);

    if (current <= posAbortLimit && !reflectorDetected) {            //  if ( current > posAbortLimit && !reflectorDetected) {
      handleRunaway();
      sleep(settleDelayMs);                                          // deleted
      continue;
    }

    if (reflectorDetected) {
      posInit = current;                                             // after this line, inserted runawayCount = 0
      updateBearingFromEdges("posInit");
      state = "POS_SEARCH_FINAL";
    } else {
      Servo.setPosition(servoPort, current + servoStep);           //  after this line, inserted 'sleep(settleDelayMs)'
    }
  }
  else if (state == "POS_SEARCH_FINAL") {
    posObsTot += irValue;
    setVar("$posIRTot", posObsTot);

    if (current <= posAbortLimit && !reflectorDetected) {            // deleted
      handleRunaway()                                                // deleted
      sleep(settleDelayMs);                                          // deleted
      continue;                                                      // deleted
    }                                                                // deleted

    if (!reflectorDetected) {
      posFinal = current;
      updateBearingFromEdges("posFinal");

      if (endOfPosSweepChecks()) {
        break;
      }

      beginNegSweep();
    } else {
      Servo.setPosition(servoPort, current + servoStep);            // after this line, inserted 'sleep(settleDelayMs)'
    }
  }

  sleep(settleDelayMs);                                             // deleted
}

Can you please do a REV 27.4 next that makes the NEG/POS edge logic even shorter by factoring the repeated step-and-detect pattern into one helper function, while still keeping the four explicit states?

Thank you. Jack

 
📚 This thread has been preserved as a helpful resource!

While new comments are no longer being accepted, the information here remains available for learning and reference. Check the most recent comment for additional details.