Add support for 25 light panels (SDK side).

master
Glenn Maynard 6 years ago
parent 55443cccb8
commit 3d5be3017b
  1. 3
      sdk/SMX.h
  2. 12
      sdk/Windows/SMX.cpp
  3. 6
      sdk/Windows/SMXDevice.cpp
  4. 1
      sdk/Windows/SMXDevice.h
  5. 149
      sdk/Windows/SMXManager.cpp

@ -69,7 +69,8 @@ SMX_API uint16_t SMX_GetInputState(int pad);
SMX_API void SMX_SetLights(const char lightData[864]);
// This is the same as SMX_SetLights, but specifies the size of the buffer. The
// buffer size must be 864 bytes (2 pads * 9 panels * 16 lights * 3).
// buffer size must be either 864 bytes (2 pads * 9 panels * 16 lights * 3) or
// 1350 bytes (2 pads * 9 panels * 25 lights * 3).
SMX_API void SMX_SetLights2(const char *lightData, int lightDataSize);
// By default, the panels light automatically when stepped on. If a lights command is sent by

@ -70,17 +70,25 @@ SMX_API void SMX_SetLights(const char lightData[864])
}
SMX_API void SMX_SetLights2(const char *lightData, int lightDataSize)
{
// The lightData into data per pad.
// The lightData into data per pad depending on whether we've been
// given 16 or 25 lights of data.
string lights[2];
const int BytesPerPad16 = 9*16*3;
const int BytesPerPad25 = 9*25*3;
if(lightDataSize == 2*BytesPerPad16)
{
lights[0] = string(lightData, BytesPerPad16);
lights[1] = string(lightData + BytesPerPad16, BytesPerPad16);
}
else if(lightDataSize == 2*BytesPerPad25)
{
lights[0] = string(lightData, BytesPerPad25);
lights[1] = string(lightData + BytesPerPad25, BytesPerPad25);
}
else
{
Log(ssprintf("SMX_SetLights2: lightDataSize is invalid (must be %ii)\n", 2*BytesPerPad16));
Log(ssprintf("SMX_SetLights2: lightDataSize is invalid (must be %i or %i)\n",
2*BytesPerPad16, 2*BytesPerPad25));
return;
}

@ -162,6 +162,12 @@ bool SMX::SMXDevice::IsPlayer2Locked() const
bool SMX::SMXDevice::GetConfig(SMXConfig &configOut)
{
LockMutex Lock(m_Lock);
return GetConfigLocked(configOut);
}
bool SMX::SMXDevice::GetConfigLocked(SMXConfig &configOut)
{
m_Lock.AssertLockedByCurrentThread();
// If SetConfig was called to write a new configuration but we haven't sent it
// yet, return it instead of the configuration we read alst, so GetConfig

@ -54,6 +54,7 @@ public:
// Get the configuration of the connected device (or the most recently read configuration if
// we're not connected).
bool GetConfig(SMXConfig &configOut);
bool GetConfigLocked(SMXConfig &configOut);
// Set the configuration of the connected device.
//

@ -265,29 +265,59 @@ void SMX::SMXManager::SetLights(const string sPanelLights[2])
// 89AB 89AB 89AB
// CDEF CDEF CDEF
//
// Set sLightsCommand[iPad][0] to include 0123 4567, and [1] to 89AB CDEF.
string sLightCommands[2][2]; // sLightCommands[command][pad]
// If we're on a 25-light device, we have an additional grid of 3x3 LEDs:
//
//
// x x x x
// 0 1 2
// x x x x
// 3 4 5
// x x x x
// 6 7 8
// x x x x
//
// Set sLightsCommand[iPad][0] to include 0123 4567, [1] to 89AB CDEF,
// and [2] to the 3x3 grid.
string sLightCommands[3][2]; // sLightCommands[command][pad]
// Read the linearly arranged color data we've been given and split it into top and
// bottom commands for each pad.
for(int iPad = 0; iPad < 2; ++iPad)
{
// If there's no data for this pad, leave the command empty.
const string &sLightsDataForPad = sPanelLights[iPad];
string sLightsDataForPad = sPanelLights[iPad];
if(sLightsDataForPad.empty())
continue;
// Sanity check the lights data. It should have 9*4*4*3 bytes of data: RGB for each of 4x4
// LEDs on 9 panels.
if(sPanelLights[iPad].size() != 9*16*3)
// Sanity check the lights data. For 4x4 lights, it should have 9*4*4*3 bytes of
// data: RGB for each of 4x4 LEDs on 9 panels. For 25-light panels there should
// be 4x4+3x3 (25) lights of data.
int LightSize4x4 = 9*4*4*3;
int LightSize25 = 9*5*5*3;
if(sLightsDataForPad.size() != LightSize4x4 && sLightsDataForPad.size() != LightSize25)
{
Log(ssprintf("SetLights: Lights data should be %i bytes, received %i", 3*3*16*3, sPanelLights[iPad].size()));
Log(ssprintf("SetLights: Lights data should be %i or %i bytes, received %i",
LightSize4x4, LightSize25, sLightsDataForPad.size()));
continue;
}
// The 2 command sends the top 4x2 lights, and 3 sends the bottom 4x2.
sLightCommands[0][iPad] = "2";
sLightCommands[1][iPad] = "3";
// If we've been given 16 lights, pad to 25.
if(sLightsDataForPad.size() == LightSize4x4)
sLightsDataForPad.append(LightSize25 - LightSize4x4, '\0');
// Lights are sent in three commands:
//
// 4: the 3x3 inner grid
// 2: the top 4x2 lights
// 3: the bottom 4x2 lights
//
// Command 4 is only used by firmware version 4+.
//
// Always send all three commands if the firmware expects it, even if we've
// been given 4x4 data.
sLightCommands[0][iPad] = "4";
sLightCommands[1][iPad] = "2";
sLightCommands[2][iPad] = "3";
int iNextInputByte = 0;
auto scaleLight = [](uint8_t iColor) {
// Apply color scaling. Values over about 170 don't make the LEDs any brighter, so this
@ -302,13 +332,22 @@ void SMX::SMXManager::SetLights(const string sPanelLights[2])
uint8_t iColor = sLightsDataForPad[iNextInputByte++];
iColor = scaleLight(iColor);
int iCommandIndex = iByte < 4*2*3? 0:1;
int iCommandIndex = iByte < 4*2*3? 1:2;
sLightCommands[iCommandIndex][iPad].append(1, iColor);
}
// Create the 4 command.
for(int iByte = 0; iByte < 3*3*3; ++iByte)
{
uint8_t iColor = sLightsDataForPad[iNextInputByte++];
iColor = scaleLight(iColor);
sLightCommands[0][iPad].append(1, iColor);
}
}
sLightCommands[0][iPad].push_back('\n');
sLightCommands[1][iPad].push_back('\n');
sLightCommands[2][iPad].push_back('\n');
}
// Each update adds one entry to m_aPendingLightsCommands for each lights command.
@ -325,40 +364,92 @@ void SMX::SMXManager::SetLights(const string sPanelLights[2])
// Note that m_aPendingLightsCommands contains the update for both pads, to guarantee
// we always send light updates for both pads together and they never end up out of
// phase.
if(m_aPendingLightsCommands.size() < 3)
{
static const double fDelayBetweenLightsCommands = 1/60.0;
// There's a subtle but important difference between command timing in
// firmware version 4 compared to earlier versions:
//
// Earlier firmwares would process host commands as soon as they're received.
// Because of this, we have to wait before sending the '3' command to give
// the master controller time to finish sending the '2' command to panels.
// If we don't do this everything will still work, but the master will block
// while processing the second command waiting for panel data to finish sending
// since the TX queue will be full. If this happens it isn't processing HID
// data, which reduces input timing accuracy.
//
// Firmware version 4 won't process a host command if there's data still being
// sent to the panels. It'll wait until the data is flushed. This means that
// we can queue all three lights commands at once, and just send them as fast
// as the host acknowledges them. The second command will sit around on the
// master controller's buffer until it finishes sending the first command to
// the panels, then the third command will do the same.
//
// This change is only needed due to the larger amount of data sent in 25-light
// mode. Since we're spending more time sending data from the master to the
// panels, the timing requirements are tighter. Doing it in the same manual-delay
// fashion causes too much latency and makes it harder to maintain 30 FPS.
//
// If two controllers are connected, they should either both be 4+ or not. We
// don't handle the case where they're different and both timings are needed.
double fNow = GetMonotonicTime();
double fSendCommandAt = max(fNow, m_fDelayLightCommandsUntil);
float fFirstCommandTime = fSendCommandAt;
float fSecondCommandTime = fFirstCommandTime + fDelayBetweenLightsCommands;
double fCommandTimes[3] = { fNow, fNow, fNow };
// Update m_fDelayLightCommandsUntil, so we know when the next
m_fDelayLightCommandsUntil = fSecondCommandTime + fDelayBetweenLightsCommands;
bool masterIsV4 = false;
for(int iPad = 0; iPad < 2; ++iPad)
{
SMXConfig config;
if(m_pDevices[iPad]->GetConfigLocked(config) && config.masterVersion >= 4)
masterIsV4 = true;
}
// Add two commands to the list, scheduled at fFirstCommandTime and fSecondCommandTime.
m_aPendingLightsCommands.push_back(PendingCommand(fFirstCommandTime));
m_aPendingLightsCommands.push_back(PendingCommand(fSecondCommandTime));
// Log(ssprintf("Scheduled commands at %f and %f", fFirstCommandTime, fSecondCommandTime));
// If we're on master firmware < 4, set delay times. For 4+, just queue commands.
// We don't need to set fCommandTimes[0] since the '4' packet won't be sent.
if(!masterIsV4)
{
const double fDelayBetweenLightsCommands = 1/60.0;
fCommandTimes[1] = fSendCommandAt;
fCommandTimes[2] = fCommandTimes[0] + fDelayBetweenLightsCommands;
}
// Update m_fDelayLightCommandsUntil, so we know when the next
// lights command can be sent.
m_fDelayLightCommandsUntil = fSendCommandAt + 1/30.0f;
// Wake up the I/O thread if it's blocking on WaitForMultipleObjectsEx.
SetEvent(m_hEvent->value());
// Add three commands to the list, scheduled at fFirstCommandTime and fSecondCommandTime.
m_aPendingLightsCommands.push_back(PendingCommand(fCommandTimes[0]));
m_aPendingLightsCommands.push_back(PendingCommand(fCommandTimes[1]));
m_aPendingLightsCommands.push_back(PendingCommand(fCommandTimes[2]));
}
// Set the pad commands.
PendingCommand *pPendingCommands[2];
pPendingCommands[0] = &m_aPendingLightsCommands[m_aPendingLightsCommands.size()-2]; // 2
pPendingCommands[1] = &m_aPendingLightsCommands[m_aPendingLightsCommands.size()-1]; // 3
for(int iPad = 0; iPad < 2; ++iPad)
{
// If the command for this pad is empty, leave any existing pad command alone.
if(sLightCommands[0][iPad].empty())
continue;
m_aPendingLightsCommands[0]->sPadCommand[iPad] = sLightCommands[0][iPad];
m_aPendingLightsCommands[1]->sPadCommand[iPad] = sLightCommands[1][iPad];
SMXConfig config;
if(!m_pDevices[iPad]->GetConfigLocked(config))
continue;
// If this pad is firmware version 4, send the 4 command. Otherwise, leave the 4 command
// empty and no command will be sent.
PendingCommand *pPending4Commands = &m_aPendingLightsCommands[m_aPendingLightsCommands.size()-3]; // 3
if(config.masterVersion >= 4)
pPending4Commands->sPadCommand[iPad] = sLightCommands[0][iPad];
else
pPending4Commands->sPadCommand[iPad] = "";
PendingCommand *pPending2Commands = &m_aPendingLightsCommands[m_aPendingLightsCommands.size()-2]; // 2
pPending2Commands->sPadCommand[iPad] = sLightCommands[1][iPad];
PendingCommand *pPending3Commands = &m_aPendingLightsCommands[m_aPendingLightsCommands.size()-1]; // 3
pPending3Commands->sPadCommand[iPad] = sLightCommands[2][iPad];
}
// Wake up the I/O thread if it's blocking on WaitForMultipleObjectsEx.
SetEvent(m_hEvent->value());
}
void SMX::SMXManager::ReenableAutoLights()

Loading…
Cancel
Save