Asked — Edited
Resolved Resolved by Athena!

Improving IR Sensor Efficiency Javascript

Hi @Athena,

I am working on repeatedly pulsing a Sharp IR sensor using JavaScript within my ARC project. Here's a snippet of my current script:

var increment = 3; 
var irValue = 50;

var lowIR = ADC.get(2);                
sleep(10);               

// IR THRESHOLD FOR A HIT
if (lowIR <= irValue) { 
    missCount = 0;
    cycleCount++;
    kick = false; 

    if (scanDir == 0) {
        negHit = Servo.getPosition(17);
        setVar("$actualCourse", (negHit + 1.5)); 
        Servo.SetPosition(17, (negHit + increment));   
        scanDir = 1;
    } else {
        posHit = Servo.getPosition(17);
        setVar("$actualCourse", (posHit - 1.5));   
        Servo.SetPosition(17, (posHit - increment));
        scanDir = 0;  
    }
    sleep(1);    
}

My issue involves the use of the ADC.get(2) command in this script. The entire script's execution speed decreases by about 75% when including the ADC command compared to running without it. Specifically, without ADC, the cycle count reaches around 400, while it drops below 100 when including the ADC call. Is there a more efficient approach to pulsing the IR sensor to maintain better performance? Thanks, Jack.



Related Hardware (view all EZB hardware)
EZ-B V4 by EZ-Robot
EZ-B v4/2 robot controller: dual Cortex ARM, Wi-Fi, audio/video, 24 servo/digital ports, I2C/UART, camera and Synthiam ARC control for custom robots
Wi-Fi / USB
Servos 24
Camera
Audio
UART 3
I2C
ADC 8
Digital 24

Related Robot Skills (view all robot skills)
ADC Graph by Synthiam
Historical ADC graph and numeric readout (0-255) for 0-3.3/5V inputs; configurable board/port, sample interval, color, and pause for ezb controllers.
ADC Value by Synthiam
Display ADC port voltage and 0-255 linear values (0-3.3/5V); configurable board/port, units, multiplier and sample interval.

ARC Pro

Upgrade to ARC Pro

Experience early access to the latest features and updates. You'll have everything that is needed to unleash your robot's potential.

Author Avatar
PRO
Canada
#1  

Good details - thanks Jack. You’re correct that repeatedly calling ADC.get() inside a tight loop will drastically slow the whole script (and can flood the EZ-B communication channel), especially over Wi-Fi. Below I explain why it’s slow and give three practical, efficient alternatives (with JavaScript examples) you can use right away.

Why ADC.get() slows things down

  • ADC.get() performs a blocking read to the EZ-B. Each call waits for the EZ-B reply (TCP/WiFi roundtrip or serial), which adds many milliseconds per call.
  • Tight loops that call ADC.get() many times per second multiply that latency and reduce overall cycle count.
  • ARC was designed to avoid tight polling - use sleep() and event/wait functions or use an already-running robot skill that polls for you.

Options (choose one)

  1. Best for performance: Use ADC.waitFor* (event-based polling)
  • These functions suspend your script and let ARC handle periodic reads. You avoid busy polling and you only wake when the condition occurs (or timeout).
  • Example: wait until ADC port 2 drops below threshold (a "hit") - sample every 50 ms, 1 s timeout.
var adcPort = adc2;            // use the constant (no quotes)
var threshold = 50;           // your IR threshold
var sampleMs = 50;            // how often the board is polled
var timeoutMS = 1000;         // 1 second timeout (change as needed)

while (true) {
  // This suspends until the value is lower than threshold or timeout (returns -1)
  var val = ADC.waitForLower(adcPort, threshold, sampleMs, 0, timeoutMS);
  if (val === -1) {
    // timeout: no hit, continue sweep / do other tasks
    sleep(50);
    continue;
  }
  // Hit detected; 'val' is the ADC reading (0-255)
  print("IR HIT: " + val);
  // handle servo/sweep logic here (example)
  var pos = Servo.getPosition(d17);
  Servo.setPosition(d17, pos + 3); // or your logic
  // small pause so servo commands are not spammed
  sleep(50);
}

Why this is efficient: ARC handles the periodic reads for you. Your script is not hammering ADC.get() repeatedly and other ARC threads get CPU/EZB time.

  1. Use the ADC Value robot skill as the single poller and read its variable (zero extra EZB reads)
  • You said your project already has the ADC Value skill. That skill polls the ADC at the interval configured in its settings.
  • Instead of calling ADC.get(), read the global variable the skill populates (so you don’t make extra EZB calls).
  • Steps:
    • Open the ADC Value skill (use the kebab/hamburger menu Settings) and set the sampling Interval to a value you need (e.g., 50 ms).
    • Note the skill’s title or the variable name it publishes (if the skill creates a variable, paste the exact name here and I’ll show code). If you don’t see a variable field, tell me the skill title and I’ll suggest how to identify the variable it publishes.
  • Example pattern (replace $MyADCVar with the actual variable name):
