Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
27 commits
Select commit Hold shift + click to select a range
68055fd
HttpServer in CMakeLists.txt
ConnorC432 Mar 24, 2026
b98a9ce
HTTP/WS server init
ConnorC432 Mar 24, 2026
0003637
on_frame sends frames to websocket
ConnorC432 Mar 24, 2026
792e370
Merge branch 'main' into http-api
ConnorC432 Mar 29, 2026
686f4fc
HTTP API setting option
ConnorC432 Mar 29, 2026
87b1126
websocket test script
ConnorC432 Mar 29, 2026
fa55d08
Merge branch 'main' into http-api
ConnorC432 Apr 8, 2026
41bf5d1
refactor capture card websocket
ConnorC432 Apr 8, 2026
85b45aa
list game categories method
ConnorC432 Apr 8, 2026
2b9ad4d
better addRoute method
ConnorC432 Apr 8, 2026
cade547
server init moved out of main
ConnorC432 Apr 8, 2026
fad5565
add program registry and routes for HTTP API
ConnorC432 Apr 8, 2026
15e474f
add global settings routes to HTTP API
ConnorC432 Apr 8, 2026
b7f5cfa
API enable option is experimental
ConnorC432 Apr 8, 2026
d6e1264
chmod +x test_websocket.py
ConnorC432 Apr 8, 2026
e31b9c9
better API settings
ConnorC432 Apr 8, 2026
0a5d821
docs markdown
ConnorC432 Apr 8, 2026
6dce635
clean up comments and unused includes
ConnorC432 Apr 8, 2026
35a6d05
add Discord link to API documentation
ConnorC432 Apr 8, 2026
a85d89b
this looks a bit better
ConnorC432 Apr 8, 2026
1c836b9
clearer docs
ConnorC432 Apr 8, 2026
18c568e
Merge remote-tracking branch 'origin/http-api' into http-api
ConnorC432 Apr 8, 2026
883e77e
remove redundant connection sections in API docs
ConnorC432 Apr 8, 2026
28e1ef5
add qtwebsockets and qthttpserver to CI
ConnorC432 Apr 8, 2026
0ec0704
enable api by default for now
ConnorC432 Apr 8, 2026
9fb1321
update clientCount return type to qsizetype to hopefully fix mac build
ConnorC432 Apr 8, 2026
27d3fa8
this may or may not fix windows build?
ConnorC432 Apr 8, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 4 additions & 4 deletions .azure-pipelines/azure-pipelines.yml
Original file line number Diff line number Diff line change
Expand Up @@ -116,7 +116,7 @@ stages:
compiler: 'MSVC'
qt_version: '6.10.2'
qt_version_major: '6'
qt_modules: 'qtserialport qtmultimedia'
qt_modules: 'qtserialport qtmultimedia qthttpserver qtwebsockets'
cmake_preset: 'RelWithDebInfo'
cmake_version_params: '-DVERSION_MAJOR=${{ parameters.versionMajor }} -DVERSION_MINOR=${{ parameters.versionMinor }} -DVERSION_PATCH=${{ parameters.versionPatch }} -DIS_BETA=${{ lower(eq(parameters.buildType, ''PrivateBeta'')) }}'
cmake_additional_param: '-DCMAKE_PREFIX_PATH=C:/Qt/$(qt_version)/msvc2022_64/lib/cmake -DIS_AZURE_BUILD=TRUE'
Expand Down Expand Up @@ -305,7 +305,7 @@ stages:
compiler: 'GCC'
qt_version: '6.10.2'
qt_version_major: '6'
qt_modules: 'qtserialport qtmultimedia'
qt_modules: 'qtserialport qtmultimedia qthttpserver qtwebsockets'
cmake_preset: 'RelWithDebInfo'
linux_path: '/opt/Qt/$(qt_version)/gcc_64/lib/cmake:/opt/Qt/$(qt_version)/gcc_64/bin:/opt/Qt/$(qt_version)/gcc_64/plugins:$PATH'
cmake_version_params: '-DVERSION_MAJOR=${{ parameters.versionMajor }} -DVERSION_MINOR=${{ parameters.versionMinor }} -DVERSION_PATCH=${{ parameters.versionPatch }} -DIS_BETA=${{ lower(eq(parameters.buildType, ''PrivateBeta'')) }}'
Expand Down Expand Up @@ -470,7 +470,7 @@ stages:
compiler: Clang
qt_version: '6.10.2'
qt_version_major: '6'
qt_modules: 'qtserialport qtmultimedia'
qt_modules: 'qtserialport qtmultimedia qthttpserver qtwebsockets'
cmake_preset: RelWithDebInfo
macos_path: '/opt/Qt/6.10.2/macos/lib/cmake:/opt/Qt/6.10.2/macos/bin:/opt/homebrew/bin:/opt/homebrew/sbin:/opt/homebrew:$PATH'
cmake_version_params: '-DVERSION_MAJOR=${{ parameters.versionMajor }} -DVERSION_MINOR=${{ parameters.versionMinor }} -DVERSION_PATCH=${{ parameters.versionPatch }} -DIS_BETA=${{ lower(eq(parameters.buildType, ''PrivateBeta'')) }}'
Expand All @@ -492,7 +492,7 @@ stages:
compiler: Clang
qt_version: '6.10.2'
qt_version_major: '6'
qt_modules: 'qtserialport qtmultimedia'
qt_modules: 'qtserialport qtmultimedia qthttpserver qtwebsockets'
cmake_preset: RelWithDebInfo
macos_path: '/usr/local/Qt/6.10.2/macos/lib/cmake:/usr/local/Qt/6.10.2/macos/bin:/usr/local/bin:/usr/local/sbin:/usr/local:$PATH'
cmake_version_params: '-DVERSION_MAJOR=${{ parameters.versionMajor }} -DVERSION_MINOR=${{ parameters.versionMinor }} -DVERSION_PATCH=${{ parameters.versionPatch }} -DIS_BETA=${{ lower(eq(parameters.buildType, ''PrivateBeta'')) }}'
Expand Down
2 changes: 1 addition & 1 deletion .github/workflows/cpp-ci-serial-programs-base.yml
Original file line number Diff line number Diff line change
Expand Up @@ -56,7 +56,7 @@ jobs:
uses: jurplel/install-qt-action@v4
with:
version: '6.10.2'
modules: 'qtmultimedia qtserialport'
modules: 'qtmultimedia qtserialport qthttpserver qtwebsockets'

