当前位置: 首页 > article >正文

Linux基础项目开发day2:量产工具——输入系统

文章目录

  • 前言
  • 一、数据结构抽象
    • 1、数据本身
    • 2、设备本身
    • 3、input_manager.h
  • 二、触摸屏编程
    • 1、touchscreen.c
  • 三、触摸屏单元测试
    • 1、touchscreen.c
    • 2、上机测试
  • 四、网络编程
    • netiput.c
  • 五、网络单元测试
    • 1、netiput.c
    • 2、client.c
    • 3、上机测试
  • 六、输入系统的框架
    • 1、框架思路
    • 2、input_,manager.c
    • 3、环形缓冲区
    • 4、input_,manager.c加入环形Buffer操作函数
  • 七、输入管理单元测试
    • 1、input_test.c
    • 2、input下的Makefile
    • 3、uinttest下的Makefile
    • 4、上板测试

前言

前面我们实现了显示系统,现在我们来学习输入系统,类似的,都需要我们来进行学习搭建!具体分为三层:
在这里插入图片描述

一、数据结构抽象

对于每一个设备,每一个模块,都用结构体来比表示他,以后就可以很方便的去替换这些模块,所以我们需要抽象出两个结构体,分别是:1、数据本身,2、设备本身。
在这里插入图片描述
输入来源:1、网络数据输入 2、点击时间输入(触摸屏)
在这里插入图片描述

1、数据本身

在这里插入图片描述

iType——判断是什么数据
INPUT_TYPE_TOUCH——触摸屏事件
INPUT_TYPE_NET——网络输入事件
触摸屏数据
int iX;、int iY: 判断具体坐标
int iPressure: 压力值
网络数据
char str[1024]: 网络数据

2、设备本身

在这里插入图片描述

*name: 设备名字
(*GetInputEvent)(PInputEvent ptInputEvent):上层代码通过这个函数获得数据,返回值判断是否成功,如果成功结果保存至PInputEvent ptInputEvent中
(*DeviceInit)(void):提供初始化函数,比如打开设备节点等
(*DeviceExit)(void):提供退出函数
InputDevice *ptNext;链表所需要

3、input_manager.h

#ifndef _INPUT_MANAGER_H
#define _INPUT_MANAGER_H

#include <sys/time.h>

#define INPUT_TYPE_TOUCH 1
#define INPUT_TYPE_NET   2


typedef struct InputEvent {
	struct timeval	tTime;
	int iType;
	int iX;
	int iY;
	int iPressure;
	char str[1024];
}InputEvent, *PInputEvent;


typedef struct InputDevice {
	char *name;
	int (*GetInputEvent)(PInputEvent ptInputEvent);
	int (*DeviceInit)(void);
	int (*DeviceExit)(void);
	struct InputDevice *ptNext;
}InputDevice, *PInputDevice;


#endif

二、触摸屏编程

1、touchscreen.c

#include <input_manager.h>
#include <tslib.h>

//定义一个指向触摸屏设备的静态全局变量
static struct tsdev *g_ts;

//用于从触摸屏设备获取输入事件
static int TouchscreenGetInputEvent(PInputEvent ptInputEvent)
{
	struct ts_sample samp;
	int ret;
	
	//从触摸屏读取一个样品
	ret = ts_read(g_ts, &samp, 1);
	
	if (ret != 1)
		return -1;
    //填充数据,便于保存
	ptInputEvent->iType     = INPUT_TYPE_TOUCH;
	ptInputEvent->iX        = samp.x;
	ptInputEvent->iY        = samp.y;
	ptInputEvent->iPressure = samp.pressure;
	ptInputEvent->tTime     = samp.tv;

    //读取成功,返回0
	return 0;
}

//用于初始化触摸屏设备
static int TouchscreenDeviceInit(void)
{
    //初始化触摸屏设备
	g_ts = ts_setup(NULL, 0);
	if (!g_ts)
	{
		printf("ts_setup err\n");
		return -1;
	}

	return 0;
}

