Tuesday, May 26, 2026

Flex Radio RIT/XIT Control via Encoder Knobs

This is a variant of the Poor Man's Stream Deck project project we did to allow for adjustment of the RIT and XIT values on a Flex Radio.   Turns out not having to point the mouse at the somewhat small RIT and XIT buttons in SSDR makes using these controls a lot more efficient.  The CYD Poor Man's Stream Deck works great, but lacks tactical feedback.   We fixed that by using EC11 Encoders.

-----
The project is cheap and easy to duplicate; nothing special.  We used separate encoders for RIT and XIT.   Pressing in the button resets that encoder value to 0.   If the rig is idle for 2 hours the values also get set to 0.  The encoders are connected to an ESP32 like so:
-----
Here is a short video demo of the rig in action after being put in a 3D printed project box.
-----
And below is the simple software for the ESP32.  Upload it to your uC via the Arduino IDE.

/*
  ESP32 + FLEX Radio RIT/XIT Controller
  -------------------------------------
 * Arduino IDE Board Setting: ESP32 Dev Module, 240MHz (WiFi/BT), 921600

 * MAY2026
 * Project details at: whiskeytangohotel.com 

  Encoder 1 = RIT
  Encoder 2 = XIT

  ClockWise  = +10
  CCWise = -10
  Pushbutton = Reset to 0

  Sends commands directly to FLEX over LAN.

  Serial Monitor displays:
  - RIT value
  - XIT value
  - ESP32 IP Address
*/

#include <WiFi.h>

const char* ssid     = "UR-SSID";
const char* password = "UR-WIFI-PASSWORD";

// =====================================================
//   CUSTOMIZE THE FLEX RADIO SETTINGS
// =====================================================

const char* flexIP = "192.168.1.2";
const int   flexPort = 4992;

WiFiClient flexClient;

unsigned long sequenceNumber = 0;

// =====================================================
//       AUTO ZERO SETTING AFTER XX MINUTES W/O USE
// =====================================================

const unsigned long AUTO_ZERO_MINUTES = 240; 

unsigned long lastRitActivity = 0;
unsigned long lastXitActivity = 0;

// =====================================================
//  ENCODER PIN DEFINES
// =====================================================

// Encoder 1 = RIT
#define ENC1_A   32
#define ENC1_B   33
#define ENC1_SW  25

// Encoder 2 = XIT
#define ENC2_A   26
#define ENC2_B   27
#define ENC2_SW  14

int ritValue = 0;
int xitValue = 0;

// Previous states
int lastState1A;
int lastState2A;

// Button debounce timers
unsigned long lastButton1 = 10;
unsigned long lastButton2 = 10;

// =====================================================
//  PRINT STATUS TO SERIAL MONITOR
// =====================================================

void printValues() {

  Serial.print("IP: ");
  Serial.print(WiFi.localIP());

  Serial.print("    RIT: ");
  Serial.print(ritValue);

  Serial.print("    XIT: ");
  Serial.println(xitValue);
}

// =====================================================
//    SEND FLEX COMMAND
// =====================================================

void sendFlexCommand(String cmd) {

  if (!flexClient.connected()) return;

  String fullCommand =
    "C" + String(sequenceNumber++) +
    "|" + cmd + "\n";

  flexClient.print(fullCommand);
}

// =====================================================
//     UPDATE FLEX RADIO
// =====================================================

void updateRIT() {

  sendFlexCommand(
    "slice s 0 rit_on=1 rit_freq=" + String(ritValue)
  );
}

void updateXIT() {

  sendFlexCommand(
    "slice s 0 xit_on=1 xit_freq=" + String(xitValue)
  );
}

// =====================================================
//   CONNECT TO FLEX
// =====================================================

void connectToFlex() {

  Serial.println();
  Serial.println("Connecting to FLEX...");

  if (flexClient.connect(flexIP, flexPort)) {

    Serial.println("Connected to FLEX.");

    sendFlexCommand("client program ESP32_RIT");
    sendFlexCommand("sub slice all");

  } else {

    Serial.println("FLEX connection FAILED.");
  }
}

