Canada
Asked — Edited

Managing Servo Commands Without TCP Server

How can I manage ports that exceed 23 using ARC @Athena? My ESP module currently supports ports higher than 23 with the integration of two PCA9685 PWM controllers, each controlling a set of servos. All connected ports have servos attached. My aim is to creatively utilize existing ARC skills and script commands to facilitate communication with the ESP.

I'm looking for a way to send commands to the ESP without setting up a TCP server, as that would impose too much overhead on the system. Ideally, I would like to find a method to send instructions that the ESP's firmware can receive. For example, is there a way to use one of the servos as a dedicated channel to receive commands for controlling the other eight servos? Please suggest a practical solution that would work efficiently.

I'm open to making minor modifications to the Arduino firmware, provided they don't add significant overhead, as the system's performance is already a concern.



Related Hardware (view all EZB hardware)
ESP32 Cam by Espressif
ESP32-CAM as an EZB for Synthiam ARC: stream video, control GPIO/servos, AP/Client WiFi, and ARC D0-D23 to GPIO pin mapping.
Wi-Fi
Servos Varies
Camera
Audio
UART Varies
I2C
ADC Varies
Digital Varies

Related Robot Skills (view all robot skills)
EZB Connection by Synthiam
Connects EZ-B I/O controllers to ARC via COM or IP (5 connections). Supports init scripts, battery monitoring, TCP/EZ-Script server and serial/I2C.
Control up to 99 servos via Vx virtual ports over serial (Arduino/custom firmware), integrating with ARC for scalable servo management.

ARC Pro

Upgrade to ARC Pro

ARC Pro is your passport to a world of endless possibilities in robot programming, waiting for you to explore.

Author Avatar
PRO
Canada
#1  

Good description - thanks. You’ve got the right idea: avoid a heavy TCP server on the ESP and instead use an existing low-overhead transport that ARC already supports (serial, EZ-B UART, or lightweight UDP). Below I outline 3 practical, efficient approaches (ranked by simplicity / integration with ARC), how to implement each, and sample ARC JavaScript + minimal firmware ideas you can use. Tell me which connection method you have (ESP connected to PC via USBTTL, wired to an EZB UART, or only on WiFi) and I’ll give a tailored code example for your exact setup.

Summary of recommended options

  • Best if ESP has USBSerial: Use the existing Custom servo Controller skill (Vx virtual servos). Flash small firmware on the ESP that implements the serial Vx protocol and routes Vx PCA9685 channels. No TCP server, direct ARC integration (up to 99 servos).
  • Best if ESP is wired to an EZB UART: Leave ESP WiFi/camera alone and use ARC’s UART functions (UART.initHardwareUart / UART.hardwareUartWriteString / UART.hardwareUartReadStringAvailable) to send compact commands to the ESP. Lightweight and no extra robot skill required.
  • Best if ESP is WiFionly (no serial wiring): Use UDP packets from ARC script (Net.sendUDP) to the ESP’s IP. UDP is lightweight (no waiting/setup of TCP), and the ESP just listens for small packets and applies commands to the PCA9685.

Detailed plans and examples

  1. Custom servo Controller (best ARC integration) - if ESP is available as a COM device Why: The Synthiam "Custom servo Controller" skill already exposes Vx virtual servos in ARC. If you run firmware on the ESP that speaks the same serial protocol (or adapt the Arduino example), ARC will control Vx servos with standard servo robot skills and scripts - no TCP server on the ESP.

What to do:

  • Flash a small ESP32 firmware that:
    • Presents a serial port (USBTTL adapter to PC).
    • Accepts the Custom_Servo_Controller serial protocol (or a simplified Vx packet: [CMD, servoId, pos]).
    • Controls the two PCA9685 chips (via I2C) and maps servoId PCA9685 channel.
  • In ARC:
    • Project -> Add robot skill -> servo -> Custom servo Controller.
    • Click the kebab (three-line/hamburger) menu on that skill Configure:
      • Pick the COM port and baud.
      • Select the Vx virtual ports that map to your servo IDs (V0..V99).
    • Use any servo control skill (Servo Panel, scripts) and address vX servos (the skill handles the serial).

Minimal ESP firmware idea (pseudocode):

  • Parse simple 3byte packets like [0xAA, servoIndex, position].
  • servoIndex: 0..N (map to PCA9685 chip+channel).
  • position: 0..180 (map to PCA9685 PWM value). This keeps firmware tiny and CPU overhead negligible.

Why I recommend this: Zero extra load on WiFi, full compatibility with ARC’s servo UI, and no custom robot skill coding in ARC.

Useful links:

  1. UART via EZB (if ESP connected to EZB UART pins) Why: If your ESP32 is wired to an EZB UART, ARC scripts can write directly to hardware UART with no extra robot skills and very low overhead.

