Asked
Resolved Resolved by DJ Sures!

Intermittent EZ-B Controllers Disconnect Issue

I'm experiencing an intermittent issue with ARC where two out of my three EZ-B controllers lose their connection.

  • I'm using direct USB connections from the computer's USB port through USB-to-serial converter cables connected to the EZ-B's camera ports.
  • At startup, ARC connects to the two EZ-Bs using an initialization script. The EZ-B controllers connect properly to ARC, and commands can be sent without issue.
  • Randomly, connections 0 and 2 will disconnect from the computer. This could happen to either connection or both, but at different times. It seems they don't disconnect simultaneously.
  • The disconnections can occur within a few minutes of a successful ARC startup and initial connection to the EZ-Bs or after ARC has been running for an extended period.
  • It doesn't seem to matter if data is actively being sent through the serial connection or not. Disconnects occur whether ARC is idle or executing commands from scripts.
  • The EZ-B controllers do not lock up. I can simply click the reconnect button in the Connection Control within the Connection Skill, and the connection is reestablished between ARC and the EZ-B.

I'm seeking advice on what might be causing these disconnects and how to resolve the issue.

Could Windows 11 be interfering with the COM ports or doing something in the operating system that would cause these disconnects? Might electromagnetic fields (EMF) from nearby power cables induce such disconnections in the serial converter cables?

Thanks in advance for any suggestions.



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)
Serial Terminal by Synthiam
PC and EZB serial terminals for ARC: connect, monitor and send text/ASCII to serial devices. Supports COM, flow control, baud, DTR/RTS, hex
Diagnose EZ-B Wi-Fi connection issues with verbose EZ-B communication logging, ping tests, log export and diagnostic reports for Synthiam support.

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.

#57  

In the last paragraph of my last post the polling speed change should have read "slowed down the polling in that script from 100 ms to 350 ms.

Author Avatar
PRO
Canada
#58  

Excellent progress - thanks for the clear status updates. Slowing the poll to 350 ms and seeing 12 hours of stability is a very useful datapoint: it strongly suggests the problem is related to polling frequency / command traffic (or correlated power/EM events that happen when polling is heavy).

You asked about switching from ADC.get() to ADC.waitFor*(...) with a frequency parameter. That’s a fine idea - both can work. Below I explain the trade-offs, give a ready-to-run ADC.waitFor-based single-watcher script (with hysteresis/debounce), show how to integrate it safely with your existing actions (Action Runner pattern), and give short next-step tests and things I still need from you if instability persists.

Short trade-off summary

  • ADC.get() + sleep(loopMs): simple, predictable. Easy to do debounce/hysteresis and keep one centralized watcher. Works well for button polling at 100-500 ms.
  • ADC.waitForHigher/Lower(..., frequencyMs, ezbIndex, timeoutMS): built-in polling with a frequency parameter that reduces busy-spinning. It still blocks while waiting for the specified timeout, so for multi-port scanning you either:
    • run one waitFor* per port in separate scripts (multiple watchers), or
    • use short timeouts per port and scan them sequentially (effectively a low-frequency scanner).
  • Either method is fine. If you want lower CPU / less busy time, ADC.waitFor* with frequencyMs is appropriate. If you want simpler control over debounce/hysteresis, ADC.get is easier.

Recommended safe approach (minimal change)

  1. Keep only one watcher script active (no other ADC scripts or ADC robot skill polling running).
  2. Use a single watcher that posts a tiny event variable when a press is detected (setVar("$LastButtonEvent", "port|ts")). That keeps the watcher lightweight.
  3. Use a separate Action Runner script that reads $LastButtonEvent and executes heavy ControlCommand() sequences one at a time (serializes heavy actions, reduces bursts).
  4. Keep poll interval around 250-400 ms for the watcher. That gave you improved stability at 350 ms - good sign.

If you want a waitFor* implementation, here is a single-watcher version using ADC.waitForHigher/Lower with frequency and timeouts. It scans ports sequentially with a small per-port timeout so total loop time stays modest. It posts a small event var for an external Action Runner to handle heavy tasks.