var threshold = 50;

while (true) {
  var s = getVar("$MyADCVar", "");   // replace with your ADC Value skill variable
  if (s === "") { sleep(50); continue; }
  var val = parseInt(s, 10);
  if (val <= threshold) {
    print("IR Hit: " + val);
    // do servo logic
  }
  sleep(50); // non-tight loop, adjust sample interval as needed
}

Why this is efficient: only the ADC Value skill performs EZB reads. Your script reads a global variable locally - no extra EZB traffic.

  1. Minimal change: reduce call frequency and avoid tight loop
  • If you must use ADC.get(), add an appropriate sleep() so you don’t call it too fast.
  • Even sleep(50) or sleep(100) will significantly increase cycles-per-second and relieve the EZB comm channel.

Example:

var threshold = 50;
while (true) {
  var lowIR = ADC.get(adc2); // this is blocking
  if (lowIR <= threshold) {
    // hit logic
  }
  // give CPU and EZ-B time: tune between 20-200 ms
  sleep(50);
}

Other considerations / advanced

  • If you have many skills/scripts reading the same ADC port, consolidate to a single poller (either the ADC Value skill or one script) and share the result via a global variable.
  • If you’re using WiFi EZB and need very fast sampling, consider putting IR sensors on a separate EZB (or inexpensive microcontroller) so the main EZB isn’t flooded.
  • Use ADC.waitForBetween/Equals/Higher/Lower variants to match your logic.
  • Avoid sleep(1) in tight loops - it’s too small to relieve the comm channel. Use 20-200 ms depending on required responsiveness.
  • Make sure your ADC Value skill Interval (kebab menu settings) isn’t very slow; set it to match desired sample rate.

Next steps I can help with

  • I can rewrite your full loop to use ADC.waitForLower or to read the ADC Value skill variable. Tell me:
    • Are you connected to the EZB over WiFi or USB?
    • Which ADC Value skill variable name is being published (or what is the skill title)?
    • What maximum latency is acceptable for detection (e.g., 50 ms, 100 ms)?
  • If you want, paste the ADC Value skill settings (Interval) and your desired detection timing and I’ll produce a ready-to-drop-in script.

Useful links

Which option would you like me to implement for you (waitFor*, read the skill variable, or reduce polling with bigger sleep)?


I am a customer support GPT-powered AI and still learning. If you have doubts about my response, please use the support documentation to verify my answer.
#2  

@Athena, For option #1 (  ADC.waitForLower(adcPort, threshold, sampleMs, 0, timeoutMS).  Does this ADC call "block" the script from running until timeout or trigger from a "hit"?   I require the script to loop as fast as possible to detect deviations from a desired course.   Thanks Jack

Author Avatar
PRO
Canada
#3  

Short answer: Yes - ADC.waitForLower(...) does block that script’s thread until either the ADC reading meets the condition or the timeout occurs. It does NOT freeze ARC or other robot skills - only that script waits (suspends) efficiently rather than burning CPU or flooding the EZ-B.

Below is what that means for your requirement to "loop as fast as possible," plus two recommended approaches (with copy/paste JavaScript) so you can choose the best trade-off for responsiveness vs communication load.

  1. Behavior of ADC.waitForLower
  • It suspends your script until either:
    • a value lower than the threshold is detected, OR
    • the timeoutMS expires (returns -1).
  • The frequencyMs parameter controls how often ARC polls the EZ-B internally while the wait is active.
  • Because it’s a blocking wait, the script yields CPU/EZB access to other threads while waiting - much more efficient than repeatedly calling ADC.get() in a tight loop.
  1. Recommended approaches

Option A - Fast, event-driven (use ADC.waitForLower with short sample and short timeout)

  • Good when you want the script to be efficient but still react quickly.
  • Use a small sampleMs (10-20 ms) and a short timeoutMS (e.g. 30-100 ms). Short timeout lets the loop wake periodically to perform other checks, so behavior looks like a tight loop but without hammering the EZ-B.
  • Example (adjust thresholds/timings for your use case):
var adcPort = adc2;      // use the constant (no quotes)
var threshold = 50;      // IR threshold
var sampleMs = 10;       // how often ARC polls the EZ-B internally while waiting
var timeoutMS = 50;      // how long to wait before timing out (ms)

while (true) {
  var val = ADC.waitForLower(adcPort, threshold, sampleMs, 0, timeoutMS);
  if (val === -1) {
    // timeout (no hit in timeoutMS). Perform course-deviation checks, steering, etc.
    // Do lightweight work here
  } else {
    // Hit detected. 'val' is the ADC reading (0-255)
    print("IR HIT: " + val);
    // Handle servo/sweep/avoid logic
  }
  // Keep a small pause if needed (tune to your responsiveness needs)
  sleep(5);
}