//用于退出触摸屏设备
static int TouchscreenDeviceExit(void)
{
    //关闭触摸屏设备
	ts_close(g_ts);
	return 0;
}

//定义一个触摸屏实例
static InputDevice g_tTouchscreenDev ={
	.name = "touchscreen",
	.GetInputEvent  = TouchscreenGetInputEvent,
	.DeviceInit     = TouchscreenDeviceInit,
	.DeviceExit     = TouchscreenDeviceExit,
};


三、触摸屏单元测试

直接在touchscreen.c添加一个主函数进行测试!

1、touchscreen.c

#if 1

int main(int argc, char **argv)
{
	InputEvent event;
	int ret;
	
	g_tTouchscreenDev.DeviceInit();

	while (1)
	{
		ret = g_tTouchscreenDev.GetInputEvent(&event);
		if (ret) {
			printf("GetInputEvent err!\n");
			return -1;
		}
		else
		{
			printf("Type      : %d\n", event.iType);
			printf("iX        : %d\n", event.iX);
			printf("iY        : %d\n", event.iY);
			printf("iPressure : %d\n", event.iPressure);
		}
	}
	return 0;
}

#endif
EXTRA_CFLAGS  := 
CFLAGS_file.o := 
 
#obj-y += disp_test.o
 

将unittest文件夹中的Makefile中的main函数部分注释掉,因为一个程序只能允许有一个main

EXTRA_CFLAGS  := 
CFLAGS_file.o := 
 
obj-y += touchscreen.o

input目录下的Makefile

 
CROSS_COMPILE ?= 
AS		= $(CROSS_COMPILE)as
LD		= $(CROSS_COMPILE)ld
CC		= $(CROSS_COMPILE)gcc
CPP		= $(CC) -E
AR		= $(CROSS_COMPILE)ar
NM		= $(CROSS_COMPILE)nm
 
STRIP		= $(CROSS_COMPILE)strip
OBJCOPY		= $(CROSS_COMPILE)objcopy
OBJDUMP		= $(CROSS_COMPILE)objdump
 
export AS LD CC CPP AR NM
export STRIP OBJCOPY OBJDUMP
 
CFLAGS := -Wall -O2 -g
CFLAGS += -I $(shell pwd)/include
 
LDFLAGS := -lts
 
export CFLAGS LDFLAGS
 
TOPDIR := $(shell pwd)
export TOPDIR
 
TARGET := test
 
 
obj-y += display/
obj-y += input/
 
all : start_recursive_build $(TARGET)
	@echo $(TARGET) has been built!
 
start_recursive_build:
	make -C ./ -f $(TOPDIR)/Makefile.build
 
$(TARGET) : built-in.o
	$(CC) -o $(TARGET) built-in.o $(LDFLAGS)
 
clean:
	rm -f $(shell find -name "*.o")
	rm -f $(TARGET)
 
distclean:
	rm -f $(shell find -name "*.o")
	rm -f $(shell find -name "*.d")
	rm -f $(TARGET)
	

需要修改顶层目录下的Makefile
第20行:LDFLAGS := -lts 设置链接
第31行:打开input目录下的文件

2、上机测试

上机效果:
在这里插入图片描述

四、网络编程

对网络输入构造出同触摸屏编程中一样的结构体!
在这里插入图片描述

netiput.c

关于网络编程API的学习也可以看看我写的一个文章:网络编程(TCP&UDP)



#include <input_manager.h>
#include <sys/types.h>          /* See NOTES */
#include <sys/socket.h>
#include <string.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <unistd.h>
#include <stdio.h>
#include <string.h>

/* socket
 * bind
 * sendto/recvfrom
 */

/*  定义服务端口号 */
#define SERVER_PORT 8888

/*  用于储存套接字描述符 */
static int g_iSocketServer;

