@Athena,
Thread theme: Scanning strategy
Since IR_TRACKER_v1 and OBS_SCAN_v1 share the same servo and the same IR sensor and make incompatible assumptions about the sensor values, they should not run at the same time. These are both Robot Skills in my ARC project, so my intended method is:
- Let IR_TRACKER_v1 own the main loop (it will be the active robot skill controlling the IR sensor most of the time).
- Have IR_TRACKER_v1 check for $obsScanRequested == 1.
- Pause the tracker logic when a scan is requested.
- Start OBS_SCAN_v1 with controlCommand("OBS_SCAN_v1", "ScriptStartWait").
- Resume IR_TRACKER_v1 after the scanning script completes.
I think this is a clean design because it provides:
- One robot skill in control of the IR sensor at a time.
- No conflicting servo motion.
- Predictable scan behavior.
- Easier debugging.
For the initial scan before movement, I have ROVER_CMD_v5 start OBS_SCAN_v1 by inserting it into the startup order along with UART_v7, LEG_v5, and IR_TRACKER_v1. All flags start at 0:
- setVar("$waypointSent", 0);
- setVar("$sendWaypointNow", 0);
- setVar("$obsScanRequested", 0);
- etc.
Startup sequence (ROVER_CMD_v5):
- FIRST START: controlCommand("UART_v7", "ScriptStart") - UART is running; flags are 0. The UART script loops waiting for flag changes.
- SECOND START: controlCommand("OBS_SCAN_v1", "ScriptStartWait") - OBS_SCAN_v1 completes the obstacle scan and either sets a global variable $obsDet = 1 or times out and issues a global variable to STOP. UART picks this up and the rover is shut down if needed.
- THIRD START: controlCommand("LEG_v5", "ScriptStartWait") - LEG_v5 populates all waypoint data global variables and sets "$sendWaypointNow = 1". The UART loop detects this flag and sends WAYPOINT DATA to the Arduino.
- FOURTH START: controlCommand("IR_TRACKER_v1", "ScriptStart") - from this point forward, the only flag toggled for obstacle scanning will be "$obsScanRequested". It is initially 0 and is set to 1 when the Arduino counts steps to the first scan interval.
ROVER_CMD_v5 (current script):
// ROVER_CMD_v5
controlCommand("UART_v7.1", "ScriptStart");
controlCommand("OBS_SCAN_v1", "ScriptStartWait");
// Load the leg values
controlCommand("LEG_v5", "ScriptStartWait");//scriptstartwait
// At this point LEG_v5 will have queued the waypoint transmission
print("ROVER_CMD_v5: leg loaded and UART running");
controlCommand("IR_TRACKER_v1", "ScriptStart");
What do you think of this strategy for the initial obstacle scan? The remaining obstacle scans will be handled by IR_TRACKER_v1 to avoid conflicts with the IR sensor data.
Note: It appears the Arduino is connected to ARC via a UART/serial robot skill (UART_v7) running on the PC. Also, there is a small naming ambiguity in the scripts: earlier I refer to $obsScanRequested but the UART_v7 handler sets $obstacleScanRequested on receiving an RSP_OBS_SCAN_REQUEST. I’m calling this out so we can ensure the global variable names match across Robot Skills.
If you agree with the strategy above, can you rewrite an updated Arduino_v7 that will work with the latest UART_v7 posted below?
Thank you, Jack
UART_v7
var UART_INDEX = 0;
var UART_HEADER = 0xA5;
var CMD_STOP_MOTORS = 0x01;
var CMD_ACTUAL_BEARING = 0x03;
var CMD_RIGHT_PIVOT = 0x04;
var CMD_LEFT_PIVOT = 0x05;
var CMD_WAYPOINT_DATA = 0x06;
var RSP_NAV_ACK = 0x10;
var RSP_TOTAL_STEPS = 0x11;
var RSP_DESIRED_BEARING = 0x12;
var RSP_OBS_SCAN_REQUEST = 0x13;
var rxState = 0;
var rxCmd = 0;
var rxLen = 0;
var rxIndex = 0;
var rxPayload = [];
var rxChecksum = 0;
function safeInt(v, def) {
var n = parseInt(v);
if (isNaN(n)) return def;
return n;
}
function clamp(v, min, max) {
if (v < min) return min;
if (v > max) return max;
return v;
}
function u16ToBytes(value) {
value = value & 0xFFFF;
return [value & 0xFF, (value >> 8) & 0xFF];
}
function u32ToBytes(value) {
value = value >>> 0;
return [
value & 0xFF,
(value >> 8) & 0xFF,
(value >> 16) & 0xFF,
(value >> 24) & 0xFF
];
}
function bytesToU16LE(b0, b1) {
return (b0 & 0xFF) | ((b1 & 0xFF) << 8);
}
function bytesToU32LE(b0, b1, b2, b3) {
return ((b0 & 0xFF) |
((b1 & 0xFF) << 8) |
((b2 & 0xFF) << 16) |
((b3 & 0xFF) << 24)) >>> 0;
}
function checksumXor(cmd, len, payload) {
var c = (cmd ^ len) & 0xFF;
for (var i = 0; i < payload.length; i++) {
c ^= (payload[i] & 0xFF);
}
return c & 0xFF;
}
function sendPacket(cmd, payload) {
var checksum = checksumXor(cmd, payload.length, payload);
UART.hardwareUartWrite(UART_INDEX, [UART_HEADER, cmd, payload.length]);
if (payload.length > 0) {
UART.hardwareUartWrite(UART_INDEX, payload);
}
UART.hardwareUartWrite(UART_INDEX, [checksum]);
}
function sendStopMotors() {
sendPacket(CMD_STOP_MOTORS, []);
print("TX: STOP MOTORS");
}
function sendActualBearing() {
var bearing100 = safeInt(getVar("$actualBearing100", 9000), 9000);
bearing100 = clamp(bearing100, 0, 18000);
sendPacket(CMD_ACTUAL_BEARING, u16ToBytes(bearing100));
print("TX: ACTUAL BEARING x100 = " + bearing100);
}
function sendWaypointData() {
var pathTotalDistance = safeInt(getVar("$pathTotalDistance", 0), 0) >>> 0;
var combinedStepsDesired = safeInt(getVar("$combinedStepsDesired", 0), 0) >>> 0;
var oppositeDistance = safeInt(getVar("$oppositeDistance", 0), 0) >>> 0;
var bearingAlignment = safeInt(getVar("$bearingAlignment", 0), 0) & 0xFF;
var trackDir = safeInt(getVar("$trackDir", 0), 0);
trackDir = (trackDir === 0) ? 0 : 1;
var desiredBearing100 = clamp(safeInt(getVar("$desiredBearing100", 9000), 9000), 0, 18000);
var actualBearing100 = clamp(safeInt(getVar("$actualBearing100", 9000), 9000), 0, 18000);
var scanIntervalSteps = safeInt(getVar("$scanIntervalSteps", 3056), 3056) >>> 0;
setVar("$desiredBearing100", desiredBearing100);
setVar("$actualBearing100", actualBearing100);
var payload = [];
payload = payload.concat(u32ToBytes(pathTotalDistance));
payload = payload.concat(u32ToBytes(combinedStepsDesired));
payload = payload.concat(u32ToBytes(oppositeDistance));
payload.push(bearingAlignment);
payload = payload.concat(u16ToBytes(desiredBearing100));
payload = payload.concat(u16ToBytes(actualBearing100));
payload.push(trackDir);
payload = payload.concat(u32ToBytes(scanIntervalSteps));
if (payload.length !== 22) {
print("Waypoint payload length error: " + payload.length);
return;
}
sendPacket(CMD_WAYPOINT_DATA, payload);
print("TX: WAYPOINT DATA");
print(" pathTotalDistance=" + pathTotalDistance);
print(" combinedStepsDesired=" + combinedStepsDesired);
print(" oppositeDistance=" + oppositeDistance);
print(" bearingAlignment=" + bearingAlignment);
print(" desiredBearing100=" + desiredBearing100);
print(" actualBearing100=" + actualBearing100);
print(" trackDir=" + trackDir);
print(" scanIntervalSteps=" + scanIntervalSteps);
}
function sendRightPivot() {
var steps = safeInt(getVar("$pivotSteps", 0), 0) >>> 0;
sendPacket(CMD_RIGHT_PIVOT, u16ToBytes(steps));
print("TX: RIGHT PIVOT steps=" + steps);
}
function sendLeftPivot() {
var steps = safeInt(getVar("$pivotSteps", 0), 0) >>> 0;
sendPacket(CMD_LEFT_PIVOT, u16ToBytes(steps));
print("TX: LEFT PIVOT steps=" + steps);
}
function handleRxPacket(cmd, payload) {
if (cmd == RSP_NAV_ACK) {
print("RX: NAV ACK");
setVar("$navAckReceived", 1);
return;
}
if (cmd == RSP_TOTAL_STEPS) {
if (payload.length == 4) {
var steps = bytesToU32LE(payload[0], payload[1], payload[2], payload[3]);
setVar("$totalStepsTaken", steps);
setVar("$legComplete", 1);
print("RX: TOTAL STEPS = " + steps);
}
return;
}
if (cmd == RSP_DESIRED_BEARING) {
if (payload.length == 2) {
var bearing100 = bytesToU16LE(payload[0], payload[1]);
print("RX: DESIRED BEARING x100 = " + bearing100);
setVar("$desiredBearing100", bearing100);
}
return;
}
if (cmd == RSP_OBS_SCAN_REQUEST) {
print("RX: OBSTACLE SCAN REQUEST");
setVar("$obstacleScanRequested", 1);
return;
}
print("RX: Unknown cmd=" + cmd + " len=" + payload.length);
}
function processIncomingBytes() {
var available = UART.hardwareUartAvailable(UART_INDEX);
while (available > 0) {
var b = UART.hardwareUartRead(UART_INDEX, 1)[0] & 0xFF;
available--;
if (rxState == 0) {
if (b == UART_HEADER) rxState = 1;
continue;
}
if (rxState == 1) {
rxCmd = b;
rxChecksum = rxCmd;
rxState = 2;
continue;
}
if (rxState == 2) {
rxLen = b;
rxChecksum ^= rxLen;
rxPayload = [];
rxIndex = 0;
rxState = (rxLen == 0) ? 4 : 3;
continue;
}
if (rxState == 3) {
rxPayload.push(b);
rxChecksum ^= b;
rxIndex++;
if (rxIndex >= rxLen) rxState = 4;
continue;
}
if (rxState == 4) {
var cs = b & 0xFF;
if (cs == (rxChecksum & 0xFF)) {
handleRxPacket(rxCmd, rxPayload);
} else {
print("RX checksum error. cmd=" + rxCmd + " expected=" + (rxChecksum & 0xFF) + " got=" + cs);
}
rxState = 0;
rxCmd = 0;
rxLen = 0;
rxIndex = 0;
rxPayload = [];
rxChecksum = 0;
continue;
}
}
}
print("UART_v7 started");
while (true) {
if (getVar("$sendWaypointNow", 0) == 1 && getVar("$waypointSent", 0) == 0) {
sendWaypointData();
print("$sendWaypointNow: " + getVar("$sendWaypointNow",0));
setVar("$sendWaypointNow", 0);
setVar("$waypointSent", 1);
}
if (getVar("$sendStopNow", 0) == 1) {
sendStopMotors();
setVar("$sendStopNow", 0);
}
if (getVar("$sendActualBearingNow", 0) == 1) {
sendActualBearing();
setVar("$sendActualBearingNow", 0);
}
if (getVar("$sendRightPivotNow", 0) == 1) {
sendRightPivot();
setVar("$sendRightPivotNow", 0);
}
if (getVar("$sendLeftPivotNow", 0) == 1) {
sendLeftPivot();
setVar("$sendLeftPivotNow", 0);
}
processIncomingBytes();
sleep(20);
}
Related Hardware (view all EZB hardware)
Upgrade to ARC Pro
Don't limit your robot's potential – subscribe to ARC Pro and transform it into a dynamic, intelligent machine.
While new comments are no longer being accepted, the information here remains available for learning and reference. Check the most recent comment for additional details.

