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

v1.0.3 Updates #14

Merged
merged 8 commits into from
May 10, 2020
5 changes: 5 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
- [Demo of Operation](#demo-of-operation)
- [Installation](#installation)
- [Downloads](#downloads)
- [Version Update](#version-update)
- [Initial Configuration](#initial-configuration)
- [Video Walkthrough](#video-walkthrough)
- [Source code](#source-code)
Expand Down Expand Up @@ -50,6 +51,10 @@ More detailed instructions can be found in: [Settings Help Documentation](Source

- Also within [Releases](https://github.com/charlestytler/streamdeck-dcs-interface/releases) is an optional `icon_library.zip` you can download for use with Streamdeck Profiles.

### Version Update

If you have a prior version already installed on your StreamDeck, you will have to uninstall it first before installing the latest version. To do this click the "More Actions..." button at the bottom-right of the StreamDeck GUI and click "Uninstall" next to the DCS Interface plugin.

### Initial Configuration

If you plan to only use DCS Interface for Streamdeck with the DCS-ExportScript and not [Ikarus](https://github.com/s-d-a/Ikarus), you can modify the file `DCS-ExportScript\Config.lua` to have the following settings (where `IkarusPort` is changed from `1625` to `1725` for DCS Interface) to get everything connected:
Expand Down
Binary file modified Release/com.ctytler.dcs.streamDeckPlugin
Binary file not shown.
13 changes: 12 additions & 1 deletion Sources/DcsInterface/StreamdeckContext.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -46,12 +46,23 @@ void StreamdeckContext::updateContextState(DcsInterface *dcs_interface, ESDConne
current_title_ = updated_title;
mConnectionManager->SetTitle(current_title_, context_, kESDSDKTarget_HardwareAndSoftware);
}

if (delay_for_force_send_state_) {
if (delay_for_force_send_state_.value()-- <= 0) {
mConnectionManager->SetState(static_cast<int>(current_state_), context_);
delay_for_force_send_state_.reset();
}
}
}

void StreamdeckContext::forceSendState(ESDConnectionManager *mConnectionManager) {
mConnectionManager->SetState(static_cast<int>(current_state_), context_);
}

void StreamdeckContext::forceSendStateAfterDelay(const int delay_count) {
delay_for_force_send_state_.emplace(delay_count);
}

void StreamdeckContext::updateContextSettings(const json &settings) {
// Read in settings.
const std::string dcs_id_increment_monitor_raw =
Expand Down Expand Up @@ -190,7 +201,7 @@ bool StreamdeckContext::determineSendValueForSwitch(const KeyEvent event,
const ContextState state,
const json &settings,
std::string &value) {
if (event == KEY_DOWN) {
if (event == KEY_UP) {
if (state == FIRST) {
value = EPLJSONUtils::GetStringByName(settings, "send_when_first_state_value");
} else {
Expand Down
15 changes: 14 additions & 1 deletion Sources/DcsInterface/StreamdeckContext.h
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
#include "../Common/ESDConnectionManager.h"
#endif

#include <optional>
#include <string>

using KeyEvent = enum { KEY_DOWN, KEY_UP };
Expand All @@ -29,13 +30,21 @@ class StreamdeckContext {
void updateContextState(DcsInterface *dcs_interface, ESDConnectionManager *mConnectionManager);

/**
* @brief Forces an update to the Streamdeck of the context's current state be sent.
* @brief Forces an update to the Streamdeck of the context's current state be sent with current static values.
* (Normally an update is sent to the Streamdeck only on change of current state).
*
* @param mConnectionManager Interface to StreamDeck.
*/
void forceSendState(ESDConnectionManager *mConnectionManager);

/**
* @brief Forces an update to the Streamdeck of the context's current state be sent after a specified delay.
* (Normally an update is sent to the Streamdeck only on change of current state).
*
* @param delay_count Number of frames before a force send of the context state is sent.
*/
void forceSendStateAfterDelay(const int delay_count);

/**
* @brief Updates settings from received json payload.
*
Expand Down Expand Up @@ -100,6 +109,10 @@ class StreamdeckContext {
bool compare_monitor_is_set_ = false; // True if all DCS ID comparison monitor settings have been set.
bool string_monitor_is_set_ = false; // True if all DCS ID string monitor settings have been set.

// Optional settings.
std::optional<int> delay_for_force_send_state_; // When populated, requests a force send of state to Streamdeck
// after counting down the stored delay value.

// Context state.
ContextState current_state_ = FIRST; // Stored state of the context.
std::string current_title_ = ""; // Stored title of the context.
Expand Down
18 changes: 10 additions & 8 deletions Sources/MyStreamDeckPlugin.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -140,10 +140,6 @@ void MyStreamDeckPlugin::KeyDownForAction(const std::string &inAction,
if (dcs_interface_ != nullptr) {
mVisibleContextsMutex.lock();
mVisibleContexts[inContext].handleButtonEvent(dcs_interface_, KEY_DOWN, inAction, inPayload);
// The Streamdeck will by default change a context's state after a button action, so a force send of the
// current context's state will keep the button state in sync with the plugin. (Not performed for switches
// as generally the change in state is desired there).
mVisibleContexts[inContext].forceSendState(mConnectionManager);
mVisibleContextsMutex.unlock();
}
}
Expand All @@ -155,10 +151,16 @@ void MyStreamDeckPlugin::KeyUpForAction(const std::string &inAction,

if (dcs_interface_ != nullptr) {
mVisibleContextsMutex.lock();
mVisibleContexts[inContext].handleButtonEvent(dcs_interface_, KEY_UP, inAction, inPayload);
// The Streamdeck will by default change a context's state after a button action, so a force send of the current
// The Streamdeck will by default change a context's state after a KeyUp event, so a force send of the current
// context's state will keep the button state in sync with the plugin.
mVisibleContexts[inContext].forceSendState(mConnectionManager);
if (inAction.find("switch") != std::string::npos) {
// For switches use a delay to avoid jittering and a race condition of Streamdeck and Plugin trying to
// change state.
mVisibleContexts[inContext].forceSendStateAfterDelay(3);
} else {
mVisibleContexts[inContext].forceSendState(mConnectionManager);
}
mVisibleContexts[inContext].handleButtonEvent(dcs_interface_, KEY_UP, inAction, inPayload);
mVisibleContextsMutex.unlock();
}
}
Expand All @@ -173,7 +175,7 @@ void MyStreamDeckPlugin::WillAppearForAction(const std::string &inAction,
EPLJSONUtils::GetObjectByName(inPayload, "settings", settings);
mVisibleContexts[inContext] = StreamdeckContext(inContext, settings);
if (dcs_interface_ != nullptr) {
mVisibleContexts[inContext].updateContextState(dcs_interface_, mConnectionManager);
mVisibleContexts[inContext].forceSendState(mConnectionManager);
}
mVisibleContextsMutex.unlock();
}
Expand Down
56 changes: 48 additions & 8 deletions Sources/Test/StreamdeckContextTest.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -373,6 +373,46 @@ TEST_F(StreamdeckContextTestFixture, force_send_state_update) {
EXPECT_EQ(esd_connection_manager.state_, 0);
}

TEST_F(StreamdeckContextTestFixture, force_send_state_update_with_zero_delay) {
// Test 1 -- With updateContextState and no detected state changes, no state is sent to connection manager.
fixture_context.updateContextState(&dcs_interface, &esd_connection_manager);
EXPECT_EQ(esd_connection_manager.context_, "");

// Test -- force send will send current state regardless of state change.
int delay_count = 0;
fixture_context.forceSendStateAfterDelay(delay_count);
fixture_context.updateContextState(&dcs_interface, &esd_connection_manager);
EXPECT_EQ(esd_connection_manager.context_, "abc123");
EXPECT_EQ(esd_connection_manager.state_, 0);
}

TEST_F(StreamdeckContextTestFixture, force_send_state_update_after_delay) {
// Test -- force send will send current state regardless of state change.
int delay_count = 3;
fixture_context.forceSendStateAfterDelay(delay_count);
while (delay_count > 0) {
fixture_context.updateContextState(&dcs_interface, &esd_connection_manager);
EXPECT_EQ(esd_connection_manager.context_, "");
delay_count--;
}
fixture_context.updateContextState(&dcs_interface, &esd_connection_manager);
EXPECT_EQ(esd_connection_manager.context_, "abc123");
EXPECT_EQ(esd_connection_manager.state_, 0);
}

TEST_F(StreamdeckContextTestFixture, force_send_state_update_negative_delay) {
// Test 1 -- With updateContextState and no detected state changes, no state is sent to connection manager.
fixture_context.updateContextState(&dcs_interface, &esd_connection_manager);
EXPECT_EQ(esd_connection_manager.context_, "");

// Test -- force send will occur as delay is already less than zero.
int delay_count = -3;
fixture_context.forceSendStateAfterDelay(delay_count);
fixture_context.updateContextState(&dcs_interface, &esd_connection_manager);
EXPECT_EQ(esd_connection_manager.context_, "abc123");
EXPECT_EQ(esd_connection_manager.state_, 0);
}

class StreamdeckContextKeyPressTestFixture : public StreamdeckContextTestFixture {
public:
StreamdeckContextKeyPressTestFixture()
Expand Down Expand Up @@ -459,38 +499,38 @@ TEST_F(StreamdeckContextKeyPressTestFixture, handle_keydown_momentary_empty_valu
EXPECT_EQ(expected_command, ss_received.str());
}

TEST_F(StreamdeckContextKeyPressTestFixture, handle_keydown_switch_in_first_state) {
TEST_F(StreamdeckContextKeyPressTestFixture, handle_keyup_switch_in_first_state) {
const std::string action = "com.ctytler.dcs.switch.two-state";
fixture_context.handleButtonEvent(&dcs_interface, KEY_DOWN, action, payload);
fixture_context.handleButtonEvent(&dcs_interface, KEY_UP, action, payload);
const std::stringstream ss_received = mock_dcs.DcsReceive();
std::string expected_command =
"C" + device_id + "," + std::to_string(button_id) + "," + send_when_first_state_value;
EXPECT_EQ(expected_command, ss_received.str());
}

TEST_F(StreamdeckContextKeyPressTestFixture, handle_keydown_switch_in_second_state) {
TEST_F(StreamdeckContextKeyPressTestFixture, handle_keyup_switch_in_second_state) {
payload["state"] = 1;
const std::string action = "com.ctytler.dcs.switch.two-state";
fixture_context.handleButtonEvent(&dcs_interface, KEY_DOWN, action, payload);
fixture_context.handleButtonEvent(&dcs_interface, KEY_UP, action, payload);
const std::stringstream ss_received = mock_dcs.DcsReceive();
std::string expected_command =
"C" + device_id + "," + std::to_string(button_id) + "," + send_when_second_state_value;
EXPECT_EQ(expected_command, ss_received.str());
}

TEST_F(StreamdeckContextKeyPressTestFixture, handle_keyup_switch) {
TEST_F(StreamdeckContextKeyPressTestFixture, handle_keydown_switch) {
const std::string action = "com.ctytler.dcs.switch.two-state";
fixture_context.handleButtonEvent(&dcs_interface, KEY_UP, action, payload);
fixture_context.handleButtonEvent(&dcs_interface, KEY_DOWN, action, payload);
const std::stringstream ss_received = mock_dcs.DcsReceive();
// Expect no command sent (empty string is due to mock socket functionality).
std::string expected_command = "";
EXPECT_EQ(expected_command, ss_received.str());
}

TEST_F(StreamdeckContextKeyPressTestFixture, handle_keydown_switch_empty_value) {
TEST_F(StreamdeckContextKeyPressTestFixture, handle_keyup_switch_empty_value) {
payload["settings"]["send_when_first_state_value"] = "";
const std::string action = "com.ctytler.dcs.switch.two-state";
fixture_context.handleButtonEvent(&dcs_interface, KEY_DOWN, action, payload);
fixture_context.handleButtonEvent(&dcs_interface, KEY_UP, action, payload);
const std::stringstream ss_received = mock_dcs.DcsReceive();
std::string expected_command = "";
EXPECT_EQ(expected_command, ss_received.str());
Expand Down
Binary file modified Sources/com.ctytler.dcs.sdPlugin/dcs_interface.exe
Binary file not shown.
4 changes: 3 additions & 1 deletion Sources/com.ctytler.dcs.sdPlugin/helpDocs/helpContents.md
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,8 @@ The momentary buttons are used to send commands to clickabledata items of BTN ty

_Note: Repetition of sent value while button is held pressed is not supported at this time._

**Use with Axis (LEV) Type** -- Some of the radio frequency and comms channel selectors are designed to accept axis input, and they expect a single value to increment their value. For example a volume knob with limits 0,1 can be rotated with small rotations by setting "Send Value while Pressed" to `0.01` and disabling the release value (as the release value will just interfere). To rotate a greater amount per press, increase the send value.

### Increment Settings

Increment buttons are used for clickabledata items of TUMB type, which are rotary dials or other items that have multiple values you want to iterate through.
Expand Down Expand Up @@ -167,7 +169,7 @@ _Click Value_ -- The value sent for a left/right click of the mouse. May have di
- BTN: Click value is generally the "pressed" button value (usually 1).
- TUMB (Rotary Knobs): This is the value the knob will be incremented within the range (usually +/-0.1).
- TUMB (Switches): This is the value the switch will be incremented within the range on click (usually +/-1, but sometimes +/-0.1 also).
- LEV: Click value is the increment value within the range, however is often listed as 0 in the table. For these you will need to manually enter a Send value in Command settings that provides a desirable response.
- LEV: Click value is the increment value within the range, however is often listed as 0 in the table. For these you will need to manually enter a Send value in Command settings that provides a desirable response. (Note: depending on the item you will need either an increment type to vary the values sent, or a momentary button with the "Send on Release" disabled to provide a constant increase/decrease value).

_Limit Min_ -- The minimum value the item can be commanded to.
_Limit Max_ -- The maximum value the item can be commanded to.
Expand Down
10 changes: 9 additions & 1 deletion Sources/com.ctytler.dcs.sdPlugin/helpDocs/helpWindow.html
Original file line number Diff line number Diff line change
Expand Up @@ -84,7 +84,8 @@ <h2 id="dcs-command-on-button-press-settings">DCS Command (on Button Press) Sett
<p>The remaining settings differ according to what type of DCS Interface button is used and can be categorized into
Momentary, Increment, and Switch.</p>
<h3 id="momentary-button-settings">Momentary Button settings</h3>
<p>The momentary buttons are used to send commands to clickabledata items of BTN type.</p>
<p>The momentary buttons are used to send commands to clickabledata items of BTN type, or LEV type in special cases,
see note below.</p>
<p><strong>Send Value while Pressed</strong> -- This is the value that is sent immediately upon pressing the
Streamdeck button. By default most buttons will use <code>1</code> for a pressed value. Sometimes pairs of
buttons (such as up/down arrow buttons on a panel) will have <code>1</code> as the pressed value for the up
Expand All @@ -96,6 +97,11 @@ <h3 id="momentary-button-settings">Momentary Button settings</h3>
<p><strong>Disable (Send Release) Check</strong> -- The checkbox next to the &quot;Send Value while Released&quot;
will disable any command being sent when releasing a button.</p>
<p><em>Note: Repetition of sent value while button is held pressed is not supported at this time.</em></p>
<p><strong>Use with Axis (LEV) Type</strong> -- Some of the radio frequency and comms channel selectors are designed
to accept axis input, and they expect a single value to increment their value. For example a volume knob with
limits 0,1 can be rotated with small rotations by setting "Send Value while Pressed" to `0.01` and disabling the
release value (as the release value will just interfere). To rotate a greater amount per press, increase the
send value.</p>
<h3 id="increment-settings">Increment Settings</h3>
<p>Increment buttons are used for clickabledata items of TUMB type, which are rotary dials or other items that have
multiple values you want to iterate through.</p>
Expand Down Expand Up @@ -238,6 +244,8 @@ <h2 id="aircraft-module-clickabledata">Aircraft Module Clickabledata</h2>
but sometimes +/-0.1 also).</li>
<li>LEV: Click value is the increment value within the range, however is often listed as 0 in the table. For
these you will need to manually enter a Send value in Command settings that provides a desirable response.
(Note: depending on the item you will need either an increment type to vary the values sent, or a momentary
button with the "Send on Release" disabled to provide a constant increase/decrease value).
</li>
</ul>
<p><em>Limit Min</em> -- The minimum value the item can be commanded to.<br>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -157,7 +157,8 @@
<b>TUMB:</b> Rotary/Switch - use increment to advance within range, or switch to select two
values<br />
<b>LEV:</b> Axis - use increment to advance within range, increment value can be adjusted
for sensitivity
for sensitivity. Some LEV items will need to use the momentary button with the &quot;Send on
Release&quot; disabled.
</p>
</div>
</div>
Expand Down