Skip to content
Draft
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: 2 additions & 0 deletions NERODevelopment/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -75,6 +75,8 @@ set(CMAKE_AUTORCC ON)
set(CMAKE_AUTOMOC ON)
# Create executable with all sources consolidated
qt_add_executable(NEROApp ${PROJECT_SOURCES}
src/models/socket_reciever.h
src/models/socket_reciever.cpp



Expand Down
31 changes: 31 additions & 0 deletions NERODevelopment/src/main.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@
#include "import_qml_components_plugins.h"
#include "import_qml_plugins.h"
#include "models/raspberry_model.h"
#include "models/socket_reciever.h"
#include <QGuiApplication>
#include <QQmlApplicationEngine>
#include <QQmlContext>
Expand All @@ -27,6 +28,36 @@ int main(int argc, char *argv[]) {
Model *model = new RaspberryModel();
model->connectToMQTT();

const char *button_port_str = getenv("BUTTON_PORT");
int button_port = button_port_str ? atoi(button_port_str) : 1884;
QString mqttHost = getenv("HOST") ? QString(getenv("HOST")) : QString("localhost");

QList<QString> button_topics = {};
MqttClient *buttonMqttClient = new MqttClient(nullptr, button_port, button_topics, mqttHost);
buttonMqttClient->connectToHost();

ButtonSocketReceiver *buttonReceiver = new ButtonSocketReceiver(buttonMqttClient);

QObject::connect(buttonReceiver, &ButtonSocketReceiver::buttonEventReceived,
[](const QString &button, const QString &state) {
qInfo() << "Button event:" << button << state;
});

QObject::connect(buttonReceiver, &ButtonSocketReceiver::errorOccurred,
[](const QString &error) {
qCritical() << "Button receiver error:" << error;
});

if (!buttonReceiver->start()) {
#ifdef _WIN32
qInfo() << "Button socket receiver skipped on Windows - will work on Raspberry Pi";
#else
qCritical() << "Failed to start button socket receiver";
#endif
} else {
qInfo() << "Button socket receiver started successfully";
}

HomeController homeController(model);
HeaderController headerController(model);
OffViewController offViewController(model);
Expand Down
250 changes: 250 additions & 0 deletions NERODevelopment/src/models/socket_reciever.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,250 @@
#include "socket_reciever.h"
#include <QDebug>
#include <cstring>

#ifndef _WIN32
#include <unistd.h>
#include <sys/socket.h>
#include <sys/un.h>
#endif

ButtonSocketReceiver::ButtonSocketReceiver(MqttClient *mqttClient,
const QString &socketPath,
QObject *parent)
: QObject(parent),
m_mqttClient(mqttClient),
m_socketPath(socketPath),
m_serverFd(-1),
m_clientFd(-1),
m_serverNotifier(nullptr),
m_clientNotifier(nullptr),
m_running(false) {
}

ButtonSocketReceiver::~ButtonSocketReceiver() {
stop();
}

bool ButtonSocketReceiver::start() {
#ifdef _WIN32
qWarning() << "ButtonSocketReceiver not supported on Windows";
return false;
#else
if (m_running) {
qWarning() << "ButtonSocketReceiver already running";
return true;
}

qInfo() << "Starting ButtonSocketReceiver on path:" << m_socketPath;

unlink(m_socketPath.toStdString().c_str());

m_serverFd = socket(AF_UNIX, SOCK_STREAM, 0);
if (m_serverFd == -1) {
QString error = QString("Failed to create socket: %1").arg(strerror(errno));
qCritical() << error;
emit errorOccurred(error);
return false;
}

struct sockaddr_un addr;
memset(&addr, 0, sizeof(addr));
addr.sun_family = AF_UNIX;
strncpy(addr.sun_path, m_socketPath.toStdString().c_str(), sizeof(addr.sun_path) - 1);

if (bind(m_serverFd, (struct sockaddr*)&addr, sizeof(addr)) == -1) {
QString error = QString("Failed to bind socket: %1").arg(strerror(errno));
qCritical() << error;
close(m_serverFd);
m_serverFd = -1;
emit errorOccurred(error);
return false;
}

if (listen(m_serverFd, 1) == -1) {
QString error = QString("Failed to listen on socket: %1").arg(strerror(errno));
qCritical() << error;
close(m_serverFd);
m_serverFd = -1;
emit errorOccurred(error);
return false;
}

m_serverNotifier = new QSocketNotifier(m_serverFd, QSocketNotifier::Read, this);
connect(m_serverNotifier, &QSocketNotifier::activated, this, &ButtonSocketReceiver::handleNewConnection);
m_serverNotifier->setEnabled(true);

m_running = true;
qInfo() << "ButtonSocketReceiver listening on:" << m_socketPath;

return true;
#endif
}

void ButtonSocketReceiver::stop() {
#ifndef _WIN32
if (!m_running) {
return;
}

qInfo() << "Stopping ButtonSocketReceiver";

if (m_clientNotifier) {
m_clientNotifier->setEnabled(false);
m_clientNotifier->deleteLater();
m_clientNotifier = nullptr;
}

if (m_clientFd != -1) {
close(m_clientFd);
m_clientFd = -1;
}

if (m_serverNotifier) {
m_serverNotifier->setEnabled(false);
m_serverNotifier->deleteLater();
m_serverNotifier = nullptr;
}

if (m_serverFd != -1) {
close(m_serverFd);
m_serverFd = -1;
}

unlink(m_socketPath.toStdString().c_str());

m_running = false;
qInfo() << "ButtonSocketReceiver stopped";
#endif
}

void ButtonSocketReceiver::handleNewConnection() {
#ifndef _WIN32
qInfo() << "New client connection attempt";

if (m_clientFd != -1) {
qWarning() << "Closing existing client connection for new client";
if (m_clientNotifier) {
m_clientNotifier->setEnabled(false);
m_clientNotifier->deleteLater();
m_clientNotifier = nullptr;
}
close(m_clientFd);
m_clientFd = -1;
}

m_clientFd = accept(m_serverFd, nullptr, nullptr);
if (m_clientFd == -1) {
QString error = QString("Failed to accept connection: %1").arg(strerror(errno));
qWarning() << error;
emit errorOccurred(error);
return;
}

qInfo() << "Client connected, fd:" << m_clientFd;

m_clientNotifier = new QSocketNotifier(m_clientFd, QSocketNotifier::Read, this);
connect(m_clientNotifier, &QSocketNotifier::activated, this, &ButtonSocketReceiver::handleClientData);
m_clientNotifier->setEnabled(true);
#endif
}

void ButtonSocketReceiver::handleClientData() {
#ifndef _WIN32
char buffer[BUFFER_SIZE];
ssize_t nBytes = read(m_clientFd, buffer, BUFFER_SIZE - 1);

if (nBytes > 0) {
buffer[nBytes] = '\0';
QString message = QString::fromUtf8(buffer).trimmed();

qDebug() << "Received button message:" << message;
processButtonMessage(message);

} else if (nBytes == 0) {
qInfo() << "Client disconnected gracefully";
handleClientDisconnected();
} else {
QString error = QString("Error reading from client: %1").arg(strerror(errno));
qWarning() << error;
emit errorOccurred(error);
handleClientDisconnected();
}
#endif
}

void ButtonSocketReceiver::handleClientDisconnected() {
#ifndef _WIN32
if (m_clientNotifier) {
m_clientNotifier->setEnabled(false);
m_clientNotifier->deleteLater();
m_clientNotifier = nullptr;
}

if (m_clientFd != -1) {
close(m_clientFd);
m_clientFd = -1;
}

qInfo() << "Client cleanup complete, ready for new connection";
#endif
}

void ButtonSocketReceiver::processButtonMessage(const QString &message) {
QStringList parts = message.split('_');

if (parts.size() != 2) {
qWarning() << "Invalid button message format:" << message;
return;
}

QString buttonName = parts[0].toLower();
QString state = parts[1].toLower();

if (state != "down" && state != "up") {
qWarning() << "Invalid button state:" << state;
return;
}

QString topic = getMqttTopicForButton(buttonName, state);
float value = getButtonValue(buttonName, state);

if (topic.isEmpty()) {
qWarning() << "Unknown button name:" << buttonName;
return;
}

qDebug() << "Publishing button event:" << topic << "=" << value;
m_mqttClient->sendMessage(topic, value);

emit buttonEventReceived(buttonName, state);
}

QString ButtonSocketReceiver::getMqttTopicForButton(const QString &buttonName, const QString &state) {
QMap<QString, QString> buttonTopicMap = {
{"forward", "Button/Forward"},
{"backward", "Button/Backward"},
{"right", "Button/Right"},
{"enter", "Button/Enter"},
{"up", "Button/Up"},
{"down", "Button/Down"},
{"home", "Button/Home"}
};

return buttonTopicMap.value(buttonName, "");
}

float ButtonSocketReceiver::getButtonValue(const QString &buttonName, const QString &state) {
if (state == "down") {
if (buttonName == "backward") return 0;
if (buttonName == "right") return 1;
if (buttonName == "enter") return 5;
if (buttonName == "up") return 4;
if (buttonName == "down") return 3;
if (buttonName == "home") return 1;
if (buttonName == "forward") return 1;
return 1;
} else {
return 10;
}
}
51 changes: 51 additions & 0 deletions NERODevelopment/src/models/socket_reciever.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
#ifndef SOCKET_RECIEVER_H
#define SOCKET_RECIEVER_H

#include <QObject>
#include <QString>
#include <QSocketNotifier>
#include "mqtt_client.h"

/**
* @brief ButtonSocketReceiver listens on a Unix domain socket for button
* press messages from a GPIO client and publishes them to MQTT topics
*/
class ButtonSocketReceiver : public QObject {
Q_OBJECT

public:
explicit ButtonSocketReceiver(MqttClient *mqttClient,
const QString &socketPath = "/tmp/nero_buttons_socket",
QObject *parent = nullptr);
~ButtonSocketReceiver();

bool start();
void stop();
bool isRunning() const { return m_running; }

signals:
void buttonEventReceived(const QString &buttonName, const QString &state);
void errorOccurred(const QString &errorMessage);

private slots:
void handleNewConnection();
void handleClientData();
void handleClientDisconnected();

private:
void processButtonMessage(const QString &message);
QString getMqttTopicForButton(const QString &buttonName, const QString &state);
float getButtonValue(const QString &buttonName, const QString &state);

MqttClient *m_mqttClient;
QString m_socketPath;
int m_serverFd;
int m_clientFd;
QSocketNotifier *m_serverNotifier;
QSocketNotifier *m_clientNotifier;
bool m_running;

static const int BUFFER_SIZE = 256;
};

#endif