-
-
Notifications
You must be signed in to change notification settings - Fork 3.9k
Usermod I2C Rotary Encoder #5243
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
base: main
Are you sure you want to change the base?
Changes from all commits
245c9a9
0cef8df
fc07abc
aac4943
43aae79
2b23d1c
5d1a0b8
0d2ae55
3fd0c6c
c109632
ff49287
996f12b
43fe90c
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,37 @@ | ||
| # usermod_i2c_encoder | ||
|
|
||
| This usermod enables the use of a [DUPPA I2CEncoder V2.1](https://github.com/DuPPadotnet/I2CEncoderV2.1) rotary encoder + pushbutton to control WLED. | ||
|
|
||
| Settings will be available on the Usermods page of the web UI. Here you can define which pins are used for interrupt, SCL, and SDA. Restart is needed for new values to take effect. | ||
|
|
||
| ## Features | ||
|
|
||
| - On/off | ||
| - Integrated button switch turns the strip on and off | ||
| - Brightness adjust | ||
| - Turn the encoder knob to adjust brightness | ||
| - Effect adjust (encoder LED turns red) | ||
| - Hold the button for 1 second to switch operating mode to effect adjust mode | ||
| - When in effect adjust mode the integrated LED turns red | ||
| - Rotating the knob cycles through all the effects | ||
| - Reset | ||
| - When WLED is off (brightness 0) hold the button to reset and load Preset 1. Preset 1 must be defined for this to work. | ||
|
|
||
| ## Hardware | ||
|
|
||
| This usermod is intended to work with the I2CEncoder V2.1 with the following configuration: | ||
|
|
||
| - Rotary encoder: Illuminated RGB Encoder | ||
| - This encoder includes a pushbutton switch and an internal RGB LED to illuminate the shaft and any knob attached to it. | ||
| - This is the encoder: [Sparkfun RGB Encoder](https://www.sparkfun.com/products/15141) | ||
| - Knob: Any knob works, but the black knob has a transparent ring that lets the internal LED light through for a nice glow. | ||
| - Connectors: any | ||
| - LEDs: none (this is separate from the LED included in the encoder above) | ||
|
|
||
| ## Compiling | ||
|
|
||
| Simply add `custom_usermods = i2c_encoder_button` to your platformio_override.ini environment to enable this usermod in your build. | ||
|
|
||
| See `platformio_override.sample.ini` for example usage. | ||
|
|
||
| Warning: if this usermod is enabled and no i2c encoder is connected you will have problems! |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,10 @@ | ||
| { | ||
| "name": "i2c_encoder_button", | ||
| "version": "1.0.0", | ||
| "description": "WLED usermod for DUPPA I2C Encoder rotary encoder.", | ||
| "dependencies": { | ||
| "Wire": "Wire", | ||
| "ArduinoDuPPaLib": "https://github.com/Fattoresaimon/ArduinoDuPPaLib#v1.4.1" | ||
| }, | ||
| "build": { "libArchive": false } | ||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,20 @@ | ||
| ; Example platformio_override.ini that shows how to configure your environment to use the I2C Encoder Button usermod. | ||
|
|
||
| [platformio] | ||
| default_envs = | ||
| esp01_i2c_encoder | ||
| esp32_i2c_encoder | ||
|
|
||
| ; Example using esp01 module with i2c encoder. | ||
| ; LEDPIN defaults to 2 so it needs to be defined here to avoid conflicts with SCL/SDA pins. | ||
| [env:esp01_i2c_encoder] | ||
| extends = env:esp01_1m_ota | ||
| custom_usermods = ${env:esp01_1m_ota.custom_usermods} i2c_encoder_button | ||
| build_flags = ${env:esp01_1m_ota.build_flags} | ||
| -D LEDPIN=3 | ||
|
|
||
| ; Example for esp32 | ||
| ; Pins 21 and 22 are default i2c pins on esp32 | ||
| [env:esp32_i2c_encoder] | ||
| extends = env:esp32dev | ||
| custom_usermods = ${env:esp32dev.custom_usermods} i2c_encoder_button |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,187 @@ | ||
| #include "wled.h" | ||
| #include <Wire.h> | ||
| #include <i2cEncoderLibV2.h> | ||
|
|
||
| // Default values for I2C encoder pins and address | ||
| #ifndef I2C_ENCODER_DEFAULT_ENABLED | ||
| #define I2C_ENCODER_DEFAULT_ENABLED false | ||
| #endif | ||
| #ifndef I2C_ENCODER_DEFAULT_INT_PIN | ||
| #define I2C_ENCODER_DEFAULT_INT_PIN 1 | ||
| #endif | ||
| #ifndef I2C_ENCODER_DEFAULT_SDA_PIN | ||
| #define I2C_ENCODER_DEFAULT_SDA_PIN 0 | ||
| #endif | ||
| #ifndef I2C_ENCODER_DEFAULT_SCL_PIN | ||
| #define I2C_ENCODER_DEFAULT_SCL_PIN 2 | ||
| #endif | ||
| #ifndef I2C_ENCODER_DEFAULT_ADDRESS | ||
| #define I2C_ENCODER_DEFAULT_ADDRESS 0x00 | ||
| #endif | ||
broccoliboy marked this conversation as resolved.
Show resolved
Hide resolved
|
||
|
|
||
| // v2 usermod for I2C Encoder | ||
| class UsermodI2CEncoderButton : public Usermod { | ||
| private: | ||
| i2cEncoderLibV2 * encoder_p; | ||
| bool encoderButtonDown = false; | ||
| uint32_t buttonPressStartTime = 0; // Millis when button was pressed | ||
| uint32_t buttonPressDuration = 0; | ||
| const uint32_t buttonLongPressThreshold = 1000; // Duration threshold for long press (millis) | ||
| bool wasLongButtonPress = false; | ||
|
|
||
| // EncoderMode keeps track of what function the encoder is controlling | ||
| // 0 = brightness | ||
| // 1 = effect | ||
| uint8_t encoderMode = 0; | ||
| // EncoderModes keeps track of what color the encoder LED should be for each mode | ||
| const uint32_t encoderModes[2] = {0x0000FF, 0xFF0000}; | ||
| uint32_t lastInteractionTime = 0; | ||
| const uint32_t modeResetTimeout = 30000; // Timeout for reseting mode to 0 | ||
| const int8_t brightnessDelta = 16; | ||
| bool enabled = false; | ||
| bool initDone = false; | ||
|
|
||
| // Configurable pins and address (now user-configurable via JSON config) | ||
| int8_t intPin = I2C_ENCODER_DEFAULT_INT_PIN; // Interrupt pin for I2C encoder | ||
| int8_t sdaPin = I2C_ENCODER_DEFAULT_SDA_PIN; // I2C SDA pin | ||
| int8_t sclPin = I2C_ENCODER_DEFAULT_SCL_PIN; // I2C SCL pin | ||
| uint8_t i2cAddress = I2C_ENCODER_DEFAULT_ADDRESS; // I2C address of encoder | ||
|
|
||
| void updateBrightness(int8_t deltaBrightness) { | ||
| bri = constrain(bri + deltaBrightness, 0, 255); | ||
| colorUpdated(CALL_MODE_BUTTON); | ||
| } | ||
|
|
||
| void updateEffect(int8_t deltaEffect) { | ||
| // Set new effect with rollover at 0 and MODE_COUNT | ||
| effectCurrent = (effectCurrent + MODE_COUNT + deltaEffect) % MODE_COUNT; | ||
| colorUpdated(CALL_MODE_FX_CHANGED); | ||
| } | ||
|
|
||
| void setEncoderMode(uint8_t mode) { | ||
| // Set new mode and update encoder LED color | ||
| encoderMode = mode; | ||
| encoder_p->writeRGBCode(encoderModes[encoderMode]); | ||
| } | ||
|
|
||
| void handleEncoderShortButtonPress() { | ||
| toggleOnOff(); | ||
| colorUpdated(CALL_MODE_BUTTON); | ||
| setEncoderMode(0); | ||
| } | ||
|
|
||
| void handleEncoderLongButtonPress() { | ||
| if (encoderMode == 0 && bri == 0) { | ||
| applyPreset(1); | ||
| colorUpdated(CALL_MODE_FX_CHANGED); | ||
| } else { | ||
| setEncoderMode((encoderMode + 1) % (sizeof(encoderModes) / sizeof(encoderModes[0]))); | ||
| } | ||
| buttonPressStartTime = millis(); | ||
| wasLongButtonPress = true; | ||
| } | ||
|
|
||
| void encoderRotated(i2cEncoderLibV2 *obj) { | ||
| switch (encoderMode) { | ||
| case 0: updateBrightness(obj->readStatus(i2cEncoderLibV2::RINC) ? brightnessDelta : -brightnessDelta); break; | ||
| case 1: updateEffect(obj->readStatus(i2cEncoderLibV2::RINC) ? 1 : -1); break; | ||
| } | ||
| lastInteractionTime = millis(); | ||
| } | ||
|
|
||
| void encoderButtonPush(i2cEncoderLibV2 *obj) { | ||
| encoderButtonDown = true; | ||
| buttonPressStartTime = lastInteractionTime = millis(); | ||
| } | ||
|
|
||
| void encoderButtonRelease(i2cEncoderLibV2 *obj) { | ||
| encoderButtonDown = false; | ||
| if (!wasLongButtonPress) handleEncoderShortButtonPress(); | ||
| wasLongButtonPress = false; | ||
| buttonPressDuration = 0; | ||
| lastInteractionTime = millis(); | ||
| } | ||
|
|
||
| public: | ||
|
|
||
| UsermodI2CEncoderButton() { | ||
| encoder_p = nullptr; | ||
| } | ||
|
|
||
| void setup() override { | ||
| // (Re)initialize encoder with current config | ||
| if (encoder_p) { | ||
| delete encoder_p; | ||
| encoder_p = nullptr; | ||
| } | ||
| if (!enabled) return; | ||
| encoder_p = new i2cEncoderLibV2(i2cAddress); | ||
| pinMode(intPin, INPUT); | ||
| Wire.begin(sdaPin, sclPin); | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🧩 Analysis chain🏁 Script executed: #!/bin/bash
# Search for Wire.begin usage patterns in WLED usermods
rg -n -C 3 'Wire\.begin' --type cpp --type hRepository: wled/WLED Length of output: 9935 🏁 Script executed: # Get the specific file and its context around line 107
cat -n usermods/i2c_encoder_button/usermod_i2c_encoder_button.cpp | sed -n '95,120p'Repository: wled/WLED Length of output: 1282 🏁 Script executed: # Check for global Wire initialization in main files
rg -n 'Wire\.begin' --type cpp --type h | grep -v 'usermods' | head -20Repository: wled/WLED Length of output: 981 Avoid re-initializing Wire in usermod setup(). Calling 🤖 Prompt for AI Agents |
||
| encoder_p->reset(); | ||
| encoder_p->begin( | ||
| i2cEncoderLibV2::INT_DATA | i2cEncoderLibV2::WRAP_ENABLE | i2cEncoderLibV2::DIRE_RIGHT | | ||
| i2cEncoderLibV2::IPUP_ENABLE | i2cEncoderLibV2::RMOD_X1 | i2cEncoderLibV2::RGB_ENCODER | ||
| ); | ||
|
|
||
| encoder_p->writeCounter((int32_t)0); // Reset the counter value | ||
| encoder_p->writeMax((int32_t)255); // Set the maximum threshold | ||
| encoder_p->writeMin((int32_t)0); // Set the minimum threshold | ||
| encoder_p->writeStep((int32_t)1); // Set the step to 1 | ||
| encoder_p->writeAntibouncingPeriod(5); | ||
| encoder_p->writeFadeRGB(1); | ||
| encoder_p->writeInterruptConfig( | ||
| i2cEncoderLibV2::RINC | i2cEncoderLibV2::RDEC | i2cEncoderLibV2::PUSHP | i2cEncoderLibV2::PUSHR | ||
| ); | ||
| setEncoderMode(0); | ||
| initDone = true; | ||
| } | ||
|
|
||
| void loop() override { | ||
| if (!enabled || !encoder_p) return; | ||
| if (digitalRead(intPin) == LOW) { | ||
| if (encoder_p->updateStatus()) { | ||
| if (encoder_p->readStatus(i2cEncoderLibV2::RINC) || encoder_p->readStatus(i2cEncoderLibV2::RDEC)) encoderRotated(encoder_p); | ||
| if (encoder_p->readStatus(i2cEncoderLibV2::PUSHP)) encoderButtonPush(encoder_p); | ||
| if (encoder_p->readStatus(i2cEncoderLibV2::PUSHR)) encoderButtonRelease(encoder_p); | ||
| } | ||
| } | ||
| if (encoderButtonDown) buttonPressDuration = millis() - buttonPressStartTime; | ||
| if (buttonPressDuration > buttonLongPressThreshold) handleEncoderLongButtonPress(); | ||
| if (encoderMode != 0 && millis() - lastInteractionTime > modeResetTimeout) setEncoderMode(0); | ||
| } | ||
|
|
||
| void addToJsonInfo(JsonObject& root) override { | ||
| JsonObject user = root["u"]; | ||
| if (user.isNull()) user = root.createNestedObject("u"); | ||
| JsonArray arr = user.createNestedArray(F("I2C Encoder")); | ||
| arr.add(enabled ? F("Enabled") : F("Disabled")); | ||
| } | ||
|
|
||
| void addToConfig(JsonObject& root) override { | ||
| // Add user-configurable pins and address to config | ||
| JsonObject top = root.createNestedObject(F("I2C_Encoder_Button")); | ||
| top["enabled"] = enabled; | ||
| top["intPin"] = intPin; | ||
| top["sdaPin"] = sdaPin; | ||
| top["sclPin"] = sclPin; | ||
| top["i2cAddress"] = i2cAddress; | ||
| } | ||
|
|
||
| bool readFromConfig(JsonObject& root) override { | ||
| // Read user-configurable pins and address from config | ||
| JsonObject top = root["I2C_Encoder_Button"]; | ||
| bool configComplete = !top.isNull(); | ||
| configComplete &= getJsonValue(top["enabled"], enabled, I2C_ENCODER_DEFAULT_ENABLED); | ||
| configComplete &= getJsonValue(top["intPin"], intPin, I2C_ENCODER_DEFAULT_INT_PIN); | ||
| configComplete &= getJsonValue(top["sdaPin"], sdaPin, I2C_ENCODER_DEFAULT_SDA_PIN); | ||
| configComplete &= getJsonValue(top["sclPin"], sclPin, I2C_ENCODER_DEFAULT_SCL_PIN); | ||
| configComplete &= getJsonValue(top["i2cAddress"], i2cAddress, I2C_ENCODER_DEFAULT_ADDRESS); | ||
| return configComplete; | ||
| } | ||
|
|
||
| uint16_t getId() override { return USERMOD_ID_I2C_ENCODER_BUTTON; } | ||
| }; | ||
|
|
||
| static UsermodI2CEncoderButton usermod_i2c_encoder_button; | ||
| REGISTER_USERMOD(usermod_i2c_encoder_button); | ||
Uh oh!
There was an error while loading. Please reload this page.