void setup() {

  Serial.begin(115200);

  delay(1000);

  Serial.println();
  Serial.println("ESP32 FLEX Controller Starting...");
  Serial.println();

  pinMode(ENC1_A, INPUT_PULLUP);
  pinMode(ENC1_B, INPUT_PULLUP);
  pinMode(ENC1_SW, INPUT_PULLUP);

  pinMode(ENC2_A, INPUT_PULLUP);
  pinMode(ENC2_B, INPUT_PULLUP);
  pinMode(ENC2_SW, INPUT_PULLUP);

  lastState1A = digitalRead(ENC1_A);
  lastState2A = digitalRead(ENC2_A);

  // WiFi
  Serial.print("Connecting to WiFi");

  WiFi.begin(ssid, password);

  while (WiFi.status() != WL_CONNECTED) {

    delay(500);
    Serial.print(".");
  }

  Serial.println();
  Serial.println("WiFi Connected.");

  Serial.print("ESP32 IP Address: ");
  Serial.println(WiFi.localIP());

  connectToFlex();

  printValues();

  // ============================
  // INIT AUTO ZERO TIMERS
  // ============================
  lastRitActivity = millis();
  lastXitActivity = millis();
}

void loop() {   // LOOP FOREVER AND EVER

  if (!flexClient.connected()) {
    connectToFlex();
    delay(2000);
  }

  unsigned long now = millis();
  unsigned long timeout = AUTO_ZERO_MINUTES * 60UL * 1000UL;

  // ==========================================
  // ENCODER 1 = RIT
  // ==========================================

  int currentState1A = digitalRead(ENC1_A);

  if (lastState1A == HIGH && currentState1A == LOW) {

    if (digitalRead(ENC1_B) == currentState1A) {
      ritValue += 10;
    } else {
      ritValue -= 10;
    }

    updateRIT();
    printValues();

    lastRitActivity = now;   // <<< ADDED
  }

  lastState1A = currentState1A;

  // PUSHBUTTON 1
  if (digitalRead(ENC1_SW) == LOW) {

    if (millis() - lastButton1 > 250) {

      ritValue = 0;

      sendFlexCommand("slice s 0 rit_on=0 rit_freq=0");

      printValues();

      lastButton1 = millis();

      lastRitActivity = now;   // <<< ADDED
    }
  }

  // ==========================================
  // ENCODER 2 = XIT
  // ==========================================

  int currentState2A = digitalRead(ENC2_A);

  if (lastState2A == HIGH && currentState2A == LOW) {

    if (digitalRead(ENC2_B) == currentState2A) {
      xitValue += 10;
    } else {
      xitValue -= 10;
    }

    updateXIT();
    printValues();

    lastXitActivity = now;   // <<< ADDED
  }

  lastState2A = currentState2A;

  // PUSHBUTTON 2
  if (digitalRead(ENC2_SW) == LOW) {

    if (millis() - lastButton2 > 250) {

      xitValue = 0;

      sendFlexCommand("slice s 0 xit_on=0 xit_freq=0");

      printValues();

      lastButton2 = millis();

      lastXitActivity = now;   // <<< ADDED
    }
  }

  // ==========================================
  // AUTO ZERO LOGIC
  // ==========================================

  if ((now - lastRitActivity > timeout) &&
      (now - lastXitActivity > timeout)) {

    if (ritValue != 0 || xitValue != 0) {

      ritValue = 0;
      xitValue = 0;

      sendFlexCommand("slice s 0 rit_on=0 rit_freq=0");
      sendFlexCommand("slice s 0 xit_on=0 xit_freq=0");

      Serial.println("AUTO-ZERO triggered");

      printValues();

      lastRitActivity = now;
      lastXitActivity = now;
    }
  }
}
-----