Irobot Roomba Legacy icon Irobot Roomba Legacy Legacy iRobot Discovery and 4xxx control via EZB software-serial (D0), supports Arduino/EZ-Robot, adjustable baud rate; not for hardware UART/USB Try it →
Asked — Edited
Resolved Resolved by Athena!

Arduino PWM Bearing Control Help

Hi @Athena,

I'm requesting help to add capabilities to an existing Arduino sketch. The sketch is included at the end of this post. Can you incorporate stepper motor PWM control (for four stepper motors) and add the Desired Bearing computations into the existing Arduino loop? Thank you in advance.

For clarity, here is the system context:

  • ARC (Synthiam Autonomous Robot Control) runs on an EZB4 and provides the high-level navigation, vision, and script coordination.
  • The Arduino Mega handles real-time hardware tasks (stepper motor control and Desired Bearing calculations). Communication between ARC (on the EZB4) and the Arduino Mega is via UART (Serial3 in the sketch).
  • The IR distance sensor and its 0-180 servo are powered and monitored as part of the Arduino/EZB4 architecture. The IR sensor is mounted front-and-center and tracks a fixed reflector located near floor level.
  • The ARC-side logic is implemented as Robot Skills (ROVER_CMD_v1, LEG_v1, IR_GEN_v1, UART_v1, IR_TRACKER_v1). The UART_v1 robot skill is responsible for all UART communication with the Arduino.
  • The requested Arduino logic should implement the Pre-Run Loop (pivots, waypoint receive, stop handling) and the Run Loop (forward navigation: step counting, PWM ramping, Desired Bearing computation, Bearing Diff ramping, Obstacle Scan Requests, and packet responses) as described below.

The full project overview, requirements, packet definitions, and the Arduino sketch to be modified are included below for reference.

ARC Rover: Arduino PWM Control and Bearing Computation

PART 1.   General Overview
PART 2.   Term Definitions
PART 3.   Requested Criteria for the Arduino Sketch
PART 4.   Arduino Sketch to be modified with PART 3 Criteria

PART 1
General Overview for informational purposes:

This rover is driven by 4 stepper motors rolling on 4" mecanum wheels and powered by a 24v 5000mAh lithium battery. Individual buck converters supply the appropriate DC voltage to the EZB4, Arduino Mega, IR distance measuring sensor, and the IR sensor servo. The IR sensor is mounted to a 0-180 degree servo centered at 90 degrees. This gives the IR sensor equal travel left (180) and right (0) of center (90).  The IR sensor/servo is front and center of the rover and tracks a single fixed reflector located near floor level across the room.
The principal purpose of this rover is to explore and develop a navigation system based upon the Sharp IR distance measuring sensor. Overall controlling architecture is accomplished via Synthiam ARC on the EZB4.  Certain high CPU demands such as stepper motor control and Desired Bearing computations are accomplished by an Arduino Mega.  Necessary communication between ARC and the Arduino is via UART.

This rover project is very much a work in progress. Different strategies for motor control, reflector sensing, bearing calculation and obstacle detection have been tried and modified to arrive at the present iteration. What follows represents the latest embodiment of this rover project.

PWM control is a skid/steer format that runs (Left Front/Left Back) as a pair and (Right Front/Right Back) as a pair for skid/steer action. Other mecanum wheel capabilities will be developed as necessary.

ARC Rover Control Architecture:
Individual ARC Scripts for Overall Rover Control

1. ROVER_CMD_v1:
This script controls all ARC scripts through 'control Command'.

2. LEG_v1:
This script defines the Waypoint Data for the rover path. It sets global variables necessary for each leg and operates only once.

3. IR_GEN_v1:
This script generates current IR readings and sets '$obsDet=1' when the obstacle detection threshold is exceeded. It operates in a continuous loop.

4. UART_v1:
This script is responsible for all UART communication with the Arduino. It operates in a continuous loop, acting upon global variable status.

5. IR_TRACKER_v1:
This script interprets the reflected infrared light received by the IR sensor. Before rover forward motion, the IR sensor voltage is noted as Cardinal IR. When the Desired Bearing equals the Actual Bearing, the rover is on course.  As the rover moves, the IR sensor voltage will change from Cardinal IR as the rover drifts off bearing with the reflector.  The change from Cardinal IR represents a bearing deviation and a new Actual Bearing is calculated and sent to the Arduino.

PART 2
Term Definitions:

Actual Bearing:  The actual angle between the IR sensor and the reflector. ARC calculation.

Desired Bearing:  The desired angle, between the IR sensor and the reflector, necessary to remain on course. 
                  Arduino calculation.
 
Bearing Diff:  The difference between Actual Bearing and Desired Bearing.  Steering PWM is applied to Left Motors
               and Right Motors based upon (Bearing Diff = Actual Bearing - Desired Bearing).
               Actual Bearing is sent to the Arduino by ARC as an X100 representation.

      EXAMPLE (Actual Bearing of 79.00 is sent by ARC as 7900). Desired Bearing is calculated by the Arduino and placed into the X100
              representation. Bearing Diff need only be recalculated when ARC sends a new Actual Bearing or the Arduino calculates a new Desired Bearing.

Leg always refers to the physical track of the rover.  Measured in inches and converted to Combined Steps. One inch = 437.5 Combined Steps.

Leg Start Point:  The beginning point of navigation.

Leg Stop Point:  The end point of navigation.

Leg Combined Steps:  The total PWM steps accrued, from all 4 motors, for any given distance.  One inch = 437.5 Combined Steps.

Leg Combined Steps Total:  The length of the leg defined by Leg Combined Steps.  The distance between the Leg Start Point and the Leg Stop Point.
                           
Leg Combined Steps Taken:  The distance traveled in Leg Combined Steps.

Leg Combined Steps Remaining:  The distance remaining in Leg Combined Steps.  (Leg Combined Steps Total - Leg Combined Steps Taken).

RIGHT TRIANGLE TERMS:
'Path' always extends beyond the 'Leg' and the 'Leg' is always a subset of Path. One inch = 437.5 Combined Steps.

'Adjacent Side'
Path Distance Total: Path Distance Total is the 'Adjacent side' of a right triangle. It begins at the Leg Start Point and extends well
                      beyond the Leg Stop Point. Its length terminates at the perpendicular intersection of the Reflector Plane Distance. Measured
                      in Leg Combined Steps.
 
Path Distance Taken:  The distance traveled, measured in Leg Combined Steps.

Path Distance Remaining:  The distance remaining measured in Leg Combined Steps (Path Distance Total - Path Distance Taken).

'Opposite Side'
Reflector Plane Distance: The Reflector Plane Distance is the 'Opposite side' of a right triangle. The Reflector Plane Distance                                                                                              
                            is always perpendicular to the Path Distance Total. Its length is a fixed distance, measured in Leg Combined Steps,                                                                             

                            between the Path Distance Total and the physical reflector.

PART 3
Requested Criteria to be added to the Arduino Sketch:

    1. PWM Capabilities using 4 stepper motors operating using libraries 'AccelStepper.h' and 'MultiStepper.h'

           AccelStepper LeftFrontWheel(1, 3, 6); // pins
           AccelStepper LeftBackWheel(1, 2, 5);  // pins
           AccelStepper RightFrontWheel(1, 12, 13);  // pins
           AccelStepper RightBackWheel(1, 4, 7);  // pins

    Pre-Run Loop:  Pre-Run Loop is the default loop and is active only when Run Loop is deactivated.
    Monitors for the following packets/commands sent by ARC:

    Each pass through the Pre-Run Loop:

    1. Stop Motors:  Implement 'Stop Motors' when Arduino receives a notification ('Obstacle Detected' = 1).
                     Respond to Stop Motor command by sending 'Total Steps Taken' to ARC.

    2. Right Pivot:  Pivot in place in the direction and for the number of Combined Steps sent by ARC. Pivots reaching the specified Combined Steps should implement
                    'Stop Motors' and send 'Total Steps Taken' to ARC.

    3. Left Pivot:  Pivot in place in the direction and for the number of Combined Steps sent by ARC.  Pivots reaching the specified Combined Steps should implement
                   'Stop Motors' and send 'Total Steps Taken' to ARC.

    4. Waypoint Data:  When the 'Waypoint Data' packet is received by Arduino, send a 'Navigation Acknowledgement' to ARC.

    Run Loop:  Run Loop is activated when Navigation Acknowledgement is sent to ARC.  Pre-Run Loop is deactivated.
    Monitors for the following packets/conditions sent by ARC:

    Each pass through the Run Loop:

    1. Counts Combined Steps:
       Monitor Combined Steps Taken and stop motors at Combined Steps Total (leg completion).

    2. Motor starting PWM is applied with a 'ramp' up to normal speed.  Incremental ramp steps accrue during each pass through the Run Loop.  
       When normal speed is reached, this ramp up to normal speed is terminated.

    3. Monitors when Actual Bearing changes are sent by ARC.  Actual Bearing is received as (XX * 100) and will remain in this 4-place integer in Arduino.

    4. Calculates Desired Bearing at selected intervals. 
       Desired Bearing is converted from XX.XX to (XX.XX * 100) and remains in this 4-place integer.
       Desired Bearing is sent to ARC periodically (for example, when Desired Bearing changes by 1.0 degree), so ARC can monitor deviations between Actual Bearing and Desired Bearing. 
   
       (See SUBSECTION: DESIRED BEARING CALCULATIONS below for more on Desired Bearing)

 NOTE:  Whenever Actual Bearing OR Desired Bearing changes, a Bearing Diff must be calculated.

    5. Bearing Diff:

     a.  Bearing Diff is reset to '0'.  Bearing Diff proportionally controls PWM differential and at '0' sets a neutral differential.
     b.  A new Bearing Diff is calculated. (Actual Bearing - Desired Bearing = Bearing Diff)
     c.  Bearing Diff is 'ramped' from '0' to the new Bearing Diff.  Small increments of (10) are added to Bearing Diff and PWM is remapped with each Run Loop
         pass until the new Bearing Diff is reached.
         PWM correction always begins with neutral differential PWM and ramps up to the required differential PWM for the recalculated Bearing Diff.

 Example:  Actual Bearing = 8800 (88.00).  Desired Bearing = 9000 (90.00).  Bearing Diff = -200.    (8800 - 9000) = -200
           Bearing Diff is set to '0'.  Each pass through Run Loop accumulates an additional (-10).  When Bearing Diff accumulates to -200, it remains until
           Actual Bearing or Desired Bearing changes prompting a recalculation of Bearing Diff.

           Actual Bearing = 9200 (92.00).  Desired Bearing = 9000 (90.00).  Bearing Diff = 200.     (9200 - 9000) = 200
           Bearing Diff is set to '0'.  Each pass through Run Loop accumulates an additional (+10).  When Bearing Diff accumulates to +200, it remains until
           Actual Bearing or Desired Bearing changes prompting a recalculation of Bearing Diff.

     6. Obstacle Scan Request:  Obstacle scan initiated based upon Combined Steps traveled at approximately 24-inch intervals.
     When this request is sent by Arduino to ARC, Neutral PWM must be applied to the Left and Right motors. Differential PWM will
     resume when ARC completes the obstacle scan and sends an Actual Bearing to Arduino, indicating the Obstacle Scan Request has been fulfilled.

     7. Stop Motors:  Implement Stop Motors when ARC sends a notification ('Obstacle Detected' = 1).  
                      Send Total Steps Taken in response to the Stop Motor command.

        SUBSECTION:     DESIRED BEARING CALCULATIONS

    Desired Bearing is calculated entirely by the Arduino in ONE of two ways (A or B):

    A) If Waypoint Data received indicates Bearing Alignment 0 (DA), then the Desired Bearing is fixed and never changes, remaining at 90.  [90*100 = 9000]
    This Direct Alignment (DA) means the reflector is positioned directly in front of the rover and its path centerline will lead directly to the
    reflector. Desired Bearing is never recalculated.

    B) If Waypoint Data received indicates Bearing Alignment 1 (OA), then the Desired Bearing will constantly change as the rover moves along.  The
    Desired Bearing may, for instance, start at 83.00 [8300] and end at 71.00 [7100].  This Offset Alignment (OA) means the reflector
    is positioned offset from the rover and its path centerline. Desired Bearing is calculated at Combined Step intervals of 100.

    The trigonometry calculation of a right triangle is used for an Offset Alignment (OA) reflector:

                       Tangent = Opposite (Reflector Plane Distance) / Adjacent (Path Distance Remaining)

    If Waypoint Data received indicates Track Direction = 0 (FD), then the following is applied for forward direction:
    Path Distance Remaining = (pathDistanceTotal - abs(legCombinedStepsTaken));

    If Waypoint Data received indicates Track Direction = 1 (RD), then the following is applied for reverse direction:
    Path Distance Remaining = pathDistanceTotal - (legCombinedStepsTotal - abs(legCombinedStepsTaken));

    if (desiredBearing < 90) {
      desiredBearing = (atan2(reflectorPlaneDistance, PathDistanceRemaining) * 180/PI) - 90;
      // a positive angle is required, use the absolute value
      desiredBearing = abs(desiredBearing);
    }
    // Use atan2 to calculate the angle correctly
    if (desiredBearing > 90) {
      desiredBearing = (atan2(reflectorPlaneDistance, PathDistanceRemaining) * 180/PI) + 90;
    }