What to do:

  • Modify ESP firmware to parse a compact serial command format (single-byte header + servo + pos).
  • In ARC JavaScript use the UART.* functions to send commands to the ESP.

Example ARC JavaScript to send one command (send servo id and pos):

// Example: send a 3-byte packet [0xAA, servoId, pos] to UART1 (uartIndex=1)
// Replace uartIndex and baud with your configuration

// Initialize UART (run once during startup)
UART.initHardwareUart(1, 115200);

// Build the packet
var servoId = 5;    // 0..N (your mapping to PCA9685)
var pos = 90;       // 0..180
var packet = [0xAA, servoId, pos];

// Send packet
UART.hardwareUartWrite(1, packet);
print("Sent UART packet to ESP: servo " + servoId + " => " + pos);

Notes:

  • Use UART.hardwareUartAvailable and UART.hardwareUartReadStringAvailable to get responses if you want feedback.
  • Keep packet format tiny (3-4 bytes) to minimize CPU.
  1. UDP over WiFi (if ESP is only on WiFi and you don’t want a TCP server) Why: UDP is stateless and very lightweight. ARC’s script API has Net.sendUDP(hostname, port, byteArray), so you can push small command frames to the ESP’s UDP listener. ESP firmware only needs a simple UDP receive loop to decode commands and update PCA9685.

Example packet format suggestion:

  • Start byte 0xAA, command byte (e.g., 0x01 == set servo), servoIndex (8-bit), position (0..180) (8-bit), optional checksum

Example ARC JavaScript that sends a UDP packet:

// UDP example: send [0xAA, 0x01, servoId, pos] to ESP at 192.168.1.50:4210
var host = "192.168.1.50";
var port = 4210;
var servoId = 12;
var pos = 120;

var packet = [0xAA, 0x01, servoId & 0xFF, pos & 0xFF];
Net.sendUDP(host, port, packet);
print("Sent UDP packet to " + host + ":" + port + " servo " + servoId + " => " + pos);

ESP side: create a UDP listener (WiFiUDP) to parse the 4byte packet and call PCA9685.setPWM(channel, pwmValue).

Which approach is right for you?

  • If you can attach a USBTTL and let ARC / PC treat the ESP as a serial device: use Custom servo Controller. Best integration with ARC (no scripting work per servo).
  • If your ESP is wired to the EZB UART: use ARC UART functions (very lightweight).
  • If you can’t wire and only have WiFi: use UDP (no TCP server; minimal firmware code).

Other options and notes

Followups I need from you

  1. How is your ESP physically connected now? (USBTTL to PC, wired to EZB UART, or only WiFi/camera stream?)
  2. Do you prefer a pure ARC-side solution (use existing Custom servo Controller skill) or do you want a tiny firmware change on the ESP (serial/UDP listener)?
  3. Which PCA9685 addresses / how many channels in each chip? (so I can show exact mapping code)

If you tell me the connection type I’ll:

  • Provide a ready-to-run ARC JavaScript snippet (UDP or UART) you can paste into a Script robot skill or an input button,
  • And a minimal ESP32 firmware snippet (Arduino) that receives the packets and writes PCA9685 PWM values (or a short change to extend your existing EZB firmware with a custom command).

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
Canada
#2  

Thanks.  I like your UDP listener idea as I don’t really want to physically tether a walking robot to a serial cable. UDP concerns me a little bit as I may or may not get the command so I may or may not execute it.  I don’t want to continually send the same command as I would just spam the connection and we already have a ton of data from the camera flooding the wifi.  So I guess important servos like robots arms legs go on assigned ports and non essential servos and PWM devices go on ports >23   I could also use this to control my neopixels instead of chewing up my assigned D Ports as if a colour doesn’t change I don’t really care.

Author Avatar
PRO
Canada
#3  

Great - UDP is a sensible choice for an untethered walking robot. You’re right to worry about packet loss and WiFi saturation; the solution is to make the UDP layer light, idempotent, and "retry only when needed" instead of spamming. Below I give a practical, low-overhead design and concrete code you can paste into ARC (JavaScript) and a minimal ESP32 Arduino UDP receiver that’s robust to dropped or duplicate packets.

High-level strategy (what I recommend)

  • Use UDP for low overhead and simplicity (no TCP server on the ESP).
  • Make commands idempotent and include:
    • start byte
    • command type
    • servo or device id
    • payload (position/RGB)
    • sequence number
    • small checksum
  • ARC sends a command only when a value changes. For important servos, send the packet 2-3 times with a short backoff. For nonessential devices (NeoPixels) send only on change and once.
  • ESP ignores duplicate/old commands using sequence numbers. ESP only applies commands if the sequence number is newer than the last applied one for that servo/device.
  • Keep packet format tiny (4-8 bytes) so you won’t add much load to WiFi/camera traffic.
  • Optional: If you want guaranteed delivery, run a tiny ACK server on the PC (Python) and have the ESP send a UDP ACK back. This is optional and not required in most cases if you use the above pattern.

