Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
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
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -98,3 +98,4 @@ Software/GroundTruthAnnotator/yolo/labels/*.cache
# Dev scripts cache
Dev/scripts/__pycache__
packaging/pitrac
*.save
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

what is this?

132 changes: 132 additions & 0 deletions PiTrac/Software/web-server/templates/dashboard.html
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

remove

Original file line number Diff line number Diff line change
@@ -0,0 +1,132 @@
{% extends "base.html" %}

{% block title %}PiTrac Launch Monitor{% endblock %}

{% block extra_css %}
<link rel="stylesheet" href="/static/css/dashboard.css">
{% endblock %}

{% block content %}
<div class="ball-ready-indicator" id="ball-ready-indicator">
<div class="ball-status-icon" id="ball-status-icon">
<div class="ball-icon"></div>
</div>
<div class="ball-status-text">
<div class="ball-status-title" id="ball-status-title">System Status</div>
<div class="ball-status-message" id="ball-status-message">Initializing...</div>

<!-- OpenGolfSim connection badge -->
<div class="sim-badge" id="ogs-badge"
data-connected="{{ 'true' if ogs_connected else 'false' }}">
OpenGolfSim:
<span class="sim-badge__state">
{{ 'Connected' if ogs_connected else 'Disconnected' }}
</span>
</div>

</div>
</div>

<div class="metrics-grid">
<div class="metric-card">
<div class="metric-header">Ball Speed</div>
<div class="metric-value">
<span id="speed">{{ shot.speed }}</span>
<span class="metric-unit">mph</span>
</div>
</div>

<div class="metric-card">
<div class="metric-header">Carry Distance</div>
<div class="metric-value">
<span id="carry">{{ shot.carry }}</span>
<span class="metric-unit">yds</span>
</div>
</div>

<div class="metric-card">
<div class="metric-header">Launch Angle</div>
<div class="metric-value">
<span id="launch_angle">{{ shot.launch_angle }}</span>
<span class="metric-unit">°</span>
</div>
</div>

<div class="metric-card">
<div class="metric-header">Side Angle</div>
<div class="metric-value">
<span id="side_angle">{{ shot.side_angle }}</span>
<span class="metric-unit">°</span>
</div>
</div>

<div class="metric-card">
<div class="metric-header">Back Spin</div>
<div class="metric-value">
<span id="back_spin">{{ shot.back_spin }}</span>
<span class="metric-unit">rpm</span>
</div>
</div>

<div class="metric-card">
<div class="metric-header">Side Spin</div>
<div class="metric-value">
<span id="side_spin">{{ shot.side_spin }}</span>
<span class="metric-unit">rpm</span>
</div>
</div>
</div>

<!-- <div class="message-section">
<div class="result-type" id="result_type">{{ shot.result_type }}</div>
<div class="message" id="message">{{ shot.message }}</div>
</div> -->

<!-- NEW: OpenGolfSim Controls -->
{% if ogs_connected %}
<div class="ogs-controls" id="ogs-controls">
<h2>OpenGolfSim Controls</h2>

<div class="ogs-controls-grid">
<!-- Aim pad (supports hold) -->
<button class="ogs-btn hold" data-command="up">▲</button>
<div></div>
<button class="ogs-btn hold" data-command="club-up">Club +</button>

<button class="ogs-btn hold" data-command="left">◀</button>
<button class="ogs-btn press" data-command="drop">Drop</button>
<button class="ogs-btn hold" data-command="right">▶</button>

<button class="ogs-btn hold" data-command="down">▼</button>
<button class="ogs-btn press" data-command="mulligan">Mulligan</button>
<button class="ogs-btn press" data-command="rehit">Rehit</button>

<div></div>
<button class="ogs-btn press" data-command="club-down">Club -</button>
<div></div>
</div>

<div class="ogs-controls-hint">
Tip: arrows support hold (down/up). Most other buttons use press.
</div>
</div>
{% endif %}

<div class="image-gallery">
<h2>Shot Images</h2>
<div class="image-grid" id="image-grid">
{% for image in shot.images %}
<img src="/images/{{ image }}" alt="Shot {{ loop.index }}" class="shot-image" loading="lazy">
{% endfor %}
</div>
</div>

<div class="controls">
<button class="btn btn-primary" onclick="resetShot()">Reset</button>
<button class="btn btn-secondary" onclick="location.reload()">Refresh</button>
</div>
{% endblock %}

{% block extra_scripts %}
<script src="/static/js/dashboard.js"></script>
{% endblock %}
Original file line number Diff line number Diff line change
Expand Up @@ -295,6 +295,7 @@
"kCamera2ComparisonGain": "0.8",
"kCamera2CalibrateOrLocationGain": "1.0",
"kCamera2Contrast": "1.2",
"kCamera2Saturation": "0.0",
"kCamera2PuttingGain": "1.5",
"kCamera2PuttingContrast": "1.2",
"kCamera1StillShutterTimeuS": "40000",
Expand Down Expand Up @@ -475,10 +476,15 @@
"kGSProConnectPort": "0921"
},
"E6": {
"kE6Comment": "USE CMD LINE - Exampe: --e6_host_address 10.0.0.29",
"kE6Comment": "USE CMD LINE - Example: --e6_host_address 10.0.0.29",
"kE6ConnectAddress": "",
"kE6ConnectPort": "2483",
"kE6InterMessageDelayMs": "50"
},
"OpenGolfSim":{
"kOGSComment":"USE CMD LINE OPTION - Example: --open_golf_sim_host_address 10.0.0.25",
"kOGSConnectAddress": "",
"kOGSConnectPort": "3111"
}
},
"club_data": {
Expand Down
3 changes: 2 additions & 1 deletion Software/LMSourceCode/ImageProcessing/gs_config.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -177,7 +177,8 @@ bool GolfSimConfiguration::ReadValues() {
SetConstant("gs_config.cameras.kCamera1HighFPSGain", LibCameraInterface::kCamera1HighFPSGain);
SetConstant("gs_config.cameras.kCamera1Contrast", LibCameraInterface::kCamera1Contrast);
SetConstant("gs_config.cameras.kCamera2Gain", LibCameraInterface::kCamera2Gain);

SetConstant("gs_config.comeras.kCamera2Saturations", LibCameraInterface::kCamera2Saturation);
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

fix indentation


// Let the command-line gain parameter override the .json config file parameter
// TBD - May want to have separate gain options?
if (GolfSimOptions::GetCommandLineOptions().camera_gain_ > 0.0) {
Expand Down
196 changes: 196 additions & 0 deletions Software/LMSourceCode/ImageProcessing/gs_opengolfsim_interface.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,196 @@
#include <string>
#include <cstdlib>

#include "logging_tools.h"
#include "gs_config.h"
#include "gs_options.h"
#include "gs_ui_system.h"

#include "gs_opengolfsim_interface.h"
#include "gs_opengolfsim_results.h"

namespace golf_sim {

static const int kDefaultOGSPort = 3111;
static const char* kDefaultOGSHost = ""; // empty = disabled by default

static std::string ToString(SimConnState s) {
switch (s) {
case SimConnState::kDisabled: return "Disabled";
case SimConnState::kDisconnected: return "Disconnected";
case SimConnState::kConnecting: return "Connecting";
case SimConnState::kConnected: return "Connected";
case SimConnState::kError: return "Error";
}
return "Unknown";
}

void GsOpenGolfSimInterface::OnConnectionStateChanged(
SimConnState /*from*/, SimConnState to, const std::string& reason)
{
GsIPCResultType uiType = GsIPCResultType::kWaitingForSimulatorArmed;
std::string human;

switch (to) {
case SimConnState::kDisabled:
human = "OpenGolfSim disabled (no host/port configured)";
break;

case SimConnState::kConnecting:
human = "OpenGolfSim connecting to " + socket_connect_address_ + ":" + socket_connect_port_;
break;

case SimConnState::kConnected:
break;

case SimConnState::kDisconnected:
human = "OpenGolfSim disconnected" + (reason.empty() ? "" : (": " + reason));
break;

case SimConnState::kError:
default:
uiType = GsIPCResultType::kError;
human = "OpenGolfSim socket error" + (reason.empty() ? "" : (": " + reason));
break;
}

// machine tag (Python parses this)
std::string state =
(to == SimConnState::kConnected) ? "connected" :
(to == SimConnState::kConnecting) ? "connecting" :
(to == SimConnState::kDisabled) ? "disabled" :
(to == SimConnState::kDisconnected) ? "disconnected" : "error";

std::string tag = "SIM_CONN OpenGolfSim state=" + state;
if (!reason.empty()) tag += " reason=" + reason;

// ONE message: easy parse + still readable
std::string combined = "[" + tag + "] " + human;

GsUISystem::SendIPCStatusMessage(uiType, combined);
}