PACKETS:

ARC -> Arduino Packets:

1. Stop Motors (rover full stop)

Byte	Value
Header	0xA5
Command	0x01
Payload	none
Packet size: 2 bytes

2. Actual Bearing (Bearing to the reflector)

Actual bearing is sent as a whole number scaled by 100.
Example:
79.00 degrees becomes 7900
sent as a 16-bit unsigned integer
Byte	Value
Header	0xA5
Command	0x03
Byte 2	Low byte of bearing
Byte 3	High byte of bearing
Packet size: 4 bytes

3. Right Pivot

Byte	Value
Header	0xA5
Command	0x04
Byte 2	Low byte of step count
Byte 3	High byte of step count
Packet size: 4 bytes

4. Left Pivot

Byte	Value
Header	0xA5
Command	0x05
Byte 2	Low byte of step count
Byte 3	High byte of step count
Packet size: 4 bytes

5. Waypoint Data

Byte	Field	Type	Notes
0	Header	0xA5	packet marker
1	Command	0x06	waypoint packet
2-5	Path Total Distance	uint32	max 120000
6-9	Combined Steps Desired	uint32	max 84000
10-13	Opposite Distance	uint32	max 24000
14	Bearing Alignment	uint8	0 = DA, 1 = OA
15-16	Reflector Bearing	uint16	scaled by 100
17	Sensor Prime	uint8	1-10
18-19	Desired Bearing	uint16	scaled by 100
20-21	Actual Bearing	uint16	scaled by 100
22	Track Direction	uint8	0 = FD, 1 = RD
Packet size: 23 bytes

Arduino -> ARC Packets

1. Navigation Acknowledgement (confirmation of Waypoint Data received)

Byte	Value
Header	0xA5
Command	0x10
Payload	none
Packet size: 2 bytes

2. Total Steps Taken (response to Stop Motor command)

Use a 32-bit unsigned integer.
Byte	Value
Header	0xA5
Command	0x11
Byte 2	Total steps byte 0
Byte 3	Total steps byte 1
Byte 4	Total steps byte 2
Byte 5	Total steps byte 3
Packet size: 6 bytes

3. Desired Bearing (course computed for desired rover path)

Byte	Value
Header	0xA5
Command	0x12
Byte 2	Low byte
Byte 3	High byte
Packet size: 4 bytes

4. Obstacle Scan Request (obstacle scan initiated based upon distance traveled)