Packet format (recommended, minimal)

  • Byte[0] = 0xAA (start)
  • Byte[1] = CMD (0x01 = set servo, 0x02 = neopixel, 0x03 = set group, etc)
  • Byte[2] = id (servo index or NeoPixel index)
  • Byte[3] = value (servo pos 0..180 OR For neopixel RGB usage, expand)
  • Byte[4] = seq (0..255, wrap OK)
  • Byte[5] = checksum (sum of bytes 1..4 & 0xFF)

This is tiny and cheap to decode on the ESP.

ARC side: best-practice JavaScript example

  • Only send when value changed.
  • Keep a per-servo last-sent in global variables ($ServoLast_v5 etc).
  • For critical servos, retry N times with sleep() gaps.

Paste this into a Script robot skill (Project -> Add robot skill -> Scripting) or run from a button script. Update host/port to your ESP IP and UDP port.

// ARC JavaScript: UDP send with change-only + optional retries
// Configure these:
var ESP_HOST = "192.168.1.50"; // change to your ESP IP
var ESP_PORT = 4210;           // change to the ESP UDP port

// helper: compute simple checksum
function checksum(bytes) {
  var s = 0;
  for (var i = 0; i < bytes.length; i++) {
    s += bytes[i];
  }
  return s & 0xFF;
}

// send raw packet array via UDP
function sendUdpPacket(packet) {
  Net.sendUDP(ESP_HOST, ESP_PORT, packet);
  print("UDP -> " + ESP_HOST + ":" + ESP_PORT + " : " + packet.toString());
}

// send servo command (servoIndex 0..98, pos 0..180)
// criticalRetries: number of times to retransmit (0 = one shot)
// Returns true (packet(s) sent)
function sendServo(servoIndex, pos, criticalRetries) {
  if (criticalRetries === undefined) criticalRetries = 0;

  // store last sent value per servo to avoid spam
  var varName = "$ServoLast_" + servoIndex;
  var last = getVar(varName, "-999");
  if (parseInt(last) === pos) {
    // no change, do not send
    return false;
  }

  // increment sequence number (global)
  var seq = parseInt(getVar("$UDPSeq", "0")) || 0;
  seq = (seq + 1) & 0xFF;
  setVar("$UDPSeq", seq.toString());

  var cmd = 0x01; // set servo
  var packet = [0xAA, cmd & 0xFF, servoIndex & 0xFF, pos & 0xFF, seq & 0xFF];
  packet.push(checksum(packet.slice(1))); // checksum at end

  // send initial and retries if critical
  sendUdpPacket(packet);
  for (var r = 0; r < criticalRetries; r++) {
    sleep(120); // 120ms small backoff - tune to your network
    sendUdpPacket(packet);
  }

  // update last-sent record
  setVar(varName, pos.toString());
  return true;
}

// send neopixel color - example packet (CMD=0x02)
// For single LED: [0xAA,0x02,ledIndex,R,G,B,seq,chk]
// Use same change-only policy above (store last color)
function sendNeopixel(ledIndex, r, g, b) {
  var varName = "$NeoLast_" + ledIndex;
  var last = getVar(varName, "");
  var colorStr = r + "," + g + "," + b;
  if (last === colorStr) return false;

  var seq = (parseInt(getVar("$UDPSeq", "0")) + 1) & 0xFF;
  setVar("$UDPSeq", seq.toString());

  var packet = [0xAA, 0x02, ledIndex & 0xFF, r & 0xFF, g & 0xFF, b & 0xFF, seq & 0xFF];
  packet.push(checksum(packet.slice(1)));
  sendUdpPacket(packet);
  setVar(varName, colorStr);
  return true;
}

// Usage examples:
// sendServo(12, 90, 2); // servo 12 -> pos90 with 2 retransmits (total 3 sends)
// sendNeopixel(3, 255, 0, 0); // set LED 3 to red

ESP32 Arduino UDP receiver (minimal)

  • Uses a per-servo last-seq array to prevent duplicates/old commands.
  • Maps servoIndex to PCA9685 (chip+channel).
  • Only update PWM if position changed (reduces PWM writes).
  • You will need Adafruit PWM library or equivalent.

Example (simplified) - sketch skeleton. Adapt to your PCA addresses and mapping:

#include <WiFi.h>
#include <WiFiUdp.h>
#include <Adafruit_PWMServoDriver.h>

const char* ssid = "YOUR_SSID";
const char* password = "YOUR_PASS";
WiFiUDP Udp;
const unsigned int localUdpPort = 4210;