ADC.waitFor-based single-watcher (use this if you want waitFor* with frequency)

  • Edit ports/thresholds, paste into a Project Add Script JavaScript.
  • Disable any ADC robot skill polling or other scripts that read the same ADC ports.
// ADC.waitFor-based single watcher
// CONFIGURE:
var adcPorts = [0,1,2,3,4,5,6,7]; // ADC0..ADC7 numeric indexes
var pressThreshold = 170;         // press threshold
var releaseThreshold = 150;       // release threshold (hysteresis)
var perPortFreqMs = 50;           // frequency parameter for ADC.waitFor* (ms)
var perPortTimeoutMs = 60;        // per-port timeout for each waitFor call (ms)
var ezbIndex = 0;                 // EZB index for these ADC ports

// INTERNAL
print("ADC.waitFor Watcher: starting. Ports: " + adcPorts.length + "  press>=" + pressThreshold);
function nowMs(){ return (new Date()).getTime(); }

// We'll scan ports sequentially using small timeouts.
// When a press is detected, post an event: $LastButtonEvent = "port|timestamp"
// The Action Runner should consume the event and run heavy actions.
while(true){
  try{
    for(var i=0;i<adcPorts.length;i++){
      var port = adcPorts[i];
      // Wait until ADC value is higher than pressThreshold, but only for a short time
      // This returns a value when pressed or -1 on timeout.
      var pressedValue = ADC.waitForHigher(port, pressThreshold, perPortFreqMs, ezbIndex, perPortTimeoutMs);
      if(pressedValue !== -1){
        var t = nowMs();
        print("ADC.waitFor Watcher: port " + port + " PRESSED val:" + pressedValue + " @ " + new Date(t).toLocaleString());
        // Post tiny event for Action Runner (do not run heavy actions here)
        setVar("$LastButtonEvent", port + "|" + t);

        // Wait for release (use waitForLower with a larger timeout so single press doesn't retrigger)
        // This blocks until value is <= releaseThreshold or timeout.
        var rel = ADC.waitForLower(port, releaseThreshold, perPortFreqMs, ezbIndex, 10000); // 10s max
        if(rel === -1){
          print("ADC.waitFor Watcher: port " + port + " release wait TIMEOUT");
        } else {
          print("ADC.waitFor Watcher: port " + port + " RELEASED val:" + rel);
        }
        // small pause before continuing; let system breathe
        sleep(50);
      }
      // If not pressed, we go to next port quickly
    }
    // end for
    // short sleep so full loop is predictable (adjust to target total scan rate)
    sleep(10);
  }catch(e){
    print("ADC.waitFor Watcher: error: " + e);
    sleep(200);
  }
}

Notes about this waitFor script

  • It scans ports sequentially and uses small timeouts (perPortTimeoutMs). Worst-case a full scan takes roughly adcPorts.length * perPortTimeoutMs. Adjust perPortTimeoutMs so the loop period meets your responsiveness needs (with 8 ports × 60ms = ~480 ms worst-case).
  • When a press is found, we post $LastButtonEvent and then wait for release so the same button won’t trigger repeatedly. The heavy actions should not run in this watcher.
  • If you prefer a more responsive system, increase perPortFreqMs or use fewer ports per EZB, or move to polling with ADC.get where you can centralize exact timing.

Action Runner (lightweight consumer)

  • Create a separate JavaScript script in Project Add Script JavaScript.
  • The Action Runner reads $LastButtonEvent and runs the heavy sequences (your motors, relays, ControlCommand calls). This ensures heavy actions are serialized.

Example Action Runner skeleton:

// Action Runner - consumes $LastButtonEvent and executes actions in series
var pollMs = 400; // check event variable every 400ms
var lastProcessedTime = 0;

// Define your actions by port index:
var actions = [];
actions[0] = function(){ ControlCommand("Soundboard 2","Track_26"); sleep(600); ControlCommand("Soundboard v4","Track_20"); };
actions[1] = function(){ sleep(6250); ControlCommand("Shutdown PC","ScriptStart"); };
// actions[2] ... fill in like your existing functions, but keep them serialized

