HTTP Server (Custom) icon HTTP Server (Custom) HTTP server for ARC serving HTML/CSS/PNG/JPG from ARC/HTTP Server Root; supports AJAX tags (ez-move, ez-script, ez-camera) to control the robot. Try it →
Asked — Edited
Resolved Resolved by Athena!

IR Sensor Reading Optimization

Hello @Athena,

I would appreciate your review of the following JavaScript code within the ARC environment. The script is designed to process infrared (IR) sensor readings, which can vary significantly due to sensor noise. My objective is to obtain a reliable result, finalLowIR, that only changes when the last three samples are consistent.

The current algorithm involves collecting three samples from an IR sensor connected via the ADC feature, calculating the lowest value from each set of samples, and then using a moving average to stabilize the reading. The final variable finalLowIR is adjusted to limit variability.

//$IRSAMPLE consists of three sample IR readings.
//lowIR is identified as the lowest of the three $IRSAMPLE readings.
//totLowIR aggregates the sum of three lowIR readings.
//avgLowIR is calculated as ((totLowIR * 1000)/3).
//finalLowIR is rounded and divided by 10000 to manage IR reading variability.

while(true) {
    var totLowIR = 0;
    var avgLowIR = 0;

    for(var count1 = 0; count1 < 3; count1++) { // Loop for 3 samples
        var lowIR = 300;
        
        for (var count = 0; count < 3; count++) { // Capture individual samples
            setVar("$IRSAMPLE", ADC.get(2)); // Obtain IR sample
            if (getVar("$IRSAMPLE") < lowIR) {
                lowIR = getVar("$IRSAMPLE");
            }
        }
        totLowIR += lowIR;
    }
    avgLowIR = ((totLowIR * 1000) / 3);
    finalLowIR = Math.round(avgLowIR / 10000);
    
    setVar("$IRADC", finalLowIR); // Store the final reading
    sleep(100); // Pause the loop for 100ms
}

Could you provide feedback or suggestions for a more efficient script in this context? Your expertise would be greatly appreciated.

Thank you,
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)
Script by Synthiam
Multi-language ARC Script: build, run and debug Blockly, JavaScript, EZ-Script or Python with Intellisense, run/save/load and Roboscratch support
Run scripts automatically when specified ARC variables change; define variable-to-script pairs, monitor status, and trigger actions.

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
Twitter YouTube GitHub
#1  

Thanks - nice explanation of your intent. Your script works but has a few inefficiencies and some strange math that reduces precision unnecessarily. Below I explain the issues and provide two cleaner, robust JavaScript approaches you can drop into an ARC Script robot skill.

Key notes up front

  • Use ADC port constants (ADC0..ADC7) instead of numeric literals (per ARC notes): ADC.get(ADC2) rather than ADC.get(2).
  • ADC.get() returns 0-255 (use ADC.get12Bit() if you need finer resolution).
  • Avoid setVar/getVar in the inner sampling loop - read ADC.get() directly, and only setVar() when you want to publish the stable result.
  • Your current math reduces the result by a factor of 10 (totLowIR/30 -> average/10). That loses resolution and makes results weird. I assume you intended smoothing, but use explicit smoothing or stability checks instead.
  • For IR noise, median-of-samples + a 3-sample stability (or tolerance-based equality) is a good approach. Alternatively, an exponential moving average (EMA) provides smooth responsiveness.

Option A - Median sampling + 3-sample stability (recommended) Behavior: for each cycle, take several quick ADC samples, compute a robust sample (median). Keep the last 3 robust samples and only update final reading when those 3 are consistent within a small tolerance. This matches your requirement "only changes when the last three samples are consistent."

// Median + 3-sample stability
function median(arr){
  arr = arr.slice().sort(function(a,b){return a-b;});
  return arr[Math.floor(arr.length/2)];
}

const ADC_PORT = ADC2;         // ADC0..ADC7 - change if needed
const samplesPerCycle = 5;     // number of quick samples per cycle
const sampleDelayMs = 20;      // delay between quick samples (ms)
const loopDelayMs = 100;       // main loop delay (ms)
const stabilityTolerance = 2;  // allowed variation between last 3 readings

