@Athena,
Please review the JavaScript code below.
Context:
- This script (IR_TRACKER_v20) runs on the EZ-B v4 (EZBv4) firmware as a JavaScript script that is part of my ARC project.
- The hardware (servo and IR sensor) is connected through the EZB (not directly to the PC). The servo is on port 17 and the IR sensor is read from ADC channel 2.
- The code treats IR <= 15 as a reflector "hit" and IR >= 16 as an obstruction. It updates values back to ARC via setVar() and uses the EZB servo and ADC APIs.
- Ultimately I want to transpose this JavaScript to C++ to run on a Teensy 4.1. For the review, please keep portability and timing/ADC/servo considerations in mind, but do not implement the port yet.
Requested review items:
- Check the overall construction and organization for efficiency (loops, variable usage, control flow).
- If changes are needed, please update the code while preserving the stated objectives and behavior.
- Keep in mind the eventual goal of porting this logic to C++ for a Teensy 4.1.
Thanks, Jack
var negIR = 0;
var posIR = 0;
var negIRTot = 0;
var posIRTot = 0;
setVar("$posIRTot", posIRTot);
setVar("$negIRTot", negIRTot);
var negHit = 0;
var posHit = 0;
var negInit = 90;
var negFinal = 90;
var lastNegInit = negInit;
var lastNegFinal = negFinal;
var posInit = 90;
var posFinal = 90;
var lastPosInit = posInit;
var lastPosFinal = posFinal;
var servoStart = 90;
var actualBearing = 90;
var desiredBearing = 90;
var actualBearingCurrent = 90;
var lastActualBearingCurrent = 90;
var lastAB = 90;
var prevLastAB = 90;
var bearingCount = 0;
var equalCount = 0;
var sleepVal = 20;
/* IR_TRACKER_v20 operates a single servo and IR sensor to track a single reflector across the room.
The servo swings back and forth, equal degrees from the desiredBearing. It collects IR readings
that meet the criteria of a reflector (IR <= 15), and those that do not (IR >= 16). IR >= 16 are
considered obstructions. When an IR reading exceeds an obstruction threshold, the robot will stop,
run a separate script to precisely locate the bearing and distance to the offending obstruction,
maneuver around it, and continue on its path relative to the reflector.
*/
// INITIAL PRINT
setVar("$sendActualBearingNow", 1);
print(" AC===actualBearingCurrent: " + actualBearingCurrent);
// Only called from the main loop when enough criteria are met to possibly change actualBearingCurrent.
function courseDetermination() {
// bearingCount > 5 keeps actualBearingCurrent from being computed until both
// negScanInit/negScanFinal and posScanInit/posScanFinal have run once.
if (bearingCount > 5) {
// lastActualBearing (lastAB) and prevLastActualBearing (prevLastAB) are to prevent
// unnecessary cycling back and forth from 'close' actualBearingCurrent.
if (actualBearingCurrent != lastAB) {
if (actualBearingCurrent != prevLastAB) {
print(" AC===actualBearingCurrent: " + actualBearingCurrent + " prevLastAB: " + prevLastAB);
prevLastAB = lastAB;
lastAB = actualBearingCurrent;
}
}
}
}
// Part 1 of a 2-part sequence for swinging the servo in a Neg direction (Right).
// Called at the beginning of the swing Right (starts at posScanInit position). Ends at first IR <= 15.
function negScanInit() {
servoStart = Servo.getPosition(17, 0);
for (a = 0; a < 30; a++) {
negIR = ADC.get(2, 0);
sleep(20); // previously 10
if (negIR <= 15) {
negInit = Servo.getPosition(17, 0);
print(" negInit: " + negInit);
negHit = 1;
actualBearingCurrent = Math.round(negInit + ((posInit - negInit) / 2));
if (actualBearingCurrent == lastActualBearingCurrent) {
equalCount++;
} else {
equalCount = 0;
}
bearingCount++;
setVar("$bearingCount", bearingCount);
setVar("$posIRTot", posIRTot);
posIRTot = 0;
lastActualBearingCurrent = actualBearingCurrent;
return;
} else {
if (Servo.getPosition(17, 0) >= desiredBearing && Servo.getPosition(17, 0) < (posFinal + 5)) {
posIRTot = posIRTot + negIR;
} else if (Servo.getPosition(17, 0) < desiredBearing && Servo.getPosition(17, 0) > (negFinal - 5)) {
negIRTot = negIRTot + negIR;
}
}
Servo.decrement(17, 1);
}
// servoStart only sequences if no IR <= 15 is detected.
Servo.setPosition(17, servoStart);
sleep(50);
sleepVal = 10;
print("neg-servoStart: " + servoStart);
}
// Part 2 of a 2-part sequence swinging the servo in a Neg direction (Right).
// Called only after part 1 noted a negHit (IR <= 15). Continues until IR >= 16.
// The servo resets to negInit (to save time) before continuing to posScanInit.
function negScanFinal() {
for (aa = 0; aa < 30; aa++) {
negIR = ADC.get(2, 0);
sleep(20); // previously 10
if (negIR >= 16) {
negIRTot = negIRTot + negIR;
negFinal = Servo.getPosition(17, 0);
print(" negFinal: " + negFinal);
actualBearingCurrent = Math.round(negFinal + ((posFinal - negFinal) / 2));
bearingCount++;
setVar("$bearingCount", bearingCount);
Servo.setPosition(17, negInit);
posIR = 0;
sleep(10);
return;
}
Servo.decrement(17, 1);
}
}
// Part 1 of a 2-part sequence for swinging the servo in a Pos direction (Left).
// Called at the beginning of the swing Left (starts at negScanInit position). Ends at first IR <= 15.
function posScanInit() {
servoStart = Servo.getPosition(17, 0);
for (b = 0; b < 30; b++) {
posIR = ADC.get(2, 0);
sleep(sleepVal); // previously 10
if (posIR <= 15) {
posInit = Servo.getPosition(17, 0);
print(" posInit: " + posInit);
posHit = 1;
actualBearingCurrent = Math.round(posInit - ((posInit - negInit) / 2));
bearingCount++;
setVar("$bearingCount", bearingCount);
setVar("$negIRTot", negIRTot);
negIRTot = 0;
sleepVal = 20;
return;
} else {
if (Servo.getPosition(17, 0) < desiredBearing && Servo.getPosition(17, 0) > (negFinal - 5)) {
negIRTot = negIRTot + posIR;
} else if (Servo.getPosition(17, 0) >= desiredBearing && Servo.getPosition(17, 0) < (posFinal + 5)) {
posIRTot = posIRTot + posIR;
}
}
Servo.increment(17, 1);
}
// servoStart only sequences if no IR <= 15 is detected.
Servo.setPosition(17, servoStart);
sleep(50);
sleepVal = 10;
print("pos-servoStart: " + servoStart);
}
// Part 2 of a 2-part sequence swinging the servo in a Pos direction (Left).
// Called only after part 1 noted a posHit (IR <= 15). Continues until IR >= 16.
// The servo resets to posInit (to save time) before continuing to negScanInit.
function posScanFinal() {
for (bb = 0; bb < 30; bb++) {
posIR = ADC.get(2, 0);
sleep(20); // previously 10
if (posIR >= 16) {
posIRTot = posIRTot + posIR;
posFinal = Servo.getPosition(17, 0);
print(" posFinal: " + posFinal);
actualBearingCurrent = Math.round(posFinal - ((posFinal - negFinal) / 2));
bearingCount++;
setVar("$bearingCount", bearingCount);
Servo.setPosition(17, posInit);
negIR = 0;
sleep(10);
return;
}
Servo.increment(17, 1);
}
}
// Main loop
// courseDetermination() is called only after certain criteria are met to avoid unnecessary calls.
while (true) {
negScanInit();
if (negInit > lastNegInit && negFinal != lastNegFinal) {
courseDetermination();
}
if (negHit == 1) {
negHit = 0;
negScanFinal();
if (negFinal > lastNegFinal && negInit != lastNegInit) {
courseDetermination();
}
lastNegInit = negInit;
lastNegFinal = negFinal;
}
posScanInit();
if (posInit < lastPosInit && posFinal != lastPosFinal) {
courseDetermination();
}
if (posHit == 1) {
posHit = 0;
posScanFinal();
if (posFinal < lastPosFinal && posInit != lastPosInit) {
courseDetermination();
}
lastPosInit = posInit;
lastPosFinal = posFinal;
}
}

