在Ubuntu上用sane api实现通用扫描功能

最近由于工作需要,要写一套扫描相关的接口。

在这里记录一下,实现还有有点复杂的。

目录

依赖

主要功能

初始化

获取当前扫描仪列表

打开扫描仪

 sane_open

设置扫描选项

sane_control_option

扫描

关闭设备

结束使用

参考资料


依赖

sudo apt install libsane-dev  sane-utils

主要功能

初始化

我们在操作扫描仪之前需要初始化才能正常使用。

初始化使用的是sane里的sane_init。

void scanner_init()
{printf("[%s] Start\n", __FUNCTION__);SANE_Int version_code = 0;sane_init(&version_code, auth_callback);printf("SANE version code: %d\n", version_code);
}static void
auth_callback(SANE_String_Const resource,SANE_Char *username, SANE_Char *password)
{
}

初始化成功则version_code为SANE_STATUS_GOOD(0)。

获取当前扫描仪列表

核心是sane_get_devices函数。

先通过sane_get_devices获取扫描仪列表,然后申请一个二维数组,将扫描仪列表放入二维数组中返回。

const char **scanner_get_available_list()
{printf("[%s] Start\n", __FUNCTION__);SANE_Status status;SANE_Int num_devices = 0;const SANE_Device **device_list;//获取扫描仪列表status = sane_get_devices(&device_list, SANE_FALSE);if (status != SANE_STATUS_GOOD){printf("Error getting device list: %s\n", sane_strstatus(status));return NULL;}// 获取当前设备数量while (device_list[num_devices])num_devices++;// 如果设备列表为空,返回if (num_devices == 0){printf("No scanners found.\n");sane_exit();return NULL;}// 分配内存const char **scanner_list = malloc(sizeof(SANE_Device *) * (num_devices + 1));if (!scanner_list){printf("Failed to allocate memory.\n");sane_exit();return NULL;}// 继续分配内存for (int i = 0; i < num_devices; i++){scanner_list[i] = strdup(device_list[i]->name);if (!scanner_list[i]){printf("Failed to allocate memory.\n");for (int j = 0; j < i; j++){free(scanner_list[j]);}free(scanner_list);sane_exit();return NULL;}}scanner_list[num_devices] = NULL;// 返回设备列表return scanner_list;
}