let lastThree = [];
let finalLowIR = null;

while(true){
  // gather quick samples and take median to remove spikes
  let quick = [];
  for(let i=0;i<samplesPerCycle;i++){
    quick.push( ADC.get(ADC_PORT) );
    sleep(sampleDelayMs);
  }
  let robustSample = median(quick);

  // save last 3 robust samples
  lastThree.push(robustSample);
  if(lastThree.length > 3) lastThree.shift();

  // if we have 3, check for stability
  if(lastThree.length === 3){
    let mx = Math.max(lastThree[0], lastThree[1], lastThree[2]);
    let mn = Math.min(lastThree[0], lastThree[1], lastThree[2]);

    if((mx - mn) <= stabilityTolerance){
      // stable -> publish averaged stable value
      let newVal = Math.round((lastThree[0] + lastThree[1] + lastThree[2]) / 3);
      if(finalLowIR === null || newVal !== finalLowIR){
        finalLowIR = newVal;
        setVar("$IRADC", finalLowIR); // publish stable IR value
        print("IR updated: " + finalLowIR);
      }
    }
    // otherwise ignore (not stable)
  }
  sleep(loopDelayMs);
}

Why this is good:

  • Median per-cycle reduces single-sample spikes.
  • Stability is explicit: only updates when three consecutive robust samples are within tolerance.
  • You keep full ADC resolution (0-255) and only average/round at the point you publish.

Tuning:

  • samplesPerCycle: increase for more robust median, but slower response.
  • sampleDelayMs: fast sensors can use 10-30ms.
  • loopDelayMs: determines how often you attempt to update final value.
  • stabilityTolerance: if sensor jitter is larger, increase tolerance (e.g., 3-6). If very strict, set 0 or 1.

Option B - Exponential Moving Average (EMA) + threshold (more responsive) Behavior: smooths the value continuously; update final only when smoothed value changes by a meaningful threshold (debounce).

// EMA smoothing + threshold
const ADC_PORT = ADC2;
const sampleDelayMs = 50;
const loopDelayMs = 100;
const alpha = 0.2;           // 0 < alpha <= 1 (higher alpha -> faster response)
const publishThreshold = 2;  // only publish if smoothed value changes by at least this

let ema = ADC.get(ADC_PORT); // initialize EMA to first reading
setVar("$IRADC", Math.round(ema));
print("IR initial: " + Math.round(ema));

while(true){
  // single sample per loop (you can replace with median of quick samples if desired)
  let r = ADC.get(ADC_PORT);
  ema = (alpha * r) + (1 - alpha) * ema;

  let rounded = Math.round(ema);
  if(Math.abs(rounded - getVar("$IRADC", -9999)) >= publishThreshold){
    setVar("$IRADC", rounded);
    print("IR updated EMA: " + rounded);
  }

  sleep(loopDelayMs);
}

Why this is good:

  • EMA is computationally cheap and smooths noise while remaining responsive.
  • publishThreshold prevents tiny fluctuations from triggering changes.

Extra tips & links

  • If you need higher resolution, try ADC.get12Bit(ADC2) (returns 0-4095) and adapt the smoothing/tolerances.
  • Display live values with the ADC Graph or ADC Value skills while tuning:
  • Use print() for debugging inside ARC scripts (preferred to console.log()).
  • If you want to base logic on "closest reading" (min) instead of median, you can replace median() with Math.min(...quick), but min is susceptible to spikes - median is safer.

Questions so I can make this even more specific

  1. Which ADC port are you using? (Use ADC0..ADC7 constant - e.g., ADC2)
  2. Do you prefer absolute stability (no publish until all 3 identical) or "stable within N" tolerance?
  3. Do you want faster updates or maximum stability? (I can tune alpha, sample counts, tolerances accordingly.)
  4. Are you using ADC.get (8-bit) or would you like to switch to ADC.get12Bit for better resolution?

