-
-
Notifications
You must be signed in to change notification settings - Fork 4k
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
Open
broccoliboy
wants to merge
19
commits into
wled:main
Choose a base branch
from
broccoliboy:i2c_encoder_v2
base: main
Could not load branches
Branch not found: {{ refName }}
Loading
Could not load tags
Nothing to show
Loading
Are you sure you want to change the base?
Some commits from the old base branch may be removed from the timeline,
and old review comments may become outdated.
Open
Changes from all commits
Commits
Show all changes
19 commits
Select commit
Hold shift + click to select a range
245c9a9
update i2c_encoder usermod to v2 format with json config settings in …
broccoliboy 0cef8df
Merge branch 'main' into i2c_encoder_v2
broccoliboy fc07abc
update i2c encoder usermod
broccoliboy aac4943
Merge branch 'main' into i2c_encoder_v2
broccoliboy 43aae79
Merge branch 'main' into i2c_encoder_v2
broccoliboy 2b23d1c
fix usermod id #define
broccoliboy 5d1a0b8
update url in readme
broccoliboy 0d2ae55
update usermod id comment in const.h
broccoliboy 3fd0c6c
Merge remote-tracking branch 'upstream/main' into i2c_encoder_v2
broccoliboy c109632
fix bugs and implement recommendations from coderabbit
broccoliboy ff49287
use explicit types for encoder params (fix CI build issue on esp32c3)
broccoliboy 996f12b
don't run setup if !enabled
broccoliboy 43fe90c
formatting
broccoliboy 3b83193
use global i2c pins and pinmanager for irq
broccoliboy 54c69e2
Merge branch 'main' into i2c_encoder_v2
broccoliboy 5282f25
improve brightness and effect set functions, clean up code
broccoliboy c9a8665
deallocate irq pin if usermod not enabled
broccoliboy 654e9cf
update sample override and readme
broccoliboy 47f036c
Merge remote-tracking branch 'origin/main' into i2c_encoder_v2
broccoliboy File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Some comments aren't visible on the classic Files Changed page.
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| 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 SDA, SCL, and interrupt. Global I2C settings are used for SDA and SCL but interrupt setting will be located in the i2c_encoder_button section at the bottom of the page. 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! |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| 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 } | ||
| } | ||
22 changes: 22 additions & 0 deletions
22
usermods/i2c_encoder_button/platformio_override.sample.ini
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,22 @@ | ||
| ; 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_full | ||
| custom_usermods = ${env:esp01_1m_full.custom_usermods} | ||
| i2c_encoder_button | ||
| build_flags = ${env:esp01_1m_full.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 |
208 changes: 208 additions & 0 deletions
208
usermods/i2c_encoder_button/usermod_i2c_encoder_button.cpp
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,208 @@ | ||
| #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_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: | ||
| static const char _name[]; | ||
| 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 = I2C_ENCODER_DEFAULT_ENABLED; | ||
|
|
||
| // Configurable pins and address (now user-configurable via JSON config) | ||
| int8_t irqPin = I2C_ENCODER_DEFAULT_INT_PIN; // Interrupt pin for I2C encoder | ||
| uint8_t i2cAddress = I2C_ENCODER_DEFAULT_ADDRESS; // I2C address of encoder | ||
|
|
||
| void update() { | ||
| stateUpdated(CALL_MODE_BUTTON); | ||
| updateInterfaces(CALL_MODE_BUTTON); | ||
| } | ||
|
|
||
| void updateBrightness(bool increase) { | ||
| int8_t delta = bri < 40 ? brightnessDelta / 2 : brightnessDelta; | ||
| bri = constrain(bri + (increase ? delta : -delta), 0, 255); | ||
| update(); | ||
| } | ||
|
|
||
| void updateEffect(bool increase) { | ||
| // Set new effect with rollover at 0 and MODE_COUNT | ||
| effectCurrent = (effectCurrent + MODE_COUNT + (increase ? 1 : -1)) % MODE_COUNT; | ||
| stateChanged = true; | ||
| Segment& seg = strip.getSegment(strip.getMainSegmentId()); | ||
| seg.setMode(effectCurrent); | ||
| update(); | ||
| } | ||
|
|
||
| void setEncoderMode(uint8_t mode) { | ||
| // Set new mode and update encoder LED color | ||
| encoderMode = mode; | ||
| encoder_p->writeRGBCode(encoderModes[encoderMode]); | ||
| } | ||
|
|
||
| void handleEncoderShortButtonPress() { | ||
| DEBUG_PRINTLN(F("Encoder short button press")); | ||
| toggleOnOff(); | ||
| update(); | ||
| setEncoderMode(0); | ||
| } | ||
|
|
||
| void handleEncoderLongButtonPress() { | ||
| DEBUG_PRINTLN(F("Encoder long button press")); | ||
| if (encoderMode == 0 && bri == 0) { | ||
| applyPreset(1); | ||
| update(); | ||
| } else { | ||
| setEncoderMode((encoderMode + 1) % (sizeof(encoderModes) / sizeof(encoderModes[0]))); | ||
| } | ||
| buttonPressStartTime = millis(); | ||
| wasLongButtonPress = true; | ||
| } | ||
|
|
||
| void encoderRotated(i2cEncoderLibV2 *obj) { | ||
| DEBUG_PRINTLN(F("Encoder rotated")); | ||
| switch (encoderMode) { | ||
| case 0: updateBrightness(obj->readStatus(i2cEncoderLibV2::RINC)); break; | ||
| case 1: updateEffect(obj->readStatus(i2cEncoderLibV2::RINC)); break; | ||
| } | ||
| lastInteractionTime = millis(); | ||
| } | ||
|
|
||
| void encoderButtonPush(i2cEncoderLibV2 *obj) { | ||
| DEBUG_PRINTLN(F("Encoder button pushed")); | ||
| encoderButtonDown = true; | ||
| buttonPressStartTime = lastInteractionTime = millis(); | ||
| } | ||
|
|
||
| void encoderButtonRelease(i2cEncoderLibV2 *obj) { | ||
| DEBUG_PRINTLN(F("Encoder button released")); | ||
| encoderButtonDown = false; | ||
| if (!wasLongButtonPress) handleEncoderShortButtonPress(); | ||
| wasLongButtonPress = false; | ||
| buttonPressDuration = 0; | ||
| lastInteractionTime = millis(); | ||
| } | ||
|
|
||
| public: | ||
|
|
||
| UsermodI2CEncoderButton() { | ||
| encoder_p = nullptr; | ||
| } | ||
|
|
||
| void setup() override { | ||
| // Clean up existing encoder if any | ||
| if (encoder_p) { | ||
| delete encoder_p; | ||
| encoder_p = nullptr; | ||
| } | ||
|
|
||
| if (!enabled) { | ||
| if (irqPin >= 0) PinManager::deallocatePin(irqPin, PinOwner::UM_I2C_ENCODER_BUTTON); | ||
| return; | ||
| } | ||
|
|
||
| if (i2c_sda < 0 || i2c_scl < 0) { | ||
| DEBUG_PRINTLN(F("I2C pins not set, disabling I2C encoder usermod.")); | ||
| enabled = false; | ||
| return; | ||
| } else { | ||
| if (irqPin >= 0 && PinManager::allocatePin(irqPin, false, PinOwner::UM_I2C_ENCODER_BUTTON)) { | ||
| pinMode(irqPin, INPUT); | ||
| } else { | ||
| DEBUG_PRINTLN(F("Unable to allocate interrupt pin, disabling I2C encoder usermod.")); | ||
| irqPin = -1; | ||
| enabled = false; | ||
| return; | ||
| } | ||
| } | ||
|
|
||
| // (Re)initialize encoder with current config | ||
| encoder_p = new i2cEncoderLibV2(i2cAddress); | ||
| 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(20); | ||
| encoder_p->writeFadeRGB(1); | ||
| encoder_p->writeInterruptConfig( | ||
| i2cEncoderLibV2::RINC | i2cEncoderLibV2::RDEC | i2cEncoderLibV2::PUSHP | i2cEncoderLibV2::PUSHR | ||
| ); | ||
| setEncoderMode(0); | ||
| } | ||
|
|
||
| void loop() override { | ||
| if (!enabled || !encoder_p) return; | ||
| if (digitalRead(irqPin) == 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(FPSTR(_name)); | ||
| top["enabled"] = enabled; | ||
| top["irq_pin"] = irqPin; | ||
| top["i2c_address"] = i2cAddress; | ||
| } | ||
|
|
||
| bool readFromConfig(JsonObject& root) override { | ||
| // Read user-configurable pins and address from config | ||
| JsonObject top = root[FPSTR(_name)]; | ||
| bool configComplete = !top.isNull(); | ||
| configComplete &= getJsonValue(top["enabled"], enabled, I2C_ENCODER_DEFAULT_ENABLED); | ||
| configComplete &= getJsonValue(top["irq_pin"], irqPin, I2C_ENCODER_DEFAULT_INT_PIN); | ||
| configComplete &= getJsonValue(top["i2c_address"], i2cAddress, I2C_ENCODER_DEFAULT_ADDRESS); | ||
| return configComplete; | ||
| } | ||
|
|
||
| uint16_t getId() override { return USERMOD_ID_I2C_ENCODER_BUTTON; } | ||
| }; | ||
|
|
||
| const char UsermodI2CEncoderButton::_name[] PROGMEM = "i2c_encoder_button"; | ||
|
|
||
| static UsermodI2CEncoderButton usermod_i2c_encoder_button; | ||
| REGISTER_USERMOD(usermod_i2c_encoder_button); | ||
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.