Initial commit: add environment monitoring TCP backend
This commit is contained in:
@@ -0,0 +1,5 @@
|
|||||||
|
/bin/
|
||||||
|
/data/
|
||||||
|
/logs/
|
||||||
|
*.log
|
||||||
|
*.o
|
||||||
@@ -0,0 +1,23 @@
|
|||||||
|
CC = g++
|
||||||
|
CXXFLAGS = -std=c++17 -O2 -Wall -pthread
|
||||||
|
SRCDIR = src
|
||||||
|
BINDIR = bin
|
||||||
|
TARGETS = $(BINDIR)/device_backend $(BINDIR)/console_client
|
||||||
|
SRC_BACKEND = $(SRCDIR)/server.cpp
|
||||||
|
SRC_CLIENT = console_client.cpp
|
||||||
|
|
||||||
|
all: $(TARGETS)
|
||||||
|
|
||||||
|
$(BINDIR):
|
||||||
|
mkdir -p $(BINDIR)
|
||||||
|
|
||||||
|
$(BINDIR)/device_backend: $(SRC_BACKEND) | $(BINDIR)
|
||||||
|
$(CC) $(CXXFLAGS) -o $(BINDIR)/device_backend $(SRC_BACKEND)
|
||||||
|
|
||||||
|
$(BINDIR)/console_client: $(SRC_CLIENT) | $(BINDIR)
|
||||||
|
$(CC) $(CXXFLAGS) -o $(BINDIR)/console_client $(SRC_CLIENT)
|
||||||
|
|
||||||
|
clean:
|
||||||
|
rm -f $(TARGETS) backend.log
|
||||||
|
|
||||||
|
.PHONY: all clean
|
||||||
@@ -0,0 +1,56 @@
|
|||||||
|
#include <arpa/inet.h>
|
||||||
|
#include <netinet/in.h>
|
||||||
|
#include <sys/socket.h>
|
||||||
|
#include <unistd.h>
|
||||||
|
|
||||||
|
#include <cstring>
|
||||||
|
#include <iostream>
|
||||||
|
|
||||||
|
int main(int argc, char **argv) {
|
||||||
|
if (argc < 3) {
|
||||||
|
std::cout << "Usage: " << argv[0] << " <host> <port>\n";
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
const char *host = argv[1];
|
||||||
|
int port = std::atoi(argv[2]);
|
||||||
|
|
||||||
|
int sock = socket(AF_INET, SOCK_STREAM, 0);
|
||||||
|
if (sock < 0) {
|
||||||
|
perror("socket");
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
struct sockaddr_in addr;
|
||||||
|
std::memset(&addr, 0, sizeof(addr));
|
||||||
|
addr.sin_family = AF_INET;
|
||||||
|
addr.sin_port = htons(port);
|
||||||
|
if (inet_pton(AF_INET, host, &addr.sin_addr) <= 0) {
|
||||||
|
perror("inet_pton");
|
||||||
|
close(sock);
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (connect(sock, (struct sockaddr *)&addr, sizeof(addr)) < 0) {
|
||||||
|
perror("connect");
|
||||||
|
close(sock);
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
std::cout << "Connected to " << host << ":" << port << std::endl;
|
||||||
|
|
||||||
|
char buf[1024];
|
||||||
|
while (true) {
|
||||||
|
ssize_t n = recv(sock, buf, sizeof(buf)-1, 0);
|
||||||
|
if (n > 0) {
|
||||||
|
buf[n] = '\0';
|
||||||
|
std::cout << buf;
|
||||||
|
std::cout.flush();
|
||||||
|
} else if (n == 0) {
|
||||||
|
std::cout << "Server closed connection\n";
|
||||||
|
break;
|
||||||
|
} else {
|
||||||
|
perror("recv");
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
close(sock);
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
+489
@@ -0,0 +1,489 @@
|
|||||||
|
#include <arpa/inet.h>
|
||||||
|
#include <netinet/in.h>
|
||||||
|
#include <sys/socket.h>
|
||||||
|
#include <sys/types.h>
|
||||||
|
#include <unistd.h>
|
||||||
|
|
||||||
|
#include <atomic>
|
||||||
|
#include <cstring>
|
||||||
|
#include <fstream>
|
||||||
|
#include <iostream>
|
||||||
|
#include <sys/stat.h>
|
||||||
|
#include <mutex>
|
||||||
|
#include <string>
|
||||||
|
#include <thread>
|
||||||
|
#include <vector>
|
||||||
|
#include <algorithm>
|
||||||
|
#include <signal.h>
|
||||||
|
#include <map>
|
||||||
|
#include <chrono>
|
||||||
|
#include <ctime>
|
||||||
|
#include <iomanip>
|
||||||
|
|
||||||
|
static std::mutex log_mutex;
|
||||||
|
static std::atomic<bool> g_running{true};
|
||||||
|
static std::vector<int> g_clients;
|
||||||
|
static std::mutex g_clients_mutex;
|
||||||
|
|
||||||
|
// Device connection management
|
||||||
|
struct DeviceInfo {
|
||||||
|
std::string device_id;
|
||||||
|
std::string last_seen;
|
||||||
|
std::string status; // "connected", "disconnected"
|
||||||
|
int client_fd;
|
||||||
|
};
|
||||||
|
|
||||||
|
static std::map<std::string, DeviceInfo> g_devices;
|
||||||
|
static std::mutex g_devices_mutex;
|
||||||
|
|
||||||
|
void write_log(const std::string &line)
|
||||||
|
{
|
||||||
|
std::lock_guard<std::mutex> lk(log_mutex);
|
||||||
|
std::ofstream ofs("backend.log", std::ios::app);
|
||||||
|
if (ofs) {
|
||||||
|
ofs << line << std::endl;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// data directory for per-device files
|
||||||
|
static const char *DATA_DIR = "data";
|
||||||
|
static std::mutex file_mutex;
|
||||||
|
|
||||||
|
static void ensure_data_dir()
|
||||||
|
{
|
||||||
|
struct stat st;
|
||||||
|
if (stat(DATA_DIR, &st) != 0) {
|
||||||
|
// try to create
|
||||||
|
mkdir(DATA_DIR, 0755);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// write content to data/<device>_<type>.json (type = sensor|control)
|
||||||
|
static void write_device_file(const std::string &device_id, const std::string &type, const std::string &content)
|
||||||
|
{
|
||||||
|
std::lock_guard<std::mutex> lk(file_mutex);
|
||||||
|
ensure_data_dir();
|
||||||
|
std::string fname = std::string(DATA_DIR) + "/" + device_id + "_" + type + ".json";
|
||||||
|
std::ofstream ofs(fname, std::ios::trunc);
|
||||||
|
if (ofs) {
|
||||||
|
ofs << content << std::endl;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Initialize default control state for a device if not exists
|
||||||
|
static void init_default_control_state(const std::string &device_id)
|
||||||
|
{
|
||||||
|
std::string fname = std::string(DATA_DIR) + "/" + device_id + "_control.json";
|
||||||
|
struct stat st;
|
||||||
|
if (stat(fname.c_str(), &st) != 0) {
|
||||||
|
// File doesn't exist, create default control state
|
||||||
|
std::string default_control = "{\"device_id\":\"" + device_id + "\",\"buzzer\":0,\"led0\":0,\"led1\":0,\"led2\":0}";
|
||||||
|
write_device_file(device_id, "control", default_control);
|
||||||
|
std::cout << "Created default control state for device: " << device_id << std::endl;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// read content from data/<device>_<type>.json, return empty string if missing
|
||||||
|
static std::string read_device_file(const std::string &device_id, const std::string &type)
|
||||||
|
{
|
||||||
|
std::lock_guard<std::mutex> lk(file_mutex);
|
||||||
|
ensure_data_dir();
|
||||||
|
std::string fname = std::string(DATA_DIR) + "/" + device_id + "_" + type + ".json";
|
||||||
|
std::ifstream ifs(fname);
|
||||||
|
if (!ifs) return std::string();
|
||||||
|
std::string s;
|
||||||
|
std::string line;
|
||||||
|
while (std::getline(ifs, line)) {
|
||||||
|
if (!s.empty()) s += "\n";
|
||||||
|
s += line;
|
||||||
|
}
|
||||||
|
return s;
|
||||||
|
}
|
||||||
|
|
||||||
|
// read control state, return default if missing
|
||||||
|
static std::string read_control_state(const std::string &device_id)
|
||||||
|
{
|
||||||
|
// Ensure default control state exists
|
||||||
|
init_default_control_state(device_id);
|
||||||
|
|
||||||
|
std::string content = read_device_file(device_id, "control");
|
||||||
|
if (content.empty()) {
|
||||||
|
// If still empty, return default
|
||||||
|
return "{\"device_id\":\"" + device_id + "\",\"buzzer\":0,\"led0\":0,\"led1\":0,\"led2\":0}";
|
||||||
|
}
|
||||||
|
return content;
|
||||||
|
}
|
||||||
|
|
||||||
|
// add client fd to global list
|
||||||
|
void add_client_fd(int fd)
|
||||||
|
{
|
||||||
|
std::lock_guard<std::mutex> lk(g_clients_mutex);
|
||||||
|
g_clients.push_back(fd);
|
||||||
|
std::cout << "add_client_fd: " << fd << " (total=" << g_clients.size() << ")" << std::endl;
|
||||||
|
}
|
||||||
|
|
||||||
|
// remove client fd from global list
|
||||||
|
void remove_client_fd(int fd)
|
||||||
|
{
|
||||||
|
std::lock_guard<std::mutex> lk(g_clients_mutex);
|
||||||
|
auto it = std::find(g_clients.begin(), g_clients.end(), fd);
|
||||||
|
if (it != g_clients.end()) g_clients.erase(it);
|
||||||
|
std::cout << "remove_client_fd: " << fd << " (total=" << g_clients.size() << ")" << std::endl;
|
||||||
|
}
|
||||||
|
|
||||||
|
// broadcast a line to all connected clients except exclude_fd (if exclude_fd==-1 send to all)
|
||||||
|
// Forward declaration
|
||||||
|
void broadcast_line(const std::string &line, int exclude_fd);
|
||||||
|
|
||||||
|
// Get current timestamp string
|
||||||
|
std::string get_timestamp()
|
||||||
|
{
|
||||||
|
auto now = std::chrono::system_clock::now();
|
||||||
|
auto time_t = std::chrono::system_clock::to_time_t(now);
|
||||||
|
std::stringstream ss;
|
||||||
|
ss << std::put_time(std::localtime(&time_t), "%Y-%m-%d %H:%M:%S");
|
||||||
|
return ss.str();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Register or update device connection
|
||||||
|
void register_device(const std::string &device_id, int client_fd, const std::string &action)
|
||||||
|
{
|
||||||
|
std::lock_guard<std::mutex> lk(g_devices_mutex);
|
||||||
|
DeviceInfo &info = g_devices[device_id];
|
||||||
|
info.device_id = device_id;
|
||||||
|
info.last_seen = get_timestamp();
|
||||||
|
info.client_fd = client_fd;
|
||||||
|
|
||||||
|
if (action == "connect") {
|
||||||
|
info.status = "connected";
|
||||||
|
std::cout << "Device registered: " << device_id << " (fd=" << client_fd << ")" << std::endl;
|
||||||
|
// Initialize default control state for new device
|
||||||
|
init_default_control_state(device_id);
|
||||||
|
} else if (action == "reconnect") {
|
||||||
|
info.status = "connected";
|
||||||
|
std::cout << "Device reconnected: " << device_id << " (fd=" << client_fd << ")" << std::endl;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Broadcast device connection status to UI clients
|
||||||
|
std::string status_msg = "{\"type\":\"device_status\",\"device_id\":\"" + device_id +
|
||||||
|
"\",\"status\":\"" + info.status + "\",\"timestamp\":\"" + info.last_seen + "\"}";
|
||||||
|
broadcast_line(status_msg, client_fd);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Mark device as disconnected
|
||||||
|
void unregister_device(int client_fd)
|
||||||
|
{
|
||||||
|
std::lock_guard<std::mutex> lk(g_devices_mutex);
|
||||||
|
for (auto &pair : g_devices) {
|
||||||
|
if (pair.second.client_fd == client_fd && pair.second.status == "connected") {
|
||||||
|
pair.second.status = "disconnected";
|
||||||
|
pair.second.last_seen = get_timestamp();
|
||||||
|
std::string device_id = pair.first;
|
||||||
|
std::cout << "Device disconnected: " << device_id << " (fd=" << client_fd << ")" << std::endl;
|
||||||
|
|
||||||
|
// Broadcast device disconnection status to UI clients
|
||||||
|
std::string status_msg = "{\"type\":\"device_status\",\"device_id\":\"" + device_id +
|
||||||
|
"\",\"status\":\"disconnected\",\"timestamp\":\"" + pair.second.last_seen + "\"}";
|
||||||
|
broadcast_line(status_msg, client_fd);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get list of all devices
|
||||||
|
std::string get_devices_list()
|
||||||
|
{
|
||||||
|
std::lock_guard<std::mutex> lk(g_devices_mutex);
|
||||||
|
std::string result = "{\"type\":\"devices_list\",\"devices\":[";
|
||||||
|
bool first = true;
|
||||||
|
for (const auto &pair : g_devices) {
|
||||||
|
if (!first) result += ",";
|
||||||
|
result += "{\"device_id\":\"" + pair.second.device_id +
|
||||||
|
"\",\"status\":\"" + pair.second.status +
|
||||||
|
"\",\"last_seen\":\"" + pair.second.last_seen + "\"}";
|
||||||
|
first = false;
|
||||||
|
}
|
||||||
|
result += "]}";
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
void broadcast_line(const std::string &line, int exclude_fd)
|
||||||
|
{
|
||||||
|
std::lock_guard<std::mutex> lk(g_clients_mutex);
|
||||||
|
std::string out = line + "\n";
|
||||||
|
for (int fd : g_clients) {
|
||||||
|
if (fd == exclude_fd) continue;
|
||||||
|
ssize_t sent = send(fd, out.c_str(), (ssize_t)out.size(), MSG_NOSIGNAL);
|
||||||
|
if (sent <= 0) {
|
||||||
|
int err = errno;
|
||||||
|
std::cout << "broadcast_line: send to fd=" << fd << " failed, sent=" << sent << " errno=" << err << "\n";
|
||||||
|
} else {
|
||||||
|
std::cout << "broadcast_line: sent to fd=" << fd << " bytes=" << sent << "\n";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void handle_client(int client_fd, std::string peer)
|
||||||
|
{
|
||||||
|
std::cout << "Client connected: " << peer << std::endl;
|
||||||
|
// client already added in accept loop
|
||||||
|
std::string buffer;
|
||||||
|
char tmp[512];
|
||||||
|
while (g_running) {
|
||||||
|
ssize_t n = recv(client_fd, tmp, sizeof(tmp), 0);
|
||||||
|
if (n > 0) {
|
||||||
|
buffer.append(tmp, tmp + n);
|
||||||
|
// process lines
|
||||||
|
size_t pos = 0;
|
||||||
|
while ((pos = buffer.find('\n')) != std::string::npos) {
|
||||||
|
std::string line = buffer.substr(0, pos);
|
||||||
|
// trim \r
|
||||||
|
if (!line.empty() && line.back() == '\r')
|
||||||
|
line.pop_back();
|
||||||
|
if (!line.empty()) {
|
||||||
|
std::cout << "RECV: " << line << std::endl;
|
||||||
|
write_log(line);
|
||||||
|
// If this is a GET request: "GET sensor <device>" or "GET control <device>" or "GET devices"
|
||||||
|
if (line.rfind("GET ", 0) == 0) {
|
||||||
|
// tokenize
|
||||||
|
std::string cmd = line.substr(4);
|
||||||
|
size_t sp = cmd.find(' ');
|
||||||
|
if (sp != std::string::npos) {
|
||||||
|
std::string type = cmd.substr(0, sp);
|
||||||
|
if (type == "devices") {
|
||||||
|
// Send devices list
|
||||||
|
std::string content = get_devices_list();
|
||||||
|
std::string out = content + "\n";
|
||||||
|
ssize_t sent = send(client_fd, out.c_str(), (ssize_t)out.size(), MSG_NOSIGNAL);
|
||||||
|
if (sent <= 0) std::cout << "GET devices: send to fd=" << client_fd << " failed\n";
|
||||||
|
} else {
|
||||||
|
std::string device = cmd.substr(sp + 1);
|
||||||
|
if (!device.empty()) {
|
||||||
|
std::string content;
|
||||||
|
if (type == "control") {
|
||||||
|
// For control requests, always return valid control state
|
||||||
|
content = read_control_state(device);
|
||||||
|
} else {
|
||||||
|
// For other types (sensor), read from file
|
||||||
|
content = read_device_file(device, type);
|
||||||
|
if (content.empty()) content = "{}";
|
||||||
|
}
|
||||||
|
std::string out = content + "\n";
|
||||||
|
ssize_t sent = send(client_fd, out.c_str(), (ssize_t)out.size(), MSG_NOSIGNAL);
|
||||||
|
if (sent <= 0) std::cout << "GET " << type << " for " << device << ": send to fd=" << client_fd << " failed\n";
|
||||||
|
else std::cout << "GET " << type << " for " << device << ": sent " << sent << " bytes to fd=" << client_fd << "\n";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// Single word command like "GET devices"
|
||||||
|
if (cmd == "devices") {
|
||||||
|
// Send devices list
|
||||||
|
std::string content = get_devices_list();
|
||||||
|
std::string out = content + "\n";
|
||||||
|
ssize_t sent = send(client_fd, out.c_str(), (ssize_t)out.size(), MSG_NOSIGNAL);
|
||||||
|
if (sent <= 0) std::cout << "GET devices: send to fd=" << client_fd << " failed\n";
|
||||||
|
else std::cout << "GET devices: sent " << sent << " bytes to fd=" << client_fd << "\n";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// For JSON messages: detect registration, sensor data, or control data
|
||||||
|
bool is_json = (!line.empty() && (line.front() == '{' || line.front() == '['));
|
||||||
|
if (is_json) {
|
||||||
|
// Check if this is a registration message
|
||||||
|
if (line.find("\"type\":\"register\"") != std::string::npos) {
|
||||||
|
// Extract device_id and action
|
||||||
|
std::string device_id = "unknown";
|
||||||
|
std::string action = "connect";
|
||||||
|
size_t p = line.find("\"device_id\"");
|
||||||
|
if (p != std::string::npos) {
|
||||||
|
p = line.find(':', p);
|
||||||
|
if (p != std::string::npos) {
|
||||||
|
size_t q = line.find('"', p);
|
||||||
|
if (q != std::string::npos) {
|
||||||
|
size_t r = line.find('"', q + 1);
|
||||||
|
if (r != std::string::npos) device_id = line.substr(q + 1, r - q - 1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
p = line.find("\"action\"");
|
||||||
|
if (p != std::string::npos) {
|
||||||
|
p = line.find(':', p);
|
||||||
|
if (p != std::string::npos) {
|
||||||
|
size_t q = line.find('"', p);
|
||||||
|
if (q != std::string::npos) {
|
||||||
|
size_t r = line.find('"', q + 1);
|
||||||
|
if (r != std::string::npos) action = line.substr(q + 1, r - q - 1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
register_device(device_id, client_fd, action);
|
||||||
|
} else {
|
||||||
|
// Regular sensor or control data
|
||||||
|
bool is_sensor = (line.find("\"temp\"") != std::string::npos) || (line.find("\"humidity\"") != std::string::npos);
|
||||||
|
bool is_control = (line.find("\"buzzer\"") != std::string::npos) || (line.find("\"led0\"") != std::string::npos) ||
|
||||||
|
(line.find("\"led1\"") != std::string::npos) || (line.find("\"led2\"") != std::string::npos);
|
||||||
|
// extract device_id if present
|
||||||
|
std::string device_id = "unknown";
|
||||||
|
size_t p = line.find("\"device_id\"");
|
||||||
|
if (p != std::string::npos) {
|
||||||
|
p = line.find(':', p);
|
||||||
|
if (p != std::string::npos) {
|
||||||
|
size_t q = line.find('"', p);
|
||||||
|
if (q != std::string::npos) {
|
||||||
|
size_t r = line.find('"', q + 1);
|
||||||
|
if (r != std::string::npos) device_id = line.substr(q + 1, r - q - 1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Update last_seen time for connected devices
|
||||||
|
{
|
||||||
|
std::lock_guard<std::mutex> lk(g_devices_mutex);
|
||||||
|
auto it = g_devices.find(device_id);
|
||||||
|
if (it != g_devices.end() && it->second.status == "connected") {
|
||||||
|
it->second.last_seen = get_timestamp();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Separate handling for sensor and control data
|
||||||
|
if (is_sensor && is_control) {
|
||||||
|
// Message contains both sensor and control data (from device)
|
||||||
|
// Store sensor data ONLY, ignore control states from sensor messages
|
||||||
|
std::cout << "Received combined sensor+control from device " << device_id << ", storing sensor data only" << std::endl;
|
||||||
|
|
||||||
|
// Create clean sensor data with only device_id, temp, and humidity
|
||||||
|
std::string clean_sensor_data = "{\"device_id\":\"" + device_id + "\"";
|
||||||
|
|
||||||
|
// Extract temp if present
|
||||||
|
size_t temp_pos = line.find("\"temp\":");
|
||||||
|
if (temp_pos != std::string::npos) {
|
||||||
|
size_t colon_pos = line.find(':', temp_pos);
|
||||||
|
size_t comma_pos = line.find(',', colon_pos);
|
||||||
|
if (comma_pos == std::string::npos) comma_pos = line.find('}', colon_pos);
|
||||||
|
if (comma_pos != std::string::npos) {
|
||||||
|
std::string temp_value = line.substr(colon_pos + 1, comma_pos - colon_pos - 1);
|
||||||
|
clean_sensor_data += ",\"temp\":" + temp_value;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Extract humidity if present
|
||||||
|
size_t hum_pos = line.find("\"humidity\":");
|
||||||
|
if (hum_pos != std::string::npos) {
|
||||||
|
size_t colon_pos = line.find(':', hum_pos);
|
||||||
|
size_t comma_pos = line.find(',', colon_pos);
|
||||||
|
if (comma_pos == std::string::npos) comma_pos = line.find('}', colon_pos);
|
||||||
|
if (comma_pos != std::string::npos) {
|
||||||
|
std::string hum_value = line.substr(colon_pos + 1, comma_pos - colon_pos - 1);
|
||||||
|
clean_sensor_data += ",\"humidity\":" + hum_value;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
clean_sensor_data += "}";
|
||||||
|
write_device_file(device_id, "sensor", clean_sensor_data);
|
||||||
|
|
||||||
|
} else if (is_sensor) {
|
||||||
|
// Pure sensor data (without control states)
|
||||||
|
write_device_file(device_id, "sensor", line);
|
||||||
|
} else if (is_control) {
|
||||||
|
// Pure control data (from Qt interface)
|
||||||
|
write_device_file(device_id, "control", line);
|
||||||
|
std::cout << "Stored control command from Qt for device " << device_id << std::endl;
|
||||||
|
|
||||||
|
// Send control command back to the device that should execute it
|
||||||
|
std::lock_guard<std::mutex> lk(g_devices_mutex);
|
||||||
|
auto it = g_devices.find(device_id);
|
||||||
|
if (it != g_devices.end() && it->second.status == "connected") {
|
||||||
|
std::string control_msg = line + "\n";
|
||||||
|
ssize_t sent = send(it->second.client_fd, control_msg.c_str(), (ssize_t)control_msg.size(), MSG_NOSIGNAL);
|
||||||
|
if (sent > 0) {
|
||||||
|
std::cout << "Forwarded control command to device " << device_id << " (fd=" << it->second.client_fd << "), sent " << sent << " bytes" << std::endl;
|
||||||
|
} else {
|
||||||
|
std::cout << "Failed to forward control command to device " << device_id << std::endl;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// broadcast received line to other connected clients (e.g. UI)
|
||||||
|
broadcast_line(line, client_fd);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
buffer.erase(0, pos + 1);
|
||||||
|
}
|
||||||
|
} else if (n == 0) {
|
||||||
|
// closed by peer
|
||||||
|
break;
|
||||||
|
} else {
|
||||||
|
// error
|
||||||
|
if (errno == EINTR) continue;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
close(client_fd);
|
||||||
|
// Mark device as disconnected
|
||||||
|
unregister_device(client_fd);
|
||||||
|
// remove from global client list
|
||||||
|
remove_client_fd(client_fd);
|
||||||
|
std::cout << "Client disconnected: " << peer << std::endl;
|
||||||
|
}
|
||||||
|
|
||||||
|
int main(int argc, char **argv)
|
||||||
|
{
|
||||||
|
// ignore SIGPIPE so sends to closed sockets do not kill the process
|
||||||
|
signal(SIGPIPE, SIG_IGN);
|
||||||
|
|
||||||
|
int port = 5000;
|
||||||
|
if (argc >= 2) port = std::atoi(argv[1]);
|
||||||
|
|
||||||
|
int listen_fd = socket(AF_INET, SOCK_STREAM, 0);
|
||||||
|
if (listen_fd < 0) {
|
||||||
|
perror("socket");
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
int opt = 1;
|
||||||
|
setsockopt(listen_fd, SOL_SOCKET, SO_REUSEADDR, &opt, sizeof(opt));
|
||||||
|
|
||||||
|
struct sockaddr_in addr;
|
||||||
|
std::memset(&addr, 0, sizeof(addr));
|
||||||
|
addr.sin_family = AF_INET;
|
||||||
|
addr.sin_addr.s_addr = INADDR_ANY;
|
||||||
|
addr.sin_port = htons(port);
|
||||||
|
|
||||||
|
if (bind(listen_fd, (struct sockaddr *)&addr, sizeof(addr)) < 0) {
|
||||||
|
perror("bind");
|
||||||
|
close(listen_fd);
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (listen(listen_fd, 10) < 0) {
|
||||||
|
perror("listen");
|
||||||
|
close(listen_fd);
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::cout << "Server listening on port " << port << std::endl;
|
||||||
|
|
||||||
|
// accept loop
|
||||||
|
while (g_running) {
|
||||||
|
struct sockaddr_in client_addr;
|
||||||
|
socklen_t client_len = sizeof(client_addr);
|
||||||
|
int client_fd = accept(listen_fd, (struct sockaddr *)&client_addr, &client_len);
|
||||||
|
if (client_fd < 0) {
|
||||||
|
if (errno == EINTR) continue;
|
||||||
|
perror("accept");
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
char ipbuf[INET_ADDRSTRLEN];
|
||||||
|
inet_ntop(AF_INET, &client_addr.sin_addr, ipbuf, sizeof(ipbuf));
|
||||||
|
int client_port = ntohs(client_addr.sin_port);
|
||||||
|
std::string peer = std::string(ipbuf) + ":" + std::to_string(client_port);
|
||||||
|
// track client and spawn handler thread
|
||||||
|
add_client_fd(client_fd);
|
||||||
|
std::thread t(handle_client, client_fd, peer);
|
||||||
|
t.detach();
|
||||||
|
}
|
||||||
|
|
||||||
|
close(listen_fd);
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user