If you tell me your port and the acceptable tolerance & update rate, I’ll adapt the code to those exact numbers and give recommended values for sample counts/delays.


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  

Thank you @ Athena for your help.  I have run Option A and Option B in my IR sensor setup and see too many deviations in the final IR number.  I have adjusted the variables in each program to attempt a more satisfactory result without success.  I'm not sure the strategy in your Options suit the manner in which I need to parse the IR readings.  Let me explain more fully.  Below is a reprint from the previous post of my original script

//$IRSAMPLE consists of three sample IR readings.
//lowIR is identified as the lowest of the three $IRSAMPLE readings.
//totLowIR aggregates the sum of three lowIR readings.
//avgLowIR is calculated as ((totLowIR * 1000)/3).
//finalLowIR is rounded and divided by 10000 to manage IR reading variability.

while(true) {
    var totLowIR = 0;
    var avgLowIR = 0;

    for(var count1 = 0; count1 < 3; count1++) { // Loop for 3 samples
        var lowIR = 300;
        
        for (var count = 0; count < 3; count++) { // Capture individual samples
            setVar("$IRSAMPLE", ADC.get(2)); // Obtain IR sample
            if (getVar("$IRSAMPLE") < lowIR) {
                lowIR = getVar("$IRSAMPLE");
            }
        }
        totLowIR += lowIR;
    }
    avgLowIR = ((totLowIR * 1000) / 3);
    finalLowIR = Math.round(avgLowIR / 10000);
    
    setVar("$IRADC", finalLowIR); // Store the final reading
    sleep(100); // Pause the loop for 100ms
}

I am using an IR sensor (SHARP GP2Y0A02YK0F) to measure the reflective light from a typical reflector element (such as found on a bicycle).  When the IR sensor is pointed directly at the reflector, the reflected light saturates the IR sensor CCD and returns a '0'.  The center zone where the reflected light saturates the CCD is quite wide (5 degrees).   As the IR sensor is pivoted away from the center of the reflector, less reflected light is received resulting in a higher number that is relational to the distance "off-center".  I am positioning the IR sensor off-center about 5 degrees, and this results in a 50 IR reading.  In this position, the IR sensor becomes very sensitive to any lateral pivot and the IR reading changes by 10 IR for each 1 degree of pivot.  So, if the baseline was 50 IR, a reading of 60 IR would be a 1 deg deviation Right and a reading of 40 IR would be a deviation of 1 deg Left.  The manner in which I am measuring the reflected light is sort of like using a spectrometer.

I need fast and accurate IR readings.   I have found that IR readings deviate 'HIGH' when noise is present.  Typically, a lower IR reading is more accurate.  Therefore, my strategy for the above code is as follows:   STEP 1.    3 IR samples are taken, and the lowest IR reading is noted as 'lowIR'. STEP 2.    3 'lowIR' samples are totaled and noted as 'totLowIR'.    STEP 3.    'totLowIR' is averaged and meant to result in a 'large number' (X 1000) so as to push IR reading variances out to the right of the first decimal. STEP 4.     'finalLowIR' is meant to result in a single digit (round / 10k) effectively "chopping off" the IR reading variances.

This results in a single digit (4...5...6), where 5 is the baseline and 4 equals 1 degree deviation Left and 6 equals 1 degree deviation Right.

The above strategy seems to work well in producing a fast, reliable single digit number relating to a one-degree deviation.  My problem is that sometimes the transition from baseline (5) to a 4 or baseline (5) to a 6 produces bounce and, for a while, oscillates 'finalLowIR'.

I wish to incorporate the following:  1.  If 'finalLowIR' has not changed from the previous 'finalLowIR', do nothing. 2.  if 'finalLowIR changes, check for 3 subsequent and equal 'finalLowIR' before publishing the value to "$IRADC". Could you please modify the code printed above to incorporate the changes I need to make to 'finalLowIR'? Thank you for your help. Jack

Author Avatar
PRO
Canada
Twitter YouTube GitHub
#3  

