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;
}
}
}
-----

