Skip to content

Add Stream Deck Plus rotary encoder support#143

Open
yalec wants to merge 1 commit intoenertial:masterfrom
yalec:feature/streamdeck-plus-encoder-support
Open

Add Stream Deck Plus rotary encoder support#143
yalec wants to merge 1 commit intoenertial:masterfrom
yalec:feature/streamdeck-plus-encoder-support

Conversation

@yalec
Copy link

@yalec yalec commented Dec 21, 2025

Add Stream Deck Plus rotary encoder support

Features:

  • Rotary encoder rotation with separate CW/CCW increment values
  • Encoder press support with fixed value
  • LCD display using setFeedback API with B2 layout
  • Value-to-text mapping system with dynamic UI
  • Automatic gauge/indicator based on min/max/current values
  • Real-time settings update without restart

Technical changes:

  • Add dialRotate, dialDown, dialUp event handlers
  • Create encoder_prop_inspector.html for configuration
  • Implement getCurrentDisplayValue() with mapping support
  • Update manifest.json with two encoder actions
  • Upgrade Visual Studio toolset to v145 (VS 2025)
  • Update README with encoder features documentation
  • Add CONTRIBUTING.md for fork information

Add Stream Deck Plus rotary encoder support

Features:
- Rotary encoder rotation with separate CW/CCW increment values
- Encoder press support with fixed value
- LCD display using setFeedback API with B2 layout
- Value-to-text mapping system with dynamic UI
- Automatic gauge/indicator based on min/max/current values
- Real-time settings update without restart

Technical changes:
- Add dialRotate, dialDown, dialUp event handlers
- Create encoder_prop_inspector.html for configuration
- Implement getCurrentDisplayValue() with mapping support
- Update manifest.json with two encoder actions
- Upgrade Visual Studio toolset to v145 (VS 2025)
- Update README with encoder features documentation
- Add CONTRIBUTING.md for fork information
@charlestytler
Copy link
Collaborator

Thanks for putting up a PR! I'll try to review it soon, but may not get to it until after Christmas.

Copy link
Collaborator

@charlestytler charlestytler left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is fantastic! Thanks for working out the Streamdeck rotary details and also following my existing codebase style.

I don't have a rotary device so can't test it fully, but it looks reasonable. I've requested some changes in mostly just organization to better align with how different button types are separated. And sorry for the code review delay, it took me a while to re-orient myself in the codebase after not looking at it for several years.

One final note, since you're the first person other than myself to dig into the codebase in 5 years, let me know if some of the naming was confusing. In my re-review I feel "StreamdeckContext" which refers to the context instantiated for each button/rotary is not too obvious, but StreamdeckButtonContext doesn't really work if rotary encoders are now included.

I'll also look into the github actions that seem to have gone stale so C++ tests and linters can run.

mConnectionManager->LogMessage("[DialPress] Simulator NOT connected");
}
} else {
mConnectionManager->LogMessage("[DialPress] Context NOT found in visible contexts");
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Leaving a reminder - can these LogMessages be removed before landing?

}

// Update encoder display with current increment value if this is an encoder action
if (send_action_) {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It would be helpful if the logic in here could be contained to its own encoder_display_monitor_ (or similar name) entity along with other items in the ExportMonitors/ directory. Then it can also be easily tested outside of this updateContextState() function.

*/
void handleEncoderPress(SimulatorInterface *simulator_interface,
ESDConnectionManager *mConnectionManager,
const json &inPayload);
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Since the context for Streamdeck encoders seem to not share events with the Button press/release increment actions, I think it would be cleaner to create a new EncoderAction.h with these four functions.

This new action can still use an IncrementMonitor internally to help track state.

button_action_from_uuid_["com.ctytler.dcs.increment.dial.two-state"] = ButtonAction::INCREMENT;
button_action_from_uuid_["com.ctytler.dcs.increment.textdial.two-state"] = ButtonAction::INCREMENT;
button_action_from_uuid_["com.ctytler.dcs.encoder.rotary"] = ButtonAction::INCREMENT;
button_action_from_uuid_["com.ctytler.dcs.encoder.rotary.text"] = ButtonAction::INCREMENT;
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Creating a new ButtonAction::ENCODER enum to map to a new EncoderAction will help avoid dealing with the dynamic casts I believe below, as the factory will return a unique pointer to an EncoderAction.

int ticks)
{
// Cast to IncrementAction to access encoder-specific methods
auto *increment_action = dynamic_cast<IncrementAction *>(send_action_.get());
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm not sure if this dynamic cast should be needed, as the SendActionFactory should be constructing the correct Action type of send_action_. Maybe there was an issue you were running into.

std::string current_title_ = ""; // Stored title of the context.
std::string last_encoder_display_value_ = ""; // Last value displayed on encoder LCD.
std::string last_encoder_image_path_ = ""; // Last image path set for encoder background.
json settings_; // Stored settings for this context.
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Would prefer to avoid keeping this raw json settings as stored state. Having an encoder monitor would allow it to access the json settings from updateContextSettings(json settings) and save just what it needs.

@charlestytler charlestytler linked an issue Jan 9, 2026 that may be closed by this pull request
@charlestytler
Copy link
Collaborator

@yalec I've landed #145 which fixed the broken github actions so it would be good to merge master on this.
Also the github actions servers don't have support for VS2026 yet, so for the time being I'd like to keep the build files tied to VS2022 (should be a simple find/replace of v145->v143 in the vcxproj files before pushing if you prefer).

I'm also working on a PR to migrate from Visual Studio management of build files to Cmake which will hopefully make things easier going forward. But let me know if you intend to edit this anytime soon and I can hold off landing it to save you some rebasing. Let me know if you have any questions... or if you don't have time to continue modifying this PR for a while. Thanks.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Streamdeck+ Dial Compatability?

2 participants