Thanks Jack - that extra detail about the SHARP sensor and how a lower reading is more accurate helped a lot. I kept your original algorithm (pick the minimum of 3 quick samples, repeat that 3 times, average) but simplified it, removed inefficient getVar/setVar calls in the sampling loop, and added the exact "publish only after 3 equal finalLowIR" debounce logic you requested.

Drop this into an ARC Script robot skill (JavaScript tab). Notes:

  • I use ADC.get(ADC2) (per ARC constants) instead of ADC.get(2).
  • The first computed finalLowIR is published immediately so $IRADC exists. Subsequent changes require 3 consecutive identical finalLowIR samples before publishing.
  • Use sleep(ms) for timing (ms). Adjust loopDelayMs to your required speed.

Code:

// IR stable-publish script
// Uses the same low-of-3 / 3-cycles average strategy you described,
// but only publishes a changed value after 3 consecutive equal finalLowIR samples.

// ADC port (use ADC0..ADC7 constants)
const ADC_PORT = ADC2;

// Timing (tune these as needed)
const loopDelayMs = 100;     // main loop delay (ms) - matches your original sleep(100)
const samplesPerMiniSet = 3; // number of raw samples per "lowIR" determination (you used 3)
const miniSetsPerCycle = 3;  // number of lowIRs summed to form totLowIR (you used 3)

// Debounce logic: require 3 consecutive identical finalLowIR values before publishing
const requiredConsecutive = 3;

let pendingValue = null;
let pendingCount = 0;

// Helper: take N quick samples and return the minimum (lowIR)
function getLowOfSamples() {
  let low = 9999;
  for (let s = 0; s < samplesPerMiniSet; s++) {
    let r = ADC.get(ADC_PORT); // fast raw ADC sample (0-255). Use ADC.get12Bit() if you switch to 12-bit.
    if (r < low) low = r;
    // optional tiny delay: sleep(2);   // only if needed for your sensor sampling timing
  }
  return low;
}

// Compute one cycle's finalLowIR using your tot/avg/scale logic.
// I simplified the math: original formula Math.round(((totLowIR * 1000)/3) / 10000)
// equals Math.round(totLowIR / 30). We'll use the simpler expression for clarity.
function computeFinalLowIR() {
  let totLowIR = 0;
  for (let i = 0; i < miniSetsPerCycle; i++) {
    totLowIR += getLowOfSamples();
  }
  // same scaling as your algorithm: finalLowIR = round(totLowIR/30)
  let finalLowIR = Math.round(totLowIR / 30);
  return finalLowIR;
}

// Publish the first value immediately so $IRADC exists
let firstVal = computeFinalLowIR();
setVar("$IRADC", firstVal);
print("IR initial published:", firstVal);

// Main loop
while (true) {
  let val = computeFinalLowIR();

  let published = getVar("$IRADC", null);

  if (val === published) {
    // unchanged from last published value -> reset pending and do nothing
    pendingValue = null;
    pendingCount = 0;
  } else {
    // value differs from published
    if (pendingValue === val) {
      // same pending value again -> increment counter
      pendingCount++;
    } else {
      // new pending candidate -> start count
      pendingValue = val;
      pendingCount = 1;
    }

    // If candidate has appeared requiredConsecutive times, publish it
    if (pendingCount >= requiredConsecutive) {
      setVar("$IRADC", val);
      print("IR updated (stable):", val);
      // reset pending
      pendingValue = null;
      pendingCount = 0;
    } else {
      // optional debug:
      print("IR candidate:", val, "pendingCount:", pendingCount, "published:", published);
    }
  }

  sleep(loopDelayMs);
}

Tuning suggestions

  • loopDelayMs: smaller = faster responsiveness but possibly more noise. You already used 100ms - try 50ms if you need faster updates and your electronics handle it.
  • samplesPerMiniSet (3) and miniSetsPerCycle (3): increasing samplesPerMiniSet reduces single-sample spikes; increasing miniSetsPerCycle increases cycle smoothing but slows response.
  • If you want more resolution, switch to ADC.get12Bit(ADC2) (0-4095) and adapt the division factor (right now totLowIR/30 used 8-bit range). I can provide the 12-bit version if you want.
  • If noise spikes create false low values (rare with min-of-3), consider using median-of-3 for each mini-set instead of min-of-3; but you’ve said lower readings are more accurate and you deliberately want min-of-3.