/*  用于从网络套接字获取输入事件 */
static int NetinputGetInputEvent(PInputEvent ptInputEvent)
{
	struct sockaddr_in tSocketClientAddr;
	int iRecvLen;
	unsigned char ucRecvBuf[1000];
	
	int iAddrLen = sizeof(struct sockaddr);
	
	/* 获取数据 */
	iRecvLen = recvfrom(g_iSocketServer, ucRecvBuf, 999, 0, (struct sockaddr *)&tSocketClientAddr, &iAddrLen);
	if (iRecvLen > 0)
	{
		ucRecvBuf[iRecvLen] = '\0';//在接受缓冲区尾端添加字符串结束符

		//填充输入事件结构体
		ptInputEvent->iType 	= INPUT_TYPE_NET;
		gettimeofday(&ptInputEvent->tTime, NULL);
		strncpy(ptInputEvent->str, ucRecvBuf, 1000);
		ptInputEvent->str[999] = '\0';
		return 0;
	}
	else
		return -1;
}

/*  用于初始网络输入设备 */
static int NetinputDeviceInit(void)
{
	struct sockaddr_in tSocketServerAddr;//服务地址结构体
	int iRet;//返回值

	int iClientNum = -1;
	
	/* 创建一个UDP套接字 */
	g_iSocketServer = socket(AF_INET, SOCK_DGRAM, 0);
	if (-1 == g_iSocketServer)//创建失败
	{
		printf("socket error!\n");
		return -1;
	}

    //设置服务地址结构体
	tSocketServerAddr.sin_family      = AF_INET;
	tSocketServerAddr.sin_port        = htons(SERVER_PORT);  /* host to net, short */
 	tSocketServerAddr.sin_addr.s_addr = INADDR_ANY;
	memset(tSocketServerAddr.sin_zero, 0, 8);
	
	//绑定套接字到服务器地址
	iRet = bind(g_iSocketServer, (const struct sockaddr *)&tSocketServerAddr, sizeof(struct sockaddr));
	if (-1 == iRet)
	{
		printf("bind error!\n");
		return -1;
	}

	return 0;
}

/* 用于退出网络输入设备 */
static int NetinputDeviceExit(void)
{
	close(g_iSocketServer);	
	return 0;
}

/* 定义一个结构体实例 */
static InputDevice g_tNetinputDev ={
	.name = "touchscreen",
	.GetInputEvent  = NetinputGetInputEvent,
	.DeviceInit     = NetinputDeviceInit,
	.DeviceExit     = NetinputDeviceExit,
};

/* 注册网络输入设备到输入管理器 */
void NetInputRegister(void)
{
	RegisterInputDevice(&g_tNetinputDev);
	/*void RegisterInputDevice(PInputDevice ptInputDev)
     *{
	 *	 ptInputDev->ptNext = g_InputDevs;
	 *	 g_InputDevs = ptInputDev;
	 *}
	 */
	
}

五、网络单元测试

1、netiput.c

这里直接在netiput.c里面添加测试!这里充当服务端

#if 1

int main(int argc, char **argv)
{
	InputEvent event;
	int ret;
	
	g_tNetinputDev.DeviceInit();

	while (1)
	{
		ret = g_tNetinputDev.GetInputEvent(&event);
		if (ret) {
			printf("GetInputEvent err!\n");
			return -1;
		}
		else
		{
			printf("Type      : %d\n", event.iType);
			printf("str       : %s\n", event.str);
		}
	}
	return 0;
}

#endif

注意:需要把触摸屏里面的main函数注释掉!!!
Makefile:

EXTRA_CFLAGS  := 
CFLAGS_file.o := 
 
obj-y += touchscreen.o
obj-y += netinput.o

2、client.c

这里充当客户端,用于发送数据

#include <sys/types.h>          /* See NOTES */
#include <sys/socket.h>
#include <string.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <unistd.h>
#include <stdio.h>