我们再把获取到的设备列表循环打印一下。

    const char **scanner_list = scanner_get_available_list();if (scanner_list != NULL){for (int num_devices = 0; scanner_list[num_devices]; ++num_devices){if (scanner_list[num_devices] != NULL) // 添加一个检查{printf("Device %d: name=%s \n",num_devices, scanner_list[num_devices]);}}}else{return;}

打开扫描仪

 sane_open

这里介绍一下sane_open,第一个参数是扫描仪的名称,第二个参数是一个空的句柄,打开后通过句柄进行后续操作。

extern SANE_Status sane_open (SANE_String_Const devicename,SANE_Handle * handle);

 函数的具体实现如下:

static SANE_Handle sane_handle = NULL; // 扫描仪设备句柄,全局变量int scanner_open_device(char *scanner_name)
{printf("[%s] Start\n", __FUNCTION__);SANE_Status sane_status = 0;if (sane_status = sane_open(scanner_name, &sane_handle)){printf("sane_open status: %s\n", sane_strstatus(sane_status));}if (sane_status != SANE_STATUS_GOOD)sane_handle = NULL;return sane_status;
}

这里的入参scanner_name是扫描仪列表的scanner_list,如果要打开第一个扫描仪的话是scanner_list[0]。

如果函数的返回值不是SANE_STATUS_GOOD,表示打开失败了。


设置扫描选项

sane_control_option

扫描的所有参数都是通过sane_control_option实现的,每个参数的功能详见备注。

extern SANE_Status sane_control_option (SANE_Handle handle,  //sane_open的句柄SANE_Int option,     //要设置选项的序号,2是颜色,3是分辨率SANE_Action action,  //操作的类型,给选项赋值或者获取当前值void *value,         //value的实际值SANE_Int *info);     //没发现有什么用

 操作的类型一共有以下三种,我们这里只用到第二种。

typedef enum{SANE_ACTION_GET_VALUE = 0,SANE_ACTION_SET_VALUE,SANE_ACTION_SET_AUTO}
SANE_Action;

我这里设置了颜色,扫描的分辨率和纸张大小,还有很多可以设置的选项,可以自行探索。

static SANE_Handle sane_handle = NULL; // 扫描仪设备句柄,全局变量// 设置指定扫描仪颜色,通过传入参数val_color进行设置扫描设备的颜色
int scanner_set_color(SANE_String val_color)
{printf("[%s] Start!\n", __FUNCTION__);SANE_Status status;status = sane_control_option(sane_handle, 2,SANE_ACTION_SET_VALUE, val_color, NULL);if (status != SANE_STATUS_GOOD){printf("Option did not set, desc = %s\n", sane_strstatus(status));return status;}printf("set color option success!\n");return status;
}// 设置指定扫描仪扫描的分辨率(清晰程度,分辨率越大越清晰)
int scanner_set_resolutions(SANE_Int val_resolution)
{printf("[%s] Start!\n", __FUNCTION__);SANE_Status status;status = sane_control_option(sane_handle, 3, SANE_ACTION_SET_VALUE, &val_resolution, NULL);if (status != SANE_STATUS_GOOD){printf("Option did not set, desc = %s\n", sane_strstatus(status));return status;}printf("set resolution option success!\n");return status;
}

设置纸张大小有点复杂,因为纸张大小没有对应的option。因此曲线救国,通过设置扫描的纸张的长和宽来实现。长和宽的option序号分别是9和10。

enum sizes_type
{A2 = 1,A3,A4,A5,A6
};static double g_saneSizeA4BrY = 297;int scanner_set_size(SANE_String size)
{printf("[%s] Start!\n", __FUNCTION__);SANE_Status status = SANE_STATUS_GOOD;int type;if (!strcmp("A2", size)){type = A2;}else if (!strcmp("A3", size)){type = A3;}else if (!strcmp("A4", size)){type = A4;}else if (!strcmp("A5", size)){type = A5;}else if (!strcmp("A6", size)){type = A6;}else{type = 0;}switch (type){case A2:status = kdk_scanner_set_size_real(sane_handle, 420, 594);break;case A3:status = kdk_scanner_set_size_real(sane_handle, 297, 420);break;case A4:status = kdk_scanner_set_size_real(sane_handle, 210, g_saneSizeA4BrY);break;case A5:status = kdk_scanner_set_size_real(sane_handle, 148, 210);break;case A6:status = kdk_scanner_set_size_real(sane_handle, 105, 144);break;default:status = SANE_STATUS_UNSUPPORTED;}return status;
}/*** @brief scanner_set_size_real 统一设置扫描设备尺寸** @param sane_handle 扫描句柄** @param val_size_br_x 扫描设备右下角的x坐标** @param val_size_br_y 扫描设备右下角的y坐标** @return 返回扫描设备设置尺寸的情况*/
SANE_Status scanner_set_size_real(SANE_Handle sane_handle, SANE_Int val_size_br_x,SANE_Int val_size_br_y)
{printf("[%s] Start!\n", __FUNCTION__);SANE_Status status = SANE_STATUS_GOOD;SANE_Word x = SANE_FIX(val_size_br_x);SANE_Word y = SANE_FIX(val_size_br_y);SANE_Word zero = SANE_FIX(0.0);status = sane_control_option(sane_handle, 7, SANE_ACTION_SET_VALUE, &zero, NULL);if (status != SANE_STATUS_GOOD){return status;}status = sane_control_option(sane_handle, 8, SANE_ACTION_SET_VALUE, &zero, NULL);if (status != SANE_STATUS_GOOD){return status;}status = sane_control_option(sane_handle, 9,SANE_ACTION_SET_VALUE, &x, NULL);if (status != SANE_STATUS_GOOD){printf("Option x did not set, desc = %s\n", sane_strstatus(status));return status;}status = sane_control_option(sane_handle, 10,SANE_ACTION_SET_VALUE, &y, NULL);if (status != SANE_STATUS_GOOD){printf("Option y did not set, desc = %s\n", sane_strstatus(status));return status;}return status;
}

和前面一样,如果函数的返回值不是SANE_STATUS_GOOD,表示失败了。

扫描

扫描我分成两类,分为单页单面扫描和多页双面扫描。

单页和多页也是一种可以设置的扫描属性,单页和多页的主要是这个这个属性的区别,别的部分都差不多。

//设置扫描是单页还是多页
int scanner_set_page_type(SANE_Int type)
{printf("[%s] Start!\n", __FUNCTION__);SANE_Status status;//对应的option序号为4status = sane_control_option(sane_handle, 4,SANE_ACTION_SET_VALUE, &type, NULL);if (status != SANE_STATUS_GOOD){printf("Option did not set, desc = %s\n", sane_strstatus(status));return status;}printf("set page type option success!\n");return status;
}

单页单面扫描,就是不管有多少页都只扫描第一页的第一面。

  /*** @brief 指定扫描仪进行扫描(统一按照多页,双面处理)** @param fileName:保存扫描文件的文件名,比如传test的话,扫描后的文件会是test_1,test_2之类的形式* * @param type:扫描类型 0:单面单面扫描,1:多页双面扫描** @return 操作的返回值,0或者7为成功,其他为失败*/
int scanner_start_scan(SANE_String_Const fileName, int type)
{printf("[%s] Start\n", __FUNCTION__);SANE_Status sane_status = 0;switch (type){case 0:return do_scan_one(fileName);case 1:return do_scan_all(fileName);default:return do_scan_all(fileName);}
}

这是扫描单页的接口

// 单页扫描
SANE_Status do_scan_one(const char *fileName)
{printf("[%s] Start\n", __FUNCTION__);del_old_pic();//扫描之前删掉上一次的内容SANE_Status status;FILE *ofp = NULL;char path[PATH_MAX];char part_path[PATH_MAX];buffer_size = (32 * 1024);buffer = (SANE_Byte *)malloc(buffer_size);int i = 1;// 设置打印机单页进纸张status = kdk_scanner_set_page_type(1);if (status != SANE_STATUS_GOOD){printf("set page type fail:%s\n", sane_strstatus(status));return status;}do{// 设置保存路径sprintf(path, "/tmp/%s-%d.pnm", fileName, i); // 格式化PNM文件路径strcpy(part_path, path);strcat(part_path, ".part");printf("picture name: %s\n", path);// 开始扫描status = sane_start(sane_handle);if (status != SANE_STATUS_GOOD){break;}if (NULL == (ofp = fopen(part_path, "w"))){status = SANE_STATUS_ACCESS_DENIED;break;}// 保存扫描数据status = scan_it(ofp);switch (status){case SANE_STATUS_GOOD:case SANE_STATUS_EOF:{status = SANE_STATUS_GOOD;if (!ofp || 0 != fclose(ofp)){status = SANE_STATUS_ACCESS_DENIED;break;}else{ofp = NULL;if (rename(part_path, path)){status = SANE_STATUS_ACCESS_DENIED;break;}}}break;default:break;}} while (0);if (ofp){fclose(ofp);ofp = NULL;}if (buffer){free(buffer);buffer = NULL;}return status;
}// 删除上一次扫描的文件
void del_old_pic()
{DIR *dir;struct dirent *entry;char path[] = "/tmp/";char ext[] = ".pnm";dir = opendir(path);if (dir == NULL){perror("Unable to open directory");exit(EXIT_FAILURE);}while ((entry = readdir(dir)) != NULL){// Check if the entry is a file and ends with .pnmif (entry->d_type == DT_REG &&strstr(entry->d_name, ext) != NULL &&strcmp(entry->d_name + strlen(entry->d_name) - strlen(ext), ext) == 0){char full_path[512];sprintf(full_path, "%s%s", path, entry->d_name);if (remove(full_path) == 0){printf("Deleted %s\n", full_path);}else{perror("Unable to delete file");}}}closedir(dir);
}// sane 设置扫描方式
int kdk_scanner_set_page_type(SANE_Int type)
{printf("[%s] Start!\n", __FUNCTION__);SANE_Status status;status = sane_control_option(sane_handle, 4,SANE_ACTION_SET_VALUE, &type, NULL);if (status != SANE_STATUS_GOOD){printf("Option did not set, desc = %s\n", sane_strstatus(status));return status;}printf("set page type option success!\n");return status;
}

保存图片,这一部分细节很多,我也没仔细研究,直接用就行。

static SANE_Status scan_it(FILE *ofp)
{int i, len, first_frame = 1, offset = 0, must_buffer = 0, hundred_percent;SANE_Byte min = 0xff, max = 0;SANE_Parameters parm;SANE_Status status;Image image = {0, 0, 0, 0, 0};static const char *format_name[] = {"gray", "RGB", "red", "green", "blue"};SANE_Word total_bytes = 0, expected_bytes;SANE_Int hang_over = -1;do{if (!first_frame){status = sane_start(sane_handle);if (status != SANE_STATUS_GOOD){goto cleanup;}}status = sane_get_parameters(sane_handle, &parm);if (status != SANE_STATUS_GOOD){goto cleanup;}if (first_frame){switch (parm.format){case SANE_FRAME_RED:case SANE_FRAME_GREEN:case SANE_FRAME_BLUE:assert(parm.depth == 8);must_buffer = 1;offset = parm.format - SANE_FRAME_RED;break;case SANE_FRAME_RGB:assert((parm.depth == 8) || (parm.depth == 16));case SANE_FRAME_GRAY:assert((parm.depth == 1) || (parm.depth == 8) || (parm.depth == 16));if (parm.lines < 0){must_buffer = 1;offset = 0;}else{write_pnm_header(parm.format, parm.pixels_per_line, parm.lines, parm.depth, ofp);}break;default:break;}if (must_buffer){image.width = parm.bytes_per_line;if (parm.lines >= 0)image.height = parm.lines - STRIP_HEIGHT + 1;elseimage.height = 0;image.x = image.width - 1;image.y = -1;if (!advance(&image)){status = SANE_STATUS_NO_MEM;goto cleanup;}}}else{assert(parm.format >= SANE_FRAME_RED && parm.format <= SANE_FRAME_BLUE);offset = parm.format - SANE_FRAME_RED;image.x = image.y = 0;}hundred_percent = parm.bytes_per_line * parm.lines * ((parm.format == SANE_FRAME_RGB || parm.format == SANE_FRAME_GRAY) ? 1 : 3);// 这段是写图片数据while (1){double progr;status = sane_read(sane_handle, buffer, buffer_size, &len);total_bytes += (SANE_Word)len;progr = ((total_bytes * 100.) / (double)hundred_percent);if (progr > 100.)progr = 100.;if (status != SANE_STATUS_GOOD){if (status != SANE_STATUS_EOF){return status;}break;}if (must_buffer){switch (parm.format){case SANE_FRAME_RED:case SANE_FRAME_GREEN:case SANE_FRAME_BLUE:for (i = 0; i < len; ++i){image.data[offset + 3 * i] = buffer[i];if (!advance(&image)){status = SANE_STATUS_NO_MEM;goto cleanup;}}offset += 3 * len;break;case SANE_FRAME_RGB:for (i = 0; i < len; ++i){image.data[offset + i] = buffer[i];if (!advance(&image)){status = SANE_STATUS_NO_MEM;goto cleanup;}}offset += len;break;case SANE_FRAME_GRAY:for (i = 0; i < len; ++i){image.data[offset + i] = buffer[i];if (!advance(&image)){status = SANE_STATUS_NO_MEM;goto cleanup;}}offset += len;break;default:break;}}else /* ! must_buffer */{if ((parm.depth != 16))fwrite(buffer, 1, len, ofp);else{
#if !defined(WORDS_BIGENDIAN)int i, start = 0;/* check if we have saved one byte from the last sane_read */if (hang_over > -1){if (len > 0){fwrite(buffer, 1, 1, ofp);buffer[0] = (SANE_Byte)hang_over;hang_over = -1;start = 1;}}/* now do the byte-swapping */for (i = start; i < (len - 1); i += 2){unsigned char LSB;LSB = buffer[i];buffer[i] = buffer[i + 1];buffer[i + 1] = LSB;}/* check if we have an odd number of bytes */if (((len - start) % 2) != 0){hang_over = buffer[len - 1];len--;}
#endiffwrite(buffer, 1, len, ofp);}}if (verbose && parm.depth == 8){for (i = 0; i < len; ++i)if (buffer[i] >= max)max = buffer[i];else if (buffer[i] < min)min = buffer[i];}}first_frame = 0;} while (!parm.last_frame);if (must_buffer){image.height = image.y;write_pnm_header(parm.format, parm.pixels_per_line, image.height, parm.depth, ofp);#if !defined(WORDS_BIGENDIAN)if (parm.depth == 16){int i;for (i = 0; i < image.height * image.width; i += 2){unsigned char LSB;LSB = image.data[i];image.data[i] = image.data[i + 1];image.data[i + 1] = LSB;}}
#endiffwrite(image.data, 1, image.height * image.width, ofp);}fflush(ofp);cleanup:if (image.data)free(image.data);return status;
}void write_pnm_header(SANE_Frame format, int width, int height, int depth, FILE *ofp)
{printf("[%s] Start\n", __FUNCTION__);switch (format){case SANE_FRAME_RED:case SANE_FRAME_GREEN:case SANE_FRAME_BLUE:case SANE_FRAME_RGB:fprintf(ofp, "P6\n# SANE data follows\n%d %d\n%d\n", width, height, (depth <= 8) ? 255 : 65535);break;default:if (depth == 1)fprintf(ofp, "P4\n# SANE data follows\n%d %d\n", width, height);elsefprintf(ofp, "P5\n# SANE data follows\n%d %d\n%d\n", width, height, (depth <= 8) ? 255 : 65535);break;}
}static void *
advance(Image *image)
{if (++image->x >= image->width){image->x = 0;if (++image->y >= image->height || !image->data){size_t old_size = 0, new_size;if (image->data)old_size = image->height * image->width;image->height += STRIP_HEIGHT;new_size = image->height * image->width;if (image->data)image->data = realloc(image->data, new_size);elseimage->data = malloc(new_size);if (image->data)memset(image->data + old_size, 0, new_size - old_size);}}if (!image->data)fprintf(stderr, "can't allocate image buffer (%dx%d)\n",image->width, image->height);return image->data;
}

双页扫描,用do_scan_all替换do_scan_one,其他的函数都一样。

// 双面扫描全部文件+保存为PNM图像格式
SANE_Status do_scan_all(const char *fileName)
{printf("[%s] Start\n", __FUNCTION__);SANE_Status status;           // 返回状态FILE *ofp = NULL;             // 输出文件指针char path[PATH_MAX];          // PNM文件路径char part_path[PATH_MAX];     // 临时PNN文件路径buffer_size = (32 * 1024);    // 缓冲区大小buffer = malloc(buffer_size); // 动态分配缓冲区int i = 1;del_old_pic();//设置打印机多页进纸张status = kdk_scanner_set_page_type(0);if (status != SANE_STATUS_GOOD){printf("set page type fail:%s\n", sane_strstatus(status));return status;}do{sprintf(path, "/tmp/%s-%d.pnm", fileName, i); // 格式化PNM文件路径strcpy(part_path, path);                      // 复制PNM文件路径到临时文件路径strcat(part_path, ".part");                   // 在临时文件路径后添加扩展名".part"// 启动扫描过程status = sane_start(sane_handle);if (status != SANE_STATUS_GOOD){break;}// 创建临时文件if (NULL == (ofp = fopen(part_path, "w"))){status = SANE_STATUS_ACCESS_DENIED;break;}// 进行扫描,并将结果写入到临时文件中status = scan_it(ofp);switch (status){case SANE_STATUS_GOOD:case SANE_STATUS_EOF:{// 扫描成功或结束status = SANE_STATUS_GOOD;// 关闭临时文件,并检查是否成功关闭if (!ofp || 0 != fclose(ofp)){status = SANE_STATUS_ACCESS_DENIED;break;}else{ofp = NULL; // 将文件指针设置为NULL,避免重复关闭// 将临时文件重命名为正式的PNM文件if (rename(part_path, path)){status = SANE_STATUS_ACCESS_DENIED;break;}}}break;default:break;}i++;} while (status == SANE_STATUS_GOOD);// 如果出现错误,则取消扫描进程if (SANE_STATUS_GOOD != status){sane_cancel(sane_handle);}// 关闭输出文件if (ofp){fclose(ofp);ofp = NULL;}// 释放缓冲区内存if (buffer){free(buffer);buffer = NULL;}if ((status == SANE_STATUS_NO_DOCS) && (i > 1))status = SANE_STATUS_GOOD;return status; // 返回状态
}

扫描完成会会在tmp下生成扫描文件。

关闭设备

void scanner_close_device()
{printf("[%s] Start\n", __FUNCTION__);if (sane_handle != NULL){sane_close(sane_handle);}sane_handle = NULL;
}

结束使用

void scanner_exit()
{printf("[%s] Start\n", __FUNCTION__);sane_exit();
}

参考资料

Linux下通用扫描仪API——SANE( Scanner Access Now Easy)_linux sane-CSDN博客

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/150304.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

极域电子教室-教师机无法找到学生机

软件环境 极域电子教室2016 V6豪华版 场景还原 因为某软件安装闪退&#xff0c;只能使用自己的电脑进行控制演示&#xff0c;故拔掉原来教室机的网线。 恢复网络后&#xff0c;发现控屏软件无法找到台下的任何学生机。 PS: 拔掉网线前还是可以控制学生机 原因排查 1、网…

操作系统(六)| 文件系统下 文件使用 共享 保护

目录 4 空闲存储空间的管理 4.1 空闲区表 4.2 空闲块链 4.3 位图 5 文件的使用 6 文件共享 6.1 普通的文件共享方法 6.1.1 按路径名访问共享文件 6.1.2 链接法 6.1.3 基本文件目录BFD 6.2 基于I节点的文件共享方法&#xff08;Unix采用&#xff09; 6.2.1 硬链接 6…

transformer学习资料

一、NLP 自然语言处理 NLP 是机器学习在语言学领域的研究&#xff0c;专注于理解与人类语言相关的一切。NLP 的目标不仅是要理解每个单独的单词含义&#xff0c;而且也要理解这些单词与之相关联的上下文之间的意思。 常见的NLP 任务列表&#xff1a; 对整句的分类&#xff1…

课程32:.Net Core Web API部署IIS

这里写目录标题 🚀前言前言一、服务器环境配置1.1 安装 ASP.NET Core模块/托管捆绑包1.2 检查是否安装成功二、项目发布2.1 选择发布方式2.2 发布配置2.3 发布三、服务器部署3.1 IIS添加网站3.2 数据库链接配置3.3 让IIS支持.NET Web Api3.4 验证四、最后🚀前言 本文是《.…

Vue.js2+Cesium1.103.0 十四、绘制视锥,并可实时调整视锥姿态

Vue.js2Cesium1.103.0 十四、绘制视锥&#xff0c;并可实时调整视锥姿态 Demo <template><divid"cesium-container"style"width: 100%; height: 100%;"><divclass"control"style"position: absolute;right: 50px;top: 50px…

【周报2023-11-17】

周报2023-11-17 本周的主要工作下周的工作内容 本周的主要工作 本周的主要工作的话主要是小程序页面的搭建 那截止到目前也就是今天为止的话&#xff0c;小程序的主题页面已完成百分之80% 那剩下的话就是一些细节性的问题&#xff1a;例如说首页的三张图片是一个动态轮播动画 …

SpringBoot中文乱码问题解决方案

在Spring Boot中&#xff0c;确实没有像传统Web应用程序中需要使用web.xml配置文件。对于中文乱码问题&#xff0c;你可以采取以下几种方式来解决&#xff1a; 在application.properties文件中添加以下配置&#xff1a; spring.http.encoding.charsetUTF-8 spring.http.encod…

基于轻量级yolov5的瓷砖瑕疵检测系统

该专栏仅支持购买本专栏的同学学习使用,不支持以超级会员、VIP等形式使用,请谅解!【购买专栏后可选择其中一个完整源码项目】 本文是我新开设的专栏《完整源码项目实战》 的第十二篇全源码文章,包含数据集在内的所有资源,可以实现零基础上手入门学习。前面系列文章链接如下…

714. 买卖股票的最佳时机含手续费

给定一个整数数组 prices&#xff0c;其中 prices[i]表示第 i 天的股票价格 &#xff1b;整数 fee 代表了交易股票的手续费用。 你可以无限次地完成交易&#xff0c;但是你每笔交易都需要付手续费。如果你已经购买了一个股票&#xff0c;在卖出它之前你就不能再继续购买股票了…

基于51单片机水位监测控制报警仿真设计( proteus仿真+程序+设计报告+讲解视频)

这里写目录标题 &#x1f4a5;1. 主要功能&#xff1a;&#x1f4a5;2. 讲解视频&#xff1a;&#x1f4a5;3. 仿真&#x1f4a5;4. 程序代码&#x1f4a5;5. 设计报告&#x1f4a5;6. 设计资料内容清单&&下载链接&#x1f4a5;[资料下载链接&#xff1a;](https://doc…

NX二次开发UF_CAM_ask_f_s_db_object 函数介绍

文章作者&#xff1a;里海 来源网站&#xff1a;里海NX二次开发3000例专栏 UF_CAM_ask_f_s_db_object Defined in: uf_cam.h int UF_CAM_ask_f_s_db_object(UF_CAM_db_object_t * db_obj ) overview 概述 This function provides the database object which is currently u…

syslog-ng 发送metric 到 Prometheus + Grafana

1: 背景: syslog-ng 作为很多linux 收集日志的重要工具,当然很多splunk 的source 也是用这个收集的,下面就介绍用 Prometheus来收集数据,然后发送到 grafana 来展示,还是很直观的。下面就来详细道来: 2: 安装 syslog-ng node exporter: Prometheus: syslog-ng exporte…

11月20日,每日信息差

今天是2023年11月20日&#xff0c;以下是为您准备的10条信息差 第一、Aboitiz与可口可乐欧洲合作伙伴公司宣布联合收购可口可乐菲律宾公司 第二、微软首席执行官纳德拉&#xff1a;OpenAI创始人Sam Altman和Brockman将加入微软 第三、WeLab于印尼推出旗下第二家数字银行 第…

yum仓库

目录 什么是yum仓库 概念 yum的实现过程 Yum的实现过程如下&#xff1a; yum的配置文件 仓库设置文件 日志文件 yum命令 yum list 显示所有可用的包&#xff0c;包名&#xff0c;支持通配符 yum list*Kernel yum info 显示包的信息 yum search ftp 查…

TEE威胁评分与评级

目录 一、攻击潜力 1.1 攻击潜力网格 1、实际经过的时间: 2、访问TOE(目标设备)

【Feign】 基于 Feign 远程调用、 自定义配置、性能优化、实现 Feign 最佳实践

&#x1f40c;个人主页&#xff1a; &#x1f40c; 叶落闲庭 &#x1f4a8;我的专栏&#xff1a;&#x1f4a8; SpringCloud MybatisPlus JVM 石可破也&#xff0c;而不可夺坚&#xff1b;丹可磨也&#xff0c;而不可夺赤。 Feign 一、 基于 Feign 远程调用1.1 RestTemplate方式…

笔记53:torch.nn.rnn() 函数详解

参数解释&#xff1a; &#xff08;1&#xff09;input_size()&#xff1a;即输入信息 Xt 的每个序列的独热编码向量的长度&#xff0c;即 len(vocab) &#xff08;2&#xff09;hidden_size()&#xff1a;即隐变量 h 的维度&#xff08;维度是多少&#xff0c;就代表用几个数…

Keras训练一个基本体系化的分类模型流程案例

Keras训练一个基本体系化的分类模型流程案例 import numpy as np from keras.datasets import mnist from keras.utils import np_utils # 导入keras提供的numpy工具包 from keras.models import Sequential from keras.layers import Dense from keras.optimizers impo…

unity 打包exe设置分辨率

unity在2019以上版本不支持在打开的时候弹出分辨率设置的窗口&#xff0c;但是因为有些需求需要可以设置分辨率进行操作&#xff0c;我在查了好多办法后找到了解决方案&#xff0c;废话不多说开始干货。 1.先去百度云上下载这个文件 链接&#xff1a;https://pan.baidu.com/s/1…

如果重复定义宏,两个值不同,最终的值是哪一个?

task F&#xff1b; define AA 5 //定义一个宏&#xff0c;先编译 print(AA); # 5; define AA 10 //重复定义宏&#xff0c;后编译 // 后面的值会覆盖前面定义的宏&#xff0c;但不是覆盖所有域&#xff0c;按照编译顺序&#xff0c;只覆盖该位置往后的域 print&…