Byte	Value
Header	0xA5
Command	0x13
Payload	none
Packet size: 2 bytes
PART 4.  
Arduino Sketch to be modified:
#include <Arduino.h>
static const uint8_t UART_HEADER = 0xA5;
// ARC -> Arduinostatic const uint8_t CMD_STOP_MOTORS      = 0x01;static const uint8_t CMD_MISSED_IR        = 0x02;static const uint8_t CMD_ACTUAL_BEARING   = 0x03;static const uint8_t CMD_RIGHT_PIVOT      = 0x04;static const uint8_t CMD_LEFT_PIVOT       = 0x05;static const uint8_t CMD_WAYPOINT_DATA    = 0x06;static const uint8_t CMD_OBS_SCAN_COMPLETE = 0x07;
// Arduino -> ARCstatic const uint8_t RSP_NAV_ACK          = 0x10;static const uint8_t RSP_TOTAL_STEPS      = 0x11;static const uint8_t RSP_DESIRED_BEARING  = 0x12;static const uint8_t RSP_OBS_SCAN_REQUEST = 0x13;
enum ParserState {  WAIT_HEADER,  WAIT_CMD,  WAIT_PAYLOAD};
ParserState state = WAIT_HEADER;uint8_t currentCmd = 0;uint8_t payload[32];uint8_t payloadIndex = 0;uint8_t expectedLength = 0;
int RUN = 0; // 0 = setup / pivots / waypoint receive, 1 = forward navigation
uint32_t totalStepsTaken = 0;uint16_t desiredBearing100 = 0;uint16_t lastDesiredBearing100 = 1;uint16_t actualBearing100 = 0;
uint32_t pathTotalDistance = 0;uint32_t combinedStepsDesired = 0;uint32_t oppositeDistance = 0;uint8_t bearingAlignment = 0;uint16_t reflectorBearing100 = 0;uint8_t sensorPrime = 0;uint16_t waypointDesiredBearing100 = 0;uint16_t waypointActualBearing100 = 0;uint8_t trackDir = 0;
uint16_t readUInt16LE(const uint8_t* data) {  return (uint16_t)data[0] | ((uint16_t)data[1] << 8);}
uint32_t readUInt32LE(const uint8_t* data) {  return (uint32_t)data[0]      | ((uint32_t)data[1] << 8)      | ((uint32_t)data[2] << 16)      | ((uint32_t)data[3] << 24);}
void writeUInt16LE(uint16_t value) {  Serial3.write((uint8_t)(value & 0xFF));  Serial3.write((uint8_t)((value >> 8) & 0xFF));}
void writeUInt32LE(uint32_t value) {  Serial3.write((uint8_t)(value & 0xFF));  Serial3.write((uint8_t)((value >> 8) & 0xFF));  Serial3.write((uint8_t)((value >> 16) & 0xFF));  Serial3.write((uint8_t)((value >> 24) & 0xFF));}
void sendHeaderAndCmd(uint8_t cmd) {  Serial3.write(UART_HEADER);  Serial3.write(cmd);}
void sendNavAck() {  sendHeaderAndCmd(RSP_NAV_ACK);  Serial.println("TX: NAV ACK");}
void sendTotalSteps(uint32_t steps) {  sendHeaderAndCmd(RSP_TOTAL_STEPS);  writeUInt32LE(steps);  Serial.print("TX: TOTAL STEPS = ");  Serial.println(steps);}
void sendDesiredBearing(uint16_t bearing100) {  sendHeaderAndCmd(RSP_DESIRED_BEARING);  writeUInt16LE(bearing100);  Serial.print("TX: DESIRED BEARING x100 = ");  Serial.println(bearing100);}
void sendObstacleScanRequest() {  sendHeaderAndCmd(RSP_OBS_SCAN_REQUEST);  Serial.println("TX: OBSTACLE SCAN REQUEST");}
void stopMotors() {  Serial.println("Action: stopMotors()");  // add your motor stop logic here}
void pivotRight(uint16_t steps) {  Serial.print("Action: pivotRight(");  Serial.print(steps);  Serial.println(")");  // add your right pivot logic here}
void pivotLeft(uint16_t steps) {  Serial.print("Action: pivotLeft(");  Serial.print(steps);  Serial.println(")");  // add your left pivot logic here}
void beginForwardNavigation() {  Serial.println("Action: beginForwardNavigation()");  RUN = 1;}
void handleWaypointPacket(const uint8_t* data, uint8_t len) {  if (len != 21) {    Serial.print("Waypoint length mismatch: ");    Serial.println(len);    return;  }
  pathTotalDistance = readUInt32LE(&data[0]);  combinedStepsDesired = readUInt32LE(&data[4]);  oppositeDistance = readUInt32LE(&data[8]);  bearingAlignment = data[12];  reflectorBearing100 = readUInt16LE(&data[13]);  sensorPrime = data[15];  waypointDesiredBearing100 = readUInt16LE(&data[16]);  waypointActualBearing100 = readUInt16LE(&data[18]);  trackDir = data[20];
  desiredBearing100 = waypointDesiredBearing100;  actualBearing100 = waypointActualBearing100;
  Serial.println("RX: WAYPOINT DATA received");  Serial.print("pathTotalDistance = "); Serial.println(pathTotalDistance);  Serial.print("combinedStepsDesired = "); Serial.println(combinedStepsDesired);  Serial.print("oppositeDistance = "); Serial.println(oppositeDistance);  Serial.print("bearingAlignment = "); Serial.println(bearingAlignment);  Serial.print("reflectorBearing100 = "); Serial.println(reflectorBearing100);  Serial.print("sensorPrime = "); Serial.println(sensorPrime);  Serial.print("desiredBearing100 = "); Serial.println(desiredBearing100);  Serial.print("actualBearing100 = "); Serial.println(actualBearing100);  Serial.print("trackDir = "); Serial.println(trackDir);
  sendNavAck();  beginForwardNavigation();}
void handlePacket(uint8_t cmd, const uint8_t* data, uint8_t len) {  switch (cmd) {    case CMD_STOP_MOTORS:      Serial.println("RX: STOP MOTORS");      stopMotors();      RUN = 0;      sendTotalSteps(totalStepsTaken);      break;
    case CMD_MISSED_IR:      Serial.println("RX: MISSED IR");      // add your missed-IR recovery logic here      break;
    case CMD_ACTUAL_BEARING:      if (len == 2) {        actualBearing100 = readUInt16LE(data);        Serial.print("RX: ACTUAL BEARING x100 = ");        Serial.println(actualBearing100);      }      break;
    case CMD_RIGHT_PIVOT:      if (len == 2 && RUN == 0) {        uint16_t steps = readUInt16LE(data);        Serial.print("RX: RIGHT PIVOT steps = ");        Serial.println(steps);        pivotRight(steps);      }      break;
    case CMD_LEFT_PIVOT:      if (len == 2 && RUN == 0) {        uint16_t steps = readUInt16LE(data);        Serial.print("RX: LEFT PIVOT steps = ");        Serial.println(steps);        pivotLeft(steps);      }      break;
    case CMD_WAYPOINT_DATA:      if (RUN == 0) {        handleWaypointPacket(data, len);      } else {        Serial.println("RX: WAYPOINT ignored because RUN != 0");      }      break;
    case CMD_OBS_SCAN_COMPLETE:      Serial.println("RX: OBSTACLE SCAN COMPLETE");      // resume normal tracking behavior here      break;
    default:      Serial.print("RX: Unknown command 0x");      Serial.println(cmd, HEX);      break;  }}
uint8_t getExpectedLength(uint8_t cmd) {  switch (cmd) {    case CMD_STOP_MOTORS:       return 0;    case CMD_MISSED_IR:         return 0;    case CMD_ACTUAL_BEARING:    return 2;    case CMD_RIGHT_PIVOT:       return 2;    case CMD_LEFT_PIVOT:        return 2;    case CMD_WAYPOINT_DATA:     return 21;    case CMD_OBS_SCAN_COMPLETE: return 0;    default:                     return 0xFF;  }}
void processIncomingByte(uint8_t b) {  switch (state) {    case WAIT_HEADER:      if (b == UART_HEADER) {        state = WAIT_CMD;      }      break;
    case WAIT_CMD:      currentCmd = b;      expectedLength = getExpectedLength(currentCmd);
      if (expectedLength == 0xFF) {        state = WAIT_HEADER;      } else if (expectedLength == 0) {        handlePacket(currentCmd, nullptr, 0);        state = WAIT_HEADER;      } else {        payloadIndex = 0;        state = WAIT_PAYLOAD;      }      break;
    case WAIT_PAYLOAD:      if (payloadIndex < sizeof(payload)) {        payload[payloadIndex++] = b;      }
      if (payloadIndex >= expectedLength) {        handlePacket(currentCmd, payload, expectedLength);        state = WAIT_HEADER;      }      break;  }}
void readIncomingUart() {  while (Serial3.available() > 0) {    uint8_t b = (uint8_t)Serial3.read();    processIncomingByte(b);  }}
void setup() {  Serial.begin(115200);  Serial3.begin(115200);  Serial.println("Arduino UART sketch v2 ready");}
void loop() {  readIncomingUart();
  if (RUN == 0) {    // setup / pivot / waypoint receive phase  } else if (RUN == 1) {    // forward navigation phase    // put your step counting / PWM correction / bearing logic here
    // TEST ARDU TX FUNCTIONS
    

delay(100);

  }
  

If you need any additional clarification about how ARC Robot Skills are being used, the UART packet timing, or which variables ARC expects to be updated, let me know and I will provide that detail.

Thanks again, Jack



Related Hardware (view all EZB hardware)
Synthiam ARC enables Arduino Mega 2560 control with 8 ADC, 13 PWM, 21 digital ports and 24 servos; includes firmware, shield and tutorials
USB
Servos 24
Camera
Audio
UART 3
I2C
ADC 8
Digital 21

Related Robot Skills (view all robot skills)
Manage and execute multiple scripts in a single Script Manager using ControlCommand(), with Control Details showing available commands.
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

ARC Pro

Upgrade to ARC Pro

Your robot can be more than a simple automated machine with the power of ARC Pro!

Author Avatar
PRO
Canada
Twitter YouTube GitHub
#9  

Absolutely - below is a consistent ARC JavaScript set for:

  • UART_v1 = always running packet sender/receiver
  • LEG_v1 = builds waypoint data and stores it in globals
  • ROVER_CMD_v1 = orchestration / top-level coordinator

This version is aligned to your updated packet format:

  • Removed CMD_MISSED_IR
  • Removed CMD_OBS_SCAN_COMPLETE
  • Waypoint packet is now 18 bytes
  • Arduino keeps moving during scan requests
  • ARC handles the scan profile and decides the next action

1) UART_v1 - always-running send/receive loop

Add this as a JavaScript in the ARC Scripting skill
Start it once from ROVER_CMD_v1, and let it loop forever.

var UART_INDEX = 0;
var UART_HEADER = 0xA5;

// ARC -> Arduino
var CMD_STOP_MOTORS   = 0x01;
var CMD_ACTUAL_BEARING = 0x03;
var CMD_RIGHT_PIVOT   = 0x04;
var CMD_LEFT_PIVOT    = 0x05;
var CMD_WAYPOINT_DATA = 0x06;

// Arduino -> ARC
var RSP_NAV_ACK         = 0x10;
var RSP_TOTAL_STEPS     = 0x11;
var RSP_DESIRED_BEARING = 0x12;
var RSP_OBS_SCAN_REQUEST = 0x13;

// ----------------------
// Safe helpers
// ----------------------
function safeInt(v, def) {
  var n = parseInt(v);
  if (isNaN(n)) return def;
  return n;
}

function safeFloat(v, def) {
  var n = parseFloat(v);
  if (isNaN(n)) return def;
  return n;
}

function u16ToBytes(value) {
  value = value & 0xFFFF;
  return [
    value & 0xFF,
    (value >> 8) & 0xFF
  ];
}

function u32ToBytes(value) {
  value = value >>> 0;
  return [
    value & 0xFF,
    (value >> 8) & 0xFF,
    (value >> 16) & 0xFF,
    (value >> 24) & 0xFF
  ];
}

function sendPacket(cmd, payload) {
  var packet = [UART_HEADER, cmd].concat(payload);
  UART.hardwareUartWrite(UART_INDEX, packet);
}

function readAvailableBytes() {
  var available = UART.hardwareUartAvailable(UART_INDEX);
  if (available <= 0) return [];
  return UART.hardwareUartRead(UART_INDEX, available);
}

// ----------------------
// TX commands
// ----------------------
function sendStopMotors() {
  sendPacket(CMD_STOP_MOTORS, []);
  print("TX: STOP MOTORS");
}

function sendActualBearing() {
  var bearing = safeFloat(getVar("$actualBearing", -1), -1);
  if (bearing < 0) return;

  var bearing100 = Math.round(bearing * 100.0);
  sendPacket(CMD_ACTUAL_BEARING, u16ToBytes(bearing100));
  print("TX: ACTUAL BEARING = " + bearing.toFixed(2) + " (" + bearing100 + ")");
}

function sendRightPivot() {
  var steps = safeInt(getVar("$rightPivotSteps", 0), 0);
  sendPacket(CMD_RIGHT_PIVOT, u16ToBytes(steps));
  print("TX: RIGHT PIVOT STEPS = " + steps);
}

function sendLeftPivot() {
  var steps = safeInt(getVar("$leftPivotSteps", 0), 0);
  sendPacket(CMD_LEFT_PIVOT, u16ToBytes(steps));
  print("TX: LEFT PIVOT STEPS = " + steps);
}

function sendWaypointData() {
  // Expected globals filled by LEG_v1
  var pathTotalDistance = safeInt(getVar("$pathTotalDistance", 0), 0);
  var combinedStepsDesired = safeInt(getVar("$combinedStepsDesired", 0), 0);
  var oppositeDistance = safeInt(getVar("$oppositeDistance", 0), 0);
  var bearingAlignment = safeInt(getVar("$bearingAlignment", 0), 0);
  var desiredBearing100 = safeInt(getVar("$desiredBearing100", 9000), 9000);
  var actualBearing100 = safeInt(getVar("$actualBearing100", 9000), 9000);
  var trackDir = safeInt(getVar("$trackDir", 0), 0);

  var payload = [];
  payload = payload.concat(u32ToBytes(pathTotalDistance));
  payload = payload.concat(u32ToBytes(combinedStepsDesired));
  payload = payload.concat(u32ToBytes(oppositeDistance));
  payload.push(bearingAlignment & 0xFF);
  payload = payload.concat(u16ToBytes(desiredBearing100));
  payload = payload.concat(u16ToBytes(actualBearing100));
  payload.push(trackDir & 0xFF);

  sendPacket(CMD_WAYPOINT_DATA, payload);

  print("TX: WAYPOINT DATA");
  print("  pathTotalDistance=" + pathTotalDistance);
  print("  combinedStepsDesired=" + combinedStepsDesired);
  print("  oppositeDistance=" + oppositeDistance);
  print("  bearingAlignment=" + bearingAlignment);
  print("  desiredBearing100=" + desiredBearing100);
  print("  actualBearing100=" + actualBearing100);
  print("  trackDir=" + trackDir);
}

// ----------------------
// RX packet parser
// ----------------------
var rxState = 0; // 0=wait header, 1=wait cmd, 2=payload
var rxCmd = 0;
var rxExpected = 0;
var rxPayload = [];
var rxPayloadIndex = 0;

function expectedLengthForCmd(cmd) {
  switch (cmd) {
    case RSP_NAV_ACK: return 0;
    case RSP_TOTAL_STEPS: return 4;
    case RSP_DESIRED_BEARING: return 2;
    case RSP_OBS_SCAN_REQUEST: return 0;
    default: return -1;
  }
}

function handleRxPacket(cmd, payload) {
  if (cmd == RSP_NAV_ACK) {
    print("RX: NAV ACK");
    setVar("$run", 1);
    return;
  }

  if (cmd == RSP_TOTAL_STEPS) {
    var steps = (payload[0]) |
                (payload[1] << 8) |
                (payload[2] << 16) |
                (payload[3] << 24);
    steps = steps >>> 0;
    print("RX: TOTAL STEPS = " + steps);
    setVar("$totalStepsTaken", steps);
    return;
  }

  if (cmd == RSP_DESIRED_BEARING) {
    var bearing100 = payload[0] | (payload[1] << 8);
    var bearing = bearing100 / 100.0;
    print("RX: DESIRED BEARING = " + bearing.toFixed(2));
    setVar("$desiredBearing100FromArduino", bearing100);
    return;
  }

  if (cmd == RSP_OBS_SCAN_REQUEST) {
    print("RX: OBSTACLE SCAN REQUEST");
    setVar("$obsScan", 1);
    setVar("$scanActive", 1);
    return;
  }

  print("RX: UNKNOWN CMD = " + cmd);
}

function processIncomingBytes() {
  while (UART.hardwareUartAvailable(UART_INDEX) > 0) {
    var b = UART.hardwareUartRead(UART_INDEX, 1)[0];

    if (rxState == 0) {
      if (b == UART_HEADER) rxState = 1;
      continue;
    }

    if (rxState == 1) {
      rxCmd = b;
      rxExpected = expectedLengthForCmd(rxCmd);

      if (rxExpected < 0) {
        rxState = 0;
        continue;
      }

      if (rxExpected == 0) {
        handleRxPacket(rxCmd, []);
        rxState = 0;
        continue;
      }

      rxPayload = [];
      rxPayloadIndex = 0;
      rxState = 2;
      continue;
    }

    if (rxState == 2) {
      rxPayload.push(b);
      rxPayloadIndex++;

      if (rxPayloadIndex >= rxExpected) {
        handleRxPacket(rxCmd, rxPayload);
        rxState = 0;
      }
    }
  }
}

// ----------------------
// Main loop
// ----------------------
print("UART_v1 started");

while (true) {
  // TX triggers
  if (getVar("$sendStopNow", 0) == 1) {
    sendStopMotors();
    setVar("$sendStopNow", 0);
  }

  if (getVar("$sendActualBearingNow", 0) == 1) {
    sendActualBearing();
    setVar("$sendActualBearingNow", 0);
  }

  if (getVar("$sendRightPivotNow", 0) == 1) {
    sendRightPivot();
    setVar("$sendRightPivotNow", 0);
  }

  if (getVar("$sendLeftPivotNow", 0) == 1) {
    sendLeftPivot();
    setVar("$sendLeftPivotNow", 0);
  }

  if (getVar("$sendWaypointNow", 0) == 1) {
    sendWaypointData();
    setVar("$sendWaypointNow", 0);
  }

  // RX processing
  processIncomingBytes();

  sleep(20);
}

2) LEG_v1 - builds the waypoint globals

This script is where you compute the path values and store them in ARC globals for UART_v1 to send.

print("LEG_v1 started");

// Example values - replace with your real leg calculations
// These should be computed from your route logic.
setVar("$pathTotalDistance", 120000);
setVar("$combinedStepsDesired", 52500);
setVar("$oppositeDistance", 24000);

// 0 = Direct Alignment (DA), 1 = Offset Alignment (OA)
setVar("$bearingAlignment", 1);

// These are kept in ARC now, not sent in the waypoint packet.
setVar("$desiredBearing100", 9000);
setVar("$actualBearing100", 9000);

// 0 = Forward Direction, 1 = Reverse Direction
setVar("$trackDir", 0);

// Reset runtime flags for the new leg
setVar("$run", 0);
setVar("$obsScan", 0);
setVar("$scanActive", 0);
setVar("$totalStepsTaken", 0);

// Tell UART_v1 to send the waypoint packet
setVar("$sendWaypointNow", 1);

print("LEG_v1 loaded waypoint globals");

You’ll likely replace the example numeric values with your real leg math, but this establishes the structure.


3) ROVER_CMD_v1 - orchestration script

This is the top-level coordinator. It starts the other scripts in the right order and watches for scan events.

print("ROVER_CMD_v1 started");

// Initial state
setVar("$run", 0);
setVar("$obsScan", 0);
setVar("$scanActive", 0);
setVar("$legComplete", 0);

// Start UART listener first
controlCommand("UART_v1", "ScriptStart");

// Load leg data and request waypoint transmit
controlCommand("LEG_v1", "ScriptStartWait");

// Wait for Arduino NAV ACK to set $run = 1
while (getVar("$run", 0) == 0) {
  sleep(50);
}

print("Navigation running");

// Main orchestration loop
while (true) {
  // If Arduino requested a scan, run your scan profile
  if (getVar("$obsScan", 0) == 1) {
    print("Scan request received - running scan profile");

    // Clear the flag so we only handle it once
    setVar("$obsScan", 0);

    // Run your scan logic script here
    // Replace "IR_GEN_v1" with your actual scan skill/script if needed
    controlCommand("IR_GEN_v1", "ScriptStart");

    // After scan profile finishes, ARC decides next action:
    // - send stop if obstacle found
    // - or keep going with updated bearing
  }

  // Check for completion
  if (safeInt(getVar("$legComplete", 0), 0) == 1) {
    print("Leg complete");
    break;
  }

  sleep(50);
}

4) How this flow should work