std::string GsOpenGolfSimInterface::EnsureNewline(const std::string& s) {
if (!s.empty() && s.back() == '\n') return s;
return s + "\n";
}

GsOpenGolfSimInterface::GsOpenGolfSimInterface() {
// Defaults
socket_connect_address_ = kDefaultOGSHost;
socket_connect_port_ = std::to_string(kDefaultOGSPort); // <-- port is a string in base class
last_device_status_.clear();

// Pull from config
GolfSimConfiguration::SetConstant(
"gs_config.golf_simulator_interfaces.OpenGolfSim.kOGSConnectAddress",
socket_connect_address_
);
GolfSimConfiguration::SetConstant(
"gs_config.golf_simulator_interfaces.OpenGolfSim.kOGSConnectPort",
socket_connect_port_
);
}

GsOpenGolfSimInterface::~GsOpenGolfSimInterface() {}

bool GsOpenGolfSimInterface::InterfaceIsPresent() {
// Read config into locals (static function!)
std::string addr = kDefaultOGSHost;
std::string port_str = std::to_string(kDefaultOGSPort);

GolfSimConfiguration::SetConstant(
"gs_config.golf_simulator_interfaces.OpenGolfSim.kOGSConnectAddress",
addr
);
GolfSimConfiguration::SetConstant(
"gs_config.golf_simulator_interfaces.OpenGolfSim.kOGSConnectPort",
port_str
);

int port = -1;
try { port = std::stoi(port_str); } catch (...) { port = -1; }

// Treat empty/"disabled" as off
if (addr.empty() || addr == "disabled" || port <= 0) {
GS_LOG_TRACE_MSG(trace, "GsOpenGolfSimInterface::InterfaceIsPresent - Not Present");
return false;
}

GS_LOG_TRACE_MSG(trace,
"GsOpenGolfSimInterface::InterfaceIsPresent - Present (addr=" + addr +
", port=" + std::to_string(port) + ")"
);
return true;
}