How to install and run this in ARC

  1. Project -> Add robot skill -> Scripting -> Script.
  2. Open the Script skill settings (three-line kebab menu on the top-right of the skill window if needed), choose JavaScript tab, paste the code, Save.
  3. In the Script skill main window press Start to run.
  4. If you want to trigger actions on changes to $IRADC, open Project -> Add robot skill -> Scripting -> On Variable Changed. In its configuration add variable name $IRADC and put the script/logic you want executed when that variable changes.

A couple of quick questions so I can refine further

  1. Do you want the first computed finalLowIR to be published immediately (current code does that), or do you want the same 3-equal-sample rule even for the first publish?
  2. Would you like me to convert this to use ADC.get12Bit(ADC2) for higher resolution? If yes, tell me the typical raw 12-bit values you see so I can set the scaling to preserve your "single-digit ~5 baseline" behavior.
  3. Do you want any logging removed once it’s tuned (I left print() debug lines you can remove)?

If you want, I’ll post a 12-bit variant and a median-of-3 variant for each mini-set so you can try both quickly.


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.
#4  
  // same scaling as your algorithm: finalLowIR = round(totLowIR/30)
  let finalLowIR = Math.round(totLowIR / 30);
  return finalLowIR;
}

// Publish the first value immediately so $IRADC exists
let firstVal = computeFinalLowIR();
setVar("$IRADC", firstVal);
print("IR initial published:", firstVal);

Thank you @Athena.  Please refer to the above code snippet of the most recent posted code you provided.   I am able to print the results for 'r' and 'low' and 'totLowIR'.  I noticed that defining 'finalLowIR' by '/30' seems to unnecessarily cause the rounding up of the first single digit.  This seems to unnecessarily create a digit that will have to be rejected.  Instead of rounding a two decimal number, I was rounding a three decimal number(X1000) keeping the first digit the same.  What do you think about rounding a three decimal number?

Additionally, I am unable to print 'firstVal'.  Could you please reevaluate the provided code as to why I am unable to print 'firstVal'?

Thank you, Jack

Author Avatar
PRO
Canada
Twitter YouTube GitHub
#5  

Thanks Jack - good observations. Two points to address: (A) the math/scaling (round vs truncate), and (B) why firstVal might be undefined / not printing. I’ll explain both and give a clean, working code example you can paste into an ARC Script (JavaScript) skill.

  1. Scaling: dividing by 30 is correct
  • Your original math (totLowIR * 1000 / 3 then Math.round(...) / 10000) simplifies to Math.round(totLowIR / 30). So dividing by 30 is equivalent to your earlier approach.
  • Rounding behavior:
    • Math.round(...) will round .5 up, which can cause occasional "bounce" when a value is just below a boundary (e.g. 4.96 5).
    • If you want to "chop off" decimal places (bias toward lower value, since you said lower IR is more accurate), use Math.floor(...) to always truncate down.
    • Alternatively, if you want to avoid rounding up for tiny noise but still round normally otherwise, you can subtract a tiny epsilon before rounding: Math.round(value - 0.001).

Recommendation: use Math.floor(totLowIR / 30) if you want the single-digit to favor the lower side and avoid upward bounce.

  1. Why firstVal may not print Common causes:
  • computeFinalLowIR() does not return a value (missing or unreachable return).
  • computeFinalLowIR() is not defined in the same scope before you call it.
  • A runtime error occurs inside computeFinalLowIR(), preventing the function from completing (so the call returns undefined).
  • Printing syntax or the environment swallow the output (rare, but ensure print() is used and the Script skill Status shows output).

How to verify:

  • Ensure the function ends with: return finalLowIR;
  • Call the function only after the function declaration.
  • Add a debug print inside the function to confirm it runs and what it computes.

Working example This matches your original min-of-3 / repeat-three-times strategy, uses Math.floor to "chop off" decimals, and prints firstVal reliably. Paste it into the JavaScript tab of a Script skill and Start it.