Startup sequence

  1. ROVER_CMD_v1 starts UART_v1
  2. ROVER_CMD_v1 runs LEG_v1
  3. LEG_v1 sets globals and flags $sendWaypointNow = 1
  4. UART_v1 sends CMD_WAYPOINT_DATA
  5. Arduino replies with NAV ACK
  6. UART_v1 sets $run = 1
  7. IR_TRACKER_v1 continues sending CMD_ACTUAL_BEARING

Scan sequence

  1. Arduino sends RSP_OBS_SCAN_REQUEST
  2. UART_v1 sets $obsScan = 1
  3. ROVER_CMD_v1 notices it
  4. ARC runs the scan profile
  5. ARC decides:
    • continue
    • stop
    • compute a new leg
    • send a new waypoint

5) One important ARC note

In ARC, if you want these scripts to run as background loops, start them with:

  • ScriptStart for long-running loops like UART_v1
  • ScriptStartWait only for scripts that should finish before the next step

Also, robot skills can be configured from the kebab menu on the top right of the skill window if you need to set ports, names, or other options.


6) What I’d recommend next

If you want, I can now give you:

  1. a clean IR_TRACKER_v1 JavaScript that updates $actualBearing
  2. a clean IR_GEN_v1 scan script pattern
  3. a more realistic LEG_v1 template for computing:
    • pathTotalDistance
    • combinedStepsDesired
    • oppositeDistance
    • bearingAlignment
    • trackDir

If you’d like, I can also do a final pass on the Arduino sketch and make sure it matches this ARC script exactly, including the 18-byte waypoint layout.


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.
#10  

@Athena. Please do a final pass on the Arduino sketch and make sure it matches this ARC script exactly, including the 18-byte waypoint layout. Thanks. Jack

Author Avatar
PRO
Canada
Twitter YouTube GitHub
#11  

Absolutely - here’s a final cleaned Arduino sketch that matches the ARC JavaScript flow and the 18-byte waypoint packet layout exactly.

What this version matches

  • ARC Arduino

    • 0x01 Stop Motors
    • 0x03 Actual Bearing
    • 0x04 Right Pivot
    • 0x05 Left Pivot
    • 0x06 Waypoint Data
  • Arduino ARC

    • 0x10 Navigation Ack
    • 0x11 Total Steps Taken
    • 0x12 Desired Bearing
    • 0x13 Obstacle Scan Request

Waypoint packet layout - 18 bytes

0  Header              0xA5
1  Command             0x06

2-5   Path Total Distance      uint32
6-9   Combined Steps Desired   uint32
10-13 Opposite Distance        uint32
14    Bearing Alignment        uint8   (0 = DA, 1 = OA)
15-16 Desired Bearing x100     uint16
17    Track Direction          uint8   (0 = FD, 1 = RD)

Final Arduino Sketch

#include <AccelStepper.h>
#include <MultiStepper.h>

// =====================================================
// PACKETS
// =====================================================
static const uint8_t UART_HEADER = 0xA5;

// ARC -> Arduino
static const uint8_t CMD_STOP_MOTORS    = 0x01;
static const uint8_t CMD_ACTUAL_BEARING = 0x03;
static const uint8_t CMD_RIGHT_PIVOT    = 0x04;
static const uint8_t CMD_LEFT_PIVOT     = 0x05;
static const uint8_t CMD_WAYPOINT_DATA  = 0x06;

// Arduino -> ARC
static const uint8_t RSP_NAV_ACK         = 0x10;
static const uint8_t RSP_TOTAL_STEPS     = 0x11;
static const uint8_t RSP_DESIRED_BEARING = 0x12;
static const uint8_t RSP_OBS_SCAN_REQUEST = 0x13;

// =====================================================
// STEPPERS
// =====================================================
// DRV8825 CNC shield mapping you confirmed:
AccelStepper LeftFrontWheel(AccelStepper::DRIVER, 3, 6);
AccelStepper LeftBackWheel(AccelStepper::DRIVER, 2, 5);
AccelStepper RightFrontWheel(AccelStepper::DRIVER, 12, 13);
AccelStepper RightBackWheel(AccelStepper::DRIVER, 4, 7);

MultiStepper multiStepper;

// =====================================================
// PARSER
// =====================================================
enum ParserState { WAIT_HEADER, WAIT_CMD, WAIT_PAYLOAD };
ParserState state = WAIT_HEADER;

uint8_t currentCmd = 0;
uint8_t payload[32];
uint8_t payloadIndex = 0;
uint8_t expectedLength = 0;

// =====================================================
// NAV STATE
// =====================================================
int RUN = 0; // 0 = pre-run, 1 = forward navigation

uint32_t totalStepsTaken = 0;
uint32_t commandStepsTaken = 0;

uint32_t combinedStepsDesired = 0;
uint32_t pathTotalDistance = 0;
uint32_t oppositeDistance = 0;

uint8_t bearingAlignment = 0; // 0 = DA, 1 = OA
uint8_t trackDir = 0;         // 0 = FD, 1 = RD

uint16_t waypointDesiredBearing100 = 9000;
uint16_t waypointActualBearing100 = 0;

