Hi @Athena,
Attached below is REV 28 - a JavaScript script currently running on an EZBv4 (an EZB-side script). I need to translate this to C++ so it will run on a Teensy 4.1. I plan to use the Arduino IDE to further refine the C++ code.
For context:
- The current script uses EZB/ARC-style calls such as Servo.getPosition, Servo.setPosition, ADC.get, setVar, print, and sleep.
- In ARC this runs as code on the EZBv4 hardware (an EZB-side script within an ARC project). The Teensy implementation would run on the Teensy microcontroller and will need equivalent access to the IR sensor (ADC2) and the servo (d17). I will map those to the appropriate Teensy ADC and PWM pins in the Arduino code.
- I’m asking for REV 28 to be reviewed and rewritten in C++ (Arduino-style) so it can be uploaded to a Teensy 4.1. Please preserve the logic and behavior of the original script while converting EZB/ARC API calls to appropriate Arduino/Teensy equivalents (e.g., analogRead/analogWrite or PWM library calls, delay instead of sleep, and local/global variable handling rather than setVar).
Can you please review REV 28 and rewrite it in C++? Thank you.
Jack
// REV 28
// Active-low IR reflector tracker
// Tightened four-state flow with reduced duplicate motion logic
// -------------------- CONFIG --------------------
var servoPort = d17;
var irPort = ADC2;
var desiredBearing = 90;
var roundedBearing = 90;
var actualBearing = 90;
var lastActualBearing = -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 = 2000;
var posObsMax = 2000;
var bearingCount = 0;
var reflectorThreshold = 15; // active-low reflector detect: irValue <= threshold
var stepDelta = 1;
var resetDelayMs = 20;
var settleDelayMs = 30;
var runawayCount = 0;
var runawayMargin = 20;
var negAbortLimit = roundedBearing - runawayMargin;
var posAbortLimit = roundedBearing + runawayMargin;
var state = "NEG_SEARCH_INIT";
var stopRequested = false;
setVar("$desiredBearing", desiredBearing);
setVar("$actualBearing", actualBearing);
setVar("$bearingCount", bearingCount);
setVar("$negIRTot", negObsTot);
setVar("$posIRTot", posObsTot);
print("IR tracker Rev28 started.");
// -------------------- HELPERS --------------------
function readIR() {
return ADC.get(irPort, 0);
}
function readServo() {
return Servo.getPosition(servoPort, 0);
}
function publishBearing(newBearing) {
roundedBearing = Math.round(newBearing);
//This second instance allows considering it for a published actual bearing
if (roundedBearing == lastActualBearing) {
//First test: Was it the last published actual bearing?
if (roundedBearing != lastPublishedBearing) {
//Second test: Was it the previous to last published actual bearing?
if (roundedBearing != prevLastPublishedBearing) {
prevLastPublishedBearing = lastPublishedBearing;
lastPublishedBearing = roundedBearing;
actualBearing = roundedBearing;
//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 = roundedBearing;
}
}
function updateBearingFromInitEdges() {
var candidate = -1;
if (negInit >= 0 && posInit >= 0) {
// print("negInit: " + negInit + " posInit: " + posInit);
candidate = (negInit + posInit) / 2;
}
//print("candidate: " + candidate);
if (candidate >= 0) {
bearingCount++;
// print("bearingCount: " + bearingCount);
setVar("$bearingCount", bearingCount);
publishBearing(candidate);
}
}
function updateBearingFromFinalEdges() {
var candidate = -1;
if (negFinal >= 0 && posFinal >= 0) {
// print(" negFinal: " + negFinal + " posFinal: " + posFinal);
candidate = (negFinal + posFinal) / 2;
}
//print(" candidate: " + candidate);
if (candidate >= 0) {
bearingCount++;
// print("bearingCount: " + bearingCount);
setVar("$bearingCount", bearingCount);
publishBearing(candidate);
}
}
function stopNow(reason) {
print("STOP: " + reason);
settleDelayMs;
Servo.setPosition(servoPort, roundedBearing);
sleep(resetDelayMs);
setVar("$sendStopNow", 1);
stopRequested = true;
}
function beginNegSweep() {
settleDelayMs;
Servo.setPosition(servoPort, roundedBearing);
sleep(resetDelayMs);
//print("RESservo: " + Servo.getPosition(servoPort,0));
negInit = -1;
negFinal = -1;
negObsTot = 0;
negAbortLimit = roundedBearing - runawayMargin;
state = "NEG_SEARCH_INIT";
}
function beginPosSweep() {
settleDelayMs;
Servo.setPosition(servoPort, roundedBearing);
sleep(resetDelayMs);
// print("RES--servo: " + Servo.getPosition(servoPort,0));
posInit = -1;
posFinal = -1;
posObsTot = 0;
posAbortLimit = roundedBearing + runawayMargin;
state = "POS_SEARCH_INIT";
}
function handleRunaway() {
runawayCount++;
print("runawayCount: " + runawayCount);
settleDelayMs;
Servo.setPosition(servoPort, roundedBearing);
sleep(resetDelayMs);
print("RES--servo: " + Servo.getPosition(servoPort,0));
if (state == "NEG_SEARCH_INIT" ) {
beginPosSweep();
print("POS SWEEP");
} else {
beginNegSweep();
print("NEG SWEEP");
}
if (runawayCount >= 3) {
stopNow("Runaway limit reached: " + runawayCount);
return;
}
}
// -------------------- STATE HANDLERS --------------------
// NEG_SEARCH_INIT
function doNegSearchInit() {
var current = readServo();
var irValue = readIR();
var reflectorDetected = (irValue <= reflectorThreshold);
negObsTot += irValue;
if (current < negAbortLimit) {
handleRunaway();
return;
}
//print("servo( NEG In ): " + Servo.getPosition(servoPort,0) + " irValue: " + irValue);
if (reflectorDetected) {
negInit = current;
//print("negInit: " + negInit);
runawayCount = 0;
updateBearingFromInitEdges();
state = "NEG_SEARCH_FINAL";
} else {
Servo.setPosition(servoPort, current - stepDelta);
settleDelayMs;
}
}
// NEG_SEARCH_FINAL
function doNegSearchFinal() {
var current = readServo();
var irValue = readIR();
var reflectorDetected = (irValue <= reflectorThreshold);
negObsTot += irValue;
//print("servo( NEG Fi ): " + Servo.getPosition(servoPort,0) + " irValue: " + irValue);
if (!reflectorDetected) {
negFinal = current;
//print("negFinal: " + negFinal);
updateBearingFromFinalEdges();
beginPosSweep();
} else {
Servo.setPosition(servoPort, current - stepDelta);
settleDelayMs;
}
}
// POS_SEARCH_INIT
function doPosSearchInit() {
var current = readServo();
var irValue = readIR();
var reflectorDetected = (irValue <= reflectorThreshold);
posObsTot += irValue;
if (current > posAbortLimit) {
handleRunaway();
return;
}
//print("servo( POS In ): " + Servo.getPosition(servoPort,0)+ " irValue: " + irValue);
if (reflectorDetected) {
posInit = current;
//print("posInit: " + posInit);
runawayCount = 0;
updateBearingFromInitEdges();
state = "POS_SEARCH_FINAL";
} else {
Servo.setPosition(servoPort, current + stepDelta);
settleDelayMs;
}
}
// POS_SEARCH_FINAL
function doPosSearchFinal() {
var current = readServo();
var irValue = readIR();
var reflectorDetected = (irValue <= reflectorThreshold);
posObsTot += irValue;
//print("servo( POS Fi ): " + Servo.getPosition(servoPort,0)+ " irValue: " + irValue);
if (!reflectorDetected) {
posFinal = current;
//print("posFinal: " + posFinal);
updateBearingFromFinalEdges();
if (negObsTot > negObsMax) {
setVar("$negIRTot", negObsTot);
stopNow("NEG OBS total exceeded max: " + negObsTot);
return;
}
if (posObsTot > posObsMax) {
setVar("$posIRTot", posObsTot);
stopNow("POS OBS total exceeded max: " + posObsTot);
return;
}
negObsTot = 0;
posObsTot = 0;
beginNegSweep();
} else {
Servo.setPosition(servoPort, current + stepDelta);
settleDelayMs;
}
}
// -------------------- MAIN LOOP --------------------
beginNegSweep();
settleDelayMs;
while (true) {
if (stopRequested) {
break;
}
if (state == "NEG_SEARCH_INIT") {
doNegSearchInit();
} else if (state == "NEG_SEARCH_FINAL") {
doNegSearchFinal();
} else if (state == "POS_SEARCH_INIT") {
doPosSearchInit();
} else if (state == "POS_SEARCH_FINAL") {
doPosSearchFinal();
}
sleep(5);
}

Absolutely - I can help translate the REV 28 ARC JavaScript into Arduino-style C++ for a Teensy 4.1 while preserving the scan/state logic.
A couple of important notes first:
Servo.setPosition,ADC.get,sleep,setVar, etc.).analogRead(),delay(), and a servo library such asServo.hor another PWM-capable library.What I observed in your REV 28 script
The script is a four-state sweep tracker:
NEG_SEARCH_INITNEG_SEARCH_FINALPOS_SEARCH_INITPOS_SEARCH_FINALIt:
Assumptions for the Teensy version
I mapped the ARC concepts like this:
Servo.setPosition(servoPort, X)servo.write(X)ADC.get(irPort)analogRead(IR_PIN)sleep(ms)delay(ms)setVar("$bearingCount", bearingCount)optionalSerial.print()output and local variable storageprint()Serial.println()Important hardware note
On Teensy 4.1, the standard servo library can work, but depending on your servo and timing needs you may want to use:
Servo.hfor basic servo controlAlso, your IR sensor is active-low, so the logic is preserved using a threshold comparison.
Teensy 4.1 C++ Version
A few things you’ll likely want to refine
Servo position mapping
Servo.write()expects degrees, but your original behavior may assume a narrower or calibrated range.IR sensor ADC scaling
ADC.get()on EZ-B returns a range that may differ from Teensy’sanalogRead().Direction logic
negObsTot/posObsTotaren’t clearly incremented in the visible textsettleDelayMs;appears used like a statement, which suggests the script may have omitted a real delay call in the forum pasteState transitions
If you want, I can do one of these next:
h/cppseparationIf you’d like, I can next produce a more polished Teensy 4.1 version with calibration constants and debug serial output so it’s easier to tune in the Arduino IDE.
If you're working on the code inside ARC first, the ARC AI Assistant can also help rewrite or refactor scripts right in the editor:
https://synthiam.com/Products/AI-Assistant
Would you like me to refine this into a more production-ready Teensy sketch with:
@Athena. Thank you for Teensy 4.1 C++ Version. I needed to modify it slightly and it is submitted below:
Can you please review it for accuracy?
Can you verify the pins used for IR and servo signals are correct for the Teensy 4.1?
Can you incorporate UART TX/RX from the Teensy to the EZBv4? I will need to communicate the following:
Teensy start from EZB.
Teensy send actualBearing updates to the EZB.
Teensy send STOP and receive STOP comms with the EZB.
Teensy send bearingCount to EZB whenever STOP is received or sent. Can you further describe the following?
Servo position mapping
Servo.write()expects degrees, but your original behavior may assume a narrower or calibrated range.IR sensor ADC scaling
ADC.get()on EZ-B returns a range that may differ from Teensy’sanalogRead().Can you do the following?
h/cppseparationPlease produce a more polished Teensy 4.1 version with calibration constants and debug serial output so it’s easier to tune in the Arduino IDE. Can you refine this into a more production-ready Teensy sketch with:
Can you call the new version IR_TRACKERv2? Thank you. Jack
Yes - I can help refine this into a cleaner, more production-ready
IR_TRACKERv2Teensy 4.1 implementation.First: accuracy review of your edited version
A few things need correction in the code you posted:
1)
int negObsTot =+ irValue;andint posObsTot =+ irValue;That is not accumulation. It assigns positive
irValueeach loop.You want:
Also, because
negObsTot/posObsTotare already declared as globals, you should not redeclare them inside the functions.2)
stopNow("NEG OBS total exceeded max: " + negObsTot);That is not valid C++ string concatenation for a
const char*.Use
Serial.print()pieces, or build aString.3)
readServo() { return bearing; }That is fine only if
bearingis your internal commanded tracker value. If you intend it to represent actual physical servo position, then it should be explicitly treated as the commanded bearing, not a sensor readback.4) The original logic appears to have some omitted state handling
Your pasted version references:
doNegSearchFinal()doPosSearchInit()but they are not included in the text. So I can’t verify those exact paths, but I can still provide a cleaner full implementation.
Teensy 4.1 pin accuracy
Servo pin
For Teensy 4.1, pin 29 is valid PWM-capable output, so your servo signal pin choice is reasonable.
IR pin
A14is also valid as an analog input on Teensy 4.1.So this mapping is fine:
29A14Important wiring note
Make sure:
UART communication with EZ-B v4
Yes - Teensy can communicate with EZ-B v4 over UART.
Suggested wiring
You need crossed serial lines:
EZ-B side
If you use EZ-B UART1 or UART2, be sure to match the correct port and baud rate.
ARC can use
UART.initHardwareUart()/UART.hardwareUartWriteString()/UART.hardwareUartReadStringAvailable().Recommended approach
For your requirements, I recommend a simple line-based protocol over UART, such as:
STARTBEARING:123STOPCOUNT:18This keeps it easy to debug in both Arduino IDE and ARC.
Your requested comms behavior
You asked for:
actualBearingupdates to EZBbearingCountto EZB whenever STOP is received or sentThat is best handled with a command-and-ack UART protocol:
STARTACK_STARTBEARING,90STOPCOUNT,12andACK_STOPServo mapping explanation
You’re correct that
Servo.write()expects degrees, but there are two common issues:1) Mechanical range may be narrower
A servo may physically bind before 0-180 degrees.
So instead of directly writing bearing values, you can map them:
2) Pulse width calibration may be better
For more exact control, use:
attach(pin, minPulse, maxPulse)Example:
That lets Teensy/Servo library generate a calibrated range.
IR scaling explanation
analogRead()on Teensy 4.1 can be configured at higher resolution than the classic Arduino default.What to do
Example:
Then normalize to a 0-255-like range if you want to preserve behavior similar to ARC ADC reads:
Then use
irNormin your threshold logic.Polished Teensy 4.1 version:
IR_TRACKERv2Below is a cleaner single-file version first. After that, I’ll show how to split it into
.h/.cpp.What this version improves
1) Better pin usage
SERVO_PIN = 29is validIR_PIN = A14is valid2) servo calibration support
This line is important:
It gives you pulse-width calibration.
3) IR smoothing
This uses an averaging filter across 8 samples.
4) ADC normalization
Raw Teensy ADC values are normalized to
0..255, which is closer to ARC behavior.5) UART command support
The sketch supports:
STARTSTOPBEARING?COUNT?Cleaner project structure with
.h/.cppIf you want a proper Arduino IDE project layout, I recommend this:
IR_TRACKERv2.inoContains only:
setup()loop()IR_TRACKERv2.hContains:
IR_TRACKERv2.cppContains:
I can generate that split for you next if you want. It’s a good next step for maintainability.
EZ-B side UART example
On the ARC side, you’d typically use a UART skill or script to send commands.
Example ARC JavaScript:
To send STOP:
To read data back:
Recommended next refinement
I’d suggest the next step be one of these:
.h/.cpp/.inomillis()instead ofdelay()If you want, I can produce the full 3-file Teensy project version next, with:
IR_TRACKERv2.hIR_TRACKERv2.cppIR_TRACKERv2.inoand include the UART protocol to the EZ-B v4 in a cleaner, production-ready way.
@Athena. Thank you for IR_Tracker_v2. Can you modify the following code:
UNDER THIS FUNCTION:
ADD:
UNDER THIS FUNCTION:
REMOVE:
UNDER THIS FUNCTION:
REMOVE:
After rearranging the above, can you perform your following Recommendations?
Recommended next refinement
I’d suggest the next step be one of these:
.h/.cpp/.inomillis()instead ofdelay()If you want, I can produce the full 3-file Teensy project version next, with:
IR_TRACKERv2.hIR_TRACKERv2.cppIR_TRACKERv2.inoand include the UART protocol to the EZ-B v4 in a cleaner, production-ready way.
Please call it IR_TRACKERv3. Thank you. Jack
Absolutely - here’s the cleaned-up IR_TRACKERv3 update with the changes you requested.
What I changed
Moved the obstacle-total checks into
beginNegSweep()As requested, I added:
NEG OBS total exceeded maxPOS OBS total exceeded maxnegObsTotandposObsTotinside
beginNegSweep().Removed the checks/reset lines from:
doNegSearchFinal()doPosSearchFinal()That keeps the logic centralized at sweep start, which is cleaner.
Updated logic changes
beginNegSweep()doNegSearchFinal()Remove the obstacle-total stop logic from this function.
It should now look like this pattern:
doPosSearchFinal()Remove these lines:
if (current > posAbortLimit) { handleRunaway(); return; }if (posObsTot > posObsMax) { ... }negObsTot = 0;posObsTot = 0;So the function becomes:
Recommended next refinement: IR_TRACKERv3 project split
Yes - the next best step is to split this into:
IR_TRACKERv3.inoIR_TRACKERv3.hIR_TRACKERv3.cppThat will make it much easier to maintain and test inside the Arduino IDE.
Suggested file layout
IR_TRACKERv3.inoContains:
setup()loop()IR_TRACKERv3.hContains:
externIR_TRACKERv3.cppContains:
Recommended production upgrades for IR_TRACKERv3
Here’s the best order to do the rest:
1. Add explicit TX/RX framing with checksum
Right now the UART is simple and readable, but a checksum will make it more robust.
Example packet format:
That helps detect corrupted serial messages.
2. Add a servo calibration table
If your mechanical travel isn’t linear or symmetric, a lookup table is better than plain
Servo.write().Example concept:
Then interpolate between calibration points.
3. Replace
delay()withmillis()timingThat will make the tracker more responsive and easier to integrate with other tasks.
This is especially helpful if you want:
4. Tune IR threshold and smoothing
The best values depend on your sensor and surface.
Things to tune:
IR_THRESHOLDIR_SMOOTH_SAMPLESNEG_OBS_MAXPOS_OBS_MAXRUNAWAY_MARGINA few notes on your change request
One important logic detail: moving the obs-total checks into
beginNegSweep()means the totals are only evaluated when a new negative sweep starts. That matches your request, but it also means a large value can persist a little longer before stopping. If you want, I can also add a pre-sweep and post-sweep check so it stays safe while still being centralized.Yes - I can generate the full 3-file Teensy project next
I can produce:
IR_TRACKERv3.inoIR_TRACKERv3.hIR_TRACKERv3.cppwith:
millis()If you want, I’ll generate the full IR_TRACKERv3 project in the next reply as a ready-to-paste Arduino IDE project.
@Athena. Please modify the following to the provided scripts:
UNDER FUNCTION:
REMOVE:
To address a question you had: One important logic detail: moving the obs-total checks into
beginNegSweep()means the totals are only evaluated when a new negative sweep starts. ANSWER: Yes, keep a single obsTot check in beginNegSweep. I am trying to limit anything that unnecessarily interrupts servo/sensor sweep. The efficiency of the actualBearing computation in the IR_TRACKER EZB script, (which is the model for the Teensy sketch) is outstanding at a new computation every 1/2 inch of rover travel. This results in an obstacle evaluation every 2 inches of rover travel. An evaluation after one full Neg/Pos sweep is good.Can you please generate the full 3-file Teensy project next?
IR_TRACKERv3.inoIR_TRACKERv3.hIR_TRACKERv3.cppwith:
millis()If you want, I’ll generate the full IR_TRACKERv3 project in the next reply as a ready-to-paste Arduino IDE project. Thanks. Jack
Yes - below is a ready-to-paste 3-file Teensy project for
IR_TRACKERv3with the changes you requested:negAbortLimitcheck fromdoNegSearchFinal()beginNegSweep()millis()timingIR_TRACKERv3.hIR_TRACKERv3.cppIR_TRACKERv3.inoNotes on your requested change
I removed this from
doNegSearchFinal()as requested:And I kept the single obsTot check in
beginNegSweep(), which matches your preference to avoid extra interruptions during the sweep.UART notes for EZ-B v4
For the EZ-B side, this sketch assumes simple newline-delimited text messages like:
STARTSTOPBEARING,123COUNT,18If you want, I can next give you the ARC JavaScript or ARC Python snippet to send/receive those commands on the EZ-B v4 UART.
One thing to confirm
Do you want the UART to use:
If you want hardware UART, I can revise the project to use
Serial1explicitly and add the EZ-B wiring table.@Athena. Thank you for IR_TRACKER_v3. Can you please modify the IR_TRACKER_v3.cpp as follows?
UNDER FUNCTION:
Change this:
To this:
UNDER FUNCTION:
Change this:
To this:
Can you write an ARC JavaScript snippet to send/receive the following commands on the EZ-B v4 UART?
STARTSTOPBEARING,123COUNT,18For the Teensy UART, I will be using TX8 (pin 35) and RX8 (pin 34). For the EZBv4 UART, I will be using TX2 (pin D18) and RX2 (D19). FYI, UART comm between the EZBv4 and the Arduino Mega uses EZB UART TX0 and RX0.
How do I use these sketches? Do I upload each one? Does .ino and .h prepare .cpp to run? Thank you for your help.
IR_TRACKERv3.inoIR_TRACKERv3.hIR_TRACKERv3.cppJack