/* socket
 * connect
 * send/recv
 */

#define SERVER_PORT 8888

int main(int argc, char **argv)
{
	int iSocketClient;
	struct sockaddr_in tSocketServerAddr;
	
	int iRet;
	int iSendLen;
	int iAddrLen;

	if (argc != 3)
	{
		printf("Usage:\n");
		printf("%s <server_ip> <str>\n", argv[0]);
		return -1;
	}

	iSocketClient = socket(AF_INET, SOCK_DGRAM, 0);

	tSocketServerAddr.sin_family      = AF_INET;
	tSocketServerAddr.sin_port        = htons(SERVER_PORT);  /* host to net, short */
 	//tSocketServerAddr.sin_addr.s_addr = INADDR_ANY;
 	if (0 == inet_aton(argv[1], &tSocketServerAddr.sin_addr))
 	{
		printf("invalid server_ip\n");
		return -1;
	}
	memset(tSocketServerAddr.sin_zero, 0, 8);

#if 0
	iRet = connect(iSocketClient, (const struct sockaddr *)&tSocketServerAddr, sizeof(struct sockaddr));	
	if (-1 == iRet)
	{
		printf("connect error!\n");
		return -1;
	}
#endif

	iAddrLen = sizeof(struct sockaddr);
	iSendLen = sendto(iSocketClient, argv[2], strlen(argv[2]), 0,
	              (const struct sockaddr *)&tSocketServerAddr, iAddrLen);

	close(iSocketClient);
	
	return 0;
}


3、上机测试

分别编译好后复制到板子上
在这里插入图片描述

六、输入系统的框架

1、框架思路

在这里插入图片描述

1、 输入管理器inputmanager实现函数:
InputInit
InputDeviceInit
GetInputEvent
2、输入管理器支持多个设备,那怎么将他们连起来呢?
InputInit函数创建一个链表,链表指向网络输入设备本身和触摸屏设备本身,通过链表可以找到所有的输入设备!
3.对于每一个设备都可以调用里面的 GetInputEvent 来获得数据,如果想同时获得多个设备的输入数据的话,那么该怎么获得?
调用InputDeviceInit从链表里面把每一个设备取出来,调用里面的DeviceInit 初始化,并且为每一个输入设备创造一个线程thread,线程不断调用设备里面的GetInputEvent 等待数据,一旦得到数据,就可以将获得的数据放到某个buffer
4.上层的应用程序调用GetInputEvent 时候就会去某个buffer里面查看是否有数据,有数据则返回,没数据则休眠

2、input_,manager.c

#include <pthread.h>
#include <stdio.h>
#include <unistd.h>
#include <semaphore.h>
#include <string.h>

#include <input_manager.h>

/* 定义输入设备链表头指针 */
static PInputDevice g_InputDevs  = NULL;

/* 关于线程定义的变量 */
static pthread_mutex_t g_tMutex  = PTHREAD_MUTEX_INITIALIZER;
static pthread_cond_t  g_tConVar = PTHREAD_COND_INITIALIZER;




/* 注册输入设备,将其加入链表中 */
void RegisterInputDevice(PInputDevice ptInputDev)
{
	ptInputDev->ptNext = g_InputDevs;
	g_InputDevs = ptInputDev;
}

/* 初始化输入系统 */
void InputInit(void)
{
	/* regiseter touchscreen */
	extern void TouchscreenRegister(void);
	TouchscreenRegister();

	/* regiseter netinput */
	extern void NetInputRegister(void);
	NetInputRegister();
}


/* 输入接收线程函数 */
static void *input_recv_thread_func (void *data)
{
	PInputDevice ptInputDev = (PInputDevice)data;
	InputEvent tEvent;
	int ret;
	
	while (1)
	{
		/* 读数据 */
		ret = ptInputDev->GetInputEvent(&tEvent);

		if (!ret)
		{	
			/* 保存数据 */
			pthread_mutex_lock(&g_tMutex);
			PutInputEventToBuffer(&tEvent);

			/* 唤醒等待数据的线程 */
			pthread_cond_signal(&g_tConVar); /* 通知接收线程 */
			pthread_mutex_unlock(&g_tMutex);
		}
	}

	return NULL;
}