uint16_t actualBearing100 = 0;
uint16_t desiredBearing100 = 9000;
uint16_t lastSentDesiredBearing100 = 0xFFFF;

int16_t bearingDiffTarget = 0;
int16_t bearingDiffRamped = 0;

bool obstacleScanRequested = false;
bool scanModeActive = false;
uint32_t lastObstacleScanStep = 0;

uint16_t currentSpeed = 0;
uint16_t targetSpeed = 900;
uint16_t rampIncrement = 10;
int16_t correctionScale = 10;

// 24 inches * 437.5 steps/inch
const uint32_t SCAN_INTERVAL_STEPS = (uint32_t)(24.0 * 437.5);

// =====================================================
// LITTLE-ENDIAN HELPERS
// =====================================================
uint16_t readUInt16LE(const uint8_t* data) {
  return (uint16_t)data[0] | ((uint16_t)data[1] << 8);
}

uint32_t readUInt32LE(const uint8_t* data) {
  return (uint32_t)data[0] |
         ((uint32_t)data[1] << 8) |
         ((uint32_t)data[2] << 16) |
         ((uint32_t)data[3] << 24);
}

void writeUInt16LE(uint16_t value) {
  Serial3.write((uint8_t)(value & 0xFF));
  Serial3.write((uint8_t)((value >> 8) & 0xFF));
}

void writeUInt32LE(uint32_t value) {
  Serial3.write((uint8_t)(value & 0xFF));
  Serial3.write((uint8_t)((value >> 8) & 0xFF));
  Serial3.write((uint8_t)((value >> 16) & 0xFF));
  Serial3.write((uint8_t)((value >> 24) & 0xFF));
}

void sendHeaderAndCmd(uint8_t cmd) {
  Serial3.write(UART_HEADER);
  Serial3.write(cmd);
}

void sendNavAck() {
  sendHeaderAndCmd(RSP_NAV_ACK);
  Serial.println("TX:NAV ACK");
}

void sendTotalSteps(uint32_t steps) {
  sendHeaderAndCmd(RSP_TOTAL_STEPS);
  writeUInt32LE(steps);
  Serial.print("TX:TOTAL STEPS = ");
  Serial.println(steps);
}

void sendDesiredBearing(uint16_t bearing100) {
  sendHeaderAndCmd(RSP_DESIRED_BEARING);
  writeUInt16LE(bearing100);
  Serial.print("TX:DESIRED BEARING x100 = ");
  Serial.println(bearing100);
}

void sendObstacleScanRequest() {
  sendHeaderAndCmd(RSP_OBS_SCAN_REQUEST);
  Serial.println("TX:OBSTACLE SCAN REQUEST");
}

// =====================================================
// MOTOR CONTROL
// =====================================================
void stopMotors() {
  LeftFrontWheel.setSpeed(0);
  LeftBackWheel.setSpeed(0);
  RightFrontWheel.setSpeed(0);
  RightBackWheel.setSpeed(0);

  Serial.println("Action: stopMotors()");
}

void pivotRight(uint16_t steps) {
  Serial.print("Action: pivotRight(");
  Serial.print(steps);
  Serial.println(")");

  long positions[4];
  positions[0] = LeftFrontWheel.currentPosition() + steps;
  positions[1] = LeftBackWheel.currentPosition() + steps;
  positions[2] = RightFrontWheel.currentPosition() - steps;
  positions[3] = RightBackWheel.currentPosition() - steps;

  multiStepper.moveTo(positions);
  while (multiStepper.run()) {
    // blocking pivot
  }

  totalStepsTaken += steps;
  stopMotors();
  sendTotalSteps(totalStepsTaken);
}

void pivotLeft(uint16_t steps) {
  Serial.print("Action: pivotLeft(");
  Serial.print(steps);
  Serial.println(")");

  long positions[4];
  positions[0] = LeftFrontWheel.currentPosition() - steps;
  positions[1] = LeftBackWheel.currentPosition() - steps;
  positions[2] = RightFrontWheel.currentPosition() + steps;
  positions[3] = RightBackWheel.currentPosition() + steps;

  multiStepper.moveTo(positions);
  while (multiStepper.run()) {
    // blocking pivot
  }

  totalStepsTaken += steps;
  stopMotors();
  sendTotalSteps(totalStepsTaken);
}

void applyMotorSpeeds(int leftSpeed, int rightSpeed) {
  if (leftSpeed < 0) leftSpeed = 0;
  if (rightSpeed < 0) rightSpeed = 0;

  LeftFrontWheel.setSpeed(leftSpeed);
  LeftBackWheel.setSpeed(leftSpeed);
  RightFrontWheel.setSpeed(rightSpeed);
  RightBackWheel.setSpeed(rightSpeed);
}

void beginForwardNavigation() {
  Serial.println("Action: beginForwardNavigation()");
  RUN = 1;
  currentSpeed = 0;
  bearingDiffRamped = 0;
  bearingDiffTarget = 0;
  lastObstacleScanStep = 0;
  obstacleScanRequested = false;
  scanModeActive = false;
  totalStepsTaken = 0;
  commandStepsTaken = 0;
  lastSentDesiredBearing100 = 0xFFFF;
}

// =====================================================
// BEARING COMPUTATION
// =====================================================
void computeDesiredBearing() {
  if (bearingAlignment == 0) {
    desiredBearing100 = 9000;
    return;
  }

  uint32_t pathDistanceRemaining = 1;

  if (trackDir == 0) {
    if (pathTotalDistance > totalStepsTaken) {
      pathDistanceRemaining = pathTotalDistance - totalStepsTaken;
    }
  } else {
    if (pathTotalDistance > (combinedStepsDesired - totalStepsTaken)) {
      pathDistanceRemaining = pathTotalDistance - (combinedStepsDesired - totalStepsTaken);
    }
  }

  if (pathDistanceRemaining == 0) {
    pathDistanceRemaining = 1;
  }

  float tangent = (float)oppositeDistance / (float)pathDistanceRemaining;
  float deg = atan(tangent) * 180.0 / PI;

  desiredBearing100 = (uint16_t)((deg + 90.0) * 100.0);

  if (abs((int32_t)desiredBearing100 - (int32_t)lastSentDesiredBearing100) >= 100) {
    sendDesiredBearing(desiredBearing100);
    lastSentDesiredBearing100 = desiredBearing100;
  }
}

void updateBearingDiff() {
  bearingDiffTarget = (int16_t)actualBearing100 - (int16_t)desiredBearing100;

  if (bearingDiffRamped < bearingDiffTarget) {
    bearingDiffRamped += 10;
    if (bearingDiffRamped > bearingDiffTarget) {
      bearingDiffRamped = bearingDiffTarget;
    }
  } else if (bearingDiffRamped > bearingDiffTarget) {
    bearingDiffRamped -= 10;
    if (bearingDiffRamped < bearingDiffTarget) {
      bearingDiffRamped = bearingDiffTarget;
    }
  }
}

void updateMotion() {
  if (currentSpeed < targetSpeed) {
    currentSpeed += rampIncrement;
    if (currentSpeed > targetSpeed) {
      currentSpeed = targetSpeed;
    }
  }

  computeDesiredBearing();

  if (scanModeActive) {
    bearingDiffRamped = 0;
  } else {
    updateBearingDiff();
  }

  int correction = bearingDiffRamped / correctionScale;
  int leftSpeed = (int)currentSpeed - correction;
  int rightSpeed = (int)currentSpeed + correction;

  applyMotorSpeeds(leftSpeed, rightSpeed);

  LeftFrontWheel.runSpeed();
  LeftBackWheel.runSpeed();
  RightFrontWheel.runSpeed();
  RightBackWheel.runSpeed();

  commandStepsTaken =
    abs(LeftFrontWheel.currentPosition()) +
    abs(LeftBackWheel.currentPosition()) +
    abs(RightFrontWheel.currentPosition()) +
    abs(RightBackWheel.currentPosition());

  totalStepsTaken = commandStepsTaken / 4UL;

  if (!obstacleScanRequested &&
      totalStepsTaken > 0 &&
      (totalStepsTaken - lastObstacleScanStep) >= SCAN_INTERVAL_STEPS) {
    obstacleScanRequested = true;
    scanModeActive = true;
    sendObstacleScanRequest();
  }

  if (totalStepsTaken >= combinedStepsDesired) {
    stopMotors();
    sendTotalSteps(totalStepsTaken);
    RUN = 0;
  }
}

// =====================================================
// WAYPOINT PACKET
// 18 bytes total payload
// =====================================================
void handleWaypointPacket(const uint8_t* data, uint8_t len) {
  if (len != 18) {
    Serial.print("Waypoint length mismatch: ");
    Serial.println(len);
    return;
  }

  pathTotalDistance = readUInt32LE(&data[0]);
  combinedStepsDesired = readUInt32LE(&data[4]);
  oppositeDistance = readUInt32LE(&data[8]);
  bearingAlignment = data[12];
  waypointDesiredBearing100 = readUInt16LE(&data[13]);
  trackDir = data[17];

  desiredBearing100 = waypointDesiredBearing100;
  actualBearing100 = waypointDesiredBearing100; // ARC will update actual as needed

  Serial.println("RX:WAYPOINT DATA received");
  Serial.print("pathTotalDistance = ");
  Serial.println(pathTotalDistance);
  Serial.print("combinedStepsDesired = ");
  Serial.println(combinedStepsDesired);
  Serial.print("oppositeDistance = ");
  Serial.println(oppositeDistance);
  Serial.print("bearingAlignment = ");
  Serial.println(bearingAlignment);
  Serial.print("desiredBearing100 = ");
  Serial.println(desiredBearing100);
  Serial.print("trackDir = ");
  Serial.println(trackDir);

  sendNavAck();
  beginForwardNavigation();
}

// =====================================================
// PACKET HANDLING
// =====================================================
uint8_t getExpectedLength(uint8_t cmd) {
  switch (cmd) {
    case CMD_STOP_MOTORS:    return 0;
    case CMD_ACTUAL_BEARING: return 2;
    case CMD_RIGHT_PIVOT:    return 2;
    case CMD_LEFT_PIVOT:     return 2;
    case CMD_WAYPOINT_DATA:  return 18;
    default:                 return 0xFF;
  }
}

