- I have reposted IR_TRACKERv3 below.
#include "IR_TRACKERv3.h"
Servo trackingServo;
TrackerState state = NEG_SEARCH_INIT;
int desiredBearing = DESIRED_BEARING;
int roundedBearing = DESIRED_BEARING;
int actualBearing = DESIRED_BEARING;
int bearing = DESIRED_BEARING;
int lastActualBearing = -1;
int lastPublishedBearing = -1;
int prevLastPublishedBearing = -1;
int negInit = -1;
int negFinal = -1;
int posInit = -1;
int posFinal = -1;
long negObsTot = 0;
long posObsTot = 0;
int bearingCount = 0;
int runawayCount = 0;
int negAbortLimit = 0;
int posAbortLimit = 0;
bool stopRequested = false;
bool startRequested = false;
static int irSamples[IR_SMOOTH_SAMPLES];
static int irSampleIndex = 0;
static bool irInitialized = false;
static String serialLine;
static unsigned long lastStepMs = 0;
static unsigned long lastDebugMs = 0;
static int clampAngle(int angle) {
return constrain(angle, SERVO_MIN_ANGLE, SERVO_MAX_ANGLE);
}
int clampServoAngle(int angle) {
return clampAngle(angle);
}
void setServoBearing(int angle) {
bearing = clampAngle(angle);
trackingServo.write(bearing);
}
int readServo() {
return bearing;
}
int normalizeIRTo255(int raw) {
raw = constrain(raw, 0, ADC_MAX_RAW);
return map(raw, 0, ADC_MAX_RAW, 0, 255);
}
int readIRSmooth() {
int raw = analogRead(IR_PIN);
if (!irInitialized) {
for (int i = 0; i < IR_SMOOTH_SAMPLES; i++) {
irSamples[i] = raw;
}
irInitialized = true;
} else {
irSamples[irSampleIndex] = raw;
irSampleIndex = (irSampleIndex + 1) % IR_SMOOTH_SAMPLES;
}
long sum = 0;
for (int i = 0; i < IR_SMOOTH_SAMPLES; i++) {
sum += irSamples[i];
}
return (int)(sum / IR_SMOOTH_SAMPLES);
}
void sendToEZB(const String& msg) {
Serial8.print(msg);
Serial8.print('\n');
Serial.print("TX->EZB: ");
Serial.println(msg);
}
void sendBearingUpdate() {
sendToEZB("BEARING," + String(actualBearing));
}
void sendBearingCount() {
sendToEZB("COUNT," + String(bearingCount));
}
void publishBearing(int candidate) {
roundedBearing = round(candidate);
if (roundedBearing == lastActualBearing) {
if (roundedBearing != lastPublishedBearing) {
if (roundedBearing != prevLastPublishedBearing) {
prevLastPublishedBearing = lastPublishedBearing;
lastPublishedBearing = roundedBearing;
actualBearing = roundedBearing;
Serial.print("actualBearing: ");
Serial.println(actualBearing);
sendBearingUpdate();
}
}
} else {
lastActualBearing = roundedBearing;
}
}
void updateBearingFromInitEdges() {
int candidate = -1;
if (negInit >= 0 && posInit >= 0) {
candidate = (negInit + posInit) / 2;
}
if (candidate >= 0) {
bearingCount++;
publishBearing(candidate);
Serial.print("bearingCount: ");
Serial.println(bearingCount);
}
}
void updateBearingFromFinalEdges() {
int candidate = -1;
if (negFinal >= 0 && posFinal >= 0) {
candidate = (negFinal + posFinal) / 2;
}
if (candidate >= 0) {
bearingCount++;
publishBearing(candidate);
Serial.print("bearingCount: ");
Serial.println(bearingCount);
}
}
void stopNow(const char* reason) {
Serial.print("STOP: ");
Serial.println(reason);
setServoBearing(roundedBearing);
delay(RESET_DELAY_MS);
sendToEZB("STOP");
sendBearingCount();
stopRequested = true;
state = TRACKER_STOPPED;
}
void beginNegSweep() {
delay(SETTLE_DELAY_MS);
setServoBearing(roundedBearing);
delay(RESET_DELAY_MS);
if (negObsTot > NEG_OBS_MAX) {
Serial.print("NEG OBS total exceeded max: ");
Serial.println(negObsTot);
stopNow("NEG OBS total exceeded max");
return;
}
if (posObsTot > POS_OBS_MAX) {
Serial.print("POS OBS total exceeded max: ");
Serial.println(posObsTot);
stopNow("POS OBS total exceeded max");
return;
}
negObsTot = 0;
posObsTot = 0;
negInit = -1;
negFinal = -1;
negAbortLimit = roundedBearing - RUNAWAY_MARGIN;
state = NEG_SEARCH_INIT;
}
void beginPosSweep() {
delay(SETTLE_DELAY_MS);
setServoBearing(roundedBearing);
delay(RESET_DELAY_MS);
posInit = -1;
posFinal = -1;
posAbortLimit = roundedBearing + RUNAWAY_MARGIN;
state = POS_SEARCH_INIT;
}
void handleRunaway() {
runawayCount++;
Serial.print("runawayCount: ");
Serial.println(runawayCount);
delay(SETTLE_DELAY_MS);
setServoBearing(roundedBearing);
delay(RESET_DELAY_MS);
if (state == NEG_SEARCH_INIT) {
beginPosSweep();
Serial.println("POS SWEEP");
} else {
beginNegSweep();
Serial.println("NEG SWEEP");
}
if (runawayCount >= 3) {
stopNow("Runaway limit reached");
return;
}
}
void doNegSearchInit() {
int current = readServo();
int irRaw = readIRSmooth();
int irNorm = normalizeIRTo255(irRaw);
bool reflectorDetected = (irNorm < IR_THRESHOLD);
if (current < negAbortLimit) {
handleRunaway();
return;
}
if (reflectorDetected) {
negInit = current;
runawayCount = 0;
updateBearingFromInitEdges();
state = NEG_SEARCH_FINAL;
} else {
int next = current - STEP_DELTA;
setServoBearing(next);
delay(SETTLE_DELAY_MS);
}
}
void doNegSearchFinal() {
int current = readServo();
int irRaw = readIRSmooth();
int irNorm = normalizeIRTo255(irRaw);
bool reflectorDetected = (irNorm < IR_THRESHOLD);
if (reflectorDetected) {
negFinal = current;
updateBearingFromFinalEdges();
beginPosSweep();
} else {
int next = current - STEP_DELTA;
setServoBearing(next);
delay(SETTLE_DELAY_MS);
}
}
void doPosSearchInit() {
int current = readServo();
int irRaw = readIRSmooth();
int irNorm = normalizeIRTo255(irRaw);
bool reflectorDetected = (irNorm < IR_THRESHOLD);
if (current > posAbortLimit) {
handleRunaway();
return;
}
if (reflectorDetected) {
posInit = current;
runawayCount = 0;
updateBearingFromInitEdges();
state = POS_SEARCH_FINAL;
} else {
int next = current + STEP_DELTA;
setServoBearing(next);
delay(SETTLE_DELAY_MS);
}
}
void doPosSearchFinal() {
int current = readServo();
int irRaw = readIRSmooth();
int irNorm = normalizeIRTo255(irRaw);
bool reflectorDetected = (irNorm < IR_THRESHOLD);
if (reflectorDetected) {
posFinal = current;
updateBearingFromFinalEdges();
beginNegSweep();
} else {
int next = current + STEP_DELTA;
setServoBearing(next);
delay(SETTLE_DELAY_MS);
}
}
void handleSerialCommand(const String& cmd) {
String trimmed = cmd;
trimmed.trim();
trimmed.toUpperCase();
if (trimmed == CMD_START) {
startRequested = true;
stopRequested = false;
sendToEZB("ACK_START");
return;
}
if (trimmed == CMD_STOP) {
stopRequested = true;
sendBearingCount();
sendToEZB("ACK_STOP");
return;
}
if (trimmed == "BEARING?") {
sendBearingUpdate();
return;
}
if (trimmed == "COUNT?") {
sendBearingCount();
return;
}
}
void pollSerial() {
while (Serial8.available() > 0) {
char c = (char)Serial8.read();
if (c == '\n' || c == '\r') {
if (serialLine.length() > 0) {
handleSerialCommand(serialLine);
serialLine = "";
}
} else {
serialLine += c;
}
}
}
void processTrackerState() {
if (stopRequested) {
return;
}
if (!startRequested) {
return;
}
unsigned long now = millis();
switch (state) {
case NEG_SEARCH_INIT:
doNegSearchInit();
break;
case NEG_SEARCH_FINAL:
doNegSearchFinal();
break;
case POS_SEARCH_INIT:
doPosSearchInit();
break;
case POS_SEARCH_FINAL:
doPosSearchFinal();
break;
case TRACKER_STOPPED:
default:
return;
}
if (now - lastDebugMs >= 1000) {
lastDebugMs = now;
Serial.print("state=");
Serial.print((int)state);
Serial.print(" bearing=");
Serial.print(bearing);
Serial.print(" actualBearing=");
Serial.print(actualBearing);
Serial.print(" negObsTot=");
Serial.print(negObsTot);
Serial.print(" posObsTot=");
Serial.println(posObsTot);
}
}
void setupTracker() {
Serial.begin(EZB_BAUD);
Serial8.begin(EZB_BAUD);
delay(1000);
analogReadResolution(12);
pinMode(IR_PIN, INPUT);
trackingServo.attach(SERVO_PIN, SERVO_MIN_PULSE, SERVO_MAX_PULSE);
actualBearing = DESIRED_BEARING;
roundedBearing = DESIRED_BEARING;
bearing = DESIRED_BEARING;
setServoBearing(roundedBearing);
beginNegSweep();
Serial.println("IR_TRACKERv3 READY");
Serial.println("Teensy Serial8 active on TX8=pin 35, RX8=pin 34");
Serial.println("Commands: START, STOP, BEARING?, COUNT?");
}
void loopTracker() {
pollSerial();
processTrackerState();
}
Can you make the following changes?
- UNDER function doNegSearchInit, please remove:
bool reflectorDetected = (irNorm < IR_THRESHOLD);
and add:
bool reflectorDetected = (irNorm <= IR_THRESHOLD);
- UNDER function doNegSearchFinal, please remove:
bool reflectorDetected = (irNorm < IR_THRESHOLD);
and add:
bool reflectorDetected = (irNorm > IR_THRESHOLD);
- UNDER function doPosSearchInit, please remove:
bool reflectorDetected = (irNorm < IR_THRESHOLD);
and add:
bool reflectorDetected = (irNorm <= IR_THRESHOLD);
- UNDER function doPosSearchFinal, please remove:
bool reflectorDetected = (irNorm < IR_THRESHOLD);
and add:
bool reflectorDetected = (irNorm > IR_THRESHOLD);
I am stuck getting IR_TRACKERv3 to compile and load on the Teensy 4.1. Your last instructions were:
- How to use the sketches
Yes - these are one Teensy project, not separate uploads.
You do this:
- Create a folder named IR_TRACKERv3
- Put these three files inside it:
- IR_TRACKERv3.ino
- IR_TRACKERv3.h
- IR_TRACKERv3.cpp
- Open IR_TRACKERv3.ino in the Arduino IDE
- Compile and upload to the Teensy
How they work together
- .ino = startup entry points: setup() and loop()
- .h = declarations and shared constants
- .cpp = the actual code
So yes, the .ino and .h file prepare the .cpp to compile as one project.
Questions:
- I have followed the above instructions exactly and I keep getting error messages saying "IR_TRACKERv3.h.ino file not found."
- I have tried copying IR_TRACKERv3.ino, IR_TRACKERv3.cpp, and IR_TRACKERv3.h into the IR_TRACKERv3.ino file and still get the same error.
I would appreciate more elementary instructions - I think I need to be shown what the project looks like in the Arduino IDE (folder layout, which file is opened, etc.). Can you help with that?
Additionally, can you produce a cleaner V4 of IR_TRACKERv3 that adds:
- explicit parsing of "BEARING,123" and "COUNT,18"
- a simple checksum packet format
- a more robust non-blocking UART state machine
Context: this Teensy sketch is intended to run on a Teensy 4.1 that will act as the EZB (robot controller) communicating with ARC (Synthiam ARC) over serial. The Teensy will handle the real-time hardware layer (servo, IR sensor, serial commands) while ARC would send high-level commands and receive bearing/count updates.
Thanks, Jack