Adafruit_PWMServoDriver pwm1 = Adafruit_PWMServoDriver(0x40); // PCA1
Adafruit_PWMServoDriver pwm2 = Adafruit_PWMServoDriver(0x41); // PCA2

// configure mapping: servoIndex -> (which PCA, channel)
struct ServoMap { Adafruit_PWMServoDriver* chip; uint8_t channel; };
ServoMap mapTable[100]; // support up to 100 virtual servos

uint8_t lastSeqForServo[100];     // last seq applied (0..255)
int lastPosForServo[100];         // last position applied (-1 = none)

void setupMapping() {
  // fill mapTable according to your wiring:
  // Example: servo 0-15 on pwm1 channels 0-15, 16-31 on pwm2 then onwards
  for (int i=0;i<100;i++) {
    if (i < 16) { mapTable[i].chip = &pwm1; mapTable[i].channel = i; }
    else if (i < 32) { mapTable[i].chip = &pwm2; mapTable[i].channel = i - 16; }
    else { mapTable[i].chip = &pwm1; mapTable[i].channel = i % 16; } // adjust per your layout
    lastSeqForServo[i] = 0xFF; // indicate none received yet
    lastPosForServo[i] = -1;
  }
}

void setup() {
  Serial.begin(115200);
  WiFi.mode(WIFI_STA);
  WiFi.begin(ssid, password);
  while (WiFi.status() != WL_CONNECTED) { delay(200); }

  pwm1.begin();
  pwm1.setPWMFreq(50);
  pwm2.begin();
  pwm2.setPWMFreq(50);

  setupMapping();

  Udp.begin(localUdpPort);
  Serial.printf("UDP listening on %d\n", localUdpPort);
}

uint8_t computeChecksum(uint8_t *buf, int len) {
  uint16_t s = 0;
  for (int i=0;i<len;i++) s += buf[i];
  return s & 0xFF;
}

void setServoPWM(Adafruit_PWMServoDriver* chip, uint8_t channel, int pos) {
  // Map 0..180 to PWM value (adjust min/max as needed)
  int pulseMin = 150; // tune for your servos
  int pulseMax = 600;
  int pwm = map(pos, 0, 180, pulseMin, pulseMax);
  chip->setPWM(channel, 0, pwm);
}

void loop() {
  int packetSize = Udp.parsePacket();
  if (packetSize) {
    uint8_t buf[64];
    int len = Udp.read(buf, sizeof(buf));
    if (len < 6) continue; // too small for our base format

    if (buf[0] != 0xAA) continue; // bad start

    uint8_t cmd = buf[1];
    if (cmd == 0x01 && len >= 6) { // set servo
      uint8_t id = buf[2];
      uint8_t pos = buf[3];
      uint8_t seq = buf[4];
      uint8_t chk = buf[5];
      if (computeChecksum(buf+1, 4) != chk) continue; // checksum fail

      // duplicate/old detection:
      if (lastSeqForServo[id] != 0xFF && (uint8_t)(seq - lastSeqForServo[id]) == 0) {
        // equal seq - duplicate -> ignore
        continue;
      }
      // if seq less than or old wrap logic: use unsigned compare to accept only newer
      // We just accept if seq != lastSeq (simple)
      lastSeqForServo[id] = seq;

      int oldPos = lastPosForServo[id];
      if (oldPos != pos) {
        setServoPWM(mapTable[id].chip, mapTable[id].channel, pos);
        lastPosForServo[id] = pos;
      }
    }
    else if (cmd == 0x02) {
      // neopixel or custom parse - implement as needed
    }
    // else other commands...
  }

  // other periodic tasks...
}

Notes about reliability without acks

  • UDP will drop occasionally, but you can:
    • Only send on change so retransmits are rare.
    • Retransmit a critical command N times. 2 retries (send 3 times) is common and cheap.
    • Use seq numbers so duplicates are ignored.
    • Make servo commands idempotent (setting same pos multiple times does no harm).
  • Large camera streams dominate bandwidth; a couple of tiny UDP packets are negligible in comparison.

NeoPixel specific guidance

  • NeoPixels are stateful: you only need to send changes. Avoid periodic refreshes.
  • If you control entire strips less often, send strip updates in one packet or split across frames.
  • Use a different CMD code so ESP handles them separately and doesn’t map to PCA channels.

Firewall and IP considerations

  • Make sure ARC computer and ESP are on same WiFi network/subnet.
  • If ESP IP is dynamic, either set a static IP on the ESP or use your router DHCP reservation.
  • You can use broadcast IP (e.g., 192.168.1.255) if you do not want to track the device IP, but then multiple devices will receive packets.

Optional: ACK-based reliability (if you want full reliability)

  • ARC cannot directly listen for UDP in the built-in script environment (no Net.listenUDP in the docs). If you need ACKs:
    • Run a tiny UDP listener on your PC (Python or Node) that receives ACKs from ESP and forwards status to ARC via local HTTP or writes a file or uses a small websocket. This adds a small PC-side component but keeps the ESP very light.
    • Or make the ESP do an HTTP GET to ARC (ARC must expose an HTTP endpoint or you run a light local web server) - this adds TCP work to the ESP and isn’t recommended if you want to avoid server overhead on ESP.

Next steps for me to tailor code

  1. Is the ESP running in WiFi station mode on the same subnet as your ARC PC? Do you know the ESP IP?
  2. Tell me the PCA9685 I2C addresses and how many channels on each chip (so I can give a precise mapping example).
  3. Which servos are "critical" (arm/leg joints)? I’ll flag them in the ARC script to use retries.
  4. Do you want example NeoPixel packet format and example parsing on ESP?

If you want, I’ll produce:

  • a plug-and-play ARC script tailored to your critical servo list and your ESP IP,
  • a ready-to-compile Arduino sketch tuned for your PCA9685 address map and servo mapping,
  • and a simple test plan to validate reliability (how to test 1 packet loss and recovery).

Would you like me to prepare those tailored files?


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
Canada
#4  

I guess the other option is ARC is modified to support more than 23 servo ID based on the Firmware ID of the board selected like it does today.

Author Avatar
PRO
Canada
#5  

Short answer: ARC already supports more-than-24 servos without changing ARC core - use virtual servo ports (Vx) or a small robot-skill that detects your ESP firmware and sends custom EZB protocol commands. Changing ARC itself to expose D-ports > d23 based on a firmware ID would require modifying ARC source/controls (not something you can safely do from a project), and is overkill when robot skills + EZB-protocol extension solve this cleanly.

Below I outline practical options (why, pros/cons) and give concrete implementation paths you can choose from. I’ll also include minimal example code you can reuse.

Links you should bookmark