void handlePacket(uint8_t cmd, const uint8_t* data, uint8_t len) {
  switch (cmd) {
    case CMD_STOP_MOTORS:
      Serial.println("RX:STOP MOTORS");
      stopMotors();
      RUN = 0;
      scanModeActive = false;
      obstacleScanRequested = false;
      sendTotalSteps(totalStepsTaken);
      break;

    case CMD_ACTUAL_BEARING:
      if (len == 2) {
        actualBearing100 = readUInt16LE(data);
        Serial.print("RX:ACTUAL BEARING x100 = ");
        Serial.println(actualBearing100);

        // Update immediately when ARC sends actual bearing
        bearingDiffTarget = (int16_t)actualBearing100 - (int16_t)desiredBearing100;

        // Resume steering if we were in scan mode
        scanModeActive = false;
      }
      break;

    case CMD_RIGHT_PIVOT:
      if (len == 2 && RUN == 0) {
        pivotRight(readUInt16LE(data));
      }
      break;

    case CMD_LEFT_PIVOT:
      if (len == 2 && RUN == 0) {
        pivotLeft(readUInt16LE(data));
      }
      break;

    case CMD_WAYPOINT_DATA:
      if (RUN == 0) {
        handleWaypointPacket(data, len);
      }
      break;

    default:
      Serial.print("RX:Unknown cmd 0x");
      Serial.println(cmd, HEX);
      break;
  }
}

void processIncomingByte(uint8_t b) {
  switch (state) {
    case WAIT_HEADER:
      if (b == UART_HEADER) {
        state = WAIT_CMD;
      }
      break;

    case WAIT_CMD:
      currentCmd = b;
      expectedLength = getExpectedLength(currentCmd);

      if (expectedLength == 0xFF) {
        state = WAIT_HEADER;
      } else if (expectedLength == 0) {
        handlePacket(currentCmd, nullptr, 0);
        state = WAIT_HEADER;
      } else {
        payloadIndex = 0;
        state = WAIT_PAYLOAD;
      }
      break;

    case WAIT_PAYLOAD:
      payload[payloadIndex++] = b;
      if (payloadIndex >= expectedLength) {
        handlePacket(currentCmd, payload, expectedLength);
        state = WAIT_HEADER;
      }
      break;
  }
}

void readIncomingUart() {
  while (Serial3.available() > 0) {
    uint8_t b = (uint8_t)Serial3.read();
    processIncomingByte(b);
  }
}

// =====================================================
// SETUP / LOOP
// =====================================================
void setup() {
  Serial.begin(115200);
  Serial3.begin(115200);

  LeftFrontWheel.setMaxSpeed(2000);
  LeftBackWheel.setMaxSpeed(2000);
  RightFrontWheel.setMaxSpeed(2000);
  RightBackWheel.setMaxSpeed(2000);

  LeftFrontWheel.setAcceleration(800);
  LeftBackWheel.setAcceleration(800);
  RightFrontWheel.setAcceleration(800);
  RightBackWheel.setAcceleration(800);

  multiStepper.addStepper(LeftFrontWheel);
  multiStepper.addStepper(LeftBackWheel);
  multiStepper.addStepper(RightFrontWheel);
  multiStepper.addStepper(RightBackWheel);

  Serial.println("Arduino UART sketch ready");
}

void loop() {
  readIncomingUart();

  if (RUN == 0) {
    // pre-run mode
  } else if (RUN == 1) {
    updateMotion();
  }
}

Two important notes

1) actualBearing100 = waypointDesiredBearing100;

I left that line in as an initial default so the robot has a valid bearing value immediately after waypoint load.

If you prefer, I can change it to:

actualBearing100 = waypointActualBearing100;

But since your updated ARC flow says ARC will keep and send actual bearing independently, the current code is fine as a startup baseline.

2) scanModeActive

This is the key piece for your request:

  • the rover keeps moving
  • bearingDiffRamped is held at 0
  • speed remains active
  • when ARC sends a new CMD_ACTUAL_BEARING, steering resumes

What I’d recommend next

If you want, I can now provide:

  1. a final clean IR_TRACKER_v1 script
  2. a final clean LEG_v1 script that fills the 18-byte packet values
  3. a final ROVER_CMD_v1 orchestration script that ties everything together in a startup sequence

If you’d like, I can write all three in one pass so your ARC project is complete.


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.
#12  

@Athena.

Two important notes

1) actualBearing100 = waypointDesiredBearing100;

This works perfectly, as actualBearing will always = desiredBearing to start RUN.

2) scanModeActive

This is the key piece for your request:

  • the rover keeps moving
  • bearingDiffRamped is held at 0
  • speed remains active
  • when ARC sends a new CMD_ACTUAL_BEARING, steering resumes
  • This works just as I wish.

What I’d recommend next

  1. Please provide:
  2. a final clean LEG_v1 script that fills the 18-byte packet values
  3. a final ROVER_CMD_v1 orchestration script that ties everything together in a startup sequence

Thanks. Jack

Author Avatar
PRO
Canada
Twitter YouTube GitHub
#13  

Absolutely - below are the two scripts you asked for:

  • a clean LEG_v1 that fills the 18-byte waypoint packet values
  • a final ROVER_CMD_v1 orchestration script that starts everything in a consistent startup sequence

I’ve kept these aligned with your current design:

  • Waypoint packet is 18 bytes
  • No Reflector Bearing
  • No Sensor Prime
  • actualBearing100 starts equal to desiredBearing100
  • UART_v1 runs as the continuous packet handler
  • ROVER_CMD_v1 coordinates startup and scan handling

1) LEG_v1 - builds the 18-byte waypoint globals

This script computes or loads the waypoint values and stores them in globals for UART_v1 to send.

print("LEG_v1 started");

// ----------------------------------------------------
// Replace these example values with your real leg math
// ----------------------------------------------------
var pathTotalDistance = 120000;     // uint32
var combinedStepsDesired = 52500;   // uint32
var oppositeDistance = 24000;       // uint32

// 0 = Direct Alignment (DA), 1 = Offset Alignment (OA)
var bearingAlignment = 1;

// Desired bearing starts equal to actual bearing at RUN start
var desiredBearing100 = 9000;      // 90.00 degrees x100
var actualBearing100 = 9000;       // matches desired at startup

// 0 = Forward Direction, 1 = Reverse Direction
var trackDir = 0;

// ----------------------------------------------------
// Store globals for UART_v1
// ----------------------------------------------------
setVar("$pathTotalDistance", pathTotalDistance);
setVar("$combinedStepsDesired", combinedStepsDesired);
setVar("$oppositeDistance", oppositeDistance);
setVar("$bearingAlignment", bearingAlignment);
setVar("$desiredBearing100", desiredBearing100);
setVar("$actualBearing100", actualBearing100);
setVar("$trackDir", trackDir);

// ----------------------------------------------------
// Reset runtime state
// ----------------------------------------------------
setVar("$run", 0);
setVar("$obsScan", 0);
setVar("$scanActive", 0);
setVar("$legComplete", 0);
setVar("$totalStepsTaken", 0);

// ----------------------------------------------------
// Tell UART_v1 to send the waypoint packet
// ----------------------------------------------------
setVar("$sendWaypointNow", 1);

print("LEG_v1 loaded waypoint globals and requested waypoint transmit");

2) ROVER_CMD_v1 - orchestration / startup coordinator

This is the top-level script that:

  1. starts UART_v1
  2. runs LEG_v1
  3. waits for NAV ACK by watching $run
  4. monitors for scan requests
  5. triggers scan profile handling
  6. watches for leg completion
print("ROVER_CMD_v1 started");

// ----------------------------------------------------
// Initial state
// ----------------------------------------------------
setVar("$run", 0);
setVar("$obsScan", 0);
setVar("$scanActive", 0);
setVar("$legComplete", 0);
setVar("$sendStopNow", 0);
setVar("$sendWaypointNow", 0);
setVar("$sendActualBearingNow", 0);
setVar("$sendRightPivotNow", 0);
setVar("$sendLeftPivotNow", 0);

// ----------------------------------------------------
// Start UART listener first
// ----------------------------------------------------
controlCommand("UART_v1", "ScriptStart");

// ----------------------------------------------------
// Load leg data and request waypoint transmit
// This can be repeated per leg if you build a multi-leg route
// ----------------------------------------------------
controlCommand("LEG_v1", "ScriptStartWait");

// ----------------------------------------------------
// Wait for Arduino NAV ACK -> UART_v1 will set $run = 1
// ----------------------------------------------------
while (getVar("$run", 0) == 0) {
  sleep(50);
}

print("Navigation running");

// ----------------------------------------------------
// Main orchestration loop
// ----------------------------------------------------
while (true) {
  // --------------------------------------------------
  // Scan request from Arduino
  // UART_v1 sets $obsScan = 1 when RSP_OBS_SCAN_REQUEST arrives
  // --------------------------------------------------
  if (getVar("$obsScan", 0) == 1) {
    print("Scan request received - running scan profile");

    // Clear the flag so it only triggers once
    setVar("$obsScan", 0);
    setVar("$scanActive", 1);

    // Run your scan profile here
    // Replace this with the actual script/control name you use for scanning
    // If your scan routine is a script skill, this is a good example:
    controlCommand("IR_GEN_v1", "ScriptStartWait");

    // After the scan profile completes, the scan logic determines next action.
    // Typical next step:
    // - if obstacle found: send stop
    // - if clear: continue and/or send new actual bearing
    //
    // Example if your scan script sets $sendStopNow:
    if (getVar("$sendStopNow", 0) == 1) {
      print("Obstacle confirmed - stopping rover");
      // UART_v1 will handle the send on its next loop
    }

    setVar("$scanActive", 0);
  }

  // --------------------------------------------------
  // Leg complete
  // --------------------------------------------------
  if (getVar("$legComplete", 0) == 1) {
    print("Leg complete");
    break;
  }

  sleep(50);
}

print("ROVER_CMD_v1 finished");

3) Recommended flow with these scripts

This is the intended startup sequence:

  1. ROVER_CMD_v1 starts
  2. UART_v1 starts and loops forever
  3. LEG_v1 loads the waypoint data
  4. LEG_v1 sets $sendWaypointNow = 1
  5. UART_v1 sends CMD_WAYPOINT_DATA
  6. Arduino replies NAV ACK
  7. UART_v1 sets $run = 1
  8. IR_TRACKER_v1 keeps sending CMD_ACTUAL_BEARING
  9. Arduino updates desired bearing and steering
  10. Arduino sends RSP_OBS_SCAN_REQUEST
  11. UART_v1 sets $obsScan = 1
  12. ROVER_CMD_v1 runs the scan profile
  13. ARC decides whether to stop, continue, or recompute a new leg

4) One small recommendation

For better organization, I’d suggest keeping your real leg calculation logic inside LEG_v1, and keeping ROVER_CMD_v1 as a pure coordinator. That makes it easier to expand later when you add:

  • new waypoints
  • obstacle detours
  • return-to-reflector logic
  • multiple route legs

