Skip to content

Instantly share code, notes, and snippets.

@BenJuan26
Last active November 4, 2022 06:47
Show Gist options
  • Save BenJuan26/c13758fd301f488291bb24037b43c146 to your computer and use it in GitHub Desktop.
Save BenJuan26/c13758fd301f488291bb24037b43c146 to your computer and use it in GitHub Desktop.
An Arduino sketch for Elite Dangerous that receives data from the game and keeps its switches in sync with the game state.
#include <ArduinoJson.h>
#include <Key.h>
#include <Keypad.h>
// Allow 5 seconds of lag from the device, to the game, and back to the device.
#define BUTTON_SYNC_TIME 5000
// Each button press will last about 100ms.
#define BUTTON_HOLD_TIME 100
#define BUTTON_PRESSED LOW
#define BUTTON_RELEASED HIGH
// Masks for the flags I'm interested in.
#define FLAG_LANDING_GEAR 0x00000004
#define FLAG_HARDPOINTS 0x00000040
#define FLAG_SHIP_LIGHTS 0x00000100
#define FLAG_CARGO_SCOOP 0x00000200
#define FLAG_SILENT_RUNNING 0x00000400
#define FLAG_NIGHT_VISION 0x10000000
#define NO_FLAGS 65535
long currentFlags = NO_FLAGS;
// Setting up the button matrix.
const byte rows = 5;
const byte cols = 5;
char keys[rows][cols] = {
{'A', 'B', 'C', 'D', 'E'},
{'F', 'G', 'H', 'I', 'J'},
{'K', 'L', 'M', 'N', 'O'},
{'P', 'Q', 'R', 'S', 'T'},
{'U', 'V', 'W', 'X', 'Y'}
};
// The ASCII value of the first button in the matrix.
// This will allow any key code to be turned into a zero-based
// index for use with arrays.
const int keyOffset = keys[0][0];
byte rowPins[rows] = {14, 15, 16, 17, 18};
byte colPins[cols] = {19, 20, 21, 22, 23};
Keypad kpd = Keypad(makeKeymap(keys), rowPins, colPins, rows, cols);
struct Toggleswitch {
byte currState; // State of the physical switch.
byte lastState; // Last known state of the physical switch.
byte buttonState; // Whether the joystick button is pressed.
byte joyButton; // Which joystick button the switch belongs to.
unsigned long pressedTime; // The time after which to stop pressing the button.
long flag; // The flag assigned to this switch.
// Must have a default constructor because of the array.
Toggleswitch() {}
// Constructor supports an initial state.
Toggleswitch(byte state, byte button, long theFlag) {
currState = state;
lastState = state;
buttonState = BUTTON_RELEASED;
joyButton = button;
pressedTime = 0L;
flag = theFlag;
}
// Is the game in the same state as the switch?
bool isInSync() {
// Always treat as in sync if there's no flag info.
if (currentFlags == NO_FLAGS) {
return true;
}
bool buttonPressed = lastState == BUTTON_PRESSED;
bool flagSet = currentFlags & flag;
// Need to account for the rollover of the millis() value.
// In that case this value will be huge, which is fine, as it
// will just try to sync up a little early.
unsigned long timeSinceReleased = millis() - pressedTime;
// Always assume things are in sync for a while after the button is pressed
// to give the game time to catch up.
return timeSinceReleased < BUTTON_SYNC_TIME || buttonPressed == flagSet;
}
// This will be called periodically for the switch to take care of its own updates.
void update() {
// If the switch is flipped, or the game comes out of sync, trigger a button press.
// If the button is pressed and it's time to release it, release it.
if (currState != lastState || !isInSync()) {
lastState = currState;
buttonState = BUTTON_PRESSED;
Joystick.button(joyButton, true);
pressedTime = millis();
} else if (buttonState == BUTTON_PRESSED && millis() - pressedTime >= BUTTON_HOLD_TIME) {
buttonState = BUTTON_RELEASED;
Joystick.button(joyButton, false);
}
}
};
#define NUM_SWITCHES 6
Toggleswitch switches[NUM_SWITCHES];
void setup() {
Serial.begin(9600);
Joystick.X(512);
Joystick.Y(512);
Joystick.Z(512);
Joystick.Zrotate(512);
Joystick.sliderLeft(512);
Joystick.sliderRight(512);
Joystick.hat(-1);
// Get the initial state of the switches.
// First, assume they're all switched off.
byte initialStates[NUM_SWITCHES];
for (int i = 0; i < NUM_SWITCHES; i++) {
initialStates[i] = BUTTON_RELEASED;
}
// Check the state of each switch and update the initial states accordingly.
if (kpd.getKeys()) {
for (int i = 0; i < LIST_MAX; i++) {
int keyIndex = (int)kpd.key[i].kchar - keyOffset;
KeyState state = kpd.key[i].kstate;
if (state == PRESSED || state == HOLD) {
initialStates[keyIndex] = BUTTON_PRESSED;
}
}
}
// Create the Toggleswitch objects with the given initial states.
int flags[NUM_SWITCHES] = {FLAG_HARDPOINTS, FLAG_LANDING_GEAR, FLAG_CARGO_SCOOP,
FLAG_SHIP_LIGHTS, FLAG_NIGHT_VISION, FLAG_SILENT_RUNNING};
for (int i = 0; i < NUM_SWITCHES; i++) {
switches[i] = Toggleswitch(initialStates[i], i, flags[i]);
}
}
// Handle all Serial I/O.
void serialRx() {
if (!Serial.available()) {
return;
}
DynamicJsonBuffer jsonBuffer(512);
JsonObject &root = jsonBuffer.parseObject(Serial);
if (!root.success()) {
return;
}
long flags = root["Flags"];
currentFlags = flags;
}
// Get the states of the physical switches and write them to the Toggleswitch objects.
void updateKeys() {
if (!kpd.getKeys()) {
return;
}
for (int i = 0; i < LIST_MAX; i++) {
if (kpd.key[i].kchar == NO_KEY) {
continue;
}
// Turn the key code (e.g. 'F') into a zero-based array index.
int keyIndex = (int)kpd.key[i].kchar - keyOffset;
KeyState state = kpd.key[i].kstate;
if (state == PRESSED) {
switches[keyIndex].currState = BUTTON_PRESSED;
} else if (state == RELEASED) {
switches[keyIndex].currState = BUTTON_RELEASED;
}
}
}
// Main loop.
void loop() {
serialRx();
updateKeys();
for (int i = 0; i < NUM_SWITCHES; i++) {
switches[i].update();
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment