Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Reactive LEDs #1104

Merged
merged 2 commits into from
Aug 14, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -215,6 +215,7 @@ src/addons/display.cpp
src/addons/neopicoleds.cpp
src/addons/playernum.cpp
src/addons/playerleds.cpp
src/addons/reactiveleds.cpp
src/addons/rotaryencoder.cpp
src/addons/reverse.cpp
src/addons/drv8833_rumble.cpp
Expand Down
56 changes: 56 additions & 0 deletions headers/addons/reactiveleds.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
#ifndef REACTIVELEDS_H_
#define REACTIVELEDS_H_

#include "gpaddon.h"

#ifndef REACTIVE_LED_ENABLED
#define REACTIVE_LED_ENABLED 0
#endif

#ifndef REACTIVE_LED_COUNT
#define REACTIVE_LED_COUNT 8
#endif

#ifndef REACTIVE_LED_DELAY
#define REACTIVE_LED_DELAY 1
#endif

#ifndef REACTIVE_LED_MAX_BRIGHTNESS
#define REACTIVE_LED_MAX_BRIGHTNESS 255
#endif

#ifndef REACTIVE_LED_FADE_INC
#define REACTIVE_LED_FADE_INC 1
#endif

// Reactive LED Module
#define ReactiveLEDName "ReactiveLED"

// Reactive LED
class ReactiveLEDAddon : public GPAddon
{
public:
virtual bool available();
virtual void setup();
virtual void preprocess() {}
virtual void process();
virtual std::string name() { return ReactiveLEDName; }
private:
struct ReactiveLEDPinState {
uint16_t pinNumber = -1;
ReactiveLEDMode modeDown = ReactiveLEDMode::REACTIVE_LED_STATIC_ON;
ReactiveLEDMode modeUp = ReactiveLEDMode::REACTIVE_LED_STATIC_OFF;
GpioAction action = GpioAction::NONE;
uint8_t value = 0;
bool currState = false;
bool prevState = false;
uint32_t lastUpdate;
uint32_t currUpdate;
};

ReactiveLEDPinState ledPins[REACTIVE_LED_COUNT];

void setLEDByMode(ReactiveLEDPinState &ledState, bool pressed);
};

#endif
15 changes: 15 additions & 0 deletions proto/config.proto
Original file line number Diff line number Diff line change
Expand Up @@ -750,6 +750,20 @@ message DRV8833RumbleOptions
optional float dutyMax = 7;
}

message ReactiveLEDInfo
{
optional int32 pin = 1;
optional GpioAction action = 2;
optional ReactiveLEDMode modeDown = 3;
optional ReactiveLEDMode modeUp = 4;
}

message ReactiveLEDOptions
{
optional bool enabled = 1;
repeated ReactiveLEDInfo leds = 2 [(nanopb).max_count = 8];
}

message AddonOptions
{
optional BootselButtonOptions bootselButtonOptions = 1;
Expand Down Expand Up @@ -778,6 +792,7 @@ message AddonOptions
optional RotaryOptions rotaryOptions = 24;
optional PCF8575Options pcf8575Options = 25;
optional DRV8833RumbleOptions drv8833RumbleOptions = 26;
optional ReactiveLEDOptions reactiveLEDOptions = 27;
}

message MigrationHistory
Expand Down
10 changes: 10 additions & 0 deletions proto/enums.proto
Original file line number Diff line number Diff line change
Expand Up @@ -400,3 +400,13 @@ enum RotaryEncoderPinMode
ENCODER_MODE_DPAD_X = 7;
ENCODER_MODE_DPAD_Y = 8;
};

enum ReactiveLEDMode
{
option (nanopb_enumopt).long_names = false;

REACTIVE_LED_STATIC_OFF = 0;
REACTIVE_LED_STATIC_ON = 1;
REACTIVE_LED_FADE_IN = 2;
REACTIVE_LED_FADE_OUT = 3;
};
114 changes: 114 additions & 0 deletions src/addons/reactiveleds.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,114 @@
#include "hardware/pwm.h"
#include "addons/reactiveleds.h"
#include "storagemanager.h"
#include "usbdriver.h"
#include "helper.h"
#include "config.pb.h"

bool ReactiveLEDAddon::available() {
bool pinsEnabled = false;
const ReactiveLEDOptions& options = Storage::getInstance().getAddonOptions().reactiveLEDOptions;
for (uint8_t led = 0; led < sizeof(REACTIVE_LED_COUNT); led++) {
if (isValidPin(options.leds[led].pin)) {
pinsEnabled = true;
break;
}
}
return options.enabled && pinsEnabled;
}

void ReactiveLEDAddon::setup() {
const ReactiveLEDOptions& options = Storage::getInstance().getAddonOptions().reactiveLEDOptions;

for (uint8_t led = 0; led < sizeof(REACTIVE_LED_COUNT); led++) {
ReactiveLEDInfo ledInfo = options.leds[led];

ledPins[led].pinNumber = ledInfo.pin;
ledPins[led].action = ledInfo.action;
ledPins[led].modeDown = ledInfo.modeDown;
ledPins[led].modeUp = ledInfo.modeUp;

if (isValidPin(ledPins[led].pinNumber)) {
gpio_init(ledPins[led].pinNumber);
gpio_set_dir(ledPins[led].pinNumber, GPIO_OUT);
gpio_set_function(ledPins[led].pinNumber, GPIO_FUNC_PWM);

pwm_set_wrap(pwm_gpio_to_slice_num(ledPins[led].pinNumber), REACTIVE_LED_MAX_BRIGHTNESS);
pwm_set_enabled(pwm_gpio_to_slice_num(ledPins[led].pinNumber), true);

ledPins[led].lastUpdate = to_ms_since_boot(get_absolute_time());

setLEDByMode(ledPins[led], false);
}
}
}

void ReactiveLEDAddon::process() {
Gamepad * gamepad = Storage::getInstance().GetProcessedGamepad();

uint32_t currUpdate = to_ms_since_boot(get_absolute_time());

for (uint8_t led = 0; led < sizeof(REACTIVE_LED_COUNT); led++) {
if (isValidPin(ledPins[led].pinNumber) && ledPins[led].action != GpioAction::NONE) {
ledPins[led].currUpdate = currUpdate;
switch (ledPins[led].action) {
case BUTTON_PRESS_UP: setLEDByMode(ledPins[led], gamepad->pressedDpad(GAMEPAD_MASK_UP)); break;
case BUTTON_PRESS_DOWN: setLEDByMode(ledPins[led], gamepad->pressedDpad(GAMEPAD_MASK_DOWN)); break;
case BUTTON_PRESS_LEFT: setLEDByMode(ledPins[led], gamepad->pressedDpad(GAMEPAD_MASK_LEFT)); break;
case BUTTON_PRESS_RIGHT: setLEDByMode(ledPins[led], gamepad->pressedDpad(GAMEPAD_MASK_RIGHT)); break;
case BUTTON_PRESS_B1: setLEDByMode(ledPins[led], gamepad->pressedButton(GAMEPAD_MASK_B1)); break;
case BUTTON_PRESS_B2: setLEDByMode(ledPins[led], gamepad->pressedButton(GAMEPAD_MASK_B2)); break;
case BUTTON_PRESS_B3: setLEDByMode(ledPins[led], gamepad->pressedButton(GAMEPAD_MASK_B3)); break;
case BUTTON_PRESS_B4: setLEDByMode(ledPins[led], gamepad->pressedButton(GAMEPAD_MASK_B4)); break;
case BUTTON_PRESS_L1: setLEDByMode(ledPins[led], gamepad->pressedButton(GAMEPAD_MASK_L1)); break;
case BUTTON_PRESS_R1: setLEDByMode(ledPins[led], gamepad->pressedButton(GAMEPAD_MASK_R1)); break;
case BUTTON_PRESS_L2: setLEDByMode(ledPins[led], gamepad->pressedButton(GAMEPAD_MASK_L2)); break;
case BUTTON_PRESS_R2: setLEDByMode(ledPins[led], gamepad->pressedButton(GAMEPAD_MASK_R2)); break;
case BUTTON_PRESS_S1: setLEDByMode(ledPins[led], gamepad->pressedButton(GAMEPAD_MASK_S1)); break;
case BUTTON_PRESS_S2: setLEDByMode(ledPins[led], gamepad->pressedButton(GAMEPAD_MASK_S2)); break;
case BUTTON_PRESS_A1: setLEDByMode(ledPins[led], gamepad->pressedButton(GAMEPAD_MASK_A1)); break;
case BUTTON_PRESS_A2: setLEDByMode(ledPins[led], gamepad->pressedButton(GAMEPAD_MASK_A2)); break;
case BUTTON_PRESS_L3: setLEDByMode(ledPins[led], gamepad->pressedButton(GAMEPAD_MASK_L3)); break;
case BUTTON_PRESS_R3: setLEDByMode(ledPins[led], gamepad->pressedButton(GAMEPAD_MASK_R3)); break;
default: break;
}
}
}
}

void ReactiveLEDAddon::setLEDByMode(ReactiveLEDPinState &ledState, bool pressed) {
ledState.currState = pressed;

switch (pressed ? ledState.modeDown : ledState.modeUp) {
case ReactiveLEDMode::REACTIVE_LED_STATIC_OFF:
ledState.value = 0;
break;
case ReactiveLEDMode::REACTIVE_LED_STATIC_ON:
ledState.value = 255;
break;
case ReactiveLEDMode::REACTIVE_LED_FADE_IN:
if (ledState.currUpdate - ledState.lastUpdate >= REACTIVE_LED_DELAY) {
if (ledState.prevState != pressed) ledState.value = 0;
if (ledState.value < REACTIVE_LED_MAX_BRIGHTNESS) {
ledState.value+=REACTIVE_LED_FADE_INC;
}

ledState.lastUpdate = ledState.currUpdate;
}
break;
case ReactiveLEDMode::REACTIVE_LED_FADE_OUT:
if (ledState.currUpdate - ledState.lastUpdate >= REACTIVE_LED_DELAY) {
if (ledState.prevState != pressed) ledState.value = REACTIVE_LED_MAX_BRIGHTNESS;
if (ledState.value > 0) {
ledState.value-=REACTIVE_LED_FADE_INC;
}

ledState.lastUpdate = ledState.currUpdate;
}
break;
}

pwm_set_gpio_level(ledState.pinNumber, ledState.value);

ledState.prevState = pressed;
}
12 changes: 12 additions & 0 deletions src/config_utils.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@
#include "addons/neopicoleds.h"
#include "addons/playernum.h"
#include "addons/pleds.h"
#include "addons/reactiveleds.h"
#include "addons/reverse.h"
#include "addons/slider_socd.h"
#include "addons/spi_analog_ads1256.h"
Expand Down Expand Up @@ -724,6 +725,17 @@ void ConfigUtils::initUnsetPropertiesWithDefaults(Config& config)
INIT_UNSET_PROPERTY(config.addonOptions.rotaryOptions.encoderTwo, allowWrapAround, ENCODER_TWO_WRAP);
INIT_UNSET_PROPERTY(config.addonOptions.rotaryOptions.encoderTwo, multiplier, ENCODER_TWO_MULTIPLIER);

// addonOptions.reactiveLEDOptions
INIT_UNSET_PROPERTY(config.addonOptions.reactiveLEDOptions, enabled, !!REACTIVE_LED_ENABLED);
for (uint16_t led = 0; led < REACTIVE_LED_COUNT; led++) {
INIT_UNSET_PROPERTY(config.addonOptions.reactiveLEDOptions.leds[led], pin, -1);
INIT_UNSET_PROPERTY(config.addonOptions.reactiveLEDOptions.leds[led], action, GpioAction::NONE);
INIT_UNSET_PROPERTY(config.addonOptions.reactiveLEDOptions.leds[led], modeDown, REACTIVE_LED_STATIC_ON);
INIT_UNSET_PROPERTY(config.addonOptions.reactiveLEDOptions.leds[led], modeUp, REACTIVE_LED_STATIC_OFF);
}
// reminder that this must be set or else nanopb won't retain anything
config.addonOptions.reactiveLEDOptions.leds_count = REACTIVE_LED_COUNT;

// keyboardMapping
INIT_UNSET_PROPERTY(config.addonOptions.keyboardHostOptions, enabled, KEYBOARD_HOST_ENABLED);
INIT_UNSET_PROPERTY(config.addonOptions.keyboardHostOptions, deprecatedPinDplus, KEYBOARD_HOST_PIN_DPLUS);
Expand Down
42 changes: 42 additions & 0 deletions src/configs/webconfig.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1343,6 +1343,40 @@ std::string setExpansionPins()
return serialize_json(doc);
}

std::string getReactiveLEDs()
{
DynamicJsonDocument doc(LWIP_HTTPD_POST_MAX_PAYLOAD_LEN);
ReactiveLEDInfo* ledInfo = Storage::getInstance().getAddonOptions().reactiveLEDOptions.leds;

for (uint16_t led = 0; led < 8; led++) {
writeDoc(doc, "leds", led, "pin", ledInfo[led].pin);
writeDoc(doc, "leds", led, "action", ledInfo[led].action);
writeDoc(doc, "leds", led, "modeDown", ledInfo[led].modeDown);
writeDoc(doc, "leds", led, "modeUp", ledInfo[led].modeUp);
}

return serialize_json(doc);
}

std::string setReactiveLEDs()
{
DynamicJsonDocument doc = get_post_data();

ReactiveLEDInfo* ledInfo = Storage::getInstance().getAddonOptions().reactiveLEDOptions.leds;

for (uint16_t led = 0; led < 8; led++) {
ledInfo[led].pin = doc["leds"][led]["pin"];
ledInfo[led].action = doc["leds"][led]["action"];
ledInfo[led].modeDown = doc["leds"][led]["modeDown"];
ledInfo[led].modeUp = doc["leds"][led]["modeUp"];
}
Storage::getInstance().getAddonOptions().reactiveLEDOptions.leds_count = 8;

Storage::getInstance().save();

return serialize_json(doc);
}

