@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:
- Summing IR values for negObsTot should begin when the NEG sweep begins searching for negInit.
- Summing IR values for posObsTot should begin when the POS sweep begins searching for posInit.
- 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);
}

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
negObsTotnow starts summing when the NEG sweep begins searching fornegInitposObsTotnow starts summing when the POS sweep begins searching forposInitnegObsTot/posObsTothappen only once at the end of each POS sweepI also removed a few obvious logic corruption issues from the pasted REV 26 sample:
=where comparisons were clearly intendedlastPublishedBearingREV 27 JavaScript
Notes
setVar()One small question
Do you want the totals to accumulate:
negInit/posInitare found and untilnegFinal/posFinalare 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
@Athena. I need help with the following script REV 27 posted below:
Modifications to function 'abortSweep':
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
Yes - the current REV 27 has a few structural problems that explain the freeze:
abortSweep()is recursively calling itself, which will lock ARCabortSweep()is trying to evaluate conditions by calling itself instead of checkingreasonsweepDirection("NEG")is being used like a function, but it should be a comparisonreflectorDetectedis malformed and references itself inside its own assignmentBelow is a REV 27.1 cleanup + fix that keeps the behavior you wanted:
desiredBearing, flip to opposite sweep directionREV 27.1 JavaScript
What was fixed
1.
abortSweep()recursion removedThat was the main freeze issue. Calling
abortSweep()from insideabortSweep()will eventually lock ARC.2. Runaway behavior now does what you described
When runaway is detected:
desiredBearingrunawayCountincrements3. 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 = 0instead of comparisonsnegFinal = posAbortLimitsweepDirection("NEG")reflectorDetectedI 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:
If you'd like that, I can produce a REV 27.2 performance cleanup next.
@Athena. Thank you for redoing REV 27. Below it is posted so that you can see what I did with it.
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?
Please produce a REV 27.2 performance cleanup next.
Thanks. Jack
Yes - but first, the pasted revision has a few syntax breakages from manual editing:
function resetNegEdges() function resetPosEdges()is incompletereflectorDetectedline is corrupted and won’t parseand/orare not valid JavaScript operators in ARC JavaScript0/posInit 0textSo 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:
REV 27.2 JavaScript
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:
desiredBearingrunawayCount5. Totals still accumulate correctly
negObsTotaccumulates during NEG sweepsposObsTotaccumulates during POS sweepsOne 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_INITNEG_SEARCH_FINALPOS_SEARCH_INITPOS_SEARCH_FINALThat 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++.
@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:
NEG STATE:
POS STATE:
Can you do a REV 27.3 pass that makes the edge detection fully explicit with only the following?
NEG_SEARCH_INITNEG_SEARCH_FINALPOS_SEARCH_INITPOS_SEARCH_FINALAgain, thank you for your continued support. Jack
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_INITNEG_SEARCH_FINALPOS_SEARCH_INITPOS_SEARCH_FINALIt also keeps:
REV 27.3 JavaScript
What changed in REV 27.3
Explicit sweep states
The loop now uses only:
NEG_SEARCH_INITNEG_SEARCH_FINALPOS_SEARCH_INITPOS_SEARCH_FINALThat should make edge detection much easier to follow and much more reliable.
Direction stepping is explicit
current - servoStepcurrent + servoStepRunaway handling
When runaway happens:
desiredBearingrunawayCountincrementsTotals
negObsTotaccumulates during both NEG statesposObsTotaccumulates during both POS statesOne important note
Your note about the corrupted snippets makes sense. The expressions like:
irValue = negAbortLimitcurrent = 0negFinal posAbortLimitwere 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.
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.
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