// IR algorithm: min-of-3 -> repeat 3 times -> totLowIR -> finalLowIR = floor(totLowIR / 30)
// ADC: use ARC constant (ADC0..ADC7). Example uses ADC2 - change if needed.

const ADC_PORT = ADC2;          // change to ADC0..ADC7 if needed
const samplesPerMiniSet = 3;    // min-of-3 samples per mini-set
const miniSetsPerCycle = 3;     // number of mini-sets summed into totLowIR
const loopDelayMs = 100;        // main loop delay, tune as needed

// compute one "finalLowIR" value (same scaling as your original algorithm)
function computeFinalLowIR() {
  let totLowIR = 0;

  for (let set = 0; set < miniSetsPerCycle; set++) {
    let low = 9999;
    for (let s = 0; s < samplesPerMiniSet; s++) {
      let r = ADC.get(ADC_PORT);   // direct ADC read (0..255)
      // debug print if you want: print("r:", r);
      if (r < low) low = r;
      sleep(5); // small delay between raw samples (tune if needed)
    }
    // debug print: print("mini-set low:", low);
    totLowIR += low;
  }

  // Use Math.floor to "chop" decimals (bias toward lower, avoid rounding-up bounce)
  let finalLowIR = Math.floor(totLowIR / 30);  // same scale as your design
  // If you prefer standard rounding: use Math.round(totLowIR / 30)
  // If you prefer to avoid tiny rounding-up: Math.round(totLowIR / 30 - 0.001)

  // debug print: print("totLowIR:", totLowIR, "finalLowIR:", finalLowIR);
  return finalLowIR;
}

// Publish the first value immediately so $IRADC exists
let firstVal = computeFinalLowIR();
setVar("$IRADC", firstVal);
print("IR initial published: " + firstVal);

// Main loop (example: publish only when stable, you can adapt to your 3-equal-debounce)
let pendingValue = null;
let pendingCount = 0;
const requiredConsecutive = 3;

while (true) {
  let val = computeFinalLowIR();

  // If same as published, reset any pending candidate
  if (val === getVar("$IRADC", -9999)) {
    pendingValue = null;
    pendingCount = 0;
  } else {
    // Candidate handling
    if (pendingValue === val) {
      pendingCount++;
    } else {
      pendingValue = val;
      pendingCount = 1;
    }

    if (pendingCount >= requiredConsecutive) {
      setVar("$IRADC", val);
      print("IR updated (stable): " + val);
      pendingValue = null;
      pendingCount = 0;
    } else {
      // optional debug:
      print("IR candidate:", val, "pendingCount:", pendingCount);
    }
  }
  sleep(loopDelayMs);
}

Notes and tuning

  • Sleep/sampling delays: I added a very small sleep(5) between the three quick samples; adjust to your sensor response and required speed.
  • If you prefer rounded values instead of floor, swap Math.floor for Math.round. If rounding causes oscillation, floor will reduce upward bounces.
  • If computeFinalLowIR still prints undefined, add debug prints inside the function (e.g., print("inside computeFinalLowIR r:", r) ) and check the Script status window for runtime errors.

Next steps / Questions

  1. Do you want strict truncation (Math.floor) as in the example, or prefer Math.round (nearest)? I recommend floor given your note that low readings are more accurate.
  2. Do you want me to change the initial publish so it also requires 3 identical readings before first publish? (current code publishes firstVal immediately)
  3. Want a variant using ADC.get12Bit(ADC2) for higher precision? If so, tell me the typical 12-bit values you observe so I can preserve the single-digit baseline behavior.

If you paste your exact computeFinalLowIR function (the version you used that fails to print firstVal), I can point to the exact line causing the issue.


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  

Thank you @Athena.  I needed to modify the print statements for printing to take place.  Below is an example of the change:

//  print("r:", r);    "original"

// print("r:  " + r);    "my change"

The entire script runs beautifully.  Thankyou.  I would like for 'first Val' to be published only when stable just as any change in 'finalLowIR' is tested. Can you make this change to the code for me? Thank you. Jack