Yes - I can help with both parts:
IR_TRACKERv3compiling in the Arduino IDE / Teensyduino, andFirst: the
"IR_TRACKERv3.h.ino file not found"errorThat error usually means the Arduino IDE is trying to compile a file that is not laid out as a normal sketch project.
What the folder should look like
You want a folder named:
Inside that folder, these files should exist side-by-side:
What should be opened in Arduino IDE
Open only this file:
Not the
.h, not the.cpp, and not a file pasted into another file.Important: do not paste all files into the
.inoIf you copied the
.ino,.cpp, and.hcontents into one.ino, the compiler will usually break because:A very simple way to set it up
Option A - recommended
IR_TRACKERv3IR_TRACKERv3.inoIR_TRACKERv3.hIR_TRACKERv3.cppIR_TRACKERv3.inoto open it in Arduino IDEOption B - even easier
If Arduino IDE is confusing the file names, create the sketch from inside the IDE:
IR_TRACKERv3IR_TRACKERv3.hIR_TRACKERv3.cppThat avoids accidental filename issues.
Why you may be seeing
IR_TRACKERv3.h.inoThat specific message often happens when:
IR_TRACKERv3.h.inoCheck this carefully in Windows
Make sure the file is really named:
and not:
Windows often hides extensions, so this is a common trap.
About the two lines you want removed
You mentioned removing this kind of line from both functions:
I don’t see the exact operator in your pasted code because the snippet is truncated, but in general, yes - if you want that removed from:
doNegSearchInit()doPosSearchInit()then those functions need to be rewritten so reflector detection is handled another way, or directly inlined.
Cleaner V4 direction
Yes, a V4 would be a good idea for this.
The improvements you asked for:
BEARING,123COUNT,18That is exactly the kind of refactor that will make this much easier to maintain and less fragile.
Recommended V4 packet format
A simple pattern that works well over serial is:
Where:
<= start of packet>= end of packet*XX= checksum in hexThat gives you:
Non-blocking UART state machine
Instead of waiting for a full line in one go, the Teensy should:
That way the tracker loop keeps running smoothly.
ARC side note
Since this Teensy is acting as the hardware layer for ARC, you may also want to expose the serial protocol clearly so ARC can send simple commands and read back status updates reliably. If you later want, I can help you make the ARC side talk to this Teensy using JavaScript in a Script skill, which tends to be easier to maintain than older script styles.
ARC’s Script skill supports JavaScript and Python: https://synthiam.com/Support/Skills/Scripting/Script?id=16089
What I need from you to fix this cleanly
Please paste these three files exactly as they exist now:
IR_TRACKERv3.inoIR_TRACKERv3.hIR_TRACKERv3.cppAnd tell me:
If you want, I can also give you a minimal Arduino folder example with the exact text that should go into each file.
Thank you @Athena. Your question:
What I need from you to fix this cleanly
Please paste these three files exactly as they exist now:
IR_TRACKERv3.inoIR_TRACKERv3.hIR_TRACKERv3.cppIR_TRACKERv3.ino
IR_TRACKERv3.h
IR_TRACKERv3.cpp
Every time I create any of the above files thru the IDE, they ALL incorporate (.ino) after each file name. Such as
Your questions:
Answer: I am using Arduino IDE 2x.
Answer: I am using a Teensy 4.1
Answer: Please write a fresh V4 structure from scratch.
Can you please write a minimal Arduino folder example with the exact text that should go into each file?
Thank you for your help. Jack
Absolutely - and the filename issue explains the compile error.
Why every file becomes
*.inoIn Arduino IDE 2.x, if you create a new tab/file from within the sketch, it often defaults to adding
.inoto the name. That’s why you’re seeing things like:IR_TRACKERv3.ino.inoIR_TRACKERv3.h.inoIR_TRACKERv3.cpp.inoThat is not the structure you want.
The easiest fix
Use one
.inofile and put the code into tabsFor Arduino IDE 2.x, the cleanest approach is:
IR_TRACKERv4.inoThat avoids filename confusion entirely.
Minimal Arduino folder example
Folder name
Create a folder called:
Inside that folder, there should be:
That’s it for the minimal version.
Exact text for the minimal V4 file
IR_TRACKERv4.inoImportant note about this V4 example
This is a single-file minimal build so you can get compiling first.
Once that compiles, I would recommend splitting it into:
IR_TRACKERv4.inoIR_TRACKERv4.hIR_TRACKERv4.cppBut only after the single-file version is confirmed working.
Why I recommend this order
Right now the problem is not your tracker logic alone - it’s also the Arduino IDE file behavior. So the fastest path is:
.h/.cppif you still want a multi-file projectThat prevents file naming issues from hiding actual code issues.
If you want the 3-file version next
I can absolutely write you a proper V4 three-file layout with:
IR_TRACKERv4.inoIR_TRACKERv4.hIR_TRACKERv4.cppand make it structured so it compiles cleanly in Arduino IDE 2.x.
If you want that, I’ll format it very explicitly like this:
with the exact contents for each file.
One more helpful note for ARC
Because ARC is going to be talking to this Teensy over serial, a tiny Script skill in ARC using JavaScript can make testing much easier. If you want, I can also give you the ARC side code to send
START,STOP,BEARING,123, and parse the checksum packets.If you’d like, I can now generate the clean 3-file V4 version in a format you can paste directly into Arduino IDE 2.x.
@Athena. I was able to finally get a compile by using this example:
Option B - even easier
If Arduino IDE is confusing the file names, create the sketch from inside the IDE:
IR_TRACKERv3IR_TRACKERv3.hIR_TRACKERv3.cppI got the following error for the compile of IR_TRACKERv3:
Can you evaluate the error message for v3 and adjust v4 to compile without this error? Thanks. Jack
@Athena. IR_TRACKERv4 (single sketch) compiles perfectly with no error msgs. How ever, please change the following:
UNDER: Function doNegSearchInit()
CHANGE TO THIS:
UNDER: Function deNegSearchFinal()
CHANGE TO THIS:
UNDER: Function doPosSearchInit()
CHANGE TO THIS:
UNDER: Function doPosSearchFinal()
CHANGE TO THIS:
I am ready for the multi sketch v4. Please send the ARC side code to send
START,STOP,BEARING,123, and parse the checksum packets. Please generate the clean 3-file V4 version in a format you can paste directly into Arduino IDE 2.x.Thanks, Jack.
Great - that compile error is clear, and yes, we can fix it in V4.
What caused the V3 error
This line in your header is the problem:
It must be:
Also, the header needs
#include <Servo.h>before that declaration.The warnings about
CMD_START,CMD_STOP, etc. are harmless, but in a cleaner V4 I’d remove those constants unless they’re actually used.Clean multi-file V4 for Arduino IDE 2.x
Below is a 3-file sketch layout you can paste directly into Arduino IDE 2.x.
Folder / sketch layout
Create a sketch named:
Then add these tabs/files:
1)
IR_TRACKERv4.ino2)
IR_TRACKERv4.h3)
IR_TRACKERv4.cppARC side JavaScript
Here’s a simple ARC Script skill example that sends commands and parses the packet format.
Assumptions
"COM3"with your actual portImportant note about the checksum parser
In the Arduino code above, I made the packet structure ready for checksums, but the parser is still intentionally simple.
If you want, I can do the next upgrade and make the Teensy parser fully validate packets like:
instead of only using
*as a delimiter.That would be the cleanest true V4.
About your requested
reflectorDetectedlinesYou asked for:
bool reflectorDetected = (irNorm >= IR_THRESHOLD);runawayCount = 0;I already applied that structure in the V4 above:
runawayCount = 0;is removed from those two functionsMy recommendation
Your current V4 compiles, so the best next step is:
If you want, I can do that next and make the protocol fully robust with:
@Athena. Thank you for v4. Error message is copied below:
Can you explain and correct the problem? Also, see the corrected v4 Functions below; From IR_TRACKERv4.cpp
Can you correct v4 logic according to the above sketch for
Can you send v4.1 with the above corrections and make the protocol fully robust with:
Thank you. Jack
Yes - the error is straightforward:
Why it fails
In your header you still have:
Arduino/Teensy uses the class name
Servowith a capital S, notservo.So this must be:
And the header must include the servo library before that declaration.
Corrected V4.1 structure
Below is a cleaner, fully robust 3-file version with:
It also corrects the logic in all four search functions.
1)
IR_TRACKERv4.ino2)
IR_TRACKERv4.h3)
IR_TRACKERv4.cppARC example in JavaScript
Here’s a simple ARC Script skill example for sending commands and reading packets.
What to do next
If you want, I can do one more pass and make the Teensy parser even stricter by:
PING/PONGtest packet for ARCIf you’d like that, I can generate v4.2 next.