print("Action Runner: started");
while(true){
  try{
    var ev = getVar("$LastButtonEvent","");
    if(ev !== ""){
      var parts = ev.split("|");
      if(parts.length == 2){
        var port = parseInt(parts[0]);
        var t = parseInt(parts[1]);
        if(t > lastProcessedTime){
          print("Action Runner: processing port " + port + " @ " + new Date(t).toLocaleString());
          try{
            if(typeof actions[port] === "function"){
              actions[port]();
            } else {
              print("Action Runner: no action defined for port " + port);
            }
          }catch(ae){
            print("Action Runner: action error: " + ae);
          }
          lastProcessedTime = t;
          setVar("$LastProcessedTime", lastProcessedTime.toString());
        }
      }
    }
  }catch(e){
    print("Action Runner:error: " + e);
  }
  sleep(pollMs);
}

Integration checklist (step-by-step)

  1. Pause/disable ADC robot skills in the project (kebab menu Settings uncheck Enable or set their polling rate to 0). If you prefer the ADC robot skill to be the poller, disable these scripts and use the ADC skill variables instead (getVar("$YourADCVar")).
  2. Add the ADC.waitFor Watcher script above as a single script in the Project.
  3. Add the Action Runner script and copy your action functions into actions[port].
  4. Start only:
    • Connection control (your EZB connection)
    • ADC.waitFor Watcher script
    • Action Runner script
    • Connection Diagnostic or EZB Info for monitoring (optional)
  5. Run for the same duration you normally observe disconnects (12-24 hours recommended).
  6. If stability improves, reintroduce features one at a time (or move the script pollMs lower to balance responsiveness vs stability).

Other improvements and diagnostics to try (if you still see disconnects)

  • Try a slightly longer total loop scan (increase perPortTimeoutMs) or increase pollMs on Action Runner so heavy actions are spaced more.
  • Temporarily disable any heavy motor/relay actions in an action function to see if disconnect correlates with powering motors. If stability improves then it’s power/brownout-related.
  • Run Connection Diagnostic and copy/paste the log immediately after the next disconnect. That log is very useful.
  • Check pull-down resistor value (10k is typical), ADC cable routing (use twisted pair with GND), and lengths (shorter is better).
  • Confirm USB-UART adapter make/model and drivers (FTDI recommended). Disable USB selective suspend in Windows and uncheck "allow the computer to turn off this device" for the USB Root Hubs (Device Manager).
  • If EZ-B case gets warm, measure EZ-B input voltage under load. Brownouts or feed-back onto I/O can explain intermittent disconnects.
  • If you want to see voltage/temp in the script and log them periodically I can provide a small logging snippet to setVar() or print() those values every X minutes.

Because you already had much improved stability at 350 ms, try the waitFor script with perPortTimeoutMs 60 ms and perPortFreqMs = 50, and keep Action Runner pollMs = 400. That should result in a total worst-case scan near 480 ms and keep the watcher light.

A few follow-ups to tune further

  1. What pull-down resistor value are you using (ohms)?
  2. Approximate button cable length to EZ-B and whether they run near motor/driver wiring.
  3. USB-to-serial adapter make/model (FTDI / CH340 / Prolific / PL2303 / built-in) and cable length to the EZ-B.
  4. Do disconnects most often occur right after an action that turns on motors/relays (soil sampler sequence), or do they appear unrelated to running a heavy action?

If you want, I’ll:

  • Convert the watcher to use the ADC robot skill variables (so you can keep the ADC skill enabled as the single poller), or
  • Produce a tiny script to log EZ-B input voltage and CPU temp every minute to correlate power dips to disconnects, or
  • Create an ADC.get-based single-watcher variant if you prefer the exact timing control we had before.

Which variant would you like next - the ADC.get central watcher (with explicit hysteresis/debounce), the ADC.waitFor watcher above (you already have it), or the ADC-skill-variable watcher (reads $ variables from ADC skill rather than reading directly)?


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.