Why this helps: the script is not busy-polling with ADC.get(). The EZ-B reads are performed efficiently by ARC’s wait call, and the short timeout gives you periodic wakeups to check or adjust course.

Option B - Fastest loop (single poller + local variable reads)

  • Best for the fastest in-script loop where you must detect tiny deviations as fast as possible.
  • Let the ADC Value robot skill poll the ADC port at a set interval (e.g., 10-20 ms) and publish the reading to a global variable.
  • Your script then reads that global variable with getVar("$YourADCVar") which is local and fast (no EZ-B roundtrip). This avoids extra EZB traffic entirely because only the ADC Value skill performs EZB reads.
  • Steps:
    1. Open your ADC Value skill (kebab menu / Settings) and set Interval to the sample you want (10-20 ms recommended; if on WiFi you may want 20-50 ms to avoid flooding).
    2. Note the variable the skill populates (if the skill exposes a variable name, use that). If you’re not sure, tell me the skill title and I’ll show how to identify it.
    3. Script example (replace $MyADCVar with the actual variable name):
var threshold = 50;

while (true) {
  var s = getVar("$MyADCVar","");   // read the ADC Value skill output (local)
  if (s === "") { sleep(10); continue; }
  var val = parseInt(s, 10);
  if (val <= threshold) {
    print("IR HIT: " + val);
    // servo / avoidance logic
  } else {
    // course check / normal behavior
  }
  sleep(5); // reduce to tune CPU usage (or 0 if you absolutely need)
}

Why this helps: getVar() is instant and does not generate an EZ-B read. The ADC Value skill is the only thing calling the EZ-B, so communication is consolidated and efficient.

Notes and trade-offs

  • If you’re using EZ-B over WiFi, be conservative with polling frequency. Very small intervals (<=10 ms) can flood the WiFi link; 20-50 ms is usually safer.
  • ADC.waitForLower is simpler and clean. If you require absolute minimum latency (microseconds or <10ms reaction), move the IR sensor to a separate microcontroller/EZB and send events to the main controller to avoid blocking the main board.
  • Avoid sleep(1) in tight loops - it’s too small to relieve comm channel issues. Prefer 5-50 ms depending on needs.
  • ADC.waitForLower blocks that script’s thread only. Other ARC skills and scripts will still run normally.

Quick question so I can give you a ready-to-drop-in script:

  • Are you connected to the EZ-B over WiFi or USB?
  • Which latency/response time do you need (e.g., ~20 ms, ~50 ms, ~100 ms)?
  • Do you want me to convert your existing script to Option A (waitForLower) or Option B (ADC Value skill + getVar)?

If you want, paste the ADC Value skill title / current Interval and I’ll produce exact code referencing that variable and step-by-step kebab-menu settings to configure it. Useful link: ADC Value skill - https://synthiam.com/Support/Skills/Adc/ADC-Value?id=16092


I am a customer support GPT-powered AI and still learning. If you have doubts about my response, please use the support documentation to verify my answer.
Author Avatar
PRO
Synthiam
#4  

You will have better luck if you explain what you're trying to do. If you do not define a desired outcome, Athena has to guess, just as I would. And I can't quite guess what you're trying to do either. Not because you have incorrect code, just because we only see code and don't know what it's used for. So rather than explain the issue, explain the outcome. (https://synthiam.com/Support/website-features/How-To-Ask-Robot-Building-Questions)

So, looking at your code, it's moving a servo and getting an adc value? But you're doing it as fast as possible (mind you, you have other delays), which has other issues. Such as the servo hasn't finished moving yet and is reading an ADC value. I advise you to explain what you're doing, and that'll make it a lot easier to get help from Athena.

Author Avatar
PRO
Canada
#5  

Good points from DJ_Sures - before we optimize code I need a clear statement of the outcome you want so we don’t optimize in the wrong direction.

Quick checklist - please answer these so I can give you a ready-to-drop-in script:

  • What is the desired outcome? (Examples: "sweep the sensor and stop when object detected", "steer robot immediately when IR drops below threshold while moving forward," or "scan continuously and log hits but continue course checks")
  • How fast must the loop react? (acceptable latency: ~10 ms, ~20-50 ms, ~100 ms?)
  • How is your EZ-B connected to ARC: WiFi or USB? (WiFi needs more conservative polling to avoid flooding)
  • Do you want the IR mounted on a sweeping servo (d17) and the servo to move on each detection, or is the servo sweeping continuously and you just want to detect "hits"?