bool GsOpenGolfSimInterface::Initialize() {
GS_LOG_TRACE_MSG(trace, "GsOpenGolfSimInterface Initialize called.");

// Log what we will actually try to connect to
GS_LOG_MSG(info, "OpenGolfSim connect target: " + socket_connect_address_ + ":" + socket_connect_port_);

if (!GsSimSocketInterface::Initialize()) {
GS_LOG_MSG(error, "GsOpenGolfSimInterface could not Initialize.");
return false;
}

#ifdef __unix__
usleep(500);
#endif

initialized_ = true;

// Always ready on connect
SendSimMessage(EnsureNewline(R"({"type":"device","status":"ready"})"));
last_device_status_ = "ready"; // avoid immediate duplicate heartbeat
return true;
}

void GsOpenGolfSimInterface::DeInitialize() {
GsSimSocketInterface::DeInitialize();
}

void GsOpenGolfSimInterface::SetSimSystemArmed(const bool /*is_armed*/) {}

bool GsOpenGolfSimInterface::GetSimSystemArmed() {
return true;
}

bool GsOpenGolfSimInterface::SendResults(const GsResults& r) {
if (!initialized_) return false;

// Heartbeat -> device status
if (r.result_message_is_keepalive_) {
const char* status = r.heartbeat_ball_detected_ ? "ready" : "busy";
if (last_device_status_ == status) return true;
last_device_status_ = status;

std::string msg = std::string(R"({"type":"device","status":")") + status + R"("})";
SendSimMessage(EnsureNewline(msg));
return true;
}

// Normal shot path
const std::string msg = GenerateResultsDataToSend(r);
if (msg.empty()) return true;
SendSimMessage(msg);
return true;
}


std::string GsOpenGolfSimInterface::GenerateResultsDataToSend(const GsResults& r) {
GsOpenGolfSimResults ogs(r);
const std::string payload = ogs.Format();
if (payload.empty())
return "";
return EnsureNewline(payload);
}

bool GsOpenGolfSimInterface::ProcessReceivedData(const std::string received_data) {
GS_LOG_MSG(info, "Received from OpenGolfSim: " + received_data);
return true;
}

} // namespace golf_sim
Loading
Loading