/* 初始化输入设备 */
void IntpuDeviceInit(void)
{
	int ret;
	pthread_t tid;
	
	/* for each inputdevice, init, pthread_create */
	/* 遍历所有输入设备,分别进行初始化和创建线程 */
	PInputDevice ptTmp = g_InputDevs;
	while (ptTmp)
	{
		/* 初始化设备 */
		ret = ptTmp->DeviceInit();

		/* 创建线程 */
		if (!ret)
		{
			ret = pthread_create(&tid, NULL, input_recv_thread_func, ptTmp);
		}

		ptTmp= ptTmp->ptNext;
	}
}

/* 获取输入事件 */
int GetInputEvent(PInputEvent ptInputEvent)
{
	InputEvent tEvent;
	int ret;
	/* 无数据则休眠 */
	pthread_mutex_lock(&g_tMutex);
	if (GetInputEventFromBuffer(&tEvent))//读取数据成功(有数据可读)
	{
		*ptInputEvent = tEvent;//保存数据
		pthread_mutex_unlock(&g_tMutex);
		return 0;
	}
	else//读取数据失败(没数据可读)
	{
		/* 休眠等待 */
		pthread_cond_wait(&g_tConVar, &g_tMutex);	
		if (GetInputEventFromBuffer(&tEvent))//有数据杯唤醒之后,读取数据
		{
			*ptInputEvent = tEvent;//保存数据
			ret = 0;
		}
		else
		{
			ret = -1;
		}
		pthread_mutex_unlock(&g_tMutex);		
	}
	return ret;

}

怎么避免数据丢失?
比如触摸屏,它一下子会上报很多数据,对于网络输入,也有可能会一次性有很多客户端发来数据,所以不能使用变量来保存数据,要使用一个“唤醒缓冲区

3、环形缓冲区

环形缓冲去其实也是一个数组,只不过是一个“高级”数据罢了!
例:char buf[5];
在这里插入图片描述
这里的满是指还有一个位置可以填!

4、input_,manager.c加入环形Buffer操作函数

#include <pthread.h>          /* 包含线程相关函数和数据结构头文件 */
#include <stdio.h>            /* 包含标准输入输出函数头文件 */
#include <unistd.h>           /* 包含UNIX标准函数头文件 */
#include <semaphore.h>        /* 包含信号量相关函数和数据结构头文件 */
#include <string.h>           /* 包含字符串处理函数头文件 */
#include <input_manager.h>    /* 包含输入管理器相关函数和数据结构头文件 */
 
/* 全局输入设备链表头指针 */
static PInputDevice g_InputDevs = NULL;
 
/* 互斥锁和条件变量,用于线程同步 */
static pthread_mutex_t g_tMutex = PTHREAD_MUTEX_INITIALIZER;
static pthread_cond_t g_tConVar = PTHREAD_COND_INITIALIZER;
 
/* 环形缓冲区相关定义 */
#define BUFFER_LEN 20
static int g_iRead = 0;
static int g_iWrite = 0;
static InputEvent g_atInputEvents[BUFFER_LEN];
 
/* 环形缓冲区操作函数 */

/* 判断唤醒缓冲区是否为满 */
//满了返回1,没满返回0
static int isInputBufferFull(void)
{
    return (g_iRead == ((g_iWrite + 1) % BUFFER_LEN));
}
 
/* 判断唤醒缓冲区是否为空 */
//空了返回1,没空返回0
static int isInputBufferEmpty(void)
{
    return (g_iRead == g_iWrite);
}
 