- name: Install dependencies (Ubuntu)
if: startsWith(inputs.os, 'ubuntu')
Expand Down
8 changes: 4 additions & 4 deletions SerialPrograms/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -78,10 +78,10 @@ if(WIN32 AND QT_DEPLOY_FILES)
if(QT_CANDIDATE_DIR AND EXISTS "${QT_CANDIDATE_DIR}")
message(STATUS "Using preferred Qt directory for Qt${QT_MAJOR} ${PREFERRED_QT_VER}: ${QT_CANDIDATE_DIR}")
list(APPEND CMAKE_PREFIX_PATH "${QT_CANDIDATE_DIR}")
find_package(Qt${QT_MAJOR} ${PREFERRED_QT_VER} COMPONENTS Widgets SerialPort Multimedia MultimediaWidgets REQUIRED)
find_package(Qt${QT_MAJOR} ${PREFERRED_QT_VER} COMPONENTS Widgets SerialPort Multimedia MultimediaWidgets HttpServer WebSockets REQUIRED)
else()
# Find all subdirectories in the Qt base directory
find_package(Qt${QT_MAJOR} COMPONENTS Widgets SerialPort Multimedia MultimediaWidgets REQUIRED)
find_package(Qt${QT_MAJOR} COMPONENTS Widgets SerialPort Multimedia MultimediaWidgets HttpServer WebSockets REQUIRED)
file(GLOB QT_SUBDIRS LIST_DIRECTORIES true "${QT_BASE_DIR}/${QT_MAJOR}*")

# Filter and sort the directories to find the latest version
Expand Down Expand Up @@ -109,7 +109,7 @@ if(WIN32 AND QT_DEPLOY_FILES)
REQUIRED
)
else()
find_package(Qt${QT_MAJOR} COMPONENTS Widgets SerialPort Multimedia MultimediaWidgets REQUIRED)
find_package(Qt${QT_MAJOR} COMPONENTS Widgets SerialPort Multimedia MultimediaWidgets HttpServer WebSockets REQUIRED)
endif()

# disable deprecated Qt APIs
Expand Down Expand Up @@ -188,7 +188,7 @@ endif()
# Function to apply common properties to both library and executable targets
function(apply_common_target_properties target_name)
set_target_properties(${target_name} PROPERTIES LINKER_LANGUAGE CXX)
target_link_libraries(${target_name} PRIVATE Qt${QT_MAJOR}::Widgets Qt${QT_MAJOR}::SerialPort Qt${QT_MAJOR}::Multimedia Qt${QT_MAJOR}::MultimediaWidgets)
target_link_libraries(${target_name} PRIVATE Qt${QT_MAJOR}::Widgets Qt${QT_MAJOR}::SerialPort Qt${QT_MAJOR}::Multimedia Qt${QT_MAJOR}::MultimediaWidgets Qt${QT_MAJOR}::HttpServer Qt${QT_MAJOR}::WebSockets)
target_link_libraries(${target_name} PRIVATE Threads::Threads)

#add defines
Expand Down
28 changes: 28 additions & 0 deletions SerialPrograms/Scripts/test_websocket.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
import asyncio
import websockets
import io
from PIL import Image
import numpy as np
import cv2

WS_URL = "ws://127.0.0.1:8081"
async def handle_ws():
async with websockets.connect(WS_URL) as ws:
print(f"Connected to {WS_URL}")
while True:
try:
message = await ws.recv()
if isinstance(message, str):
print(message)
elif isinstance(message, bytes):
try:
image = Image.open(io.BytesIO(message))
cv_image = cv2.cvtColor(np.array(image), cv2.COLOR_RGB2BGR)
cv2.imshow("WebSocket Image", cv_image)
cv2.waitKey(1)
except Exception as e:
print(e)
except websockets.ConnectionClosed:
break

asyncio.run(handle_ws())
3 changes: 3 additions & 0 deletions SerialPrograms/Source/CommonFramework/GlobalSettingsPanel.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@
#include "CommonFramework/VideoPipeline/VideoPipelineOptions.h"
#include "CommonFramework/ErrorReports/ErrorReports.h"
#include "Integrations/DiscordSettingsOption.h"
#include "CommonFramework/Options/Environment/APIOptions.h"
//#include "CommonFramework/Environment/Environment.h"
#include "GlobalSettingsPanel.h"

Expand Down Expand Up @@ -241,6 +242,7 @@ GlobalSettings::GlobalSettings()
LockMode::LOCK_WHILE_RUNNING,
"", ""
)
, API(CONSTRUCT_TOKEN)
{
PA_ADD_OPTION(OPEN_BASE_FOLDER_BUTTON);
PA_ADD_OPTION(CHECK_FOR_UPDATES);
Expand Down Expand Up @@ -285,6 +287,7 @@ GlobalSettings::GlobalSettings()
#endif

PA_ADD_OPTION(DEVELOPER_TOKEN);
PA_ADD_OPTION(API);

GlobalSettings::on_config_value_changed(this);
ENABLE_LIFETIME_SANITIZER0.add_listener(*this);
Expand Down
3 changes: 3 additions & 0 deletions SerialPrograms/Source/CommonFramework/GlobalSettingsPanel.h
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@ class PerformanceOptions;
class AudioPipelineOptions;
class VideoPipelineOptions;
class ErrorReportOption;
class APIOptions;



Expand Down Expand Up @@ -156,6 +157,8 @@ class GlobalSettings : public BatchOption, private ConfigOption::Listener, priva

StringOption DEVELOPER_TOKEN;

Pimpl<APIOptions> API;

// The mode that does not run Qt GUI, but instead runs some tests for
// debugging, unit testing and developing purposes.
bool COMMAND_LINE_TEST_MODE = false;
Expand Down
4 changes: 4 additions & 0 deletions SerialPrograms/Source/CommonFramework/Main.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@
#include "ControllerInput/ControllerInput.h"
#include "Integrations/DiscordWebhook.h"
#include "Windows/MainWindow.h"
#include "Server/InitialiseServer.h"

#include <iostream>
using std::cout;
Expand Down Expand Up @@ -191,6 +192,9 @@ int run_program(int argc, char *argv[]){
w.raise(); // bring the window to front on macOS
set_permissions(w);

// Start HTTP/WS Server if enabled
Server::init_server();

return application.exec();
}

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
/* API Options
*
* From: https://github.com/PokemonAutomation/
*
*/

#include "APIOptions.h"
#include "CommonFramework/Panels/PanelTools.h"

namespace PokemonAutomation{

APIOptions::APIOptions()
: GroupOption(
"API",
LockMode::LOCK_WHILE_RUNNING,
GroupOption::EnableMode::ALWAYS_ENABLED, true
)
, DESCRIPTION(
"<font color=\"orange\">Note: The API is experimental and not all features are fully implemented.</font><br><br>"
"<font color=\"red\">Warning: The API is not secure and should not be exposed to the internet.</font><br><br>"
"After changing these settings, you must restart the program for them to take effect."
)
, ENABLE_API(
"<b>Enable API:</b><br>"
"Enable the HTTP API and WebSockets to control the program remotely.<br>",
LockMode::UNLOCK_WHILE_RUNNING,
true
)
, HTTP_PORT(
"<b>HTTP Port:</b><br>"
"This can't be the same as the WebSocket Port<br>",
LockMode::UNLOCK_WHILE_RUNNING,
8080, 1025, 65535
)
, WS_PORT(
"<b>WebSocket Port:</b><br>"
"This can't be the same as the HTTP Port<br>",
LockMode::UNLOCK_WHILE_RUNNING,
8081, 1025, 65535
)
{
PA_ADD_STATIC(DESCRIPTION);
PA_ADD_OPTION(ENABLE_API);
PA_ADD_OPTION(HTTP_PORT);
PA_ADD_OPTION(WS_PORT);
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
/* API Options
*
* From: https://github.com/PokemonAutomation/
*
*/

#ifndef PokemonAutomation_APIOptions_H
#define PokemonAutomation_APIOptions_H

#include "Common/Cpp/Options/GroupOption.h"
#include "Common/Cpp/Options/BooleanCheckBoxOption.h"
#include "Common/Cpp/Options/SimpleIntegerOption.h"
#include "Common/Cpp/Options/StaticTextOption.h"

namespace PokemonAutomation{

class APIOptions : public GroupOption{
public:
APIOptions();

public:
StaticTextOption DESCRIPTION;
BooleanCheckBoxOption ENABLE_API;
SimpleIntegerOption<uint16_t> HTTP_PORT;
SimpleIntegerOption<uint16_t> WS_PORT;
};

}

#endif
1 change: 1 addition & 0 deletions SerialPrograms/Source/CommonFramework/Panels/PanelList.h
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ class PanelListDescriptor{
bool enabled() const{ return m_enabled; }

PanelListWidget* make_QWidget(QWidget& parent, PanelHolder& holder) const;
std::vector<PanelEntry> get_panels() const { return make_panels(); }

protected:
virtual std::vector<PanelEntry> make_panels() const = 0;
Expand Down
20 changes: 20 additions & 0 deletions SerialPrograms/Source/CommonFramework/Panels/ProgramRegistry.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
/* Program Registry
*
* From: https://github.com/PokemonAutomation/
*
*/

#include "ProgramRegistry.h"

namespace PokemonAutomation{

ProgramRegistry& ProgramRegistry::instance(){
static ProgramRegistry instance;
return instance;
}

void ProgramRegistry::add_category(std::unique_ptr<PanelListDescriptor> category){
m_categories.emplace_back(std::move(category));
}

}
33 changes: 33 additions & 0 deletions SerialPrograms/Source/CommonFramework/Panels/ProgramRegistry.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
/* Program Registry
*
* From: https://github.com/PokemonAutomation/
*
*/

#ifndef PokemonAutomation_ProgramRegistry_H
#define PokemonAutomation_ProgramRegistry_H

#include <vector>
#include <memory>
#include <string>
#include "CommonFramework/Panels/PanelList.h"

namespace PokemonAutomation{

class ProgramRegistry{
public:
static ProgramRegistry& instance();

void add_category(std::unique_ptr<PanelListDescriptor> category);
const std::vector<std::unique_ptr<PanelListDescriptor>>& categories() const { return m_categories; }

private:
ProgramRegistry() = default;

private:
std::vector<std::unique_ptr<PanelListDescriptor>> m_categories;
};

}

#endif
Loading
Loading