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
2 changes: 1 addition & 1 deletion core_network_setup/free5gc/free5gc/config/amfcfg.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -70,7 +70,7 @@ configuration:
emcN3: 0 # Emergency service support indicator for Non-3GPP access (uinteger, range: 0~1)
mcsi: 0 # MCS indicator (uinteger, range: 0~1)
t3502Value: 720 # timer value (seconds) at UE side
t3512Value: 3600 # timer value (seconds) at UE side
t3512Value: 10 # timer value (seconds) at UE side
non3gppDeregTimerValue: 3240 # timer value (seconds) at UE side
# retransmission timer for paging message
t3513:
Expand Down
Binary file modified demo/gazebo_launch/src/ign_turtlebot/gui_plugins/libUePowerPlugin.so
100644 → 100755
Binary file not shown.
Binary file modified demo/gazebo_launch/src/ign_turtlebot/gui_plugins/libgNBPowerPlugin.so
100644 → 100755
Binary file not shown.
Binary file not shown.
Binary file not shown.
200 changes: 154 additions & 46 deletions phine-plugins/src/phine_plugins/src/UePowerPlugin.cc
Original file line number Diff line number Diff line change
Expand Up @@ -13,34 +13,50 @@ See the License for the specific language governing permissions and
limitations under the License.*/

#include "UePowerPlugin.hh"
#include <QTimer>
#include <cstdio>
#include <cstdlib>
#include <ignition/common/Console.hh>
#include <ignition/plugin/Register.hh>
#include <regex>
#include <sstream>
#include <string>
#include <tinyxml2.h>
#include <vector>

IGNITION_ADD_PLUGIN(phine_plugins::UePowerPlugin, ignition::gui::Plugin)

using namespace phine_plugins;

