Skip to content

Commit 29d9c3e

Browse files
committed
Add preliminary SBB3 support.
1 parent 4940029 commit 29d9c3e

File tree

8 files changed

+589
-17
lines changed

8 files changed

+589
-17
lines changed

SerialPrograms/CMakeLists.txt

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -903,6 +903,9 @@ file(GLOB MAIN_SOURCES
903903
Source/NintendoSwitch/Controllers/SysbotBase/SysbotBase_ProController.cpp
904904
Source/NintendoSwitch/Controllers/SysbotBase/SysbotBase_ProController.h
905905
Source/NintendoSwitch/Controllers/SysbotBase/SysbotBase_SelectorWidget.h
906+
Source/NintendoSwitch/Controllers/SysbotBase/SysbotBase3_ControllerState.h
907+
Source/NintendoSwitch/Controllers/SysbotBase/SysbotBase3_ProController.cpp
908+
Source/NintendoSwitch/Controllers/SysbotBase/SysbotBase3_ProController.h
906909
Source/NintendoSwitch/DevPrograms/BoxDraw.cpp
907910
Source/NintendoSwitch/DevPrograms/BoxDraw.h
908911
Source/NintendoSwitch/DevPrograms/JoyconProgram.cpp
Lines changed: 76 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,76 @@
1+
/* Controller State
2+
*
3+
* From: https://github.com/PokemonAutomation/
4+
*
5+
*/
6+
7+
#ifndef PokemonAutomation_Sysbotbase3_ControllerState_H
8+
#define PokemonAutomation_Sysbotbase3_ControllerState_H
9+
10+
#include <stdint.h>
11+
12+
namespace PokemonAutomation{
13+
namespace NintendoSwitch{
14+
15+
16+
17+
struct Sysbotbase3_ControllerState{
18+
uint64_t buttons = 0;
19+
int16_t left_joystick_x = 0;
20+
int16_t left_joystick_y = 0;
21+
int16_t right_joystick_x = 0;
22+
int16_t right_joystick_y = 0;
23+
24+
bool is_neutral() const{
25+
return buttons == 0
26+
&& left_joystick_x == 0
27+
&& left_joystick_y == 0
28+
&& right_joystick_x == 0
29+
&& right_joystick_y == 0;
30+
}
31+
void clear(){
32+
buttons = 0;
33+
left_joystick_x = 0;
34+
left_joystick_y = 0;
35+
right_joystick_x = 0;
36+
right_joystick_y = 0;
37+
}
38+
};
39+
40+
41+
42+
struct Sysbotbase3_ControllerCommand{
43+
uint64_t seqnum;
44+
uint64_t milliseconds;
45+
Sysbotbase3_ControllerState state;
46+
47+
void write_to_hex(char str[64]) const{
48+
const char HEX_DIGITS[] = "0123456789abcdef";
49+
const char* ptr = (const char*)this;
50+
for (size_t c = 0; c < 64; c += 2){
51+
uint8_t hi = (uint8_t)ptr[0] >> 4;
52+
uint8_t lo = (uint8_t)ptr[0] & 0x0f;
53+
str[c + 0] = HEX_DIGITS[hi];
54+
str[c + 1] = HEX_DIGITS[lo];
55+
ptr++;
56+
}
57+
}
58+
void parse_from_hex(const char str[64]){
59+
char* ptr = (char*)this;
60+
for (size_t c = 0; c < 64; c += 2){
61+
char hi = str[c + 0];
62+
char lo = str[c + 1];
63+
hi = hi < 'a' ? hi - '0' : hi - 'a' + 10;
64+
lo = lo < 'a' ? lo - '0' : lo - 'a' + 10;
65+
ptr[0] = hi << 4 | lo;
66+
ptr++;
67+
}
68+
}
69+
};
70+
71+
72+
73+
74+
}
75+
}
76+
#endif
Lines changed: 232 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,232 @@
1+
/* Nintendo Switch Pro Controller (SysbotBase 3)
2+
*
3+
* From: https://github.com/PokemonAutomation/
4+
*
5+
*/
6+
7+
//#include "Common/Cpp/Concurrency/ReverseLockGuard.h"
8+
#include "CommonFramework/GlobalSettingsPanel.h"
9+
#include "Controllers/JoystickTools.h"
10+
#include "SysbotBase3_ControllerState.h"
11+
#include "SysbotBase3_ProController.h"
12+
13+
namespace PokemonAutomation{
14+
namespace NintendoSwitch{
15+
16+
17+
18+
ProController_SysbotBase3::ProController_SysbotBase3(
19+
Logger& logger,
20+
SysbotBase::TcpSysbotBase_Connection& connection
21+
)
22+
: ControllerWithScheduler(logger)
23+
, m_connection(connection)
24+
, m_stopping(false)
25+
, m_next_seqnum(1)
26+
, m_next_expected_seqnum_ack(1)
27+
{
28+
if (!connection.is_ready()){
29+
return;
30+
}
31+
connection.add_listener(*this);
32+
}
33+
ProController_SysbotBase3::~ProController_SysbotBase3(){
34+
m_connection.remove_listener(*this);
35+
}
36+
37+
const ControllerFeatures& ProController_SysbotBase3::controller_features() const{
38+
static const ControllerFeatures features{
39+
// ControllerFeature::TickPrecise, // TODO: Prove it is tick precise.
40+
ControllerFeature::TimingFlexibleMilliseconds,
41+
ControllerFeature::NintendoSwitch_ProController,
42+
// ControllerFeature::NintendoSwitch_DateSkip, // TODO: Prove this works
43+
};
44+
return features;
45+
}
46+
47+
void ProController_SysbotBase3::cancel_all_commands(){
48+
std::lock_guard<std::mutex> lg(m_state_lock);
49+
if (m_stopping){
50+
throw InvalidConnectionStateException();
51+
}
52+
53+
uint64_t queued = m_next_seqnum - m_next_expected_seqnum_ack;
54+
m_next_expected_seqnum_ack = m_next_seqnum;
55+
56+
m_connection.write_data("cqCancel\r\n");
57+
58+
this->clear_on_next();
59+
m_cv.notify_all();
60+
m_logger.log("cancel_all_commands(): Command Queue Size = " + std::to_string(queued), COLOR_DARKGREEN);
61+
}
62+
void ProController_SysbotBase3::replace_on_next_command(){
63+
std::lock_guard<std::mutex> lg(m_state_lock);
64+
if (m_stopping){
65+
throw InvalidConnectionStateException();
66+
}
67+
68+
uint64_t queued = m_next_seqnum - m_next_expected_seqnum_ack;
69+
m_next_expected_seqnum_ack = m_next_seqnum;
70+
71+
m_connection.write_data("cqReplaceOnNext\r\n");
72+
73+
this->clear_on_next();
74+
m_cv.notify_all();
75+
m_logger.log("replace_on_next_command(): Command Queue Size = " + std::to_string(queued), COLOR_DARKGREEN);
76+
}
77+
void ProController_SysbotBase3::wait_for_all(const Cancellable* cancellable){
78+
std::unique_lock<std::mutex> lg(m_state_lock);
79+
if (m_stopping){
80+
throw InvalidConnectionStateException();
81+
}
82+
m_cv.wait(lg, [this]{
83+
return m_stopping || m_next_seqnum == m_next_expected_seqnum_ack;
84+
});
85+
}
86+
87+
void ProController_SysbotBase3::on_receive_data(const void* data, size_t bytes){
88+
const std::string TOKEN = "cqCommandFinished";
89+
std::string message((const char*)data, bytes);
90+
auto pos = message.find(TOKEN);
91+
if (pos == std::string::npos){
92+
return;
93+
}
94+
95+
const char* ptr = message.c_str();
96+
ptr += pos;
97+
ptr += TOKEN.size();
98+
uint64_t parsed;
99+
while (true){
100+
char ch = *ptr;
101+
switch (ch){
102+
case '\0':
103+
return;
104+
case ' ':
105+
case '\t':
106+
ptr++;
107+
continue;
108+
}
109+
parsed = std::atoll(ptr);
110+
break;
111+
}
112+
113+
std::lock_guard<std::mutex> lg(m_state_lock);
114+
115+
// Old ack
116+
if (parsed <= m_next_expected_seqnum_ack){
117+
return;
118+
}
119+
120+
// Ack is ahead of what has been dispatched.
121+
if (parsed >= m_next_seqnum){
122+
return;
123+
}
124+
125+
m_next_expected_seqnum_ack = parsed + 1;
126+
m_cv.notify_all();
127+
128+
if (GlobalSettings::instance().LOG_EVERYTHING){
129+
m_logger.log("Command Finished: " + std::to_string(parsed));
130+
}
131+
}
132+
133+
void ProController_SysbotBase3::push_state(const Cancellable* cancellable, WallDuration duration){
134+
// Must be called inside "m_state_lock".
135+
136+
if (cancellable){
137+
cancellable->throw_if_cancelled();
138+
}
139+
if (m_stopping){
140+
throw InvalidConnectionStateException();
141+
}
142+
143+
// Flags map to: https://github.com/switchbrew/libnx/blob/master/nx/include/switch/services/hid.h#L584
144+
uint64_t nx_button = 0;
145+
if (m_buttons[ 0].is_busy()) nx_button |= (uint64_t)1 << 3; // Y
146+
if (m_buttons[ 1].is_busy()) nx_button |= (uint64_t)1 << 1; // B
147+
if (m_buttons[ 2].is_busy()) nx_button |= (uint64_t)1 << 0; // A
148+
if (m_buttons[ 3].is_busy()) nx_button |= (uint64_t)1 << 2; // X
149+
if (m_buttons[ 4].is_busy()) nx_button |= (uint64_t)1 << 6; // L
150+
if (m_buttons[ 5].is_busy()) nx_button |= (uint64_t)1 << 7; // R
151+
if (m_buttons[ 6].is_busy()) nx_button |= (uint64_t)1 << 8; // ZL
152+
if (m_buttons[ 7].is_busy()) nx_button |= (uint64_t)1 << 9; // ZR
153+
if (m_buttons[ 8].is_busy()) nx_button |= (uint64_t)1 << 11; // -
154+
if (m_buttons[ 9].is_busy()) nx_button |= (uint64_t)1 << 10; // +
155+
if (m_buttons[10].is_busy()) nx_button |= (uint64_t)1 << 4; // L-click
156+
if (m_buttons[11].is_busy()) nx_button |= (uint64_t)1 << 5; // R-click
157+
if (m_buttons[12].is_busy()) nx_button |= (uint64_t)1 << 18; // Home
158+
if (m_buttons[13].is_busy()) nx_button |= (uint64_t)1 << 19; // Capture
159+
if (m_buttons[14].is_busy()) nx_button |= (uint64_t)1 << 13; // Up
160+
if (m_buttons[15].is_busy()) nx_button |= (uint64_t)1 << 14; // Right
161+
if (m_buttons[16].is_busy()) nx_button |= (uint64_t)1 << 15; // Down
162+
if (m_buttons[17].is_busy()) nx_button |= (uint64_t)1 << 16; // Left
163+
if (m_buttons[18].is_busy()) nx_button |= (uint64_t)1 << 24; // Left SL
164+
if (m_buttons[19].is_busy()) nx_button |= (uint64_t)1 << 25; // Left SR
165+
if (m_buttons[20].is_busy()) nx_button |= (uint64_t)1 << 26; // Right SL
166+
if (m_buttons[21].is_busy()) nx_button |= (uint64_t)1 << 27; // Right SR
167+
168+
{
169+
DpadPosition dpad = m_dpad.is_busy() ? m_dpad.position : DPAD_NONE;
170+
SplitDpad split_dpad = convert_unified_to_split_dpad(dpad);
171+
if (split_dpad.up) nx_button |= (uint64_t)1 << 13;
172+
if (split_dpad.right) nx_button |= (uint64_t)1 << 14;
173+
if (split_dpad.down) nx_button |= (uint64_t)1 << 15;
174+
if (split_dpad.left) nx_button |= (uint64_t)1 << 16;
175+
}
176+
177+
int16_t left_x = 0;
178+
int16_t left_y = 0;
179+
int16_t right_x = 0;
180+
int16_t right_y = 0;
181+
if (m_left_joystick.is_busy()){
182+
double fx = JoystickTools::linear_u8_to_float(m_left_joystick.x);
183+
double fy = -JoystickTools::linear_u8_to_float(m_left_joystick.y);
184+
left_x = JoystickTools::linear_float_to_s16(fx);
185+
left_y = JoystickTools::linear_float_to_s16(fy);
186+
}
187+
if (m_right_joystick.is_busy()){
188+
double fx = JoystickTools::linear_u8_to_float(m_right_joystick.x);
189+
double fy = -JoystickTools::linear_u8_to_float(m_right_joystick.y);
190+
right_x = JoystickTools::linear_float_to_s16(fx);
191+
right_y = JoystickTools::linear_float_to_s16(fy);
192+
}
193+
194+
std::unique_lock<std::mutex> lg(m_state_lock, std::adopt_lock_t());
195+
196+
// Wait until there is space.
197+
m_cv.wait(lg, [this]{
198+
return m_stopping || m_next_seqnum - m_next_expected_seqnum_ack < QUEUE_SIZE;
199+
});
200+
201+
if (cancellable){
202+
cancellable->throw_if_cancelled();
203+
}
204+
205+
Sysbotbase3_ControllerCommand command;
206+
command.milliseconds = std::chrono::duration_cast<Milliseconds>(duration).count();
207+
command.seqnum = m_next_seqnum++;
208+
command.state.buttons = nx_button;
209+
command.state.left_joystick_x = left_x;
210+
command.state.left_joystick_y = left_y;
211+
command.state.right_joystick_x = right_x;
212+
command.state.right_joystick_y = right_y;
213+
214+
std::string message;
215+
message.resize(64);
216+
command.write_to_hex(message.data());
217+
message = "ccControllerState " + message + "\r\n";
218+
m_connection.write_data(message);
219+
220+
if (GlobalSettings::instance().LOG_EVERYTHING){
221+
m_logger.log("sys-botbase: " + message);
222+
}
223+
}
224+
225+
226+
227+
228+
229+
230+
231+
}
232+
}

0 commit comments

Comments
 (0)