//储存输入事件到环形缓冲区里面
static void PutInputEventToBuffer(PInputEvent ptInputEvent)
{
    if (!isInputBufferFull())//数组不满,就储存
    {
        g_atInputEvents[g_iWrite] = *ptInputEvent;
        g_iWrite = (g_iWrite + 1) % BUFFER_LEN;
    }
}

//在环形缓冲区里面读取输入事件
static int GetInputEventFromBuffer(PInputEvent ptInputEvent)
{
    if (!isInputBufferEmpty())//数组不空就读取
    {
        *ptInputEvent = g_atInputEvents[g_iRead];
        g_iRead = (g_iRead + 1) % BUFFER_LEN;
        return 1;
    }
    else
    {
        return 0;
    }
}
 
/* 注册输入设备函数 */
void RegisterInputDevice(PInputDevice ptInputDev)
{
    ptInputDev->ptNext = g_InputDevs;
    g_InputDevs = ptInputDev;
}
 
/* 初始化输入系统 */
void InputInit(void)
{
    /* 注册触摸屏设备 */
    extern void TouchscreenRegister(void);
    TouchscreenRegister();
 
    /* 注册网络输入设备 */
    extern void NetInputRegister(void);
    NetInputRegister();
}
 
/* 输入接收线程函数 */
static void *input_recv_thread_func(void *data)
{
    PInputDevice ptInputDev = (PInputDevice)data;
    InputEvent tEvent;
    int ret;
 
    while (1)
    {
        /* 从输入设备读取数据 */
        ret = ptInputDev->GetInputEvent(&tEvent);
 
        if (!ret)
        {
            /* 保存数据到环形缓冲区 */
            pthread_mutex_lock(&g_tMutex);
            PutInputEventToBuffer(&tEvent);
 
            /* 唤醒等待数据的线程 */
            pthread_cond_signal(&g_tConVar);
            pthread_mutex_unlock(&g_tMutex);
        }
    }
 
    return NULL;
}
 
/* 初始化输入设备 */
void IntpuDeviceInit(void)
{
    int ret;
    pthread_t tid;
 
    /* 遍历所有输入设备,进行初始化和创建接收线程 */
    PInputDevice ptTmp = g_InputDevs;
    while (ptTmp)
    {
        /* 初始化设备 */
        ret = ptTmp->DeviceInit();
 
        /* 创建接收线程 */
        if (!ret)
        {
            ret = pthread_create(&tid, NULL, input_recv_thread_func, ptTmp);
        }
 
        ptTmp = ptTmp->ptNext;
    }
}
 
/* 获取输入事件 */
int GetInputEvent(PInputEvent ptInputEvent)
{
    InputEvent tEvent;
    int ret;
 
    /* 无数据则休眠 */
    pthread_mutex_lock(&g_tMutex);
    if (GetInputEventFromBuffer(&tEvent))
    {
        *ptInputEvent = tEvent;
        pthread_mutex_unlock(&g_tMutex);
        return 0;
    }
    else
    {
        /* 休眠等待 */
        pthread_cond_wait(&g_tConVar, &g_tMutex);
        if (GetInputEventFromBuffer(&tEvent))
        {
            *ptInputEvent = tEvent;
            ret = 0;
        }
        else
        {
            ret = -1;
        }
        pthread_mutex_unlock(&g_tMutex);
    }
    return ret;
}

七、输入管理单元测试

1、input_test.c

#include <sys/mman.h> // 内存管理声明
#include <sys/types.h> // 基本系统数据类型
#include <sys/stat.h> // 文件状态定义
#include <unistd.h> // 提供通用的文件、目录、程序及进程操作的函数
#include <linux/fb.h> // 帧缓冲设备的定义
#include <fcntl.h> // 文件控制选项定义
#include <stdio.h> // 标准输入输出定义
#include <string.h> // 字符串操作函数定义
#include <sys/ioctl.h> // IO控制设备的函数定义
 