5) If you want the next step

I can now write either of these for you:

  1. IR_TRACKER_v1

    • reads IR sensor data
    • computes actualBearing100
    • triggers UART_v1 to send CMD_ACTUAL_BEARING
  2. IR_GEN_v1

    • scan profile handler
    • raises obstacle flags
    • decides whether to stop or keep moving
  3. A more realistic LEG_v1 template

    • computes pathTotalDistance
    • computes combinedStepsDesired
    • computes oppositeDistance
    • sets alignment and direction from route data

If you want, I can do all three next so your ARC project is fully stitched together.


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.
#14  

@Athena.

This is the intended startup sequence:

  1. ROVER_CMD_v1 starts
  2. UART_v1 starts and loops forever
  3. LEG_v1 loads the waypoint data
  4. LEG_v1 sets $sendWaypointNow = 1
  5. UART_v1 sends CMD_WAYPOINT_DATA
  6. Arduino replies NAV ACK
  7. UART_v1 sets $run = 1
  8. IR_TRACKER_v1 keeps sending CMD_ACTUAL_BEARING
  9. Arduino updates desired bearing and steering
  10. Arduino sends RSP_OBS_SCAN_REQUEST
  11. UART_v1 sets $obsScan = 1
  12. ROVER_CMD_v1 runs the scan profile
  13. ARC decides whether to stop, continue, or recompute a new leg
//UART_v1

var UART_INDEX = 0;
var UART_HEADER = 0xA5;

// ARC -> Arduino
var CMD_STOP_MOTORS   = 0x01;
var CMD_ACTUAL_BEARING = 0x03;
var CMD_RIGHT_PIVOT   = 0x04;
var CMD_LEFT_PIVOT    = 0x05;
var CMD_WAYPOINT_DATA = 0x06;

// Arduino -> ARC
var RSP_NAV_ACK         = 0x10;
var RSP_TOTAL_STEPS     = 0x11;
var RSP_DESIRED_BEARING = 0x12;
var RSP_OBS_SCAN_REQUEST = 0x13;

// ----------------------
// Safe helpers
// ----------------------
function safeInt(v, def) {
  var n = parseInt(v);
  if (isNaN(n)) return def;
  return n;
}

function safeFloat(v, def) {
  var n = parseFloat(v);
  if (isNaN(n)) return def;
  return n;
}

function u16ToBytes(value) {
  value = value & 0xFFFF;
  return [
    value & 0xFF,
    (value >> 8) & 0xFF
  ];
}

function u32ToBytes(value) {
  value = value >>> 0;
  return [
    value & 0xFF,
    (value >> 8) & 0xFF,
    (value >> 16) & 0xFF,
    (value >> 24) & 0xFF
  ];
}

function sendPacket(cmd, payload) {
  var packet = [UART_HEADER, cmd].concat(payload);
  UART.hardwareUartWrite(UART_INDEX, packet);
}

function readAvailableBytes() {
  var available = UART.hardwareUartAvailable(UART_INDEX);
  if (available <= 0) return [];
  return UART.hardwareUartRead(UART_INDEX, available);
}

// ----------------------
// TX commands
// ----------------------
function sendStopMotors() {
  sendPacket(CMD_STOP_MOTORS, []);
  print("TX: STOP MOTORS");
}

function sendActualBearing() {
  var bearing = safeFloat(getVar("$actualBearing", -1), -1);
  if (bearing < 0) return;

  var bearing100 = Math.round(bearing * 100.0);
  sendPacket(CMD_ACTUAL_BEARING, u16ToBytes(bearing100));
  print("TX: ACTUAL BEARING = " + bearing.toFixed(2) + " (" + bearing100 + ")");
}

function sendRightPivot() {
  var steps = safeInt(getVar("$rightPivotSteps", 0), 0);
  sendPacket(CMD_RIGHT_PIVOT, u16ToBytes(steps));
  print("TX: RIGHT PIVOT STEPS = " + steps);
}

function sendLeftPivot() {
  var steps = safeInt(getVar("$leftPivotSteps", 0), 0);
  sendPacket(CMD_LEFT_PIVOT, u16ToBytes(steps));
  print("TX: LEFT PIVOT STEPS = " + steps);
}

function sendWaypointData() {
  // Expected globals filled by LEG_v1
  var pathTotalDistance = safeInt(getVar("$pathTotalDistance", 0), 0);
  var combinedStepsDesired = safeInt(getVar("$combinedStepsDesired", 0), 0);
  var oppositeDistance = safeInt(getVar("$oppositeDistance", 0), 0);
  var bearingAlignment = safeInt(getVar("$bearingAlignment", 0), 0);
  var desiredBearing100 = safeInt(getVar("$desiredBearing100", 9000), 9000);
  var actualBearing100 = safeInt(getVar("$actualBearing100", 9000), 9000);
  var trackDir = safeInt(getVar("$trackDir", 0), 0);

  var payload = [];
  payload = payload.concat(u32ToBytes(pathTotalDistance));
  payload = payload.concat(u32ToBytes(combinedStepsDesired));
  payload = payload.concat(u32ToBytes(oppositeDistance));
  payload.push(bearingAlignment & 0xFF);
  payload = payload.concat(u16ToBytes(desiredBearing100));
  payload = payload.concat(u16ToBytes(actualBearing100));
  payload.push(trackDir & 0xFF);

  sendPacket(CMD_WAYPOINT_DATA, payload);

  print("TX: WAYPOINT DATA");
  print("  pathTotalDistance=" + pathTotalDistance);
  print("  combinedStepsDesired=" + combinedStepsDesired);
  print("  oppositeDistance=" + oppositeDistance);
  print("  bearingAlignment=" + bearingAlignment);
  print("  desiredBearing100=" + desiredBearing100);
  print("  actualBearing100=" + actualBearing100);
  print("  trackDir=" + trackDir);
}

// ----------------------
// RX packet parser
// ----------------------
var rxState = 0; // 0=wait header, 1=wait cmd, 2=payload
var rxCmd = 0;
var rxExpected = 0;
var rxPayload = [];
var rxPayloadIndex = 0;

function expectedLengthForCmd(cmd) {
  switch (cmd) {
    case RSP_NAV_ACK: return 0;
    case RSP_TOTAL_STEPS: return 4;
    case RSP_DESIRED_BEARING: return 2;
    case RSP_OBS_SCAN_REQUEST: return 0;
    default: return -1;
  }
}

function handleRxPacket(cmd, payload) {
  if (cmd == RSP_NAV_ACK) {
    print("RX: NAV ACK");
    setVar("$run", 1);
    return;
  }

  if (cmd == RSP_TOTAL_STEPS) {
    var steps = (payload[0]) |
                (payload[1] << 8) |
                (payload[2] << 16) |
                (payload[3] << 24);
    steps = steps >>> 0;
    print("RX: TOTAL STEPS = " + steps);
    setVar("$totalStepsTaken", steps);
    return;
  }

  if (cmd == RSP_DESIRED_BEARING) {
    var bearing100 = payload[0] | (payload[1] << 8);
    var bearing = bearing100 / 100.0;
    print("RX: DESIRED BEARING = " + bearing.toFixed(2));
    setVar("$desiredBearing100FromArduino", bearing100);
    return;
  }

  if (cmd == RSP_OBS_SCAN_REQUEST) {
    print("RX: OBSTACLE SCAN REQUEST");
    setVar("$obsScan", 1);
    setVar("$scanActive", 1);
    return;
  }

  print("RX: UNKNOWN CMD = " + cmd);
}

function processIncomingBytes() {
  while (UART.hardwareUartAvailable(UART_INDEX) > 0) {
    var b = UART.hardwareUartRead(UART_INDEX, 1)[0];

    if (rxState == 0) {
      if (b == UART_HEADER) rxState = 1;
      continue;
    }

    if (rxState == 1) {
      rxCmd = b;
      rxExpected = expectedLengthForCmd(rxCmd);

      if (rxExpected < 0) {
        rxState = 0;
        continue;
      }

      if (rxExpected == 0) {
        handleRxPacket(rxCmd, []);
        rxState = 0;
        continue;
      }

      rxPayload = [];
      rxPayloadIndex = 0;
      rxState = 2;
      continue;
    }

    if (rxState == 2) {
      rxPayload.push(b);
      rxPayloadIndex++;

      if (rxPayloadIndex >= rxExpected) {
        handleRxPacket(rxCmd, rxPayload);
        rxState = 0;
      }
    }
  }
}

// ----------------------
// Main loop
// ----------------------
print("UART_v1 started");
print("$sendWaypointNow- preloop:  " + getVar("$sendWaypointNow"));
while (true) {
  // TX triggers
  if (getVar("$sendStopNow", 0) == 1) {
    sendStopMotors();
    setVar("$sendStopNow", 0);
  }

  if (getVar("$sendActualBearingNow", 0) == 1) {
    sendActualBearing();
    setVar("$sendActualBearingNow", 0);
  }

  if (getVar("$sendRightPivotNow", 0) == 1) {
    sendRightPivot();
    setVar("$sendRightPivotNow", 0);
  }

  if (getVar("$sendLeftPivotNow", 0) == 1) {
    sendLeftPivot();
    setVar("$sendLeftPivotNow", 0);
  }

  if (getVar("$sendWaypointNow", 0) == 1) {
  print("$sendWaypointNow- post:  " + getVar("$sendWaypointNow"));
  
    sendWaypointData();
    setVar("$sendWaypointNow", 0);
  }

  // RX processing
  processIncomingBytes();     // commented out and it sent

  sleep(200);  //20
   print("$sendWaypointNow- loop:  " + getVar("$sendWaypointNow"));
  
  
}

I added some print statements because "$sendWaypointNow" was not changing to '1' and sending. When I commented out function "processIncomingBytes()", it sent as expected. Can you look at this script and help me? Thanks. Jack

Author Avatar
PRO
Canada
Twitter YouTube GitHub
#15  

Yes - this is a packet parsing conflict, not a waypoint flag problem.