While you answer, here are concrete, tested options and explanations that address DJ_Sures concerns (servo move vs ADC timing) and Jack’s performance issue. Pick one and I’ll adapt it to your exact outcome and timings.

  1. Important concept - servo movement vs ADC read
  • A servo takes tens-hundreds of ms to move and settle. If you call ADC.get() while the servo is still moving you will often read transient values.
  • Best practice: command the servo, then either wait for it to finish settling (Servo.waitForMove) or sleep an appropriate settle time (50-200 ms depending on servo & load) before trusting the ADC reading.
  1. Option A - Event-driven, efficient, responsive (recommended)
  • Use ADC.waitForLower/ADC.waitForHigher with a small sampleMs and a short timeoutMS. This suspends your script efficiently (it does block that script thread) but does NOT freeze ARC or other skills. Use a short timeout so the loop wakes periodically to do other checks.
  • Use Servo.waitForMove or a small settle sleep after sending a servo command so ADC reading isn’t taken mid-motion.

Example (adjust sampleMs/timeoutMS/settle to match your required latency and servo speed):

// Option A - ADC.waitForLower with servo settle
var adcPort = ADC2;   // use ADC port constant
var servoPort = d17;  // servo port constant
var threshold = 50;   // IR threshold (0-255)
var sampleMs = 10;    // how often ARC polls the ADC internally
var timeoutMS = 40;   // how long to wait before timing out
var increment = 3;    // servo step
var settleMS = 120;   // time to let the servo settle after moving

while (true) {
  // Blocks this script until ADC < threshold or timeout (returns -1 on timeout)
  var val = ADC.waitForLower(adcPort, threshold, sampleMs, 0, timeoutMS);

  if (val === -1) {
    // no hit within timeoutMS -> do lightweight course checks
    // (do not do heavy tasks here)
  } else {
    // Hit detected
    print("IR HIT: " + val);

    // Example: nudge servo one direction or alternate
    var cur = Servo.getPosition(servoPort);
    Servo.setPosition(servoPort, cur + increment);

    // Wait for servo motion to complete or settle
    // Servo.waitForMove(port, [ezbIndex], [timeoutMS])
    Servo.waitForMove(servoPort, 0, 300); // optional timeout
    sleep(settleMS); // additional settle if needed
  }

  // small pause so this loop doesn't starve CPU; tune 0-10 ms as needed
  sleep(5);
}

Why this helps

  • ADC.waitFor* avoids tight ADC.get() polling overhead.
  • Short timeoutMS gives you periodic wakes to run course-corrections.
  • Waiting for servo movement or settle ensures ADC reads are stable.
  1. Option B - Fastest in-script loop: single poller + local reads (lowest EZ-B load)
  • Let the ADC Value robot skill poll the ADC port (set its Interval to your desired sample rate, e.g. 10-20 ms). That skill does all EZ-B reads.
  • Your script reads the value from a global variable published by that skill (getVar("$VarName")). getVar() is local and very fast - no extra EZ-B roundtrip. This yields the fastest possible loop inside ARC because only the ADC skill talks to the EZ-B.

How to set it up

  • Open your ADC Value skill kebab menu (settings) set Interval to e.g. 10-20 ms (if using WiFi, prefer 20-50 ms).
  • Confirm the skill exposes/publishes a variable name, or tell me the skill’s title and I’ll show how to identify it.

Script skeleton (replace $MyADCVar with the actual global variable name):

var adcVar = "$MyADCVar"; // replace with actual variable name published by ADC Value skill
var threshold = 50;
var servoPort = d17;
var increment = 3;
var settleMS = 120;

while (true) {
  var s = getVar(adcVar, "");
  if (s !== "") {
    var val = parseInt(s, 10);
    if (val <= threshold) {
      print("IR HIT: " + val);
      var cur = Servo.getPosition(servoPort);
      Servo.setPosition(servoPort, cur + increment);
      Servo.waitForMove(servoPort, 0, 300);
      sleep(settleMS);
    } else {
      // normal behavior / course corrections
    }
  }
  // very small sleep to let UI/other scripts run
  sleep(5);
}

Why this helps

  • No repeated ADC.get() calls in your script - only the ADC skill reads the EZ-B.
  • Your script loops at software speed reading a local variable (getVar) - much faster and predictable.
  1. Option C - If ADC Value skill does not provide a variable
  • Create a dedicated poller script (only script that calls ADC.get()) that sets a global variable with setVar("$MyADC", value). All other scripts read $MyADC. That centralizes EZ-B reads and avoids multiple callers.

Poller example:

// Poller script - run as its own script
var adcPort = ADC2;
var interval = 20; // ms between polls
while(true){
  var v = ADC.get(adcPort); // blocking EZB read
  setVar("$MyADC", v.toString()); // publish local variable
  sleep(interval);
}

Then other scripts read getVar("$MyADC").

  1. Additional practical tips
  • If your robot is on WiFi and you need very fast sampling (<20 ms), be conservative - WiFi + many ADC reads can flood the EZ-B. 20-50 ms is a safer range for WiFi. USB/serial is more forgiving.
  • Avoid sleep(1) - it’s too small to meaningfully relieve the EZ-B comm channel. Use 5-50 ms sleeps.
  • If you absolutely must react faster than the EZ-B+ARC over WiFi can allow, move the IR to a secondary microcontroller (or second EZ-B) that handles real-time scanning and sends only events to the main ARC (via TCP/UDP or a global variable).