#include <input_manager.h> // 自定义输入管理的头文件
 
int main(int argc, char **argv)
{
	int ret; // 用于函数返回值
	InputEvent event; // 定义一个输入事件的结构体变量
	
	InputInit(); // 初始化输入系统
	IntpuDeviceInit(); // 初始化输入设备,注意这里应该是 InputDeviceInit
 
	while (1) // 主循环
	{
		// 打印当前文件名、函数名和行号
		printf("%s %s %d\n", __FILE__, __FUNCTION__, __LINE__);
		ret = GetInputEvent(&event); // 从输入设备获取一个事件
 
		// 再次打印,并显示GetInputEvent函数的返回值
		printf("%s %s %d, ret = %d\n", __FILE__, __FUNCTION__, __LINE__, ret);
		if (ret) { // 如果返回值不是0,表示获取事件时出错
			printf("GetInputEvent err!\n");
			return -1; // 出错返回-1
		}
		else // 如果成功获取事件
		{
			// 打印事件类型
			printf("%s %s %d, event.iType = %d\n", __FILE__, __FUNCTION__, __LINE__, event.iType );
			if (event.iType == INPUT_TYPE_TOUCH) // 如果是触摸屏事件
			{
				// 打印触摸屏事件的详细信息
				printf("Type      : %d\n", event.iType);
				printf("iX        : %d\n", event.iX);
				printf("iY        : %d\n", event.iY);
				printf("iPressure : %d\n", event.iPressure);
			}
			else if (event.iType == INPUT_TYPE_NET) // 如果是网络事件
			{
				// 打印网络事件的详细信息
				printf("Type      : %d\n", event.iType);
				printf("str       : %s\n", event.str);
			}
		}
	}
	return 0; // 正常退出程序	
}

2、input下的Makefile

EXTRA_CFLAGS  := 
CFLAGS_file.o := 
 
obj-y += touchscreen.o
obj-y += netinput.o
obj-y += input_manager.o
 

3、uinttest下的Makefile

EXTRA_CFLAGS  := 
CFLAGS_file.o := 
 
#obj-y += disp_test.o
obj-y += input_test.o
 

4、上板测试

在这里插入图片描述
在这里插入图片描述


http://www.kler.cn/a/354195.html

相关文章:

  • 【编辑器扩展】打开持久化路径/缓存路径/DataPath/StreamingAssetsPath文件夹
  • springboot481基于springboot社区老人健康信息管理系统(论文+源码)_kaic
  • 串口通信控制LED灯
  • Golang学习历程【第三篇 基本数据类型类型转换】
  • 梳理你的思路(从OOP到架构设计)_简介设计模式
  • 数据库管理系统——数据库设计
  • 【存储设备专栏 2.6 -- linux 启动盘制作详细介绍】
  • Vert.x,Web - Restful API
  • 记EDU某人社局的漏洞挖掘复盘
  • 四种隔离级别是如何逐步加强事务隔离的,以及在底层如何使用锁机制和多版本控制(MVCC)来实现
  • HCIP--1
  • 新媒体优势
  • Spring Boot驱动的在线考试系统:JavaWeb技术实战
  • Scala入门基础(12)抽象类
  • 梯度下降算法优化—随机梯度下降、小批次、动量、Adagrad等方法pytorch实现
  • pico+Unity交互开发教程——手指触控交互(Poke Interaction)
  • 如何利用OpenCV和yolo实现人脸检测
  • 如何利用边缘计算网关进行工厂设备数据采集?天拓四方
  • Linux创建sh脚本,实现全局调用
  • 可编辑73页PPT | 企业智慧能源管控平台建设方案
  • 机器学习【教育系统改善及其应用】
  • 线性代数基本知识
  • Web 搜索引擎优化
  • k8s部署Kafka集群超详细讲解
  • C#高级编程核心知识点
  • 智慧供排水管网在线监测为城市安全保驾护航