When you comment out processIncomingBytes(), the sendWaypointData() call works because the UART loop is no longer getting stuck inside a bad receive routine. The biggest red flags in the code you pasted are:

  • if (available >> 0; is broken/incomplete
  • if (rxExpected = rxExpected) is an assignment, not a comparison
  • the receive parser appears to be malformed and may be blocking the loop
  • UART.hardwareUartAvailable() was typed incorrectly in one place as UART.hardwareUartAvailable vs the ARC API you should be using
  • processIncomingBytes() likely never returns cleanly, so the loop can’t reach the transmit flag often enough

What’s happening

Your loop should do these two things every pass:

  1. Check transmit flags
  2. Parse any received bytes without blocking

If the RX parser is broken, it can starve the TX section or cause logic errors that prevent the packet from being sent.


Recommended fix

Use a non-blocking state machine for RX that only reads available bytes and returns immediately.

Below is a corrected UART_v1 structure that should behave properly.

Corrected UART_v1 JavaScript

var UART_INDEX = 0;
var UART_HEADER = 0xA5;

// ARC -> Arduino
var CMD_STOP_MOTORS = 0x01;
var CMD_ACTUAL_BEARING = 0x03;
var CMD_RIGHT_PIVOT = 0x04;
var CMD_LEFT_PIVOT = 0x05;
var CMD_WAYPOINT_DATA = 0x06;

// Arduino -> ARC
var RSP_NAV_ACK = 0x10;
var RSP_TOTAL_STEPS = 0x11;
var RSP_DESIRED_BEARING = 0x12;
var RSP_OBS_SCAN_REQUEST = 0x13;

function safeInt(v, def) {
  var n = parseInt(v);
  if (isNaN(n)) return def;
  return n;
}

function safeFloat(v, def) {
  var n = parseFloat(v);
  if (isNaN(n)) return def;
  return n;
}

function u16ToBytes(value) {
  value = value & 0xFFFF;
  return [value & 0xFF, (value >> 8) & 0xFF];
}

function u32ToBytes(value) {
  value = value >>> 0;
  return [
    value & 0xFF,
    (value >> 8) & 0xFF,
    (value >> 16) & 0xFF,
    (value >> 24) & 0xFF
  ];
}

function sendPacket(cmd, payload) {
  var packet = [UART_HEADER, cmd].concat(payload);
  UART.hardwareUartWrite(UART_INDEX, packet);
}

function sendStopMotors() {
  sendPacket(CMD_STOP_MOTORS, []);
  print("TX: STOP MOTORS");
}

function sendActualBearing() {
  var bearing = safeFloat(getVar("$actualBearing", -1), -1);
  if (bearing < 0) return;

  var bearing100 = Math.round(bearing * 100.0);
  sendPacket(CMD_ACTUAL_BEARING, u16ToBytes(bearing100));
  print("TX: ACTUAL BEARING = " + bearing.toFixed(2));
}

function sendRightPivot() {
  var steps = safeInt(getVar("$rightPivotSteps", 0), 0);
  sendPacket(CMD_RIGHT_PIVOT, u16ToBytes(steps));
  print("TX: RIGHT PIVOT = " + steps);
}

function sendLeftPivot() {
  var steps = safeInt(getVar("$leftPivotSteps", 0), 0);
  sendPacket(CMD_LEFT_PIVOT, u16ToBytes(steps));
  print("TX: LEFT PIVOT = " + steps);
}

function sendWaypointData() {
  var pathTotalDistance = safeInt(getVar("$pathTotalDistance", 0), 0);
  var combinedStepsDesired = safeInt(getVar("$combinedStepsDesired", 0), 0);
  var oppositeDistance = safeInt(getVar("$oppositeDistance", 0), 0);
  var bearingAlignment = safeInt(getVar("$bearingAlignment", 0), 0);
  var desiredBearing100 = safeInt(getVar("$desiredBearing100", 9000), 9000);
  var trackDir = safeInt(getVar("$trackDir", 0), 0);

  var payload = [];
  payload = payload.concat(u32ToBytes(pathTotalDistance));
  payload = payload.concat(u32ToBytes(combinedStepsDesired));
  payload = payload.concat(u32ToBytes(oppositeDistance));
  payload.push(bearingAlignment & 0xFF);
  payload = payload.concat(u16ToBytes(desiredBearing100));
  payload.push(trackDir & 0xFF);

  sendPacket(CMD_WAYPOINT_DATA, payload);
  print("TX: WAYPOINT DATA sent");
}

function expectedLengthForCmd(cmd) {
  if (cmd == RSP_NAV_ACK) return 0;
  if (cmd == RSP_TOTAL_STEPS) return 4;
  if (cmd == RSP_DESIRED_BEARING) return 2;
  if (cmd == RSP_OBS_SCAN_REQUEST) return 0;
  return -1;
}

var rxState = 0;
var rxCmd = 0;
var rxExpected = 0;
var rxPayload = [];
var rxIndex = 0;

function handleRxPacket(cmd, payload) {
  if (cmd == RSP_NAV_ACK) {
    print("RX: NAV ACK");
    setVar("$run", 1);
    return;
  }

  if (cmd == RSP_TOTAL_STEPS) {
    var steps = payload[0] |
      (payload[1] << 8) |
      (payload[2] << 16) |
      (payload[3] << 24);

    print("RX: TOTAL STEPS = " + steps);
    setVar("$totalStepsTaken", steps);
    return;
  }

  if (cmd == RSP_DESIRED_BEARING) {
    var bearing100 = payload[0] | (payload[1] << 8);
    print("RX: DESIRED BEARING = " + (bearing100 / 100.0).toFixed(2));
    setVar("$desiredBearing100", bearing100);
    return;
  }

  if (cmd == RSP_OBS_SCAN_REQUEST) {
    print("RX: OBSTACLE SCAN REQUEST");
    setVar("$obsScan", 1);
    return;
  }

  print("RX: UNKNOWN CMD = " + cmd);
}

function processIncomingBytes() {
  var available = UART.hardwareUartAvailable(UART_INDEX);

  while (available > 0) {
    var b = UART.hardwareUartRead(UART_INDEX, 1)[0];
    available--;

    if (rxState == 0) {
      if (b == UART_HEADER) {
        rxState = 1;
      }
      continue;
    }

    if (rxState == 1) {
      rxCmd = b;
      rxExpected = expectedLengthForCmd(rxCmd);

      if (rxExpected < 0) {
        rxState = 0;
        rxIndex = 0;
        rxPayload = [];
        continue;
      }

      if (rxExpected == 0) {
        handleRxPacket(rxCmd, []);
        rxState = 0;
        rxIndex = 0;
        rxPayload = [];
        continue;
      }

      rxPayload = [];
      rxIndex = 0;
      rxState = 2;
      continue;
    }

    if (rxState == 2) {
      rxPayload.push(b);
      rxIndex++;

      if (rxIndex >= rxExpected) {
        handleRxPacket(rxCmd, rxPayload);
        rxState = 0;
        rxIndex = 0;
        rxPayload = [];
      }
    }
  }
}

print("UART_v1 started");
print("$sendWaypointNow preloop: " + getVar("$sendWaypointNow", 0));

while (true) {
  if (getVar("$sendStopNow", 0) == 1) {
    sendStopMotors();
    setVar("$sendStopNow", 0);
  }

  if (getVar("$sendActualBearingNow", 0) == 1) {
    sendActualBearing();
    setVar("$sendActualBearingNow", 0);
  }

  if (getVar("$sendRightPivotNow", 0) == 1) {
    sendRightPivot();
    setVar("$sendRightPivotNow", 0);
  }

  if (getVar("$sendLeftPivotNow", 0) == 1) {
    sendLeftPivot();
    setVar("$sendLeftPivotNow", 0);
  }

  if (getVar("$sendWaypointNow", 0) == 1) {
    print("$sendWaypointNow post: " + getVar("$sendWaypointNow", 0));
    sendWaypointData();
    setVar("$sendWaypointNow", 0);
  }

  processIncomingBytes();

  sleep(20);
}

Why this version should fix it

1) RX no longer blocks TX

The parser only reads whatever is currently available and returns. That keeps the loop responsive.

2) No broken comparisons

This fixes the = vs == issue that would break packet completion.

3) TX trigger flags are checked every pass

So $sendWaypointNow = 1 will be seen and acted on.


Very likely issue in your current version

If your original processIncomingBytes() contains anything like:

  • waiting for bytes with a tight loop
  • incorrect packet-length logic
  • = used instead of ==
  • a state machine that never returns to idle

then it can appear as if $sendWaypointNow is not changing, when in reality the script is just stuck in the receive logic.


One more thing to check in ARC

Make sure:

  • UART_v1 is started only once
  • LEG_v1 is actually setting $sendWaypointNow in the same project
  • UART_INDEX points to the correct UART skill instance
  • the UART skill is configured for the right port and baud rate via the kebab menu

Best debugging test

Try this in UART_v1 temporarily:

while (true) {
  print("loop tick, sendWaypointNow=" + getVar("$sendWaypointNow", 0));

  if (getVar("$sendWaypointNow", 0) == 1) {
    sendWaypointData();
    setVar("$sendWaypointNow", 0);
  }

  sleep(500);
}

If that works, then the issue is definitely inside your RX parser, not the trigger variable.


If you want, I can also do a full merged cleanup of your UART_v1, LEG_v1, and ROVER_CMD_v1 so all three scripts are consistent with this exact parser and packet flow.


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.
#16  

@Athena.

The UART_v1 script still does not run.

UART_v1 Snippet:

function processIncomingBytes() {
  var available = UART.hardwareUartAvailable(UART_INDEX);

  while (available > 0) {
    var b = UART.hardwareUartRead(UART_INDEX, 1)[0];
    available--;

    if (rxState == 0) {
      if (b == UART_HEADER) {
        rxState = 1;
      }
      continue;
    }

    if (rxState == 1) {
      rxCmd = b;
      rxExpected = expectedLengthForCmd(rxCmd);

      if (rxExpected < 0) {
        rxState = 0;
        rxIndex = 0;
        rxPayload = [];
        continue;
      }

      if (rxExpected == 0) {
        handleRxPacket(rxCmd, []);
        rxState = 0;
        rxIndex = 0;
        rxPayload = [];
        continue;
      }

//-----------------

      rxPayload = [];
      rxIndex = 0;
      rxState = 2;
      continue;
    }

//-----------------

    if (rxState == 2) {
      rxPayload.push(b);
      rxIndex++;

      if (rxIndex >= rxExpected) {
        handleRxPacket(rxCmd, rxPayload);
        rxState = 0;
        rxIndex = 0;
        rxPayload = [];
      }
    }
  }
}

print("UART_v1 started");
print("$sendWaypointNow preloop: " + getVar("$sendWaypointNow", 0));

I marked the section that concerns me with comment //. Is this correct? Thanks, Jack

 
📚 This thread has been preserved as a helpful resource!

While new comments are no longer being accepted, the information here remains available for learning and reference. Check the most recent comment for additional details.