Next steps I can do for you

  • Convert your exact existing script to Option A or B with your preferred latencies and the exact ADC/servo ports you use.
  • If you choose Option B, tell me the ADC Value skill title (or paste its settings) so I can use the correct global variable name.
  • If you choose Option A, tell me the required max latency (10/20/50/100 ms) and whether you want Servo.waitForMove used or a fixed settle sleep.

Useful links / how-tos

Which option do you want me to produce code for? Also answer the quick checklist at top (desired outcome, acceptable latency, WiFi/USB) and I’ll make a final tailored script.


I am a customer support GPT-powered AI and still learning. If you have doubts about my response, please use the support documentation to verify my answer.
#6  

@Athena and @DJ, Thank you for your responses.  I very much appreciate your willingness to help.  I hope to avail myself of any and all assistance you can provide.  I must tell you that comprehending what you are saying sometimes feels like drinking from a firehose.  I am a rank amateur in this arena but promise to apply myself diligently to understand and learn.

Objective:

Robot rover navigation across the floor.  The rover uses a single distance measuring sensor of the Triangulation method, of which Sharp Corporation GP2Y0A02YK0F is an example.  This rover communicates via Wi-Fi to ARC and uses an EZBv4 as the overall controller for the (1) Servo, which pans the distance measuring sensor L + R; (2) Distance measuring sensor operation; (3) Calculations for Actual Course and receiving updates for Desired Course from an Arduino via UART.  The Arduino provides motor control via PWM to 4 stepper motors rolling on mecanum wheels.   The distance measuring sensor detects and tracks a fixed reflector positioned across the room at floor level.  The relative bearing to this reflector becomes the basis for navigation.  The Desired Course is calculated within the Arduino based upon distance traveled by counting motor pulses.  The EZB measures the Actual Course (servo position) to the reflector.  The EZB communicates the Actual Course to the Arduino for comparison with the Desired Course and generates PWM motor control.   This rover must use a single distance measuring sensor.  The distance measuring sensor accomplishes two tasks (1) navigation; (2) obstacle detection. A more complete description and background can be had by reviewing US11977391B1 Patent by following the link below:

US11977391B1 - Robot navigation with obstacle detection using a distance measuring sensor - Google Patents

The primary task which the distance measuring sensor must accomplish is that of tracking the reflector.  At certain distances along the way, it will provide a scan for obstacles.  This dual use is what makes its application unique and difficult at the same time.

Strategy for scanning the distance measuring sensor:

Initially, I was using deliberate and careful servo movement with timeouts for ADC IR readings.  The best I could accomplish was a minimally acceptable number of data point comparisons (Desired Course / Actual Course) for PWM motor control.  I tried various schemes for reflector tracking and have settled upon one that I wish to pursue due to the characteristics of the IR sensor.   It is as follows: 1.  The IR beam reflected back to the sensor is well defined and its width is several degrees wide.  Because of the latency of the IR sensor, when the sensor loses the IR beam reflection, a period of time must pass before it is sensitive to any more reflective light.  This characteristic slows the scan unnecessarily.  Therefore, the servo scan must remain within the beam of reflected IR light yet be close to the edges so that if the robot course changes a missed IR beam is noted.  A quick scan minimizes the sensor latency, and it recovers without delay.  2.  Trends are more useful than individual Hits or Misses when it comes to tracking reflective IR light.  Trends are most reliable when the number of data points are huge.  Think of how quick and responsive a hummingbird is to a slight breeze when feeding near a flower.  It's wing flapping rate is huge and arguably inefficient compared to say a butterfly.  However, what the hummingbird loses in efficiency, it makes up for in maneuverability.

Therefore, I wish to scan the servo 3 degrees Left + Right as rapidly as possible.  I know it seems sloppy, hard on the servo and not likely to result in actual, complete servo movement before the reverse servo command is given.  What I do get is rapid movement that allows me to measure the IR reflection within the beam.  I believe the option you provided that uses an ADC skill which produces a global variable is the best option with 100/500 millisecond sample rates.  I would like to call for this global ADC IR reading at each servo movement to the Left and Right.  I recognize that the actual IR reading is simply happening at millisecond intervals and not at the actual servo Left and Right positions.  What I am seeking is trends and the largest number of data points possible. I wanted to provide you with this information so that you may be considering how best to advise me.  I am happy to show you my scripts for review if you wish but be advised some are very long.  In any case I look forward to your comments and will be happy to consider your ideas.

Sincerely, Jack Lawrence

Author Avatar
PRO
Canada
#7  

Thanks Jack - excellent, detailed description. This makes the problem much clearer and lets us design something practical that meets your "many data points / trend" requirement while respecting the WiFi/EZB limitations.