@Athena.
Note: It appears the Arduino is connected to ARC via a UART/serial robot skill (UART_v7) running on the PC. Also, there is a small naming ambiguity in the scripts: earlier I refer to $obsScanRequested but the UART_v7 handler sets $obstacleScanRequested on receiving an RSP_OBS_SCAN_REQUEST. I’m calling this out so we can ensure the global variable names match across Robot Skills.
Thank you for this note. Any "$obstacleScanRequested" should be named "$obsScanRequested" . Thank you Jack
Yes - your strategy is solid.
Having one skill own the IR sensor and shared servo at a time is the cleanest way to avoid:
Recommended flow
Your plan makes sense:
ROVER_CMD_v5startsUART_v7ROVER_CMD_v5runsOBS_SCAN_v1once at startup withScriptStartWaitROVER_CMD_v5runsLEG_v5withScriptStartWaitROVER_CMD_v5startsIR_TRACKER_v1$obsScanRequested = 1IR_TRACKER_v1pauses its own logic, runsOBS_SCAN_v1, then resumesThat’s a good ARC design because it uses robot skill messaging cleanly, and ARC skills are meant to cooperate that way through globals and
controlCommand().Important naming fix
Yes, you should standardize on:
$obsScanRequestedand not:
$obstacleScanRequestedIf
UART_v7is setting the longer variable name but the other skills are checking the shorter one, the request will never be seen.So in
UART_v7, change:to:
One thing I would improve
For the scan handoff, I’d recommend also using a "scan busy" flag so both skills stay synchronized:
$obsScanRequested = 1tracker requests a scan$obsScanActive = 1scan skill is currently running$obsScanRequested = 0when the request is accepted$obsScanActive = 0when the scan finishesThat prevents repeated requests while the scan is already in progress.
Updated
ROVER_CMD_v5Your startup script should be a little safer like this:
If you want the initial scan to happen before the leg data is sent, that order is correct.
Suggested behavior inside
IR_TRACKER_v1When it sees a scan request:
That gives you a clean pause/resume model.
Updated
UART_v7fixHere is the exact change you need in the receive handler:
My recommendation
I would not have both
IR_TRACKER_v1andOBS_SCAN_v1directly manipulating the shared sensor at the same time. Your plan to serialize access is the right approach in ARC.If you want, I can rewrite your full
UART_v7with:obsScanActivesupportIf you paste
OBS_SCAN_v1andIR_TRACKER_v1, I can also help you make the handoff logic match perfectly.@Athena. Thank you, excellent recommendations.
For the scan handoff, I’d recommend also using a "scan busy" flag so both skills stay synchronized:
$obsScanRequested = 1tracker requests a scan$obsScanActive = 1scan skill is currently running$obsScanRequested = 0when the request is accepted$obsScanActive = 0when the scan finishesThat prevents repeated requests while the scan is already in progress.
Inside IR_TRACKER_v1:
Can you rewrite
UART_v7with the following corrections and label it UART_v8?obsScanActivesupportAlso, Arduino_v7 is posted below. Can you rewrite it to seamlessly work with the new UART_v8? Thanks. Jack
Arduino_v7
Absolutely - this is a good time to cleanly formalize the protocol.
Your scan handshake idea is solid, and I agree with your naming correction:
$obsScanRequested$obsScanActiveThat will make the ARC side much easier to reason about.
Below is a rewritten
UART_v8for ARC and a compatibleArduino_v8that matches it.What changed in
UART_v8Fixes and improvements
$obsScanRequested$obsScanActivesupportUART_v8for ARCArduino_v8compatible withUART_v8This version keeps your protocol intact but adds the missing consistency pieces and the scan handshake behavior.
One important note
I made one structural assumption:
CMD_WAYPOINT_DATAis 22 bytes in the same order as your ARC script sends it:pathTotalDistance4combinedStepsDesired4oppositeDistance4bearingAlignment1desiredBearing1002actualBearing1002trackDir1scanIntervalSteps4That matches your
UART_v8layout.Recommendation for the scan handshake
For best reliability, I’d suggest this exact sequence in
IR_TRACKER_v1:$obsScanRequested == 1$obsScanActive = 1$obsScanRequested = 0OBS_SCAN_v1withScriptStartWait$obsScanActive = 0That prevents duplicate requests while scan is running.
Small ARC scripting tip
If you want, I can also rewrite your
IR_TRACKER_v1scan-handling block in JavaScript so it cleanly cooperates withUART_v8.And for protocol work like this, ARC’s built-in AI Assistant can be handy too because it can help rewrite scripts right inside the editor: https://synthiam.com/Products/AI-Assistant
If you'd like, paste your current
IR_TRACKER_v1andOBS_SCAN_v1logic next, and I’ll help make the full handoff airtight.@Athena.
Thank you for your recommendations. I have lots to work with now. I need to verify all commands and the PWM control. I intend to get to you IR_TRACKER_v1 and OBS_SCAN_v1 when they're in a more finalized state. I am very pleased with the work you have done on UART_v8 and Arduino_v8. Thank you. I do have a question about the role of $obsScanActive:
Here is the sequence in IR_TRACKER_v1:
How is $obsScanActive doing anything to prevent OBS_SCAN_v1 from activating incorrectly or being repeatedly called unintentionally? $obsScanActive is not in an 'if' statement at all. Can you please help me understand this? Thanks. Jack
Yes - good catch.
$obsScanActiveby itself does not prevent anything unless some skill actually checks it.What
obsScanActiveis forIt’s not the lock by itself. It’s a state flag that tells the rest of the project:
So the protection comes from other code reading it, not from the assignment alone.
Why
obsScanRequestedis the actual triggerIn your sequence, this is the real gate:
Here:
$obsScanRequested == 1means "start a scan now"ScriptStartWaitblocks untilOBS_SCAN_v1finishesSo
$obsScanRequestedis the actual launch condition.What
obsScanActiveadds$obsScanActiveis useful for these situations:1. Preventing re-request spam
If something else, like
UART_v8orIR_TRACKER_v1, sees:it can refuse to queue another scan.
2. Debugging
You can watch it in ARC’s Variable Watcher and immediately know whether the rover thinks a scan is underway.
3. Coordination with other skills
If later you add another script that might also request a scan, it can check
$obsScanActivebefore setting$obsScanRequested = 1.The key point
If only IR_TRACKER_v1 is handling scans, then yes,
$obsScanActiveis optional.Your flow still works without it because:
ScriptStartWaitblocks until the scan is completeThat means the scan cannot be repeatedly launched from that same code path unless something else keeps setting
$obsScanRequestedagain.When
obsScanActivebecomes usefulIt becomes useful if you want to guard requests like this:
That way, even if some other skill mistakenly keeps asserting
$obsScanRequested, the scan won’t be started again while one is already underway.My recommendation
If your project is still evolving, I’d keep
$obsScanActivebecause it gives you:But if you want the simplest possible design, you can remove it and rely only on:
$obsScanRequestedScriptStartWaitThat will still work fine as long as only one place is responsible for launching scans.
Short answer
$obsScanActivedoes not prevent the scan by itself. It is only useful if other skills check it before requesting or starting another scan.If you want, I can show you the cleanest minimal version of the IR_TRACKER_v1 scan block with or without
$obsScanActive.@Athena. Thank you. Jack