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 - and yes, I can clarify how the files work together.
1)
doNegSearchInit()changeYou want this line:
changed to:
If your sensor is active-low, that makes sense and matches the rest of your logic.
2) UART pin mapping review
Your pin plan is valid:
Teensy 4.1
EZ-B v4
That is the correct crossed-serial style wiring direction:
Since you mentioned the Arduino Mega uses EZ-B UART TX0/RX0, that is fine too - just keep each UART pair dedicated and matched.
3) ARC JavaScript snippet for UART START / STOP / BEARING / COUNT
Here is a simple ARC JavaScript example you can use with the EZ-B v4 UART skill or from a Script skill in ARC.
If you want a loop to continuously read responses
4) Do you upload each file separately?
No - you do not upload
.h,.cpp, and.inoseparately as separate programs.How Arduino/Teensy IDE uses them
.ino= the main sketch file.h= header file with declarations.cpp= implementation file with the codeThey all belong to the same project folder.
Typical workflow
IR_TRACKERv3IR_TRACKERv3.inoIR_TRACKERv3.hIR_TRACKERv3.cpp.inofile in Arduino IDE.cppand.hfiles with it5) Does
.inoand.hprepare.cppto run?Yes - in an Arduino/Teensy project:
.inois the entry point.htells the compiler what functions/variables exist.cppcontains the actual logicSo:
.h= declarations.cpp= code.ino= setup/loop and project startThey work together as one compiled firmware image.
6) Important note about your current UART plan
If you want Teensy UART8, make sure your code is using the matching serial port object:
Serial1,Serial2, etc. map to specific pins.On Teensy 4.1, you’ll likely need to use:
Serial8for pin 35/34So in the Teensy code, your UART should probably be:
and then:
If you want, I can update the full
IR_TRACKERv3.cppto use Serial8 explicitly for those pins.7) Recommendation
I’d suggest this setup:
Serial8on pins 35 TX / 34 RXIf you want, I can do the next step and provide:
Serial8If you'd like, I can rewrite the full Teensy file now with
Serial8integrated.@Athena. Thank you for the explanations. Can you please do the next step and provide the following?
Serial8Can you rewrite the full Teensy file now with
Serial8integrated? Thanks. JackYes - here’s the next step with Serial8 integrated for the Teensy 4.1, plus the ARC JavaScript and a wiring table.
1) Updated
IR_TRACKERv3.cppusingSerial8This version uses:
Serial8on TX8 pin 35 and RX8 pin 34UART2on D18 / D19doNegSearchInit()2)
IR_TRACKERv3.ino3)
IR_TRACKERv3.h4) ARC JavaScript for UART send/receive
This is for the EZ-B v4 side in ARC using UART2 on D18/D19.
Optional ARC control snippet
If you want to send a command once:
5) UART wiring table
Teensy 4.1 to EZ-B v4
Important
6) How to use the sketches
Yes - these are one Teensy project, not separate uploads.
You do this:
IR_TRACKERv3IR_TRACKERv3.inoIR_TRACKERv3.hIR_TRACKERv3.cppIR_TRACKERv3.inoin Arduino IDEHow they work together
.ino= startup entry points:setup()andloop().h= declarations and shared constants.cpp= the actual codeSo yes, the
.inoand.hfile prepare the.cppto compile as one project.7) One important compatibility note
Your ARC JavaScript can send:
STARTSTOPBEARING,123COUNT,18But the current Teensy code is mainly written to receive control commands and send status updates like:
ACK_STARTACK_STOPBEARING,xxxCOUNT,xxxSTOPIf you want, I can do one more refinement and make the Teensy parser fully handle inbound:
BEARING,123COUNT,18as explicit commands too.
If you'd like, I can next produce a cleaner V4 of IR_TRACKERv3 that adds:
BEARING,123andCOUNT,18If you want that, I can generate it right away.
Thank you @Athena.