Below I give a robust, ready-to-run approach with two parts:

  • A single poller that reads the ADC and publishes a global variable (so only one EZB read happens). This avoids ADC.get() spam and WiFi round trip overhead.
  • A fast scanning/trend script that oscillates the pan servo 3 and reads the local global variable to collect many samples quickly, then reports an Actual Course value (setVar("$actualCourse",...)) that you can send to the Arduino via UART.

I also explain tuning recommendations and trade-offs for WiFi / responsiveness and for the sensor/servo characteristics you described.

Important notes before you run:

  • You said ARC EZB is over WiFi. Keep ADC poll interval conservative (20-50 ms) to avoid flooding the WiFi link. If you later move to USB/serial you can reduce intervals further.
  • I use ARC constants: ADC2 and d17 (your servo). Adjust if your hardware uses different ports.
  • Use print() for debug output in ARC JavaScript.
  1. Single ADC poller script (run this as its own script)
  • Purpose: Poll the IR ADC at a steady interval and publish a single global variable $IRADC that all other scripts read locally with getVar(). This consolidates all EZB ADC traffic to one source.
// ADC poller - run separately (one instance only)
// Adjust intervalMs for WiFi (20-50 ms recommended)
var adcPort = ADC2;
var intervalMs = 25; // ms - set 20-50ms for WiFi; lower if USB
while (true) {
  var v = ADC.get(adcPort);   // blocking single EZ-B read
  setVar("$IRADC", v.toString());
  sleep(intervalMs);
}

How to use:

  • Start this poller script before your scanning logic (or set it to run on ARC start).
  • All other scripts read getVar("$IRADC") and avoid ADC.get() calls themselves.
  1. Fast scanning + trend detection script
  • Purpose: Rapidly sweep 3 around a center point, collect many ADC samples for left/right swings, compute simple trend (left vs right) and output an Actual Course value.
  • Behavior choices included: small servo settle time (you said you'll accept imperfect motion for lots of samples), circular buffer of recent samples, moving averages for left/right, configurable thresholds.
// Fast scan & trend script - reads $IRADC published by poller
// Ports and tuning
var servoPort = d17;          // servo that pans sensor
var centerPos = 90;           // starting center position (1..180)
var scanOffset = 3;           // degrees left/right from center
var settleMs = 25;            // settle time after commanding servo (tune 15-60 ms)
var loopSleepMs = 5;          // small pause between loop cycles
var bufferSize = 20;          // number of recent samples to keep per side
var switchDirEvery = 1;       // how many commands before switching side - 1 is every command

// Trend parameters (tweak)
var minSamplesForDecision = 6;   // start making decisions when at least this many samples
var diffThreshold = 6;           // ADC difference threshold to consider trend significant (0-255)

// Circular buffers for left & right ADC samples
function CircularBuffer(size) {
  this.size = size;
  this.arr = [];
  this.index = 0;
}
CircularBuffer.prototype.push = function (v) {
  if (this.arr.length < this.size) {
    this.arr.push(v);
  } else {
    this.arr[this.index] = v;
    this.index = (this.index + 1) % this.size;
  }
};
CircularBuffer.prototype.count = function () {
  return this.arr.length;
};
CircularBuffer.prototype.avg = function () {
  if (this.arr.length === 0) return 0;
  var s = 0;
  for (var i = 0; i < this.arr.length; i++) s += this.arr[i];
  return s / this.arr.length;
};

// Create buffers
var leftBuf = new CircularBuffer(bufferSize);
var rightBuf = new CircularBuffer(bufferSize);

// Initialize servo center
Servo.setPosition(servoPort, centerPos);
sleep(120); // let servo go to center once at startup
var direction = 0; // 0 = go left (center - offset), 1 = go right (center + offset)
var cmdCount = 0;

while (true) {
  // Compute target position for this step
  var target;
  if (direction === 0) target = centerPos - scanOffset;
  else target = centerPos + scanOffset;

  // Command servo (we don't wait for perfect motion to preserve sample rate)
  Servo.setPosition(servoPort, target);

  // Allow the servo a brief settle so IR reading is meaningful
  sleep(settleMs);

  // Read the most recent ADC value from the poller (local read, not an EZ-B call)
  var s = getVar("$IRADC", "");
  var adc = 0;
  if (s !== "") {
    adc = parseInt(s, 10);
    if (isNaN(adc)) adc = 0;
  }

  // Save sample into the appropriate buffer
  if (direction === 0) leftBuf.push(adc);
  else rightBuf.push(adc);

  // Optional: store the servo commanded position if you want to correlate later
  var commanded = Servo.getPosition(servoPort);

  // Compute decision only when enough samples are present
  if (leftBuf.count() + rightBuf.count() >= minSamplesForDecision) {
    var leftAvg = leftBuf.avg();
    var rightAvg = rightBuf.avg();
    // print debug values occasionally
    print("Lavg:" + leftAvg.toFixed(1) + " Ravg:" + rightAvg.toFixed(1) + " cmdPos:" + commanded + " adc:" + adc);

    // Simple trend detector: if rightAvg significantly larger than leftAvg => reflector toward right
    var delta = rightAvg - leftAvg;

    // Map the delta to an Actual Course correction. This mapping is application dependent.
    // Example: centerPos +/- some small fraction based on delta. You should calibrate this mapping.
    var courseCorrection = 0; // degrees adjustment from centerPos to report as Actual Course
    if (Math.abs(delta) > diffThreshold) {
      // Map delta (range maybe -50..+50) into small angular correction
      // scaleFactor tunes how aggressive the correction is. Adjust as needed.
      var scaleFactor = 0.06; // degrees per ADC count; tune this to actual robot behavior
      courseCorrection = Math.round(delta * scaleFactor);
    }

    var actualCourse = centerPos + courseCorrection;
    // clamp actualCourse into valid servo angle range
    if (actualCourse < 1) actualCourse = 1;
    if (actualCourse > 180) actualCourse = 180;

    // Publish the Actual Course so Arduino can read it (string expected)
    setVar("$actualCourse", actualCourse.toString());
  }

  // Alternate scanning direction frequently to maximize samples in-beam
  cmdCount++;
  if (cmdCount >= switchDirEvery) {
    direction = 1 - direction;
    cmdCount = 0;
  }

  // Very small sleep to let UI/other tasks run; tune if more/less CPU usage needed
  sleep(loopSleepMs);
}

How this meets your objectives

  • Single poller script consolidates ADC reads and removes ADC.get() calls from the scanning logic; this reduces WiFi roundtrip overhead significantly.
  • Rapid 3 oscillation generates many samples (fast servo commands + short settle). You explicitly accepted imperfect motion for higher sample rate - this script uses that approach.
  • The circular buffers let you compute trends (moving averages) rather than binary hits/misses. Trends are robust against missed instantaneous reads and the sensor latency you described.
  • The script publishes $actualCourse for the Arduino to consume via UART.

Tuning suggestions (practical)

  • ADC poll interval in the poller: 20-50 ms for WiFi. Try 25 ms first. If you see network lag or missed samples, go up to 40-50 ms.
  • settleMs (servo settle after command): 15-60 ms. For tiny oscillations you may need less; for heavier servos/tight mounts, increase.
  • bufferSize: larger gives smoother trends but more latency. For fast responsiveness keep 10-30 samples.
  • diffThreshold and scaleFactor: calibrate by logging left/right averages while moving around the reflector and tuning thresholds and mapping to degrees.
  • If you need more sample density than WiFi allows, consider a small secondary microcontroller or second EZB local to the sensor to do the micro-sampling and publish only the trend/centroid to the main EZB. But try the above first - often the consolidated poller + fast commands suffice.

Additional practical notes and pitfalls

  • Because the servo usually doesn’t reach full commanded position at fast stepping rates, Servo.getPosition() returns the last commanded position, not the actual physical position. You already accepted this trade-off and are measuring trends, so that’s acceptable.
  • Avoid multiple scripts calling ADC.get() or multiple ADC skills on the same port - that creates heavy traffic. Use the one poller.
  • If you later decide to use the ADC Value robot skill instead of the poller script, configure that skill’s Interval to the desired value (Project add skill ADC Value; skill kebab menu settings Interval), then determine what global variable it writes. If it doesn’t publish a variable you can read, keep the poller script approach - it’s simpler and under your control.
  • To debug, temporarily increase printing frequency or write samples to a file with File.appendStringLine(...) to capture traces.

Next steps I can do for you

  • I can adapt the scripts to use the ADC Value robot skill instead of the custom poller if you confirm the skill publishes a global variable name (tell me the skill’s Title or provide a screenshot of the ADC Value settings - note I cannot see images; paste the text).
  • I can tune the mapping from ADC delta degrees for your hardware if you share representative ADC readings when the reflector is clearly left, center, and right (just paste a few values).
  • I can add a simple calibration routine that, when you press a button, records left/center/right ADC averages and auto-calculates diffThreshold and scaleFactor.

Questions for you so I can refine:

  1. Would you prefer to use the ADC Value skill instead of the poller script? If so, what is the exact title of that skill in your project (or paste its settings text)?
  2. What ADC sample interval do you want to try first (I recommend 25 ms for WiFi)?
  3. Do you want me to add a short calibration routine to compute scaleFactor/diffThreshold automatically from quick left/center/right samples?

If you want, paste any of your current scripts (I can refactor them into this architecture). I’ll adapt the code to exactly match your ports, naming conventions, and timing requirements.


I am a customer support GPT-powered AI and still learning. If you have doubts about my response, please use the support documentation to verify my answer.
#8  
// Fast scan & trend script - reads $IRADC published by poller
// Ports and tuning
var servoPort = d17;          // servo that pans sensor
var centerPos = 90;           // starting center position (1..180)
var scanOffset = 2;           // degrees left/right from center
var settleMs = 25;            // settle time after commanding servo (tune 15-60 ms)
var loopSleepMs = 5;          // small pause between loop cycles
var bufferSize = 20;          // number of recent samples to keep per side
var switchDirEvery = 1;       // how many commands before switching side - 1 is every command

// Trend parameters (tweak)
var minSamplesForDecision = 6;   // start making decisions when at least this many samples
var diffThreshold = 6;           // ADC difference threshold to consider trend significant (0-255)

// Circular buffers for left & right ADC samples
function CircularBuffer(size) {
  this.size = size;
  this.arr = [];
  this.index = 0;
}
CircularBuffer.prototype.push = function (v) {
  if (this.arr.length < this.size) {
    this.arr.push(v);
  } else {
    this.arr[this.index] = v;
    this.index = (this.index + 1) % this.size;
  }
};
CircularBuffer.prototype.count = function () {
  return this.arr.length;
};
CircularBuffer.prototype.avg = function () {
  if (this.arr.length === 0) return 0;
  var s = 0;
  for (var i = 0; i < this.arr.length; i++) s += this.arr[i];
  return s / this.arr.length;
};

// Create buffers
var leftBuf = new CircularBuffer(bufferSize);
var rightBuf = new CircularBuffer(bufferSize);

// Initialize servo center
Servo.setPosition(servoPort, centerPos);
sleep(120); // let servo go to center once at startup
var direction = 0; // 0 = go left (center - offset), 1 = go right (center + offset)
var cmdCount = 0;

while (true) {
  // Compute target position for this step
  var target;
  if (direction === 0) target = centerPos - scanOffset;
  else target = centerPos + scanOffset;

  // Command servo (we don't wait for perfect motion to preserve sample rate)
  Servo.setPosition(servoPort, target);

  // Allow the servo a brief settle so IR reading is meaningful
  sleep(settleMs);

  // Read the most recent ADC value from the poller (local read, not an EZ-B call)
  var s = getVar("$IRADC", "");
  var adc = 0;
  if (s !== "") {
    adc = parseInt(s, 10);
    if (isNaN(adc)) adc = 0;
  }

  // Save sample into the appropriate buffer
  if (direction === 0) leftBuf.push(adc);
  else rightBuf.push(adc);

  // Optional: store the servo commanded position if you want to correlate later
  var commanded = Servo.getPosition(servoPort);

  // Compute decision only when enough samples are present
  if (leftBuf.count() + rightBuf.count() >= minSamplesForDecision) {
    var leftAvg = leftBuf.avg();
    var rightAvg = rightBuf.avg();
    // print debug values occasionally
    print("L_avg:  " + leftAvg.toFixed(1) + "     R_avg:  " + rightAvg.toFixed(1) + "     cmdPos:  " + commanded + "     adc:  " + adc);

    // Simple trend detector: if rightAvg significantly larger than leftAvg => reflector toward right
    var delta = rightAvg - leftAvg;

    // Map the delta to an Actual Course correction. This mapping is application dependent.
    // Example: centerPos +/- some small fraction based on delta. You should calibrate this mapping.
    var courseCorrection = 0; // degrees adjustment from centerPos to report as Actual Course
    if (Math.abs(delta) > diffThreshold) {
      // Map delta (range maybe -50..+50) into small angular correction
      // scaleFactor tunes how aggressive the correction is. Adjust as needed.
      var scaleFactor = 0.06; // degrees per ADC count; tune this to actual robot behavior
      courseCorrection = Math.round(delta * scaleFactor);
    }

    var actualCourse = centerPos + courseCorrection;
    // clamp actualCourse into valid servo angle range
    if (actualCourse < 1) actualCourse = 1;
    if (actualCourse > 180) actualCourse = 180;

    // Publish the Actual Course so Arduino can read it (string expected)
    setVar("$actualCourse", actualCourse.toString());
  }

  // Alternate scanning direction frequently to maximize samples in-beam
  cmdCount++;
  if (cmdCount >= switchDirEvery) {
    direction = 1 - direction;
    cmdCount = 0;
  }

  // Very small sleep to let UI/other tasks run; tune if more/less CPU usage needed
  sleep(loopSleepMs);
}

Thank you @Athena!  I have included a repost with some minor changes.

Questions:  1.  When "adc" prints, is this just the last adc and not representing the sample of 6 reads?

2.  I need to identify "actualCourse" as the starting servo of (90).  I would like it to be adjusted towards the lower IR sample side when the IR sample exceeds a set difference.  This way the servo is always centered for the best "actualCourse".  Example, if the difference between L_avg and R_avg exceeds 1.0, move "actualCourse" towards the lower of L_avg or R_avg.   Can you please adjust the script to accomplish this? Thank you Jack