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