UePowerPlugin::UePowerPlugin() {
ignmsg << "[UePowerPlugin] constructor called" << std::endl;
// Helper function to ask Docker for data without freezing Gazebo
static void triggerDockerCheck(const std::string &name) {
std::string file_path = "/tmp/ue_" + name + "_pids.txt";
std::string tmp_path = file_path + ".tmp";
// Redirects Docker output to a temporary file, then instantly swaps it. The
// '&' ensures 0 freezing.
std::string cmd = "docker exec " + name +
" ps -eo pid,stat,args --no-headers 2>/dev/null > " +
tmp_path + " && mv " + tmp_path + " " + file_path + " &";
system(cmd.c_str());
}

UePowerPlugin::UePowerPlugin() {
const char *ue_name_env = std::getenv("UE_NAME_FOR_BUTTON");
if (ue_name_env != nullptr) {
this->container_name = QString::fromStdString(std::string(ue_name_env));
}

ignmsg << "[UePowerPlugin] UE container: "
<< this->container_name.toStdString() << std::endl;
status_timer = new QTimer(this);
status_timer->setInterval(2000);
connect(status_timer, &QTimer::timeout, this,
&UePowerPlugin::checkAndUpdateStatus);
}

UePowerPlugin::~UePowerPlugin() {}

void UePowerPlugin::LoadConfig(const tinyxml2::XMLElement *_pluginElem) {
if (this->title.empty()) {
if (this->title.empty())
this->title = "UE Power Control";
}

if (_pluginElem != nullptr) {
auto *name_elem = _pluginElem->FirstChildElement("container_name");
Expand All @@ -52,11 +68,15 @@ void UePowerPlugin::LoadConfig(const tinyxml2::XMLElement *_pluginElem) {
if (ip_elem != nullptr && ip_elem->GetText() != nullptr) {
gnb_ip = QString::fromStdString(ip_elem->GetText());
}

auto *freq_elem = _pluginElem->FirstChildElement("carrier_freq");
if (freq_elem != nullptr && freq_elem->GetText() != nullptr) {
carrier_freq = QString::fromStdString(freq_elem->GetText());
}
}
}

QString UePowerPlugin::getContainerName() const { return container_name; }

void UePowerPlugin::setContainerName(const QString &name) {
if (container_name != name) {
container_name = name;
Expand All @@ -65,22 +85,27 @@ void UePowerPlugin::setContainerName(const QString &name) {
}

QString UePowerPlugin::getGnbIp() const { return gnb_ip; }

void UePowerPlugin::setGnbIp(const QString &ip) {
if (gnb_ip != ip) {
gnb_ip = ip;
emit gnbIpChanged();
}
}

bool UePowerPlugin::isProcessRunning() const { return process_running; }
QString UePowerPlugin::getCarrierFreq() const { return carrier_freq; }
void UePowerPlugin::setCarrierFreq(const QString &freq) {
if (carrier_freq != freq) {
carrier_freq = freq;
emit carrierFreqChanged();
}
}

bool UePowerPlugin::isProcessRunning() const { return process_running; }
bool UePowerPlugin::isConnected() const { return connected; }

bool UePowerPlugin::isValidContainerName(const std::string &name) {
if (name.empty() || name.size() > 128) {
if (name.empty() || name.size() > 128)
return false;
}
static const std::regex valid_name(R"(^[a-zA-Z0-9][a-zA-Z0-9_.\-]*$)");
return std::regex_match(name, valid_name);
}
Expand All @@ -89,82 +114,165 @@ bool UePowerPlugin::isValidIpAddress(const std::string &ip) {
static const std::regex ip_regex(
R"(^(\d{1,3})\.(\d{1,3})\.(\d{1,3})\.(\d{1,3})$)");
std::smatch match;
if (!std::regex_match(ip, match, ip_regex)) {
if (!std::regex_match(ip, match, ip_regex))
return false;
}
for (int i = 1; i <= 4; ++i) {
int octet = std::stoi(match[i].str());
if (octet < 0 || octet > 255) {
if (octet < 0 || octet > 255)
return false;
}
}
return true;
}

bool UePowerPlugin::checkProcessRunning() {
std::string name = container_name.toStdString();
if (!isValidContainerName(name)) {
bool UePowerPlugin::isValidFrequency(const std::string &freq) {
if (freq.empty() || freq.size() < 8 || freq.size() > 12)
return false;
for (char c : freq) {
if (!std::isdigit(c))
return false;
}
return true;
}

std::vector<int> UePowerPlugin::getProcessPids() const {
std::vector<int> pids;
std::string name = container_name.toStdString();

if (!isValidContainerName(name))
return pids;

// Reads the file locally. This is instantaneous and immune to Docker
// lock-ups.
std::string file_path = "/tmp/ue_" + name + "_pids.txt";
std::ifstream file(file_path);
if (file.is_open()) {
std::string line;
while (std::getline(file, line)) {
std::istringstream iss(line);
std::string pid_str, stat;

if (iss >> pid_str >> stat) {
try {
int pid = std::stoi(pid_str);
if (line.find("nr-uesoftmodem") != std::string::npos &&
stat.front() != 'Z') {
pids.push_back(pid);
}
} catch (...) {
}
}
}
}
std::string cmd =
"docker exec " + name + " pgrep nr-uesoftmodem > /dev/null 2>&1";
int ret = system(cmd.c_str());
return (ret == 0);
return pids;
}

std::string UePowerPlugin::buildStartCommand() const {
std::string name = container_name.toStdString();
std::string ip = gnb_ip.toStdString();
std::string freq = carrier_freq.toStdString();

return "docker exec " + name +
" /bin/bash -c"
" '/opt/oai-nr-ue/bin/nr-uesoftmodem"
" -O /opt/oai-nr-ue/etc/nr-ue.yaml"
" -E --rfsim -r 106 --numerology 1"
" -C 3319680000"
" --rfsimulator.serveraddr " +
ip + " &'";
" -C " +
freq + " --rfsimulator.serveraddr " + ip + " > /dev/null 2>&1 &'";
}

void UePowerPlugin::checkAndUpdateStatus() {
if (!connected)
return;

std::string name = container_name.toStdString();

if (skip_checks_count > 0) {
skip_checks_count--;
triggerDockerCheck(name); // Keep background updates going
return;
}

std::vector<int> live_pids = getProcessPids();
bool is_actually_running = !live_pids.empty();

if (is_actually_running) {
failed_checks = 0;
ignmsg << "[UePowerPlugin] Polling: PROCESS FOUND (" << live_pids.size()
<< " PIDs)" << std::endl;

if (!process_running) {
process_running = true;
emit processRunningChanged();
}
} else {
failed_checks++;
ignmsg << "[UePowerPlugin] Polling: PROCESS NOT FOUND (Strike "
<< failed_checks << "/2)" << std::endl;

if (failed_checks >= 2 && process_running) {
process_running = false;
emit processRunningChanged();
}
}

// Ping Docker to gather data for the *next* cycle
triggerDockerCheck(name);
}

void UePowerPlugin::toggleProcess() {
std::string name = container_name.toStdString();
if (!isValidContainerName(name)) {
ignerr << "[UePowerPlugin] Invalid container name: " << name
<< std::endl;
ignerr << "[UePowerPlugin] Invalid container name." << std::endl;
return;
}

// First click: check actual state, then become connected
// --- 1. FIRST CLICK ---
if (!connected) {
process_running = checkProcessRunning();
ignmsg << "[UePowerPlugin] === INITIALIZING CONNECTION ==="
<< std::endl;
connected = true;

triggerDockerCheck(
name); // Fire the very first check to build the text file
status_timer->start();
emit connectedChanged();
emit processRunningChanged();
ignmsg << "[UePowerPlugin] Connected to " << name << " — process is "
<< (process_running ? "running" : "stopped") << std::endl;
return;
}

// Subsequent clicks: toggle without checking
// --- 2. SUBSEQUENT CLICKS ---
skip_checks_count =
3; // Block UI from flickering for 6 seconds while processes boot/die

if (process_running) {
std::string cmd = "docker exec " + name + " pkill nr-uesoftmodem 2>&1";
ignmsg << "[UePowerPlugin] Running: " << cmd << std::endl;
int ret = system(cmd.c_str());
ignmsg << "[UePowerPlugin] Kill exit=" << ret << std::endl;
ignmsg << "[UePowerPlugin] TURN OFF Clicked. Executing KILL..."
<< std::endl;
std::vector<int> current_live_pids = getProcessPids();
for (int pid : current_live_pids) {
std::string cmd = "docker exec " + name + " kill -9 " +
std::to_string(pid) + " > /dev/null 2>&1 &";
system(cmd.c_str());
}

// Immediately delete the text file so the timer doesn't read ghost
// processes
system(("rm -f /tmp/ue_" + name + "_pids.txt").c_str());

process_running = false;
emit processRunningChanged();
} else {
ignmsg << "[UePowerPlugin] TURN ON Clicked. Executing START..."
<< std::endl;
std::string ip = gnb_ip.toStdString();
if (!isValidIpAddress(ip)) {
ignerr << "[UePowerPlugin] Invalid gNB IP address: " << ip
<< std::endl;
std::string freq = carrier_freq.toStdString();
if (!isValidIpAddress(ip) || !isValidFrequency(freq))
return;
}
std::string cmd = buildStartCommand();
ignmsg << "[UePowerPlugin] Running: " << cmd << std::endl;
int ret = system(cmd.c_str());
ignmsg << "[UePowerPlugin] Start exit=" << ret << std::endl;

// Ensure the host doesn't freeze waiting for Docker to parse the
// command
std::string cmd = buildStartCommand() + " > /dev/null 2>&1 &";
system(cmd.c_str());

process_running = true;
emit processRunningChanged();
}
}
}
20 changes: 19 additions & 1 deletion phine-plugins/src/phine_plugins/src/UePowerPlugin.hh
Original file line number Diff line number Diff line change
Expand Up @@ -16,8 +16,11 @@ limitations under the License.*/
#define UE_POWER_PLUGIN_HH_

#include <QString>
#include <QTimer>
#include <fstream>
#include <ignition/gui/Plugin.hh>
#include <string>
#include <vector>

namespace phine_plugins {

Expand All @@ -27,6 +30,8 @@ class UePowerPlugin : public ignition::gui::Plugin {
Q_PROPERTY(QString containerName READ getContainerName WRITE
setContainerName NOTIFY containerNameChanged)
Q_PROPERTY(QString gnbIp READ getGnbIp WRITE setGnbIp NOTIFY gnbIpChanged)
Q_PROPERTY(QString carrierFreq READ getCarrierFreq WRITE setCarrierFreq
NOTIFY carrierFreqChanged)
Q_PROPERTY(
bool processRunning READ isProcessRunning NOTIFY processRunningChanged)
Q_PROPERTY(bool connected READ isConnected NOTIFY connectedChanged)
Expand All @@ -40,30 +45,43 @@ class UePowerPlugin : public ignition::gui::Plugin {
void setContainerName(const QString &name);
QString getGnbIp() const;
void setGnbIp(const QString &ip);
QString getCarrierFreq() const;
void setCarrierFreq(const QString &freq);
bool isProcessRunning() const;
bool isConnected() const;

public slots:
void toggleProcess();
void checkAndUpdateStatus();

signals:
void containerNameChanged();
void gnbIpChanged();
void carrierFreqChanged();
void processRunningChanged();
void connectedChanged();

private:
bool checkProcessRunning();
std::vector<int> getProcessPids() const;
std::string buildStartCommand() const;
static bool isValidContainerName(const std::string &name);
static bool isValidIpAddress(const std::string &ip);
static bool isValidFrequency(const std::string &freq);

QString container_name{"ue_turtlebot"};
QString gnb_ip{"192.168.70.160"};
QString carrier_freq{"3619200000"};
bool process_running{false};
bool connected{false};
QTimer *status_timer{nullptr};
std::vector<int> tracked_pids;

// Anti-Flicker trackers
int skip_checks_count{0};
int failed_checks{0};
};

} // namespace phine_plugins

#endif // UE_POWER_PLUGIN_HH_
#endif // UE_POWER_PLUGIN_HH_
Loading
Loading