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