Author Avatar
PRO
Canada
Twitter YouTube GitHub
#7  

Great - thanks for the clarification. Below is the updated JavaScript you can paste into an ARC Script robot skill. This version uses your original min-of-3 / 3-mini-set averaging and will NOT publish any $IRADC value until the computed finalLowIR has appeared requiredConsecutive times in a row (so the very first publish follows the same 3-equal-samples rule).

Notes:

  • Uses ADC constant ADC2 (change if needed).
  • Uses Math.floor(totLowIR / 30) to bias toward the lower value (truncation), per your preference.
  • Uses the concat style for print statements you indicated (e.g., "r:"+r).
  • You can remove or reduce the print() lines once tuned.

Paste into Script JavaScript and Start.

// Stable publish IR (min-of-3 per mini-set, 3 mini-sets -> totLowIR -> finalLowIR)
// Only publish when the computed finalLowIR is seen requiredConsecutive times in a row.

const ADC_PORT = ADC2;               // change to ADC0..ADC7 if needed
const samplesPerMiniSet = 3;         // min-of-3 samples per mini set (your original)
const miniSetsPerCycle = 3;          // number of mini-sets summed into totLowIR
const sampleDelayMs = 5;             // small delay between quick samples (tune if needed)
const loopDelayMs = 100;             // main loop delay (your original was 100ms)
const requiredConsecutive = 3;       // require 3 consecutive equal finalLowIR values before publishing

// compute one finalLowIR using your scaling: finalLowIR = floor(totLowIR / 30)
function computeFinalLowIR() {
  let totLowIR = 0;
  for (let set = 0; set < miniSetsPerCycle; set++) {
    let low = 9999;
    for (let s = 0; s < samplesPerMiniSet; s++) {
      let r = ADC.get(ADC_PORT);
      // debug: print quick-sample if you want
      // print("r:" + r);
      if (r < low) low = r;
      sleep(sampleDelayMs);
    }
    // debug: print("low for mini-set " + set + ":" + low);
    totLowIR += low;
  }
  // use floor to bias toward lower (truncate)
  let finalLowIR = Math.floor(totLowIR / 30);
  // debug: print("totLowIR:" + totLowIR + " finalLowIR:" + finalLowIR);
  return finalLowIR;
}

// Main loop with stable-publish logic
let pendingValue = null;
let pendingCount = 0;
// get current published value if exists (null default)
let publishedValue = getVar("$IRADC", null);

while (true) {
  let val = computeFinalLowIR();

  // If equal to currently published value, reset pending and do nothing
  if (publishedValue !== null && val === publishedValue) {
    pendingValue = null;
    pendingCount = 0;
    // debug: print("no change, published still:" + publishedValue);
  } else {
    // Value differs from published (or nothing published yet)
    if (pendingValue !== null && val === pendingValue) {
      pendingCount++;
    } else {
      // new candidate
      pendingValue = val;
      pendingCount = 1;
    }

    print("IR candidate:" + val + " pendingCount:" + pendingCount);

    if (pendingCount >= requiredConsecutive) {
      // publish stable value
      setVar("$IRADC", val);
      print("IR updated (stable):" + val);
      publishedValue = val;
      // reset pending
      pendingValue = null;
      pendingCount = 0;
    }
  }

  sleep(loopDelayMs);
}

Further suggestions / options

  • If you want less console noise, comment out or remove the print() lines once stable.
  • If you need faster responsiveness, reduce loopDelayMs or sampleDelayMs, but be mindful of noise.
  • If noise still causes issues, we can switch the mini-set computation from min-of-3 to median-of-3 and keep the same stable-publish rule - that may reduce rare false lows.

Would you like me to:

  1. Convert this to ADC.get12Bit(ADC2) and adjust the scaling so the single-digit baseline behavior is preserved?
  2. Provide a median-of-3 mini-set variant for comparison?

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  

Thankyou @Athena.  All is working as I had hoped.  Once again, you provided a wonderful service. Jack