Yes - that clarification changes the logic in an important way.
You’re not doing a simple two-edge midpoint. You’re doing a four-edge relationship and updating
desiredBearingfrom whichever pair of edges is currently valid.What I understand your strategy to be
You are detecting and maintaining these four edge positions:
negInitnegFinalposInitposFinalAnd then using them to compute the reflector center from different combinations depending on scan direction and which edges are most recently valid.
So the reflector center can be derived from:
(negInit + posInit) / 2(negFinal + posFinal) / 2(posInit + negInit) / 2(posFinal + negFinal) / 2That means the real model is:
That’s a more complete and useful strategy than the simplified midpoint approach I described earlier.
Best implementation approach
I would implement this as a 4-state edge capture loop with a small edge-memory model:
States
SCAN_NEG_INITSCAN_NEG_FINALSCAN_POS_INITSCAN_POS_FINALCore idea
Each time an edge is detected:
desiredBearingCleaner logic model
Instead of keeping many overlapping formulas in the scan code, use a single helper that recomputes bearing whenever enough edge data exists.
For example:
negInitandposInitare valid, compute from thosenegFinalandposFinalare valid, compute from thoseRecommended data structure
In ARC JavaScript, I’d keep it simple:
Then make one function to update the bearing:
That is the simplest version.
But your logic sounds a bit more directional than that
Because you mentioned:
negInitwithposInitposInitwithnegInitnegFinalwithposFinalThat suggests you may want separate edge pairs depending on current motion direction.
In that case, I would implement it like this:
negInitornegFinalposInitorposFinaldesiredBearingPractical implementation recommendation
The most efficient structure is:
1. Scan NEG until IR <= 15
negInitornegFinaldepending on whether it is the first or second NEG edge in the current cycle2. Scan POS until IR <= 15
posInitorposFinaldesiredBearing3. Use a small edge-state tracker
This avoids trying to infer everything from one or two variables.
A better way to code it
Instead of thinking in terms of "four separate formulas," I’d think in terms of:
Example concept
Then:
desiredBearingThat is the cleanest and most portable design for Teensy later.
My recommendation
Yes, I think your 4-edge method is the right one if your goal is to keep refining the bearing as the robot scans.
I would implement it as:
That will be:
Next step
If you want, I can write the actual ARC JavaScript REV_22 using this exact 4-edge strategy, with:
SCAN_NEG_INITSCAN_NEG_FINALSCAN_POS_INITSCAN_POS_FINALIf you’d like that, I’ll produce the code next.
@Athena. You wrote: ----------------------------
Practical implementation recommendation
The most efficient structure is:
1. Scan NEG until IR <= 15
negInitornegFinaldepending on whether it is the first or second NEG edge in the current cycle2. Scan POS until IR <= 15
posInitorposFinaldesiredBearing----------------------------
Please understand that during a: Neg Scan, negInit must be captured and negFinal must be captured. Not or. Actual Bearing to be determined at each edge. (negInit <=15 and negFinal > 15) Pos Scan, posInit must be captured and posFinal must be captured. Not or. Actual Bearing to be determined at each edge. (posInit <=15 and posFinal > 15)
Once the reflector is detected (IR <= 15), the servo must continue past the reflector to a servo position where the reflector is not detected (IR > 15). This physical movement of the servo is required in each direction in order for the reflector to be detected when the servo returns.
Based upon this clarification:
Next step
If you want, I can write the actual ARC JavaScript REV_22 using this exact 4-edge strategy, with:
SCAN_NEG_INITSCAN_NEG_FINALSCAN_POS_INITSCAN_POS_FINALThank you for your continued support. Jack
Absolutely, Jack - that clarification is exactly what I needed.
You’re right:
posInitandposFinalare both requirednegInitandnegFinalare both requiredThat makes this a true 4-edge tracking loop, and it’s a very solid approach for ARC JavaScript and later Teensy C++.
Recommended implementation strategy
I’d implement this as a four-state edge capture machine with one small helper that:
States
SCAN_NEG_INITSCAN_NEG_FINALSCAN_POS_INITSCAN_POS_FINALEdge handling idea
Each state captures one edge:
SCAN_NEG_INITcapturenegInitSCAN_NEG_FINALcapturenegFinalSCAN_POS_INITcaptureposInitSCAN_POS_FINALcaptureposFinalThen, after each capture, recompute bearing using the best available edge pair.
A clean ARC JavaScript REV_22
Here’s the version I’d recommend as the next step:
Why this version is better
1) It matches your real strategy
Each edge is captured only during the correct physical sweep direction.
2) It updates bearing at each edge
That’s what you asked for, and it lets ARC track the reflector continuously.
3) It’s easy to port later
This maps very naturally to a Teensy
switch(state)loop.4) It avoids unnecessary complexity
No extra duplicate suppression chains unless they prove necessary later.
One important design note
Right now,
updateBearingFromEdges()prefers the most recently available pair in this order:negFinal + posFinalnegInit + posInitnegInit + posFinalnegFinal + posInitIf you want a different priority order, I can change that.
For example, if you want:
desiredBearingactualBearingwe can separate those cleanly.
My recommendation
Yes - your 4-edge strategy is valid and I’d keep it.
The key is to make the implementation explicit:
That is the simplest efficient structure that still preserves your behavior.
If you want, I can do one more pass and produce a REV_23 version that:
desiredBearingvsactualBearingmore clearly@Athena. I do not think the following function is correct, see my notes in the comments:
Btw, the script will not run the servo if started out on desiredBearing(90). The only way I can get the servo to run is to move it off 90, and then it centers itself on 90 and the servo stops.
To review:
NEG movement identifies 2 servo positions. (negInit and negFinal) .
At negInit, actualBearing = (( negInit + posInit ) /2). At negFinal, actualBearing = ((negFinal + posFinal) /2).
POS movement identifies 2 servo positions. (posInit and posFinal).
At posInit, actualBearing = (( posInit + negInit ) /2). At posFinal, actualBearing = ((posFinal + negFinal) /2). Can you rewrite Rev22 to correct 'function updateBearingFromEdges()' ? Also, can you fix the servo movement to continually sweep NEG and POS?
Thank you. Jack
Sorry, I can't continue helping in this thread. It's grown long enough that my context window will require too much extra processing, which makes follow-ups unreliable and I can start making mistakes.
A couple of options:
@Athena. Thank you. Jack