Initial commit: IP-KVM server implementation with complete functionality
Core Features:
- Web server based on Mongoose framework listening on port 82
- WebSocket support for real-time mouse and keyboard control
- Serial communication via /dev/ttyS5 (115200 baud rate)
- GPIO-based hardware control (GPIO 34/35 for shutdown/reboot, GPIO 139 for reset)
- User authentication and management system
- Network configuration management with dynamic updates
- MJPG-streamer integration for video streaming (port 8080, 1920x1080)
- File upload support with chunked transfer capability
- Hardware reset functionality via GPIO button
Technical Implementation:
- Mouse protocol conversion to serial data format {0x57, 0xAB, ...}
- Keyboard input handling with proper HID report generation
- Real-time configuration updates without service restart
- Thread-based GPIO monitoring for hardware events
- Authentication integration with MJPG-streamer
- Default configuration restoration on hardware reset
Configuration Files:
- /mnt/user.txt - User credentials storage
- /etc/init.d/xnet.conf - Network configuration
- /www/webcam - Web interface files
Documentation:
- Complete functional specification in Chinese (功能说明.md)
- Detailed WebSocket message protocol documentation
- GPIO mapping and hardware control specifications
This is a complete standalone IP-KVM server that enables remote
keyboard, video, and mouse control over IP networks.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
94
Makefile
Normal file
94
Makefile
Normal file
@ -0,0 +1,94 @@
|
||||
#
|
||||
# Copyright (C) 2006-2015 OpenWrt.org
|
||||
#
|
||||
# Permission is hereby granted, free of charge, to any person obtaining a
|
||||
# copy of this software and associated documentation files (the "Software"),
|
||||
# to deal in the Software without restriction, including without limitation
|
||||
# the rights to use, copy, modify, merge, publish, distribute, sublicense,
|
||||
# and/or sell copies of the Software, and to permit persons to whom the
|
||||
# Software is furnished to do so, subject to the following conditions:
|
||||
#
|
||||
# The above copyright notice and this permission notice (including the next
|
||||
# paragraph) shall be included in all copies or substantial portions of the
|
||||
# Software.
|
||||
#
|
||||
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
|
||||
# THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
|
||||
# FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
|
||||
# DEALINGS IN THE SOFTWARE.
|
||||
#
|
||||
# ---
|
||||
#
|
||||
# The above is the version of the MIT "Expat" License used by X.org:
|
||||
#
|
||||
# http://cgit.freedesktop.org/xorg/xserver/tree/COPYING
|
||||
#
|
||||
|
||||
include $(TOPDIR)/rules.mk
|
||||
include $(BUILD_DIR)/package.mk
|
||||
#include ../sunxifb.mk
|
||||
|
||||
PKG_NAME:=xtell_server
|
||||
PKG_VERSION:=1.0.0
|
||||
PKG_RELEASE:=1
|
||||
|
||||
PKG_BUILD_DIR := $(COMPILE_DIR)/$(PKG_NAME)
|
||||
|
||||
define Package/$(PKG_NAME)
|
||||
SECTION:=xtell
|
||||
CATEGORY:=xtell
|
||||
DEPENDS:=
|
||||
TITLE:=xtell server 1.0.0
|
||||
MAINTAINER:=edison<edison@xtell.cn>
|
||||
endef
|
||||
|
||||
define Package/$(PKG_NAME)/config
|
||||
endef
|
||||
|
||||
define Package/$(PKG_NAME)/Default
|
||||
endef
|
||||
|
||||
define Package/$(PKG_NAME)/description
|
||||
xtell server v1.0.0
|
||||
endef
|
||||
|
||||
define Build/Prepare
|
||||
$(INSTALL_DIR) $(PKG_BUILD_DIR)/
|
||||
$(CP) ./src $(PKG_BUILD_DIR)/
|
||||
# $(CP) ../../../../take_photo $(PKG_BUILD_DIR)/src
|
||||
endef
|
||||
|
||||
define Build/Configure
|
||||
endef
|
||||
|
||||
TARGET_CFLAGS+=-I$(PKG_BUILD_DIR)/src
|
||||
|
||||
define Build/Compile
|
||||
@echo "===================================================================="
|
||||
$(MAKE) -C $(PKG_BUILD_DIR)/src\
|
||||
ARCH="$(TARGET_ARCH)" \
|
||||
AR="$(TARGET_AR)" \
|
||||
CC="$(TARGET_CC)" \
|
||||
CXX="$(TARGET_CXX)" \
|
||||
CFLAGS="$(TARGET_CFLAGS)" \
|
||||
LDFLAGS="$(TARGET_LDFLAGS)" \
|
||||
INSTALL_PREFIX="$(PKG_INSTALL_DIR)" \
|
||||
all
|
||||
@echo PATH=$(PATH)
|
||||
@echo STAGING_DIR=$(STAGING_DIR)
|
||||
|
||||
endef
|
||||
|
||||
#define Build/InstallDev
|
||||
# $(INSTALL_DIR) $(1)/usr/lib
|
||||
#endef
|
||||
|
||||
define Package/$(PKG_NAME)/install
|
||||
$(INSTALL_DIR) $(1)/usr/bin/
|
||||
$(INSTALL_BIN) $(PKG_BUILD_DIR)/src/$(PKG_NAME) $(1)/usr/bin/
|
||||
endef
|
||||
|
||||
$(eval $(call BuildPackage,$(PKG_NAME)))
|
||||
26
src/Makefile
Normal file
26
src/Makefile
Normal file
@ -0,0 +1,26 @@
|
||||
PROG ?= xtell_server # Program we are building
|
||||
DELETE = rm -rf # Command to remove files
|
||||
OUT ?= -o $(PROG) # Compiler argument for output file
|
||||
SOURCES = main.c mongoose.c # Source code files
|
||||
CFLAGS = -W -Wall -Wextra -g -I. # Build options
|
||||
|
||||
# Mongoose build options. See https://mongoose.ws/documentation/#build-options
|
||||
#CFLAGS_MONGOOSE += -DMG_ENABLE_LINES
|
||||
|
||||
|
||||
#ifeq ($(OS),Windows_NT) # Windows settings. Assume MinGW compiler. To use VC: make CC=cl CFLAGS=/MD OUT=/Feprog.exe
|
||||
# PROG ?= example.exe # Use .exe suffix for the binary
|
||||
# CC = gcc # Use MinGW gcc compiler
|
||||
# CFLAGS += -lws2_32 # Link against Winsock library
|
||||
# DELETE = cmd /C del /Q /F /S # Command prompt command to delete files
|
||||
# OUT ?= -o $(PROG) # Build output
|
||||
#endif
|
||||
|
||||
all: $(PROG) # Default target. Build and run program
|
||||
# $(RUN) ./$(PROG) $(ARGS)
|
||||
|
||||
$(PROG): $(SOURCES) # Build program from sources
|
||||
$(CC) $(SOURCES) $(CFLAGS) $(CFLAGS_MONGOOSE) $(CFLAGS_EXTRA) $(OUT)
|
||||
|
||||
clean: # Cleanup. Delete built program and all build artifacts
|
||||
$(DELETE) $(PROG) *.o *.obj *.exe *.dSYM
|
||||
469
src/main.c
Normal file
469
src/main.c
Normal file
@ -0,0 +1,469 @@
|
||||
// test the upload by using following cmd in linux
|
||||
// curl http://localhost:8000/upload?name=a.txt --data-binary @large_file.txt
|
||||
#include "mongoose.h"
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <fcntl.h>
|
||||
#include <termios.h>
|
||||
#include <unistd.h>
|
||||
#include <errno.h>
|
||||
#define GPIO_PIN 139
|
||||
#define FILE_PATH "/mnt/user.txt"
|
||||
|
||||
int global_serial_fd = -1;
|
||||
|
||||
|
||||
int read_gpio() {
|
||||
char buffer[256];
|
||||
char value_str[3];
|
||||
int fd;
|
||||
snprintf(buffer, sizeof(buffer), "/sys/class/gpio/gpio%d/value", GPIO_PIN);
|
||||
fd = open(buffer, O_RDONLY);
|
||||
if (fd == -1) {
|
||||
perror("GPIO read failed");
|
||||
return -1;
|
||||
}
|
||||
read(fd, value_str, 3);
|
||||
close(fd);
|
||||
return atoi(value_str);
|
||||
}
|
||||
|
||||
void *gpio_handler(void *arg) {
|
||||
|
||||
bool prev_gpio_state = false; // 保存上一次按键状态
|
||||
char command[1024];
|
||||
FILE *conf_file;
|
||||
while(1) {
|
||||
|
||||
bool current_gpio_state = (read_gpio() == 0); // 获取当前按键状态
|
||||
|
||||
if (current_gpio_state && !prev_gpio_state) {
|
||||
// 按键按下且上一次按键状态为未按下
|
||||
printf("GPIO triggered: Low to High\n");
|
||||
system("killall mjpg_streamer");
|
||||
sleep (2);
|
||||
// 在这里执行删除文件的操作
|
||||
if (remove(FILE_PATH) == 0) {
|
||||
conf_file = fopen("/etc/init.d/xnet.conf", "w");
|
||||
if (conf_file != NULL) {
|
||||
fprintf(conf_file, "ip 192.168.6.100\n");
|
||||
fprintf(conf_file, "port 8700\n");
|
||||
fprintf(conf_file, "mask 255.255.255.0\n");
|
||||
fprintf(conf_file, "gw 192.168.6.1\n");
|
||||
fprintf(conf_file, "dns1 1.2.3.4\n");
|
||||
fprintf(conf_file, "dns2 8.8.8.8\n");
|
||||
fclose(conf_file);
|
||||
|
||||
printf("IP configuration updated and saved.\n");
|
||||
system("/etc/init.d/xnet_init.sh");
|
||||
system("sync");
|
||||
} else {
|
||||
printf("Failed to open xnet.conf file for writing.\n");
|
||||
}
|
||||
|
||||
printf("File %s removed successfully.\n", FILE_PATH);
|
||||
// sprintf(command, "mjpg_streamer -i \"input_uvc.so -d /dev/video0 -r 1920x1080\" -o \"output_http.so -p 8080 -w /www/webcam\" &");
|
||||
sprintf(command, "mjpg_streamer -i \"input_uvc.so -d /dev/video0 -r 1920x1080\" -o \"/mnt/output_http.so -p 8080 -w /www/webcam\" &");
|
||||
system(command);
|
||||
// mg_ws_send(c, "refresh", 7, WEBSOCKET_OP_TEXT);
|
||||
} else {
|
||||
conf_file = fopen("/etc/init.d/xnet.conf", "w");
|
||||
if (conf_file != NULL) {
|
||||
fprintf(conf_file, "ip 192.168.6.100\n");
|
||||
fprintf(conf_file, "port 8700\n");
|
||||
fprintf(conf_file, "mask 255.255.255.0\n");
|
||||
fprintf(conf_file, "gw 192.168.6.1\n");
|
||||
fprintf(conf_file, "dns1 1.2.3.4\n");
|
||||
fprintf(conf_file, "dns2 8.8.8.8\n");
|
||||
fclose(conf_file);
|
||||
|
||||
printf("IP configuration updated and saved.\n");
|
||||
system("/etc/init.d/xnet_init.sh");
|
||||
system("sync");
|
||||
} else {
|
||||
printf("Failed to open xnet.conf file for writing.\n");
|
||||
}
|
||||
perror("File removal failed");
|
||||
// sprintf(command, "mjpg_streamer -i \"input_uvc.so -d /dev/video0 -r 1920x1080\" -o \"output_http.so -p 8080 -w /www/webcam\" &");
|
||||
sprintf(command, "mjpg_streamer -i \"input_uvc.so -d /dev/video0 -r 1920x1080\" -o \"/mnt/output_http.so -p 8080 -w /www/webcam\" &");
|
||||
system(command);
|
||||
// mg_ws_send(c, "refresh", 7, WEBSOCKET_OP_TEXT);
|
||||
}
|
||||
}
|
||||
|
||||
prev_gpio_state = current_gpio_state; // 更新上一次按键状态
|
||||
|
||||
usleep(100 * 1000); // 休眠一段时间,避免过于频繁地检测按键状态
|
||||
}
|
||||
}
|
||||
|
||||
void init_gpio() {
|
||||
char buffer[256];
|
||||
int fd;
|
||||
// 导出 GPIO
|
||||
fd = open("/sys/class/gpio/export", O_WRONLY);
|
||||
if (fd == -1) {
|
||||
perror("GPIO export failed");
|
||||
exit(EXIT_FAILURE);
|
||||
}
|
||||
snprintf(buffer, sizeof(buffer), "%d", GPIO_PIN);
|
||||
write(fd, buffer, strlen(buffer));
|
||||
close(fd);
|
||||
// 设置 GPIO 方向为输入
|
||||
snprintf(buffer, sizeof(buffer), "/sys/class/gpio/gpio%d/direction", GPIO_PIN);
|
||||
fd = open(buffer, O_WRONLY);
|
||||
if (fd == -1) {
|
||||
perror("GPIO direction set failed");
|
||||
exit(EXIT_FAILURE);
|
||||
}
|
||||
write(fd, "in", 2);
|
||||
close(fd);
|
||||
}
|
||||
|
||||
|
||||
|
||||
int init_serial() {
|
||||
int fd = open("/dev/ttyS5", O_RDWR | O_NOCTTY | O_SYNC);
|
||||
if (fd < 0) {
|
||||
printf("Error %i from open: %s\n", errno, strerror(errno));
|
||||
return -1;
|
||||
}
|
||||
|
||||
struct termios tty;
|
||||
memset(&tty, 0, sizeof tty);
|
||||
cfsetospeed(&tty, B115200);
|
||||
cfsetispeed(&tty, B115200);
|
||||
// ... 其他串口配置 ...
|
||||
|
||||
tty.c_cflag |= (CLOCAL | CREAD); // Ignore modem controls and enable reading
|
||||
tty.c_cflag &= ~CSIZE;
|
||||
tty.c_cflag |= CS8; // 8-bit characters
|
||||
tty.c_cflag &= ~PARENB; // No parity bit
|
||||
tty.c_cflag &= ~CSTOPB; // Only need 1 stop bit
|
||||
tty.c_cflag &= ~CRTSCTS; // No hardware flowcontrol
|
||||
|
||||
// Setup for non-canonical mode
|
||||
tty.c_iflag &= ~(IXON | IXOFF | IXANY); // Shut off xon/xoff ctrl
|
||||
tty.c_lflag &= ~(ICANON | ECHO | ECHOE | ISIG); // Disable echo etc
|
||||
tty.c_oflag &= ~OPOST; // Prevent special interpretation of output bytes
|
||||
// Fetch bytes as they become available
|
||||
tty.c_cc[VMIN] = 1;
|
||||
tty.c_cc[VTIME] = 1;
|
||||
|
||||
if (tcsetattr(fd, TCSANOW, &tty) != 0) {
|
||||
printf("Error %i from tcsetattr: %s\n", errno, strerror(errno));
|
||||
return -1;
|
||||
}
|
||||
|
||||
return fd;
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
// 以下函数提取 `src` 中存在的字符
|
||||
// 在 `m` 和 `n` 之间(不包括 `n`)
|
||||
char* substr(const char *src, int m, int n){
|
||||
// 获取目标字符串的长度
|
||||
int len = n - m;
|
||||
// 为目标分配 (len + 1) 个字符(+1 用于额外的空字符)
|
||||
char *dest = (char*)malloc(sizeof(char) * (len + 1));
|
||||
// 从源字符串中提取第 m 和第 n 索引之间的字符
|
||||
// 并将它们复制到目标字符串中
|
||||
for (int i = m; i < n && (*(src + i) != '\0'); i++){
|
||||
*dest = *(src + i);
|
||||
dest++;
|
||||
}
|
||||
// 以空值结束目标字符串
|
||||
*dest = '\0';
|
||||
// 返回目标字符串
|
||||
return dest - len;
|
||||
}
|
||||
|
||||
int chunk_num=0; //文件分块序号
|
||||
FILE *fp;
|
||||
|
||||
// HTTP request handler function.
|
||||
static void cb(struct mg_connection *c, int ev, void *ev_data, void *fn_data) {
|
||||
struct mg_http_message *hm = (struct mg_http_message *) ev_data;
|
||||
/////////////////////////////////////////////////////
|
||||
// 处理上传的文件
|
||||
if (ev == MG_EV_HTTP_CHUNK && mg_http_match_uri(hm, "/upload")) { //上传用的网址
|
||||
chunk_num++;
|
||||
if (chunk_num==1){
|
||||
fp = fopen("/mnt/UDISK/gcode.nc","w"); //上传文件固定路径
|
||||
}
|
||||
// printf("."); //log
|
||||
printf("%d\n", chunk_num++);
|
||||
// MG_INFO(("Got chunk len %lu", (unsigned long) hm->chunk.len));
|
||||
// MG_INFO(("Query string: [%.*s]", (int) hm->query.len, hm->query.ptr));
|
||||
// MG_INFO(("Chunk data:\n%.*s", (int) hm->chunk.len, hm->chunk.ptr));
|
||||
fwrite(hm->chunk.ptr, 1, hm->chunk.len, fp); //上传的数据写入文件
|
||||
mg_http_delete_chunk(c, hm); //释放内存,允许后续数据块上传
|
||||
|
||||
if (hm->chunk.len == 0) {
|
||||
chunk_num=0;
|
||||
fclose(fp);
|
||||
mg_http_reply(c, 200, "", "ok\n"); //上传结束
|
||||
MG_INFO(("Last chunk received, sending response"));
|
||||
}
|
||||
|
||||
} else {
|
||||
if (ev == MG_EV_OPEN) {
|
||||
// c->is_hexdumping = 1;
|
||||
} else if (ev == MG_EV_HTTP_MSG) {
|
||||
struct mg_http_message *hm = (struct mg_http_message *) ev_data;
|
||||
if (mg_http_match_uri(hm, "/websocket")) {
|
||||
// Upgrade to websocket. From now on, a connection is a full-duplex
|
||||
// Websocket connection, which will receive MG_EV_WS_MSG events.
|
||||
printf("websocket connected\n");
|
||||
mg_ws_upgrade(c, hm, NULL);
|
||||
}
|
||||
|
||||
} else if (ev == MG_EV_WS_MSG) {
|
||||
struct mg_ws_message *wm = (struct mg_ws_message *) ev_data;
|
||||
printf("Received WebSocket message: %.*s\n", (int) wm->data.len, wm->data.ptr);
|
||||
|
||||
if (strncmp(wm->data.ptr, "mouse,", 6) == 0) {
|
||||
int num[6];
|
||||
sscanf(wm->data.ptr + 6, "%d,%d,%d,%d,%d,%x", &num[0], &num[1], &num[2], &num[3], &num[4], &num[5]);
|
||||
unsigned char data[13] = {0x57, 0xAB, 0x00, 0x04, 0x07, 0x02, 0x00, 0x40, 0x01, 0x15, 0x02, 0x00, 0x67};
|
||||
data[6] = num[0];
|
||||
data[7] = num[1];
|
||||
data[8] = num[2];
|
||||
data[9] = num[3];
|
||||
data[10] = num[4];
|
||||
data[11] = num[5];
|
||||
unsigned char sum = 0;
|
||||
for (int i = 0; i < 12; i++) {
|
||||
sum += data[i];
|
||||
}
|
||||
data[12] = sum;
|
||||
|
||||
printf("Data to be sent: ");
|
||||
for (int i = 0; i < sizeof(data); i++) {
|
||||
printf("%02X ", data[i]);
|
||||
}
|
||||
printf("\n");
|
||||
|
||||
if (global_serial_fd >= 0) {
|
||||
write(global_serial_fd, data, sizeof(data));
|
||||
}
|
||||
} else if (strncmp(wm->data.ptr, "keyboard,", 9) == 0) {
|
||||
int num[2];
|
||||
sscanf(wm->data.ptr + 9, "%x,%x", &num[0], &num[1]);
|
||||
unsigned char data[14] = {0x57,0xAB,0x00,0x02,0x08,0x00,0x00,0x04,0x00,0x00,0x00,0x00,0x00,0x10};
|
||||
data[5] = num[1];
|
||||
data[7] = num[0];
|
||||
unsigned char sum = 0;
|
||||
for (int i = 0; i < 13; i++) {
|
||||
sum += data[i];
|
||||
}
|
||||
data[13] = sum;
|
||||
if (global_serial_fd >= 0) {
|
||||
write(global_serial_fd, data, sizeof(data));
|
||||
}
|
||||
printf("Data to be sent: ");
|
||||
for (int i = 0; i < sizeof(data); i++) {
|
||||
printf("%02X ", data[i]);
|
||||
}
|
||||
printf("\n");
|
||||
|
||||
}else if (strncmp(wm->data.ptr, "newuser,", 8) == 0) {
|
||||
char oldUsername[256];
|
||||
char oldPassword[256];
|
||||
char username[256];
|
||||
char password[256];
|
||||
|
||||
// 解析收到的数据
|
||||
sscanf(wm->data.ptr + 8, "%[^,],%[^,],%[^,],%s", oldUsername, oldPassword, username, password);
|
||||
printf("Received WebSocket message: oldUsername=%s, oldPassword=%s, username=%s, password=%s\n", oldUsername, oldPassword, username, password);
|
||||
// 读取文件中的旧用户名和密码
|
||||
FILE *file = fopen("/mnt/user.txt", "r");
|
||||
char fileUsername[256], filePassword[256];
|
||||
if (file != NULL) {
|
||||
fscanf(file, "Username: %[^,], Password: %s", fileUsername, filePassword);
|
||||
fclose(file);
|
||||
|
||||
// 比较用户名和密码
|
||||
if (strcmp(oldUsername, fileUsername) == 0 && strcmp(oldPassword, filePassword) == 0) {
|
||||
// 用户名和密码匹配,更新文件
|
||||
file = fopen("/mnt/user.txt", "w");
|
||||
if (file != NULL) {
|
||||
fprintf(file, "Username: %s, Password: %s\n", username, password);
|
||||
fclose(file);
|
||||
printf("User data updated and saved: %s %s\n", username, password);
|
||||
system("sync"); // 确保数据被写入磁盘
|
||||
system("killall mjpg_streamer");
|
||||
sleep(1);
|
||||
char command[1024];
|
||||
// sprintf(command, "mjpg_streamer -i \"input_uvc.so -d /dev/video0 -r 1920x1080\" -o \"output_http.so -p 8080 -w /www/webcam -c %s:%s\" &", username, password);
|
||||
sprintf(command, "mjpg_streamer -i \"input_uvc.so -d /dev/video0 -r 1920x1080\" -o \"/mnt/output_http.so -p 8080 -w /www/webcam -c %s:%s\" &", username, password);
|
||||
system(command);
|
||||
printf("Command executed: %s\n", command);
|
||||
mg_ws_send(c, "refresh", 7, WEBSOCKET_OP_TEXT);
|
||||
} else {
|
||||
printf("Failed to open file for writing.\n");
|
||||
}
|
||||
} else {
|
||||
if (strcmp(oldUsername, fileUsername) != 0 || strcmp(oldPassword, filePassword) != 0) {
|
||||
// 发送一条消息给客户端,通知用户名错误
|
||||
mg_ws_send(c, "oldusererror", 12, WEBSOCKET_OP_TEXT);
|
||||
}
|
||||
printf("oldusernameerror xtell\n");
|
||||
|
||||
// printf("Username or password does not match.\n");
|
||||
}
|
||||
} else {
|
||||
if (access("/mnt/user.txt", F_OK) == -1) {
|
||||
// 文件不存在,执行默认操作
|
||||
printf("No existing user file, treating as new setup.\n");
|
||||
file = fopen("/mnt/user.txt", "w");
|
||||
if (file) {
|
||||
fprintf(file, "Username: %s, Password: %s\n", username, password);
|
||||
fclose(file);
|
||||
printf("Initial user data saved: %s %s\n", username, password);
|
||||
system("sync");
|
||||
system("killall mjpg_streamer");
|
||||
sleep(1);
|
||||
char command[1024];
|
||||
// sprintf(command, "mjpg_streamer -i \"input_uvc.so -d /dev/video0 -r 1920x1080\" -o \"output_http.so -p 8080 -w /www/webcam -c %s:%s\" &", username, password);
|
||||
sprintf(command, "mjpg_streamer -i \"input_uvc.so -d /dev/video0 -r 1920x1080\" -o \"/mnt/output_http.so -p 8080 -w /www/webcam -c %s:%s\" &", username, password);
|
||||
system(command);
|
||||
printf("Command executed: %s\n", command);
|
||||
mg_ws_send(c, "refresh", 7, WEBSOCKET_OP_TEXT);
|
||||
} else {
|
||||
printf("Failed to create new user file.\n");
|
||||
}
|
||||
} else {
|
||||
printf("Failed to open existing user file.\n");
|
||||
}
|
||||
}
|
||||
}else if (strncmp(wm->data.ptr, "newip,", 6) == 0) {
|
||||
char ipAddress[256];
|
||||
char mask[256];
|
||||
char gateway[256];
|
||||
char dns1[256];
|
||||
char dns2[256];
|
||||
|
||||
// 解析收到的数据
|
||||
sscanf(wm->data.ptr + 6, "%[^,],%[^,],%[^,],%[^,],%s", ipAddress, mask, gateway, dns1, dns2);
|
||||
printf("Received WebSocket message: ipAddress=%s, mask=%s, gateway=%s, dns1=%s, dns2=%s\n", ipAddress, mask, gateway, dns1, dns2);
|
||||
|
||||
// 保存到文件
|
||||
FILE *file = fopen("/etc/init.d/xnet.conf", "w");
|
||||
if (file != NULL) {
|
||||
fprintf(file, "ip %s\n", ipAddress);
|
||||
fprintf(file, "port 8700\n");
|
||||
fprintf(file, "mask %s\n", mask);
|
||||
fprintf(file, "gw %s\n", gateway);
|
||||
fprintf(file, "dns1 %s\n", dns1);
|
||||
fprintf(file, "dns2 %s\n", dns2);
|
||||
fclose(file);
|
||||
|
||||
printf("IP configuration updated and saved.\n");
|
||||
system("/etc/init.d/xnet_init.sh");
|
||||
system("sync");
|
||||
|
||||
} else {
|
||||
printf("Failed to open xnet.conf file for writing.\n");
|
||||
}
|
||||
}else if (strncmp(wm->data.ptr, "control,", 8) == 0) {
|
||||
char action[256];
|
||||
|
||||
// 解析收到的控制指令
|
||||
sscanf(wm->data.ptr + 8, "%s", action);
|
||||
printf("Received control command: %s\n", action);
|
||||
|
||||
|
||||
if (strcmp(action, "shutdown") == 0) {
|
||||
// 执行关机操作 控制 GPIO 35 开关机
|
||||
system("echo 1 > /sys/class/gpio/gpio35/value");
|
||||
usleep(500000);
|
||||
system("echo 0 > /sys/class/gpio/gpio35/value");
|
||||
printf("Shutting down the system...\n");
|
||||
|
||||
} else if (strcmp(action, "reboot") == 0) {
|
||||
// 执行重启操作 控制 GPIO 34 重启
|
||||
system("echo 1 > /sys/class/gpio/gpio34/value");
|
||||
usleep(500000);
|
||||
system("echo 0 > /sys/class/gpio/gpio34/value");
|
||||
printf("Rebooting the system...\n");
|
||||
|
||||
} else {
|
||||
printf("Unknown control command: %s\n", action);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
}
|
||||
(void) fn_data;
|
||||
}
|
||||
|
||||
|
||||
int main(void) {
|
||||
|
||||
char command[1024];
|
||||
FILE *file = fopen("/mnt/user.txt", "r");
|
||||
if (file != NULL) {
|
||||
char username[256], password[256];
|
||||
if (fscanf(file, "Username: %[^,], Password: %s", username, password) == 2) {
|
||||
// sprintf(command, "mjpg_streamer -i \"input_uvc.so -d /dev/video0 -r 1920x1080\" -o \"output_http.so -p 8080 -w /www/webcam -c %s:%s\" &", username, password);
|
||||
sprintf(command, "mjpg_streamer -i \"input_uvc.so -d /dev/video0 -r 1920x1080\" -o \"/mnt/output_http.so -p 8080 -w /www/webcam -c %s:%s\" &", username, password);
|
||||
} else {
|
||||
printf("Failed to parse user credentials.\n");
|
||||
// sprintf(command, "mjpg_streamer -i \"input_uvc.so -d /dev/video0 -r 1920x1080\" -o \"output_http.so -p 8080 -w /www/webcam\" &");
|
||||
sprintf(command, "mjpg_streamer -i \"input_uvc.so -d /dev/video0 -r 1920x1080\" -o \"/mntoutput_http.so -p 8080 -w /www/webcam\" &");
|
||||
}
|
||||
fclose(file);
|
||||
} else {
|
||||
if (access("/mnt/user.txt", F_OK) != -1) {
|
||||
printf("File exists but could not be opened.\n");
|
||||
} else {
|
||||
printf("No credentials file found, starting mjpg_streamer with default settings.\n");
|
||||
}
|
||||
// sprintf(command, "mjpg_streamer -i \"input_uvc.so -d /dev/video0 -r 1920x1080\" -o \"output_http.so -p 8080 -w /www/webcam\" &");
|
||||
sprintf(command, "mjpg_streamer -i \"input_uvc.so -d /dev/video0 -r 1920x1080\" -o \"/mnt/output_http.so -p 8080 -w /www/webcam\" &");
|
||||
}
|
||||
system("killall mjpg_streamer"); // Ensure no previous instance is running
|
||||
sleep(1); // Wait a bit to make sure the process has been terminated
|
||||
system(command); // Execute the command
|
||||
printf("Command executed: %s\n", command);
|
||||
//读gpio重置IP和用户名+密码
|
||||
system("echo 139 > /sys/class/gpio/export");
|
||||
system("echo in > /sys/class/gpio/gpio139/direction");
|
||||
//开关机
|
||||
system("echo 34 > /sys/class/gpio/export");
|
||||
system("echo out > /sys/class/gpio/gpio34/direction");
|
||||
//重启
|
||||
system("echo 35 > /sys/class/gpio/export");
|
||||
system("echo out > /sys/class/gpio/gpio35/direction");
|
||||
system("ifconfig eth0 up");
|
||||
|
||||
pthread_t client_thread2;
|
||||
if (pthread_create(&client_thread2, NULL, gpio_handler, NULL) != 0) {
|
||||
perror("Thread creation failed");
|
||||
exit(EXIT_FAILURE);
|
||||
}
|
||||
// Detach the thread to free its resources automatically
|
||||
pthread_detach(client_thread2);
|
||||
|
||||
struct mg_mgr mgr;
|
||||
|
||||
global_serial_fd = init_serial();
|
||||
if (global_serial_fd < 0) {
|
||||
return 1;
|
||||
}
|
||||
mg_mgr_init(&mgr);
|
||||
mg_log_set(MG_LL_DEBUG); // Set debug log level
|
||||
mg_http_listen(&mgr, "http://0.0.0.0:82", cb, NULL);
|
||||
for (;;) mg_mgr_poll(&mgr, 50);
|
||||
mg_mgr_free(&mgr);
|
||||
|
||||
if (global_serial_fd >= 0) {
|
||||
close(global_serial_fd);
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
8124
src/mongoose.c
Normal file
8124
src/mongoose.c
Normal file
File diff suppressed because it is too large
Load Diff
1635
src/mongoose.h
Normal file
1635
src/mongoose.h
Normal file
File diff suppressed because it is too large
Load Diff
9
src/my_build.sh
Normal file
9
src/my_build.sh
Normal file
@ -0,0 +1,9 @@
|
||||
#!/bin/sh
|
||||
|
||||
export PATH=/home/xtell_projects_dev/allwinner/mangopi/t113/Tina-Linux/out/host/bin:/home/xtell_projects_dev/allwinner/mangopi/t113/Tina-Linux/out/t113-mq_r/staging_dir/toolchain/bin:/home/xtell_projects_dev/allwinner/mangopi/t113/Tina-Linux/prebuilt/gcc/linux-x86/arm/toolchain-sunxi-musl/toolchain/usr/bin:/home/xtell_projects_dev/allwinner/mangopi/t113/Tina-Linux/prebuilt/gcc/linux-x86/arm/toolchain-sunxi-musl/toolchain/bin:/home/xtell_projects_dev/allwinner/mangopi/t113/Tina-Linux/out/t113-mq_r/staging_dir/toolchain/bin:/home/xtell_projects_dev/allwinner/mangopi/t113/Tina-Linux/prebuilt/gcc/linux-x86/arm/toolchain-sunxi-musl/toolchain/usr/bin:/home/xtell_projects_dev/allwinner/mangopi/t113/Tina-Linux/prebuilt/gcc/linux-x86/arm/toolchain-sunxi-musl/toolchain/bin:/home/xtell_projects_dev/allwinner/mangopi/t113/Tina-Linux/out/host/bin:/home/xtell_projects_dev/allwinner/mangopi/t113/Tina-Linux/out/host/bin:/home/edison/.local/bin:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin:/usr/games:/usr/local/games:/snap/bin
|
||||
|
||||
export STAGING_DIR=/home/xtell_projects_dev/allwinner/mangopi/t113/Tina-Linux/out/t113-mq_r/staging_dir/target
|
||||
|
||||
make -C . ARCH="arm" AR="arm-openwrt-linux-muslgnueabi-ar" CC="arm-openwrt-linux-muslgnueabi-gcc" CXX="arm-openwrt-linux-muslgnueabi-g++" CFLAGS="-Os -pipe -march=armv7-a -mtune=cortex-a7 -mfpu=neon -fno-caller-saves -Wno-unused-result -mfloat-abi=hard -Wformat -Werror=format-security -fPIC -fstack-protector -D_FORTIFY_SOURCE=2 -Wl,-z,now -Wl,-z,relro -DUSE_SUNXIFB_DOUBLE_BUFFER -DUSE_SUNXIFB_CACHE -DCONF_G2D_VERSION_NEW -I/home/xtell_projects_dev/allwinner/mangopi/t113/Tina-Linux/out/t113-mq_r/compile_dir/target/lv_examples/src" LDFLAGS="-L/home/xtell_projects_dev/allwinner/mangopi/t113/Tina-Linux/out/t113-mq_r/staging_dir/target/usr/lib -L/home/xtell_projects_dev/allwinner/mangopi/t113/Tina-Linux/out/t113-mq_r/staging_dir/target/lib -L/home/xtell_projects_dev/allwinner/mangopi/t113/Tina-Linux/prebuilt/gcc/linux-x86/arm/toolchain-sunxi-musl/toolchain/usr/lib -L/home/xtell_projects_dev/allwinner/mangopi/t113/Tina-Linux/prebuilt/gcc/linux-x86/arm/toolchain-sunxi-musl/toolchain/lib -specs=/home/xtell_projects_dev/allwinner/mangopi/t113/Tina-Linux/build/hardened-ld-pie.specs -znow -zrelro" INSTALL_PREFIX="/home/xtell_projects_dev/allwinner/mangopi/t113/Tina-Linux/out/t113-mq_r/compile_dir/target/lv_examples/ipkg-install" all
|
||||
|
||||
|
||||
BIN
src/xtell_server
Normal file
BIN
src/xtell_server
Normal file
Binary file not shown.
121
功能说明.md
Normal file
121
功能说明.md
Normal file
@ -0,0 +1,121 @@
|
||||
# xtell_server 功能说明
|
||||
|
||||
## 概述
|
||||
xtell_server 是一个基于 Mongoose 库的嵌入式 Web 服务器,用于 IP-KVM (Keyboard-Video-Mouse over IP) 远程控制系统。
|
||||
|
||||
## 主要功能
|
||||
|
||||
### 1. Web 服务器功能
|
||||
- 监听端口 82,提供 HTTP 服务
|
||||
- 支持 WebSocket 连接和文件上传
|
||||
- 使用 Mongoose 库处理 HTTP 请求
|
||||
|
||||
### 2. 远程控制功能
|
||||
- **鼠标控制**: 接收 WebSocket 鼠标指令,转换为串口协议数据
|
||||
- 数据格式: `{0x57, 0xAB, 0x00, 0x04, 0x07, 0x02, ..., checksum}`
|
||||
- 通过 `/dev/ttyS5` 串口通信 (波特率 115200)
|
||||
- **键盘控制**: 接收 WebSocket 键盘指令,转换为串口协议数据
|
||||
- 数据格式: `{0x57,0xAB,0x00,0x02,0x08,0x00, ..., checksum}`
|
||||
|
||||
### 3. 用户管理系统
|
||||
- 用户名密码修改功能
|
||||
- 认证信息存储在 `/mnt/user.txt`
|
||||
- 支持 MJPG-streamer 认证配置
|
||||
- 默认配置检测和创建
|
||||
|
||||
### 4. 网络配置管理
|
||||
- IP 地址、子网掩码、网关、DNS 配置
|
||||
- 配置文件存储在 `/etc/init.d/xnet.conf`
|
||||
- 支持网络配置动态更新
|
||||
- 默认 IP: 192.168.6.100, 端口: 8700
|
||||
|
||||
### 5. 系统控制功能
|
||||
- **关机控制**: 通过 GPIO 35 控制系统关机
|
||||
- **重启控制**: 通过 GPIO 34 控制系统重启
|
||||
- 支持远程关机和重启操作
|
||||
|
||||
### 6. 视频流管理
|
||||
- 启动和管理 MJPG-streamer 服务
|
||||
- 支持认证的视频流传输 (端口 8080)
|
||||
- 分辨率: 1920x1080
|
||||
- 动态用户认证配置
|
||||
|
||||
### 7. GPIO 按键处理
|
||||
- GPIO 139 按键检测线程
|
||||
- 按键触发时恢复默认网络配置
|
||||
- 硬件重置功能
|
||||
- 自动重启 MJPG-streamer 服务
|
||||
|
||||
### 8. 文件上传功能
|
||||
- 支持分块文件上传
|
||||
- 上传端点: `/upload`
|
||||
- 上传路径: `/mnt/UDISK/gcode.nc`
|
||||
- 支持大文件分块传输
|
||||
|
||||
## 技术架构
|
||||
|
||||
### 核心依赖
|
||||
- **Mongoose**: 嵌入式 Web 服务器框架
|
||||
- **MJPG-streamer**: 视频流服务
|
||||
- **Linux GPIO**: 硬件控制接口
|
||||
- **串口通信**: `/dev/ttyS5`
|
||||
|
||||
### 配置文件
|
||||
- `/mnt/user.txt`: 用户认证信息
|
||||
- `/etc/init.d/xnet.conf`: 网络配置
|
||||
- `/www/webcam`: Web 界面文件
|
||||
|
||||
### GPIO 映射
|
||||
- GPIO 34: 系统重启控制
|
||||
- GPIO 35: 系统关机控制
|
||||
- GPIO 139: 硬件重置按键
|
||||
|
||||
## WebSocket 消息协议
|
||||
|
||||
### 鼠标控制
|
||||
```
|
||||
mouse,x1,y1,x2,y2,buttons,scroll
|
||||
```
|
||||
|
||||
### 键盘控制
|
||||
```
|
||||
keyboard,key_code,modifier
|
||||
```
|
||||
|
||||
### 用户管理
|
||||
```
|
||||
newuser,oldUser,oldPass,newUser,newPass
|
||||
```
|
||||
|
||||
### 网络配置
|
||||
```
|
||||
newip,ip,mask,gw,dns1,dns2
|
||||
```
|
||||
|
||||
### 系统控制
|
||||
```
|
||||
control,shutdown|reboot
|
||||
```
|
||||
|
||||
## 运行流程
|
||||
|
||||
1. **初始化阶段**:
|
||||
- 读取用户配置
|
||||
- 启动 MJPG-streamer
|
||||
- 配置 GPIO
|
||||
- 初始化串口
|
||||
- 启动 GPIO 监控线程
|
||||
|
||||
2. **服务阶段**:
|
||||
- 监听 HTTP/WebSocket 连接
|
||||
- 处理客户端指令
|
||||
- 转发键鼠数据到串口
|
||||
- 管理系统配置
|
||||
|
||||
3. **硬件重置**:
|
||||
- GPIO 139 按键触发
|
||||
- 恢复默认网络配置
|
||||
- 重启相关服务
|
||||
|
||||
## 独立性
|
||||
xtell_server 是一个完整的独立应用程序,不需要其他组件配合即可正常运行所有功能。
|
||||
Reference in New Issue
Block a user