Why not modify ARC core

  • ARC uses d0-d23 to model physical EZB PWM ports. Changing that behavior would require a change to ARC itself (C# codebase) and a release/update for all users.
  • You’d also need UI, validation and robot-skill compatibility updates. That’s a larger engineering task.
  • You can achieve the same result without touching ARC by using robot skills (C# plugin) or by using the existing Custom servo Controller skill (Vx ports up to v99), or by extending the EZB protocol with Command 0 and a small firmware change on your ESP.

Recommended approaches (ranked)

  1. Best / easiest integration: Use Custom servo Controller (Vx virtual ports)
  • Why: ARC already exposes V0-V99 virtual servos via this skill. It’s meant for microcontrollers or servo-expanders controlling more than 24 servos. No core ARC change required.
  • How it works: ESP firmware speaks a small serial protocol (or whatever the Custom servo Controller expects). The robot skill maps v0..vN to your PCA9685 channels and ARC servo panels / scripts can control them as vX servos.
  • Pros: Full ARC servo UI / script compatibility, no change to ARC core.
  • Cons: Requires serial or a robot skill glue if you prefer WiFi/UDP.
  1. Robust & low-overhead: Extend EZB Protocol Command 0 + robot skill that exposes extended servo set
  • Why: Use the built-in "extend EZB protocol" method (Command 0 / CmdOurCustomCmds) so ARC sees your ESP as an EZB-like device. A robot skill can detect the firmware ID and expose more servos or custom controls accordingly.
  • How it works:
    • ESP firmware: implement custom commands under EZB Cmd 0 (subcommands) to accept servoId (0..99) and position and set the PCA9685 channels.
    • Robot Skill (C#): use EZBManager.PrimaryEZB.SendCommandData(...) to send Cmd 0 + subcommand + servoId + pos - exactly like the Extend-EZB example on the site.
    • Skill settings can detect firmware ID and display/enable the extra ports.
  • Pros: Very clean integration with EZB command set and ARC internal messaging; can appear like an extension to an EZB variant.
  • Cons: requires a small robot-skill (C#) to send the custom commands; needs firmware change on ESP.

Example C# robot-skill usage (send custom command to EZB)

// Inside your  robot skill C# code
// Send a custom sub-command (SetExtendedServo) with no returned bytes:
// bytesToReturn, cmdParent, subCmd, servoId, position
// cmdParent = 0x00 (CmdOurCustomCmds), subCmd=0x10 (your subcommand)
byte responseSize = 0; // 0 = no return bytes expected
byte cmdParent = 0x00; // CmdOurCustomCmds
byte subCmd = 0x10;    // custom sub-command: SetExtendedServo
byte servoId = 42;     // 0..99
byte position = 90;    // 0..180

if (EZBManager.PrimaryEZB.IsConnected)
{
    EZBManager.PrimaryEZB.SendCommandData(responseSize, cmdParent, subCmd, servoId, position);
}

(See: https://synthiam.com/Support/Create-Robot-Skill/Examples/extend-ezb-protocol-with-custom-command)

  1. Lightweight & untethered (your chosen UDP approach) + optional ACK
  • Why: You already like UDP for untethered robot. Keep it, and add simple reliability features:
    • idempotent commands,
    • per-servo sequence numbers,
    • send-on-change only,
    • 1-2 retries for critical servos, and
    • optional light ACK mechanism if you want explicit delivery confirmations.
  • Pros: No ARC core change; firmware on ESP stays small. Works well for mobile robots.
  • Cons: ARC scripts can send UDP but ARC cannot natively receive/ack UDP in scripts; if you want a two-way ACK loop you need a small PC-side component (Python) or use a custom robot skill.

Which to pick?

  • If you want the best ARC UI integration and don’t want to write much firmware: Custom servo Controller (Vx) or a small robot skill that speaks Cmd 0.
  • If you want totally untethered and minimal ARC changes: keep UDP + sequence numbers and retries (what you already started).

Concrete path for the "Firmware-ID based extended servos" idea (recommended implementation without changing ARC core)

  • Goal: Make the ESP advertise that it supports extra servos, and have a robot skill detect it and expose the ports in ARC.
  • Steps:
    1. ESP firmware: On EZB "GetFirmwareID" request return a custom FIRMWARE_ID that documents extended capability (like your ESP32-CAM EZB firmware does).
      • Add custom Cmd0 handlers: sub-command SetExtendedServo(servoId,position) and maybe GetExtendedServoCount().
    2. Create a small robot skill (C#):
      • On connect, read EZB firmware ID and (optionally) call your custom GetExtendedServoCount() subcommand to know how many v-servos exposed.
      • Provide the skill settings UI to map virtual ports V0..Vn to physical PCA9685 channels, with a kebab-menu Settings entry (Project -> Add robot skill -> skill window -> kebab menu).
      • Send servo commands via EZBManager.PrimaryEZB.SendCommandData(...) using Cmd 0 subcommands.
    3. In ARC project:
      • Add your new robot skill (Project -> Add Robot Skill).
      • Configure COM/EZB connection, set mapping, and then you can use ARC servo panels or scripts to control the Vx ports exposed by your skill.
  • This gives the "Firmware ID drives behavior" you suggested - but as a robot skill plugin, not a change to ARC core.

Minimal ESP firmware snippet (Arduino/ESP32) parsing Cmd 0 subcommands

// Pseudocode - add into your existing EZB-like firmware command loop
#define CmdOurCustomCmds 0x00
#define SubCmd_SetExtendedServo 0x10
// ... reading from serial or TCP buffer ...
if (cmd == CmdOurCustomCmds) {
  uint8_t sub = ReadByte();
  if (sub == SubCmd_SetExtendedServo) {
    uint8_t servoId = ReadByte();   // 0..99
    uint8_t pos = ReadByte();       // 0..180
    // Map servoId -> PCA9685 chip and PWM channel
    setPCA9685Position(servoId, pos);
    // Optionally send back bytes or confirmation if requested
  }
}

Minimal ESP UDP alternative (your current plan)

  • Packet design: [0xAA, CMD, servoId, pos, seq, chk] (6 bytes)
  • ESP applies if seq is newer than lastSeq[servoId] and if checksum matches
  • ARC sends only on change, and for critical servos retries 1-2 times
  • You already have a nice plan - I can provide the Arduino UDP listener code that integrates with PCA9685 and handles seq/checksum.

How I can help next Tell me which of these you want me to produce:

  • A ready-to-drop-in ESP Arduino snippet implementing Cmd0 SetExtendedServo (for robot-skill integration), plus a small mapping function for PCA9685 addresses? OR
  • A small robot skill (C#) template that detects firmware ID and exposes Vx ports and uses SendCommandData(...) to set servo positions? OR
  • A final UDP/ESP sketch tailored to your PCA9685 addresses + a copy/paste ARC JavaScript script that sends change-only UDP packets with retries?

Also please confirm:

  1. How is your ESP connected to ARC currently? (WiFi only - I assume yes.)
  2. PCA9685 addresses and channels (e.g., 0x40 = servos 0..15, 0x41 = servos 16..31, etc.) so I can give exact mapping.
  3. Which servo indices are "critical" (arm/leg joints) so I can add smart retries in the ARC script.

Pick one of the three deliverables above and give the mapping + critical servo list, and I’ll provide the code & step-by-step for the kebab menu settings and how to add/configure the skill in ARC.


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
#6   — Edited

Yeah she nailed it. ARC will never be modified for what you refer to as "more than 24 servos (not 23)" because natively it’s the most ideal number for smoothest operation over a tcp channel. However, if a customer wishes to override our recommendation, it can be done with Vx servos as Athena mentioned.

This would require a super duper simple robot skill.  That listens to the ezb’s servo manager by subscribing to the servo movement event. Sending a custom Move command to the ezb protocol for servos that are between Vx and Vy (ie V0 and V8).

Your esp32 firmware simple needs to add an IF condition to the firmware in the Custom section.

thats it

If you’re worried about having some ports labeled Dx and some labeled Vx, just use all Vx instead.

As for neo pixels. Youd need to add a custom extension as well. Just simple commands for adjusting the lights.

next thing you know, you have a little robot skill for your robot.

My suggestion you could make a few robot skills for universal (two in this case). Call them each an ezb protocol extension. One can be for neo pixel. The other can be for servos. That way yoir robot skill can be used more dynamically rather than for a single robot. This is the direction I’d go for expandability.

just remember, if prompting an LLM to assist, don’t mix instructions for both robot skills at once. Pick one and work with her until it’s completed. Then start on the other.

also, prompting has helped us realize how we manage expectations. It’s important to work backward from an outcome when describing what we want. We have to time travel to what it’s like when completed and describe what it is in great detail - and then work backward from that into a prompt. LLM’s produce best when you do not include your own irritative creative process. So it’s best to describe the outcome (including Dos and Don’ts), not the process.

Also, separate those in different conversations with an LLM. Have the LLM work with you to generate a prompt. Then, move to a new convo and use that prompt.

LLM’s are fancy auto completes. They get confused when the conversation includes many thoughts outside of the outcome. So always use new sessions when defining or tweaking an outcome.

Author Avatar
PRO
Canada
#7  

Nice direction - DJ_Sures is right: keep ARC core alone and implement this as small, focused robot skills that forward Vx activity to your ESP32 firmware via the EZB protocol extension (Cmd 0) or a lightweight UDP/serial transport. That gives you clean behavior, no ARC changes, and reusable skills (one for extended servos, one for NeoPixels).

Below is a practical plan, sample code templates (robot-skill side and ESP side), configuration guidance (kebab menu), and follow-up questions so I can produce full drop-in files for you.

What I recommend

  • Create two tiny robot skills (C# plugin controls):
    • Extended servo Forwarder: subscribes to servo movement events (Vx range) and sends Cmd 0 sub-commands to the EZB connection (or uses SendCommandData) for those Vx ports.
    • NeoPixel Forwarder: accepts NeoPixel color changes (via a small UI or event) and sends a compact Cmd 0 packet to the ESP to change colors.
  • Keep firmware cheap: implement CmdOurCustomCmds (0x00) sub-commands on the ESP:
    • SubCmd_SetExtendedServo (0x10): servoId, position
    • SubCmd_SetNeoPixel (0x20): pixelIndex, R, G, B
  • Use idempotent commands and sequence or timestamp if you want duplicate filtering/ordering.
  • Use all virtual ports (Vx) for these extensions so you don’t mix D and V naming - simpler UX.

Links to resources

How the solution works (high-level)

  1. Robot skill subscribes to servo movement events that ARC already produces (your project uses servo Panel or script calls).
  2. When a servo movement occurs for a port mapped to the extended range (for example V0..V31), the skill captures it, maps Vx -> extended servoId (0..N), and calls: EZBManager.PrimaryEZB.SendCommandData(responseSize, CmdOurCustomCmds, subCmd, servoId, position)
  3. The ESP firmware receives the custom command (Cmd 0), decodes the sub-command, maps servoId -> PCA9685 chip+channel, and writes PWM.
  4. For NeoPixels, the NeoPixel Forwarder skill sends color packets with a different subCmd; ESP updates strip.

Minimal C# robot-skill pseudo (forwarder)

  • This is a minimal conceptual template showing the subscription and SendCommandData call. It uses the SendCommandData pattern from the docs - adapt into your robot skill class.
// PSEUDO-CODE (Robot Skill) - integrate into a real skill project per Create  robot skill guide

const byte CmdOurCustomCmds = 0x00;
const byte SubCmd_SetExtendedServo = 0x10;
const byte SubCmd_SetNeoPixel = 0x20;

public override void Init()
{
  // Subscribe to servo movement events (pseudo API - adapt to actual SDK event names)
  // The SDK exposes project events or servo manager events you can subscribe to.
  Project.ServoManager.ServoMoved += ServoMovedHandler;

  // Load skill settings: which Vx range to forward and which EZB index (0..4)
  LoadSettings();
}

private void ServoMovedHandler(object sender, ServoMoveEventArgs e)
{
  // e.PortName = "V3" or "v3" or "D0" depending on how ARC sends it
  // Ignore non-V ports if you want all-virtual
  if (!IsInForwardRange(e.PortName)) return;

  int virtualIndex = GetVirtualIndex(e.PortName); // e.g., V3 -> 3
  byte servoId = MapVirtualToExtendedId(virtualIndex); // map to your extended servo ID
  byte position = (byte)Math.Clamp(e.Position, 0, 180);

  // Fire custom EZB command: no response expected -> responseSize = 0
  byte responseSize = 0;
  EZBManager.PrimaryEZB.SendCommandData(responseSize, CmdOurCustomCmds, SubCmd_SetExtendedServo, servoId, position);
}

// For NeoPixels, call SendCommandData with SubCmd_SetNeoPixel followed by pixel,R,G,B

Notes:

  • The exact event name for servo movement in the SDK may differ; check Create robot skill examples and the EZB API. The concept is subscribe -> translate -> SendCommandData.
  • Provide skill settings (UI) to let user pick: connection index, start/end V range, mapping offsets, and whether to use retries/ACK.

ESP32 (Arduino) firmware: Cmd 0 handler (minimal)

  • Insert into your command processing area. This shows the "IF condition in the Custom section" DJ_Sures referred to.
// PSEUDO-CODE (Arduino ESP32) - inside your EZB-like command parser
#define CMD_OUR_CUSTOM_CMDS 0x00
#define SUBCMD_SET_EXTENDED_SERVO 0x10
#define SUBCMD_SET_NEOPIXEL 0x20

void handleIncomingCommand(uint8_t cmd, uint8_t* payload, int payloadLen) {
  if (cmd == CMD_OUR_CUSTOM_CMDS) {
    uint8_t sub = payload[0];
    if (sub == SUBCMD_SET_EXTENDED_SERVO && payloadLen >= 3) {
      uint8_t servoId = payload[1];   // 0..99
      uint8_t pos = payload[2];       // 0..180
      // Map servoId to PCA9685 chip & channel, then set PWM:
      setPCA9685Servo(servoId, pos);
      // optionally send ack bytes back if EZB SendCommandData requested a response
    }
    else if (sub == SUBCMD_SET_NEOPIXEL && payloadLen >= 5) {
      uint8_t pixelIndex = payload[1];
      uint8_t r = payload[2];
      uint8_t g = payload[3];
      uint8_t b = payload[4];
      setNeoPixel(pixelIndex, r, g, b);
    }
  }
}

Simple packet layout for SendCommandData

  • Parent command = 0x00
  • Sub command = 1 byte (0x10 = servo, 0x20 = neopixel)
  • Parameters follow in fixed small lengths
    • Servo: [sub, servoId (1), position (1)]
    • NeoPixel: [sub, pixelIndex (1), R (1), G (1), B (1)]
  • Keep bytes compact; SendCommandData handles encapsulation.

Kebab menu & skill settings guidance

  • Create skill settings so user can:
    • Select EZB Connection index (0..4) to send commands over.
    • Configure mapping: V-start (e.g., V0) -> extended servo base ID, and count (how many Vx map).
    • Enable/disable NeoPixel forwarding and pick pixel count / strip layout.
    • Enable debug logging and optional responses (ACK).
  • How to add your skill to a project: Project -> Add Robot Skill. Once installed, open its window and click the kebab (three-line / hamburger) menu to open Settings for mapping and connection selection. (See docs: Add robot skill to Project: https://synthiam.com/Support/ARC-Overview/robot-skills/add-robot-skill-to-project)

Why this is a good path

  • No ARC core change required.
  • Small reusable robot skills can be published and used by other users.
  • Minimal firmware change: a couple of IF branches in the custom section.
  • Control naming remains consistent using Vx for extended ports.
  • Optional ACK/back-channel if you want reliability; otherwise rely on retransmit for critical servos.

Follow-ups I need from you (so I can generate concrete files)

  1. Do you want:
    • A full C# robot-skill template that subscribes to servo events and calls SendCommandData for extended servo + NeoPixel? OR
    • Just a minimal Arduino (ESP32) firmware snippet (Cmd 0 handlers) that you’ll drop into your existing firmware? OR
    • Both (recommended)?
  2. How do you want to present ports in ARC: all virtual (V0..Vn) or mixed D + V?
  3. PCA9685 layout & addresses (example: 0x40 -> servos 0..15, 0x41 -> servos 16..31), and how many total extended servos?
  4. NeoPixel details (strip count, pixels per strip, pixel indexing scheme).
  5. Do you need ACKs for critical servos or is "send-on-change + 1-2 retries" acceptable?

If you say "yes, give me both," I will:

  • Produce a C# robot-skill skeleton with settings UI, event subscription, mapping logic, and SendCommandData calls (commented so you can extend).
  • Produce a ready-to-compile Arduino ESP32 sketch snippet showing the Cmd 0 parsing and PCA9685 / NeoPixel handling with minimal overhead.
  • Provide step-by-step instructions for installing the skill, setting the mapping via the kebab menu, and testing.

Which deliverable do you want first?


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.