std::string setAddonOptions()
{
DynamicJsonDocument doc = get_post_data();
Expand Down Expand Up @@ -1515,6 +1549,9 @@ std::string setAddonOptions()
PCF8575Options& pcf8575Options = Storage::getInstance().getAddonOptions().pcf8575Options;
docToValue(pcf8575Options.enabled, doc, "PCF8575AddonEnabled");

ReactiveLEDOptions& reactiveLEDOptions = Storage::getInstance().getAddonOptions().reactiveLEDOptions;
docToValue(reactiveLEDOptions.enabled, doc, "ReactiveLEDAddonEnabled");

DRV8833RumbleOptions& drv8833RumbleOptions = Storage::getInstance().getAddonOptions().drv8833RumbleOptions;
docToValue(drv8833RumbleOptions.enabled, doc, "DRV8833RumbleAddonEnabled");
docToPin(drv8833RumbleOptions.leftMotorPin, doc, "drv8833RumbleLeftMotorPin");
Expand Down Expand Up @@ -1944,6 +1981,9 @@ std::string getAddonOptions()
PCF8575Options& pcf8575Options = Storage::getInstance().getAddonOptions().pcf8575Options;
writeDoc(doc, "PCF8575AddonEnabled", pcf8575Options.enabled);

ReactiveLEDOptions& reactiveLEDOptions = Storage::getInstance().getAddonOptions().reactiveLEDOptions;
writeDoc(doc, "ReactiveLEDAddonEnabled", reactiveLEDOptions.enabled);

const DRV8833RumbleOptions& drv8833RumbleOptions = Storage::getInstance().getAddonOptions().drv8833RumbleOptions;
writeDoc(doc, "DRV8833RumbleAddonEnabled", drv8833RumbleOptions.enabled);
writeDoc(doc, "drv8833RumbleLeftMotorPin", cleanPin(drv8833RumbleOptions.leftMotorPin));
Expand Down Expand Up @@ -2213,6 +2253,8 @@ static const std::pair<const char*, HandlerFuncPtr> handlerFuncs[] =
{ "/api/getI2CPeripheralMap", getI2CPeripheralMap },
{ "/api/setExpansionPins", setExpansionPins },
{ "/api/getExpansionPins", getExpansionPins },
{ "/api/setReactiveLEDs", setReactiveLEDs },
{ "/api/getReactiveLEDs", getReactiveLEDs },
{ "/api/setKeyMappings", setKeyMappings },
{ "/api/setAddonsOptions", setAddonOptions },
{ "/api/setMacroAddonOptions", setMacroAddonOptions },
Expand Down
2 changes: 2 additions & 0 deletions src/gp2040aux.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
#include "addons/display.h"
#include "addons/pleds.h"
#include "addons/neopicoleds.h"
#include "addons/reactiveleds.h"
#include "addons/drv8833_rumble.h"

#include <iterator>
Expand All @@ -35,6 +36,7 @@ void GP2040Aux::setup() {
addons.LoadAddon(new BoardLedAddon(), CORE1_LOOP);
addons.LoadAddon(new BuzzerSpeakerAddon(), CORE1_LOOP);
addons.LoadAddon(new DRV8833RumbleAddon(), CORE1_LOOP);
addons.LoadAddon(new ReactiveLEDAddon(), CORE1_LOOP);

// Initialize our input driver's auxilliary functions
inputDriver = DriverManager::getInstance().getDriver();
Expand Down
16 changes: 16 additions & 0 deletions www/server/app.js
Original file line number Diff line number Diff line change
Expand Up @@ -509,6 +509,7 @@ app.get('/api/getAddonsOptions', (req, res) => {
RotaryAddonEnabled: 1,
PCF8575AddonEnabled: 1,
DRV8833RumbleAddonEnabled: 1,
ReactiveLEDAddonEnabled: 1,
usedPins: Object.values(picoController),
});
});
Expand Down Expand Up @@ -731,6 +732,21 @@ app.get('/api/getButtonLayoutDefs', (req, res) => {
});
});

app.get('/api/getReactiveLEDs', (req, res) => {
return res.send({
leds: [
{ pin: -1, action: -10, modeDown: 0, modeUp: 1 },
{ pin: -1, action: -10, modeDown: 1, modeUp: 0 },
{ pin: -1, action: -10, modeDown: 1, modeUp: 0 },
{ pin: -1, action: -10, modeDown: 1, modeUp: 0 },
{ pin: -1, action: -10, modeDown: 1, modeUp: 0 },
{ pin: -1, action: -10, modeDown: 1, modeUp: 0 },
{ pin: -1, action: -10, modeDown: 1, modeUp: 0 },
{ pin: -1, action: -10, modeDown: 1, modeUp: 0 },
],
});
});

app.get('/api/reboot', (req, res) => {
return res.send({});
});
Expand Down
Loading